背景描述
在以太坊中,只要你部署到链上的合约,任何人都是只要付出对应的gas就可以调用你的合约来执行你合约中的方法,为了防止别人调用你的合约,你一般在合约的方法中加上对合约的控制权限,假设智能合约中有一个转钱的伪代码如下:
function transferFrom(address _from, address _to, uint256 _value) public returns(bool success) {
balances[_from] -= _value;
balances[_to] += _value;
return true;
}
因为这个合约谁都能调用,明显是不安全的,那么_from中的 balance 谁都能转走。所以,一般只允许合约的发布者能让他调用这段代码。为了实现这个功能,首先要在合约里面做调用权限控制。那么类似的伪代码可能如下:
function transferFrom(address _from, address _to, uint256 _value) public returns(bool success) {
if(合约的发布者 != _from) return false;
balances[_from] -= _value;
balances[_to] += _value;
return true;
}
这么写虽然是解决了合约调用的问题。但是由于合约一旦部署,这些控制合约权限的代码将不再可更改。比如后面,我想要对from除了合约发布者之外能调用,还能让合约发布者的其他人能调用那就没法动态的更改了。之前设计了一套比较复杂的权限模型,用起来比较复杂,后面想用一套简单的合约权限模型。这就是下面要介绍的新权限模型。
合约结构
在以太坊中,合约跟账号的结构是完全一样的。合约包含的数据大概有如下:
- nonce: u256 每笔交易只能被处理一次的随机数。
- balance:u256 账户目前的以太币余额。
- storageRoot:h256 Merkle树的根节点Hash值。
- codeHash:h256 此账户EVM代码的hash值。
我们想在此结构的基础上,增加一些字段,参考iptables防火墙软件,对合约做一个动态权限的控制。增加的字段描述如下: - firewall:int 合约防火墙控制开发。1 表示打开防火墙,0 表示关闭防火墙。
- author:Address 合约发布者。
- callPermissions:Object 合约权限控制,它是一个动态删减的 vector <CallPermission> callPermissions,里面的CallPermission结构如下:
- caller:Address 合约调用者。
- funcName:String 合约方法。
- permission:int 合约权限。0 表示禁止caller访问合约方法funcName,1 表示允许caller访问合约方法funcName。
这样,合约的发布者author就可以对其他账号动态的增加合约的调用权限控制。
简要实现
第一次处需要修改的是合约的发布者author,需要在合约创建完成的时候将其作者更新进去。具体代码实现在虚拟机Executive::create
创建合约完成的时候在转移金额transferBalance
的时候加入其作者。
因为账号的数据会影响到状态数据库,所以需要将这些数据写到levelDB去。写入的时候,需要将新增的数据转为RLP数据结构写入到状态树结构中,具体部分代码实现如下:
template <class DB>
AddressHash commit(AccountMap const& _cache, SecureTrieDB<Address, DB>& _state)
{
AddressHash ret;
for (auto const& i: _cache)
{
// 合约其他数据结构RLP序列化
// ......
// 以下为新增数据RLP序列化
s << static_cast<unsigned>(i.second.firewall());
s.appendList(i.second.callPermissions().size());
const size_t callPermissionItems = 3;
for(Account::CallPermission const &c : i.second.callPermissions())
{
s.appendList(callPermissionItems) << c.caller << c.funcName << static_cast<unsigned>(c.permission);
}
s << i.second.author();
// 新增数据序列化完毕
// ......
}
return ret;
}
相应的,从levelDB读取数据的时候,需要从数据库中读取RLP数据,将其反序列化为合约结构。部分代码如下:
Account* State::account(Address const& _addr)
{
// 其他代码
// ......
// 反序列化新增字段代码
std::vector<Account::CallPermission> callPermissions;
for (auto const& b: state[(size_t)AccountRlpIndex::CallPermissions])
{
Account::CallPermission cp;
cp.caller = b[0].toHash<h160>();
cp.funcName = b[1].toString();
cp.permission = static_cast<Account::CallPermission::Permission>(b[2].toInt());
callPermissions.emplace_back(cp);
}
Account::FirewallState firewall = static_cast<Account::FirewallState>(state[static_cast<size_t>(AccountRlpIndex::Firewall)].toInt());
Address author = static_cast<Address>(state[static_cast<size_t>(AccountRlpIndex::Author)].toHash<h160>());
// 反序列化完毕
// ......
}
合约接口调用说明
全部接口使用一个内置合约来调用,该内置合约定义的接口如下:
pragma solidity ^0.4.2;
contract ExtendedCall {
function firewall(address _contract) public constant returns(uint) {}
function author(address _contract) public constant returns(address) {}
function openFirewall(address _contract) public returns(bool) {}
function closeFirewall(address _contract) public returns(bool) {}
function addAllowPermission(address _contract, address _caller, string _funcName) public returns(bool) {}
function addForbidPermission(address _contract, address _caller, string _funcName) public returns(bool) {}
function getAllowPermission(address _contract, address _caller) public constant returns(string) {}
function getForbidPermission(address _contract, address _caller) public constant returns(string) {}
function clearAllowPermission(address _contract, address _caller) returns(bool) {}
function clearForbidPermission(address _contract, address _caller) returns(bool) {}
function clearAllowFuncPermission(address _contract, address _caller, string _funcName) returns(bool) {}
function clearForbidFuncPermission(address _contract, address _caller, string _funcName) returns(bool) {}
function clearAllAllowPermission(address _contract) returns(bool) {}
function clearAllForbidPermission(address _contract) returns(bool) {}
}
各个接口说明如下:
function firewall(address _contract) public constant returns(uint _ret){}
返回合约的防火墙状态。
入参:
_contract: address
合约地址
出参:
_ret: uint
合约防火墙状态。0:防火墙关闭,1:防火墙开启。2:其他状态。比如,合约不存在。
function author(address _contract) public constant returns(address _author){}
返回合约的发布者。
入参:
_contract: address
合约地址
出参:
_author: address
合约发布者。如果合约不存在,返回一个全0地址的发布者。
function openFirewall(address _contract) public returns(bool _ret){}
打开防火墙,合约调用启用规则。
入参:
_contract: address
合约地址
出参:
_ret: bool
调用结果。true表示成功调用,false表示未调用(如:非合约发布者调用此函数,合约不存在等)。注意:由于此调用是send调用,会产生交易,所以对于合约调用来说,此返回值无实际意义。下面对于合约的send调用,不再做说明
function closeFirewall(address _contract) public returns(bool _ret){}
关闭防火墙,合约调用不启用规则,任何账号可调用此合约。
入参:
_contract: address
合约地址
出参:
_ret: bool
调用结果。true表示成功调用,false表示未调用(如:非合约发布者调用此函数,合约不存在等)。
function addAllowPermission(address _contract,address _caller,string _funcName) public returns(bool _ret){}
给合约contract的方法funcName的调用者caller添加允许调用规则。注意:增加一个funcName为all_allow的特殊字符串,如果添加此字符串,则允许_caller访问所有_contract的方法
入参:
_contract: address
合约地址
_caller: address
调用者
_funcName: string
函数名称
出参:
_ret: bool
调用结果。true表示成功调用,false表示未调用(如:非合约发布者调用此函数,合约不存在等)。
function addForbidPermission(address _contract,address _caller,string _funcName) public returns(bool _ret){}
给合约contract的方法funcName的调用者caller添加禁止调用规则。注意:增加一个funcName为all_forbid的特殊字符串,如果添加此字符串,则允许_caller访问所有_contract的方法
入参:
_contract: address
合约地址
_caller: address
调用者
_funcName: string
函数名称
出参:
_ret: bool
调用结果。true表示成功调用,false表示未调用(如:非合约发布者调用此函数,合约不存在等)。
function getAllowPermission(address _contract,address _caller) public constant returns(string _ret){}
获取合约contract的的调用者caller有哪些允许调用方法。
入参:
_contract: address
合约地址
_caller: address
调用者
出参:
_ret: string
允许调用的方法列表,一个JSON对象的字符串,如[ "set(uint256)", "get()", "hello()", "world()" ]
。
function getForbidPermission(address _contract,address _caller) public constant returns(string _ret){}
获取合约contract的的调用者caller有哪些禁止调用方法。
入参:
_contract: address
合约地址
_caller: address
调用者
出参:
_ret: string
允许调用的方法列表,一个JSON对象的字符串,如[ "hello()", "world()" ]
。
function clearAllowPermission(address _contract,address _caller) public returns(bool _ret){}
删除合约contract的调用者caller所有允许调用的方法。
入参:
_contract: address
合约地址
_caller: address
调用者
出参:
_ret: string
调用结果。true表示成功调用,false表示未调用(如:非合约发布者调用此函数,合约不存在等)。
function clearForbidPermission(address _contract,address _caller) public returns(bool _ret){}
删除合约contract的调用者caller所有禁止调用的方法。
入参:
_contract: address
合约地址
_caller: address
调用者
出参:
_ret: bool
调用结果。true表示成功调用,false表示未调用(如:非合约发布者调用此函数,合约不存在等)。
function clearAllowFuncPermission(address _contract,address _caller,string _funcName) public returns(bool _ret){}
删除合约contract的调用者caller允许调用的funcName方法。
入参:
_contract: address
合约地址
_caller: address
调用者
_funcName: string
函数名称
出参:
_ret: bool
调用结果。true表示成功调用,false表示未调用(如:非合约发布者调用此函数,合约不存在等)。
function clearForbidFuncPermission(address _contract,address _caller,string _funcName) public returns(bool _ret){}
删除合约contract的调用者caller禁止调用的funcName方法。
入参:
_contract: address
合约地址
_caller: address
调用者
_funcName: string
函数名称
出参:
_ret: bool
调用结果。true表示成功调用,false表示未调用(如:非合约发布者调用此函数,合约不存在等)。
function clearAllAllowPermission(address _contract) public returns(bool _ret){}
删除合约contract的所有允许调用的函数。
入参:
_contract: address
合约地址
出参:
_ret: bool
调用结果。true表示成功调用,false表示未调用(如:非合约发布者调用此函数,合约不存在等)。
function clearAllForbidPermission(address _contract) public returns(bool _ret){}
删除合约contract的所有禁止调用的函数。
入参:
_contract: address
合约地址
出参:
_ret: bool
调用结果。true表示成功调用,false表示未调用(如:非合约发布者调用此函数,合约不存在等)。
JSON RPC 接口调用
我简单实现了两个接口,一个是获取合约的权限列表,一个是获取合约的防火墙状态。说明如下:
eth_getCallPermissions
参数:
contract: string
合约地址。
tag: string
整数块编号,或字符串"earliest"、"latest" 或"pending"
出参:
ret: string
调用的方法列表,一个JSON对象的字符串,如
{
"0x00d328af15589069818a72f7fdb9e8555ea1bc74": {
"allow": [ "set(uint256)", "get()", "hello()", "world()", "ilovethisworld(uint256)" ],
"forbid": [ "hello()", "world()" ]
}
}
示例代码:curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getFirewallState","params":["0x2973a02eb0c9b592722c69a40e997d24b5e8fbf7", "latest"],"id":1}'
eth_getFirewallState
参数:
contract: string
合约地址。
tag: string
整数块编号,或字符串"earliest"、"latest" 或"pending"
出参:
ret: string
0 防火墙关闭,1 防火墙开启。
示例代码:curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getFirewallState","params":["0x2973a02eb0c9b592722c69a40e997d24b5e8fbf7", "latest"],"id":1}'
如何调用内置合约
所谓的内置合约,我们定义没有EVM的执行过程,我们给定一个特定的地址,直接调用C++或者其他代码完成对状态的更改。比如我们规定上面的内置合约的执行地址为0x00000000000000000000000000000000000000ec
std::pair<ExecutionResult, TransactionReceipt> State::execute(EnvInfo const& _envInfo, SealEngineFace const& _sealEngine, ...)
{
// ......
Executive e(*this, _envInfo, _sealEngine);
e.setTransactionExecMode(_execMode);
ExecutionResult res;
e.setResultRecipient(res);
e.initialize(_t);
Address to = m_t.receiveAddress();
if(to == "0x00000000000000000000000000000000000000ec")
{
// 在此实现内置合约函数的调用.....
}
else
{
if (!e.execute())
e.go(onOp);
}
e.finalize();
// ......
return make_pair(res, TransactionReceipt(rootHash(), e.gasUsed(), e.logs()));
}
合约权限流程图
需要注意的是,当一个合约对某个方法的调用同事允许与禁止的时候,禁止优先。
单元测试
用JavaScript代码写的。主要流程是,后台已生成两个账号,一个是合约MonitorTest的发布者,一个不是。发布者调用内置合约对合约MonitorTest执行操作,与预期结果进行比较。见下面代码。
const mocha = require("mocha");
const expect = require("chai").expect;
const Web3 = require("web3");
const axios = require("axios");
require("mocha-steps");
// setting begin
const url = "http://10.10.8.160:6789"; // 设置好你的地址
const pwd = "12345678"; // 所有的账号密码,为调用做准备
const address = {
ExtendedCall: "0x00000000000000000000000000000000000000ec", // 内置合约调用地址
RegisterManager: "0x0000000000000000000000000000000000000011", // 内部合约,用来获取合约MonitorTest地址
Test: "0x0000000000000000000000000000001234567890" // 用来测试随意写的一个调用者
};
// 合约权限控制的内置合约ABI
const extendedCallAbi = [
{
"constant": false,
"inputs": [ { "name": "_contract", "type": "address" }, { "name": "_caller", "type": "address" } ],
"name": "clearForbidPermission",
"outputs": [ { "name": "", "type": "bool" } ],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "name": "_contract", "type": "address" },
{ "name": "_caller", "type": "address" },
{ "name": "_funcName", "type": "string" }
],
"name": "addForbidPermission",
"outputs": [ { "name": "", "type": "bool" } ],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [ { "name": "_contract", "type": "address" } ],
"name": "clearAllAllowPermission",
"outputs": [ { "name": "", "type": "bool" } ],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [ { "name": "_contract", "type": "address" }, { "name": "_caller", "type": "address" } ],
"name": "clearAllowPermission",
"outputs": [ { "name": "", "type": "bool" } ],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "name": "_contract", "type": "address" },
{ "name": "_caller", "type": "address" },
{ "name": "_funcName", "type": "string" }
],
"name": "clearForbidFuncPermission",
"outputs": [ { "name": "", "type": "bool" } ],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [ { "name": "_contract", "type": "address" }, { "name": "_caller", "type": "address" } ],
"name": "getForbidPermission",
"outputs": [ { "name": "", "type": "string" } ],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "name": "_contract", "type": "address" },
{ "name": "_caller", "type": "address" },
{ "name": "_funcName", "type": "string" }
],
"name": "clearAllowFuncPermission",
"outputs": [ { "name": "", "type": "bool" } ],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [ { "name": "_contract", "type": "address" } ],
"name": "closeFirewall",
"outputs": [ { "name": "", "type": "bool" } ],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [ { "name": "_contract", "type": "address" }, { "name": "_caller", "type": "address" } ],
"name": "getAllowPermission",
"outputs": [ { "name": "", "type": "string" } ],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [ { "name": "_contract", "type": "address" } ],
"name": "firewall",
"outputs": [ { "name": "", "type": "uint256" } ],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [ { "name": "_contract", "type": "address" } ],
"name": "openFirewall",
"outputs": [ { "name": "", "type": "bool" } ],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "name": "_contract", "type": "address" },
{ "name": "_caller", "type": "address" },
{ "name": "_funcName", "type": "string" }
],
"name": "addAllowPermission",
"outputs": [ { "name": "", "type": "bool" } ],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [ { "name": "_contract", "type": "address" } ],
"name": "author",
"outputs": [ { "name": "", "type": "address" } ],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [ { "name": "_contract", "type": "address" } ],
"name": "clearAllForbidPermission",
"outputs": [ { "name": "", "type": "bool" } ],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}
]
// 用来获取 MonitorTest 合约是谁注册的。
const registerManagerAbi = [
{
constant: true,
inputs: [{ name: "_pageNum", type: "uint256" }, { name: "_pageSize", type: "uint256" }],
name: "getRegisteredContract",
outputs: [{ name: "_json", type: "string" }],
payable: false,
type: "function"
},
{ inputs: [], payable: false, type: "constructor" }
];
// MonitorTest 合约ABI
const monitorTestAbi = [
{
constant: false,
inputs: [{ name: "amount", type: "uint256" }],
name: "set",
outputs: [{ name: "", type: "address" }, { name: "", type: "uint256" }],
payable: false,
type: "function"
},
{
constant: true,
inputs: [],
name: "get",
outputs: [{ name: "", type: "uint256" }],
payable: false,
type: "function"
},
{ inputs: [], payable: false, type: "constructor" }
];
// setting end
// JSON RPC调用生成的ID。
function getId() {
Date.prototype.Format = function(fmt) {
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
S: this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
return fmt;
};
let id = parseInt(new Date().Format("hhmmss"));
return id;
}
let client = axios.create({
baseURL: url
});
client.dispatch = async data => {
let replay = await client.post("", data);
return new Promise((resolve, reject) => {
if (replay.status === 200) {
resolve(replay.data.result);
} else {
reject("request error");
}
});
};
const web3 = new Web3(url);
var testCaseIndex = 1;
var skip = it.skip;
var only = it.only;
let ret = {};
/* 更新合约权限
* extendedCall 内置合约的web3实例
* contractAddress 合约地址
* author 合约发布者
* caller 合约调用者
* funcName 合约方法
* callFunc 需要执行的内置合约的函数名称,如 addForbidPermission
*/
let updatePermission = async (extendedCall, contractAddress, author, caller, funcName, callFunc) => {
let inputArr = [contractAddress];
caller && inputArr.push(caller);
funcName && inputArr.push(funcName);
let func = extendedCall.methods[callFunc].apply(extendedCall.methods, inputArr);
let options = {
gas: "0xe8d4a50fff",
gasPrice: "0x174876e800",
from: author
};
replay = await func.send(options);
return replay;
};
describe("开始合约权限测试...", function() {
before(function() {
console.log("before...");
// 在本区块的所有测试用例之前执行
});
step("Test Case " + testCaseIndex++ + " 获取后台账号", async function(done) {
let data = {
method: "eth_accounts",
jsonrpc: "2.0",
id: parseInt(getId()),
params: []
};
try {
let replay = await client.dispatch(data);
expect(replay).to.have.length.least(2);
ret["accounts"] = replay;
done();
} catch (error) {
done(error);
}
});
step("Test Case " + testCaseIndex++ + " 解锁账号", async function(done) {
for (const account of ret["accounts"]) {
let data = {
method: "personal_unlockAccount",
jsonrpc: "2.0",
id: parseInt(getId()),
params: [account, pwd, 24 * 60 * 60]
};
try {
let replay = await client.dispatch(data);
expect(replay).to.be.equal(true);
} catch (error) {
done(error);
}
}
done();
});
step("Test Case " + testCaseIndex++ + " 获取 MonitorTest 合约地址", async function(done) {
try {
let contract = new web3.eth.Contract(registerManagerAbi, address["RegisterManager"], null);
let from = ret["accounts"][0];
let func = contract.methods["getRegisteredContract"].apply(contract.methods, [0, 1000]);
let replay = await func.call({ from: from });
let replayObj = JSON.parse(replay);
let items = replayObj.data.items;
for (const item of items) {
let contractName = item.contractName;
let address = item.address;
if (contractName && address && contractName === "MonitorTest") {
ret["MonitorTestAddress"] = address;
done();
break;
}
}
!ret["MonitorTestAddress"] && done("not found MonitorTest address");
} catch (error) {
done(error);
}
});
step("Test Case " + testCaseIndex++ + " 获取 MonitorTest 发布人员地址", async function(done) {
try {
let contract = new web3.eth.Contract(extendedCallAbi, address["ExtendedCall"], null);
let from = ret["accounts"][0];
let func = contract.methods["author"].apply(contract.methods, [ret["MonitorTestAddress"]]);
let replay = await func.call({ from: from });
ret["author"] = replay;
for (const account of ret["accounts"]) {
if (account !== ret["author"]) {
ret["caller"] = account;
break;
}
}
done();
} catch (error) {
done(error);
}
});
step("Test Case " + testCaseIndex++ + " 测试关闭打开防火墙 closeFirewall openFirewall", async function(done) {
try {
let contract = new web3.eth.Contract(extendedCallAbi, address["ExtendedCall"], null);
let from = ret["author"];
let callFuncs = ["closeFirewall", "openFirewall"];
let expectRet = {
closeFirewall: "0",
openFirewall: "1"
};
for (const callFunc of callFuncs) {
let func = contract.methods[callFunc].apply(contract.methods, [ret["MonitorTestAddress"]]);
let options = {
gas: "0xe8d4a50fff",
gasPrice: "0x174876e800",
from: from
};
let replay = await func.send(options);
expect(replay).to.be.an.instanceof(Object);
func = contract.methods["firewall"].apply(contract.methods, [ret["MonitorTestAddress"]]);
replay = await func.call({ from: from });
expect(replay).to.be.equal(expectRet[callFunc]);
}
done();
} catch (error) {
done(error);
}
});
// 6
step("Test Case " + testCaseIndex++ + " 测试清除合约所有权限 clearAllAllowPermission clearAllForbidPermission ", async function(done) {
try {
let contract = new web3.eth.Contract(extendedCallAbi, address["ExtendedCall"], null);
let from = ret["author"];
let func, replay;
let callFuncs = ["clearAllAllowPermission", "clearAllForbidPermission"];
let callers = [ret["caller"], address["Test"]];
for (const callFunc of callFuncs) {
replay = await updatePermission(contract, ret["MonitorTestAddress"], from, null, null, callFunc);
expect(replay).to.be.an.instanceof(Object);
}
let callGetPermissionFuncs = ["getAllowPermission", "getForbidPermission"];
for (const callFunc of callGetPermissionFuncs) {
for (const caller of callers) {
func = contract.methods[callFunc].apply(contract.methods, [ret["MonitorTestAddress"], caller]);
try {
replay = await func.call({ from: from });
expect(replay).to.equal("[]");
} catch (error) {
}
}
}
done();
} catch (error) {
done(error);
}
});
// 7
step("Test Case " + testCaseIndex++ + " 测试添加允许权限与禁止权限 addAllowPermission addForbidPermission ", async function(done) {
try {
let contract = new web3.eth.Contract(extendedCallAbi, address["ExtendedCall"], null);
let from = ret["author"];
let func, replay;
let callAddPermissionFuncs = ["addAllowPermission", "addForbidPermission"];
let funcNames = ["set(uint256)", "get()", "hello()", "world()"];
let callers = [ret["caller"], address["Test"]];
for (const funcName of funcNames) {
for (const callFunc of callAddPermissionFuncs) {
for (const caller of callers) {
let replay = await updatePermission(contract, ret["MonitorTestAddress"], from, caller, funcName, callFunc);
expect(replay).to.be.an.instanceof(Object);
}
}
}
let callGetPermissionFuncs = ["getAllowPermission", "getForbidPermission"];
for (const callFunc of callGetPermissionFuncs) {
for (const caller of callers) {
func = contract.methods[callFunc].apply(contract.methods, [ret["MonitorTestAddress"], caller]);
replay = await func.call({ from: from });
expect(JSON.parse(replay)).to.deep.equal(funcNames);
}
}
done();
} catch (error) {
done(error);
}
});
step("Test Case " + testCaseIndex++ + " 测试清除单个禁入允许函数 clearAllowFuncPermission clearForbidFuncPermission", async function(done) {
try {
let contract = new web3.eth.Contract(extendedCallAbi, address["ExtendedCall"], null);
let from = ret["author"];
let callFuncs = ["clearAllowFuncPermission", "clearForbidFuncPermission"];
let getFuncs = {
clearAllowFuncPermission: "getAllowPermission",
clearForbidFuncPermission: "getForbidPermission"
};
let funcName = "world()";
for (const callFunc of callFuncs) {
let replay = await updatePermission(contract, ret["MonitorTestAddress"], from, address["Test"], funcName, callFunc);
expect(replay).to.be.an.instanceof(Object);
func = contract.methods[getFuncs[callFunc]].apply(contract.methods, [ret["MonitorTestAddress"], address["Test"]]);
replay = await func.call({ from: from });
expect(JSON.parse(replay)).to.deep.equal(["set(uint256)", "get()", "hello()"]);
}
done();
} catch (error) {
done(error);
}
});
// 9
step("Test Case " + testCaseIndex++ + " 测试清除某个调用者禁入允许函数 clearAllowPermission clearForbidPermission", async function(done) {
try {
let contract = new web3.eth.Contract(extendedCallAbi, address["ExtendedCall"], null);
let from = ret["author"];
let callFuncs = ["clearAllowPermission", "clearForbidPermission"];
let getFuncs = {
clearAllowPermission: "getAllowPermission",
clearForbidPermission: "getForbidPermission"
};
for (const callFunc of callFuncs) {
let replay = await updatePermission(contract, ret["MonitorTestAddress"], from, address["Test"], null, callFunc);
expect(replay).to.be.an.instanceof(Object);
func = contract.methods[getFuncs[callFunc]].apply(contract.methods, [ret["MonitorTestAddress"], address["Test"]]);
replay = await func.call({ from: from });
expect(JSON.parse(replay)).to.deep.equal([]);
}
done();
} catch (error) {
done(error);
}
});
step("Test Case " + testCaseIndex++ + " 测试权限对 MonitorTest 调用", async function(done) {
try {
let contractExtendedCall = new web3.eth.Contract(extendedCallAbi, address["ExtendedCall"], null);
let contractMonitorTest = new web3.eth.Contract(monitorTestAbi, ret["MonitorTestAddress"], null);
let from = ret["author"];
let caller = ret["caller"];
let sendNum = parseInt(Math.random() * 10000);
let callerSendNum = parseInt(Math.random() * 10000);
let options = {
gas: "0xe8d4a50fff",
gasPrice: "0x174876e800",
from: from
};
let func, replay;
ret["sendNum"] = sendNum;
ret["callerSendNum"] = callerSendNum;
// 自己调用
func = contractMonitorTest.methods["set"].apply(contractMonitorTest.methods, [sendNum]);
options.from = from;
replay = await func.send(options);
expect(replay).to.be.an.instanceof(Object);
func = contractMonitorTest.methods["get"].apply(contractMonitorTest.methods, []);
replay = await func.call({ from: from });
expect(parseInt(replay)).to.equal(sendNum);
// 给get()函数权限
replay = await updatePermission(contractExtendedCall, ret["MonitorTestAddress"], from, ret["caller"], "get()", "clearForbidFuncPermission");
expect(replay).to.be.an.instanceof(Object);
// set(uint256) 禁入
func = contractMonitorTest.methods["set"].apply(contractMonitorTest.methods, [callerSendNum]);
options.from = caller;
replay = await func.send(options);
func = contractMonitorTest.methods["get"].apply(contractMonitorTest.methods, []);
replay = await func.call({ from: caller });
expect(parseInt(replay)).to.equal(sendNum); // 还是应该等于发布者设置的
// 给set(uint256)函数权限
replay = await updatePermission(contractExtendedCall, ret["MonitorTestAddress"], from, ret["caller"], "set(uint256)", "clearForbidFuncPermission");
expect(replay).to.be.an.instanceof(Object);
// set(uint256) 允许调用
func = contractMonitorTest.methods["set"].apply(contractMonitorTest.methods, [callerSendNum]);
options.from = caller;
replay = await func.send(options);
func = contractMonitorTest.methods["get"].apply(contractMonitorTest.methods, []);
replay = await func.call({ from: caller });
expect(parseInt(replay)).to.equal(callerSendNum); // 应该等于自己设置的
done();
} catch (error) {
done(error);
}
});
step("Test Case " + testCaseIndex++ + " 测试 eth_getCallPermissions", async function(done) {
let data = {
method: "eth_getCallPermissions",
jsonrpc: "2.0",
id: parseInt(getId()),
params: [ ret["MonitorTestAddress"], "latest" ]
};
try {
let replay = await client.dispatch(data);
ret[ret["MonitorTestAddress"]] = replay;
done();
} catch (error) {
done(error);
}
});
step("Test Case " + testCaseIndex++ + " 测试 eth_getFirewallState", async function(done) {
let data = {
method: "eth_getFirewallState",
jsonrpc: "2.0",
id: parseInt(getId()),
params: [ ret["MonitorTestAddress"], "latest" ]
};
try {
let replay = await client.dispatch(data);
ret["FirewallState"] = replay;
done();
} catch (error) {
done(error);
}
});
after(function() {
// 在本区块的所有测试用例之后执行
console.log("after...");
console.log(JSON.stringify(ret, undefined, 4));
});
});
测试结果如下:
开始合约权限测试...
before...
√ Test Case 1 获取后台账号
√ Test Case 2 解锁账号 (63207ms)
√ Test Case 3 获取 MonitorTest 合约地址 (1625ms)
√ Test Case 4 获取 MonitorTest 发布人员地址
√ Test Case 5 测试关闭打开防火墙 closeFirewall openFirewall (8224ms)
√ Test Case 6 测试清除合约所有权限 clearAllAllowPermission clearAllForbidPermission (8077ms)
√ Test Case 7 测试添加允许权限与禁止权限 addAllowPermission addForbidPermission (64247ms)
√ Test Case 8 测试清除单个禁入允许函数 clearAllowFuncPermission clearForbidFuncPermission (8026ms)
√ Test Case 9 测试清除某个调用者禁入允许函数 clearAllowPermission clearForbidPermission (8025ms)
√ Test Case 10 测试权限对 MonitorTest 调用 (20053ms)
√ Test Case 11 测试 eth_getCallPermissions
√ Test Case 12 测试 eth_getFirewallState
after...
{
"accounts": [
"0x1c22736623901b437ccddd56a1db6573d9ffec51",
"0x00d328af15589069818a72f7fdb9e8555ea1bc74"
],
"MonitorTestAddress": "0x0d2bf7651722048b3b055d63b8acdcd4f2a385cd",
"author": "0x1c22736623901b437ccddd56a1db6573d9ffec51",
"caller": "0x00d328af15589069818a72f7fdb9e8555ea1bc74",
"sendNum": 2138,
"callerSendNum": 6928,
"0x0d2bf7651722048b3b055d63b8acdcd4f2a385cd": {
"0x00d328af15589069818a72f7fdb9e8555ea1bc74": {
"allow": [
"set(uint256)",
"get()",
"hello()",
"world()"
],
"forbid": [
"hello()",
"world()"
]
}
},
"FirewallState": 0
}
其他
MonitorTest合约内容
/**
* @file monitorTest.sol
* @author JUZIX.IO
* @time 2018-3-14
* @desc monitorTest.sol 是一个简单的合约调用,用于监控的心跳机制监测节点情况
*/
pragma solidity ^ 0.4.2;
import "./sysbase/OwnerNamed.sol";
contract MonitorTest is OwnerNamed {
uint value;
function MonitorTest() {
register("SystemModuleManager", "0.0.1.0", "MonitorTest", "0.0.1.0");
}
function set(uint amount) public returns(address, uint) {
value = amount;
return (msg.sender, value);
}
function get() public constant returns(uint) {
return value;
}
}