以太坊C++系列(04)-给合约加个权限

背景描述

在以太坊中,只要你部署到链上的合约,任何人都是只要付出对应的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()));
}

合约权限流程图

需要注意的是,当一个合约对某个方法的调用同事允许与禁止的时候,禁止优先。

image

单元测试

用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;
    }
}
暂无评论

发送评论 编辑评论


				
上一篇
下一篇