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

背景描述

在以太坊中,只要你部署到链上的合约,任何人都是只要付出对应的gas就可以调用你的合约来执行你合约中的方法,为了防止别人调用你的合约,你一般在合约的方法中加上对合约的控制权限,假设智能合约中有一个转钱的伪代码如下:

1
2
3
4
5
function transferFrom(address _from, address _to, uint256 _value) public returns(bool success) {
balances[_from] -= _value;
balances[_to] += _value;
return true;
}

因为这个合约谁都能调用,明显是不安全的,那么_from中的 balance 谁都能转走。所以,一般只允许合约的发布者能让他调用这段代码。为了实现这个功能,首先要在合约里面做调用权限控制。那么类似的伪代码可能如下:

1
2
3
4
5
6
7
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数据结构写入到状态树结构中,具体部分代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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数据,将其反序列化为合约结构。部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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>());
// 反序列化完毕

// ......
}

合约接口调用说明

全部接口使用一个内置合约来调用,该内置合约定义的接口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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对象的字符串,如

1
2
3
4
5
6
{
"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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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执行操作,与预期结果进行比较。见下面代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
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));
});
});

测试结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
  开始合约权限测试...
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合约内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @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;
}
}

您的支持将鼓励我继续创作!
0%