solidity教程(2)-高级

solidity教程-高级,基于8.0版本

1. 概述

  1. 私有变量比公开变量节省gas

2. 两个合约之间的调用

2.1 一个项目中两个合约通过引用实现

两个合约各自发布,然后通过地址调用不同合约,下方是CallTestContract调用TestContract

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
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;

contract CallTestContract{
function setX(TestContract _test,uint _x) external{
_test.setX(_x);
}
function getX(address _test) external view returns (uint){
return TestContract(_test).getX();
}
function setXandSendEther(address _test,uint _x) external payable{
TestContract(_test).setXandReceiveEther{value: msg.value}(_x);
}
function getXandValue(address _test) external view returns (uint x, uint value){
(x, value) = TestContract(_test).getXandValue();
}
}

contract TestContract{
uint public x;
uint public value = 123;
function setX(uint _x) external{
x = _x;
}
function getX() external view returns (uint){
return x;
}
function setXandReceiveEther(uint _x) external payable{
x = _x;
value = msg.value;
}
function getXandValue() external view returns (uint,uint){
return(x,value);
}
}

2.2 两个项目中两个合约通过接口实现

知道要调用的合约,抽象出其中的接口,传入要调用的合约地址
先单独部署Counter合约

1
2
3
4
5
6
7
8
9
10
11
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;
contract Counter{
uint public count;
function inc() external{
count += 1;
}
function dec() external{
count -= 1;
}
}

再部署带有接口的合约,用来间接调用Counter合约

1
2
3
4
5
6
7
8
9
10
11
12
13
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;
interface ICounter{
function count() external view returns (uint);
function inc() external;
}
contract CallTestContract{
uint public count;
function examples(address _counter) external {
ICounter(_counter).inc();
count = ICounter(_counter).count();
}
}

2.3 使用call调用不同合约

先发布TestCall合约,再发布Call合约;
Call合约通过地址和call调用TestCall合约

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
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;
contract TestCall{
string public message;
uint public x;
event Log(string message);
//调用不存在的合约,会触发该方法
fallback() external payable{
emit Log("fallback was called");
}
function foo(string memory _message,uint _x) external payable returns(bool,uint){
message = _message;
x = _x;
return (true,999);
}
}

contract Call{
bytes public data;
function callFoo(address _test) external payable{
(bool success,bytes memory _data)=_test.call{value:111,gas:5000}(
abi.encodeWithSignature(
"foo(string,uint256)","call foo",123
)
);
require(success,"call failed");
data = _data;
}
//调用不存在的合约
function callDoesNotExit(address _test) external{
(bool success,)=_test.call{value:111,gas:5000}(abi.encodeWithSignature("doesNotExist()"));
require(success,"call failed");
}
}

2.4 通过委托delegatecall调用不同合约

适用于升级合约。
DelegateCall合约通过delegatecall只是借用了TestDelegateCall合约中的逻辑,并没有改变TestDelegateCall合约中任何参数值。
被调用的TestDelegateCall合约,参数变量结构一定要与DelegateCall合约的一致,否则调用会出现问题。(当然,在相同结构的情况下,在被调用的TestDelegateCall合约之后追加内容是可以的,但还是不建议,以防出了问题找不到原因)

另外,通过委托调用,msg.sender会始终为调用人的地址

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
// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.3;

contract TestDelegateCall{
uint public num;
address public sender;
uint public value;
function setVars(uint _num) external payable{
num = _num;
sender = msg.sender;
value = msg.value;
}
}

contract DelegateCall{
uint public num;
address public sender;
uint public value;
//通过delegatecall调用TestDelegateCall,只会修改了当前合约DelegateCall的变量值,但不会修改TestDelegateCall中num的值
//也就是说,通过delegatecall只是借用了TestDelegateCall中的逻辑,并没有改变其中任何值
//此种方式,需要TestDelegateCall中的结构和变量参数需要与DelegateCall中的保持一致。
function setVars(address _test,uint _num) external payable{
(bool success,bytes memory data) = _test.delegatecall(abi.encodeWithSelector(TestDelegateCall.setVars.selector,_num));
require(success,"delegatecall failed");
}
}

2.5 通过工厂模式调用合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;
contract Account{
address public bank;
address public owner;
constructor(address _owner) payable{
bank = msg.sender;
owner = _owner;
}
}

contract AccountFactory{
Account[] public accounts;
function createAccount(address _owner) external payable{
Account account = new Account{value: 111}(_owner);
accounts.push(account);
}
}

2.6 调用库合约

用于节约代码量
涉及到using AContract for type的使用,该功能可以将AContract库合约中的方法赋予给type数据类型使用,提高使用效率

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
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;
library Math{
function max(uint x, uint y) internal pure returns (uint){
return x >= y ? x : y;
}
}
contract Test{
function testMax(uint x, uint y) external pure returns (uint){
return Math.max(x,y);
}
}

library ArrayLib{
function find(uint[] storage arr, uint x) internal view returns (uint){
for(uint i=0;i<arr.length;i++){
if (arr[i]==x){
return i;
}
}
revert("not found");
}
}
contract TestArray{
using ArrayLib for uint[]; //将库合约中的方法赋予给了uint[]
uint[] public arr = [3,2,1];
function testFind() external view returns (uint i){
return arr.find(2);
}
}

3. 哈希运算和验证签名

3.1 hash生成

这个注意encodePacked和encode的区别:
encodePacked压缩了结果,容易造成hash碰撞,因此使用它对两个字符串进行hash时,最好在两个字符串之间加入一个数字。
注:关于哈希碰撞,aa aaa的压缩和a aaaa的压缩使用encodePacked的压缩结果是一样的,造成了碰撞

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
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;

//通常用于签名或者获取特定Id时,会用到hash
//合约中,hash值是个32位定长的bytes
contract HashFunc{
//encodePacked打包会压缩
function hash(string memory text,uint num,address addr) external pure returns (bytes32){
//正常签名,一般需要三个参数,按如下格式传入
//中间加入num,是为了隔开text和addr,防止因encodePacked压缩结果造成的hash碰撞
return keccak256(abi.encodePacked(text,num,addr));
}
//不压缩结果,返回不定长,会补0
function encode(string memory text0,string memory text1) external pure returns (bytes memory){
return abi.encode(text0,text1);
}
//压缩结果,会去掉0,容易产生hash碰撞
function encodePacked(string memory text0,string memory text1) external pure returns (bytes memory){
return abi.encodePacked(text0,text1);
}
//直接使用encodePacked生成的hash,容易发生碰撞。解决方式:
//1. 使用encode,但gas消耗会多
//2. 依旧使用encodePacked,但在text0和text1之间加一个数字num参数,相当于使用三个参数生成hash
function collision(string memory text0,string memory text1) external pure returns (bytes32){
return keccak256(abi.encodePacked(text0,text1));
}
}

3.2 验证签名

签名流程:

  1. 将消息签名
  2. 将签名后的消息进行hash
  3. 将签名并且hash后的消息和私钥进行签名 该行为是链下操作
  4. ecrecover(hash(message),signature)==signer 恢复签名。ecrecover通过hash后的消息和链下签名恢复地址,检查地址是否为期望地址

以下为用于验证签名的合约(链下签名自行实现):

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
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;
/*
签名流程:
1. 将消息签名
2. 将签名后的消息进行hash
3. 将签名并且hash后的消息和私钥进行签名 该行为是链下操作
4. ecrecover(hash(message),signature)==signer 恢复签名。ecrecover通过hash后的消息和链下签名恢复地址,检查地址是否为期望地址
*/
contract VerifySig{
//传入:
//签名人地址
//消息,这里一个字符串
//链下签名
function verify(address _signer,string memory _message,bytes memory _sig) external pure returns(bool){
//两次hash是为了提高安全性,有第三方认为,一次签名容易被破解
bytes32 messageHash = getMessageHash(_message);
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
return recover(ethSignedMessageHash,_sig) == _signer;
}
function getMessageHash(string memory _message) public pure returns (bytes32){
return keccak256(abi.encodePacked(_message));
}
function getEthSignedMessageHash(bytes32 _messageHash) public pure returns (bytes32){
return keccak256(abi.encodePacked("\x19Ethereum Singed Message:\n32",_messageHash));
}
function recover(bytes32 _ethSignedMessageHash,bytes memory _sig) public pure returns (address){
(bytes32 r,bytes32 s, uint8 v) = _split(_sig);
return ecrecover(_ethSignedMessageHash,v,r,s);
}
//_sig总共65位,其中:
//r 32位
//s 32位
//v 1位
function _split(bytes memory _sig) internal pure returns(bytes32 r,bytes32 s,uint8 v){
require(_sig.length == 65,"invalid signature length");
assembly{
//汇编中,前32位不取
r := mload(add(_sig,32)) //跳过32位后,取32位
s := mload(add(_sig,64)) //跳过64位后,取32位
v := byte(0,mload(add(_sig,96))) //跳过96位后,取剩余的
}
}
}

4. 合约自毁

selfdestruct(payable(msg.sender));,其中msg.sender是只接收eth的地址,有两个作用:

  1. 删除合约,就是自毁
  2. 自毁同时,强制发送eth到指定地址,也就是为了安全,强制转移走eth
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;
contract Kill{
constructor() payable{}
function kill() external{
//msg.sender表示,合约只要自毁,就会强制把当前合约剩余的eth转移给msg.sender
selfdestruct(payable(msg.sender));
}
//用于测试该合约是否被销毁
function testCall() external pure returns(uint){
return 123;
}
}
contract Helper {
function getBalance() external view returns (uint){
return address(this).balance;
}
//会自毁的同时,将Kill合约剩余的eth强制转移给Helper合约
function kill(Kill _kill) external{
_kill.kill();
}
}

5. 函数签名-底层逻辑

本质意思是,hash的固定前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
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;

contract FunctionSelector{
//可以获取方法的选择器,即头部hash,固定获取头部4字节
//传入 "transfer(address,uint256)"
//可得到结果:0xa9059cbb 与下面Receiver合约中的Log结果一致
function getSelector(string calldata _func) external pure returns (bytes4){
return bytes4(keccak256(bytes(_func)));
}
}

contract Receiver{
event Log(bytes data);
function transfer(address _to,uint _amount) external{
emit Log(msg.data);
//虚拟机中,事件会打印如下信息:
//0xa9059cbb0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4000000000000000000000000000000000000000000000000000000000000000b
//将该信息拆分为三部分:
//0xa9059cbb 表示函数选择器,即代表transfer函数,固定占有4个字节
//0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc //第一个参数
//4000000000000000000000000000000000000000000000000000000000000000b //第二个参数
}
}

6. 通过合约部署合约-create2

注意和通过合约调用合约进行区分。该方式是直接通过合约部署新的合约。

6.1 通过new部署合约

可以方便随时部署,
缺点是:每次new在不同的链上存储位置进行部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;

contract DeployWithCreate2{
address public owner;
constructor(address _owner){
owner = _owner;
}
}

contract Create2Factory{
event Deploy(address addr);
function deploy() external{
DeployWithCreate2 _contract = new DeployWithCreate2(msg.sender);
emit Deploy(address(_contract)); //获取到部署的合约地址
}
}

6.2 通过create2部署合约

低版本中,需要通过内联,显式调用create2来部署合约。但8.0后简化了逻辑。具体看如下。
借用了salt进行部署,只要salt不变,则表示链上该位置不能重复部署。除非该位置的合约被销毁了。
简单说,只要salt不变,则合约地址不变。
其中会涉及到个bytecode,这是机器码,也就是合约编译后的完整结果。

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
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;
contract DeployWithCreate2{
address public owner;
constructor(address _owner){
owner = _owner;
}
}

//工厂合约
contract Create2Factory{
event Deploy(address addr);
function deploy(uint _salt) external{
DeployWithCreate2 _contract = new DeployWithCreate2{
salt: bytes32(_salt)
}(msg.sender);
emit Deploy(address(_contract)); //获取到部署的合约地址
}
//用于计算合约地址
//bytecode是要部署的合约的机器码(该机器码其实)
function getAddress(bytes memory bytecode,uint _salt) public view returns(address){
bytes32 hash = keccak256(
abi.encodePacked(
//四个参数:固定的0xff、合约地址、salt、机器码
bytes1(0xff), address(this), _salt, keccak256(bytecode)
)
);
return address(uint160(uint(hash)));
}
//获取机器码,机器码相当于是要部署的合约编译后的完整内容
//传入合约初始化时需要的参数
function getBytecode(address _owner) public pure returns(bytes memory){
bytes memory bytecode = type(DeployWithCreate2).creationCode;
return abi.encodePacked(bytecode,abi.encode(_owner));
}
}

7. 多重调用

7.1 基本的Multi Call

  1. 是指把对一个或者多个合约的多次调用打包整合在一个交易中,然后再去调用合约,优点:
    比如前端短时间需要对合约几十次调用,而一个链的rpc节点又限制了调用次数,比如20秒限制只能调用一次。此时,如果能把多次调用整合成一次调用,可以提高效率。
  2. 因为链上的延迟,多次调用,获取到的链上的结果可能有所不同,比如获取块高度。打包后调用,将会获取到同一个块的结果。

使用该方式的不足之处是,被调用合约TestMultiCallmsg.sender表示的是合约MultiCall的地址,并不是调用合约MultiCall的真实用户的地址。

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
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;
contract TestMultiCall{
function func1() external view returns (uint,uint){
return (1,block.timestamp);
}
function func2() external view returns (uint,uint){
return (2,block.timestamp);
}
function getData1() external pure returns (bytes memory){
//获取选择器
//等价于:abi.encodeWithSignature("func1()")
return abi.encodeWithSelector(this.func1.selector);
}
function getData2() external pure returns (bytes memory){
return abi.encodeWithSelector(this.func2.selector);
}
}
contract MultiCall{
//data 表示编码后的要调用的方法和内容,该数据可以通过链下计算获取,或者合约中实现该计算
//比如上面TestMultiCall的func1()方法,转换成data后,即可通过multiCall来调用
function multiCall(address[] calldata targets,bytes[] calldata data) external view returns(bytes[] memory){
require(targets.length == data.length,"target length != data length");
bytes[] memory results = new bytes[](data.length);
for (uint i;i<targets.length;i++){
//需要用staticcall,而不是call。是因为call可能会产生动态写入,与multiCall的view属性不一致
//result结果是abi编码返回的
(bool success,bytes memory result) = targets[i].staticcall(data[i]);
require(success,"call failed");
results[i] = result; //记录返回结果
}
return results;
}
}

7.2 多重委托调用Multi Delegate Call

为了解决上面的Multi Call的不足之处:被调用合约的msg.sender表示的是上一个合约的地址,并不是进行调用的用户的地址。
通过委托调用,msg.sender在不同合约中会始终表示的是调用人的真是地址。
缺点是:

  1. 委托合约和业务合约,都需要是在同一个项目中。继承关系,可以看下方代码
  2. 警告:不建议在多重委托调用中接收处理代币逻辑。比如,只传入一笔token,但重复调用3次转账,会凭空出来两笔token。如果非要在多重委托调用中进行代币业务处理,那业务逻辑一定要规划好。
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
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;
contract MultiDelegatecall{
error DelegatecallFailed();
function multiDelegatecall(bytes[] calldata data) external payable returns(bytes[] memory results){
results = new bytes[](data.length);
for (uint i;i<data.length;i++){
(bool ok,bytes memory res) = address(this).delegatecall(data[i]);
if (!ok){
revert DelegatecallFailed();
}
results[i] = res;
}
}
}
contract TestMultiDelegatecall is MultiDelegatecall{
event Log(address caller, string func, uint i);
function func1(uint x, uint y) external{
emit Log(msg.sender,"func1", x+y);
}
function func2() external returns (uint){
emit Log(msg.sender,"func2",2);
return 111;
}
}
contract Helper {
function getFunc1Data(uint x, uint y) external pure returns (bytes memory){
return abi.encodeWithSelector(TestMultiDelegatecall.func1.selector,x,y);
}
function getFunc2Data() external pure returns (bytes memory){
return abi.encodeWithSelector(TestMultiDelegatecall.func2.selector);
}
}

8. ABI编码和解码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;
contract AbiDecode{
struct MyStruct{
string name;
uint[2] nums;
}
function encode(
uint x,
address addr,
uint[] calldata arr,
MyStruct calldata myStruct
) external pure returns(bytes memory) {
return abi.encode(x,addr,arr,myStruct);
}
function decode(bytes calldata data) external pure returns(
uint x,
address addr,
uint[] memory arr,
MyStruct memory myStruct
) {
(x,addr,arr,myStruct) = abi.decode(data,(uint,address,uint[],MyStruct));
}
}

9. gas优化

当然,如果能通过evm内联汇编来优化是更好,但可读性低。折中后,在现有代码逻辑中优化会更稳妥一些。
通过案例来观察优化细节:

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
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;

contract GasGolf{
/*
初始gas:50992
第一次优化:将memory 改为calldata gas消耗:49163
第二次优化,引入_total内存变量,降低原先循环更新total频率,即降低写入storage频率 gas消耗:48952
第三次优化,短路。将两个判断直接写入if语句中,第一个判断失败就不会执行第二个判断。 gas消耗:48634
第四次优化,将i+=1改为++i gas消耗:48244
第五次优化,缓存数组长度 gas消耗:48209
第六次优化,将数组元素提前拷贝到内存中 gas消耗:48047
*/
uint public total;

//输入参数:[1,2,3,4,5,100]
function sumIfEvenAndLessThan99(uint[] calldata nums) external{
uint _total = total;
uint len = nums.length;
for (uint i=0;i<len;++i){
uint num = nums[i];
if (num%2 ==0 && num<99){
_total += num;
}
}
total = _total;
}
}

12. 综合案例-时间锁合约

队列中交易,只有到了指定时间,才能手动触发

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract TimeLock{
error NotOwnerError();
error AlreadyQueuedError(bytes32 txId);
error TimestampNotInRangeError(uint blockTimestamp,uint timestamp);
error NotQueuedError(bytes32 txId);
error TimestampNotPassedError(uint blockTimestamp,uint timestamp);
error TimestampExpiredError(uint blockTimestamp,uint expireAt);
error TxFailedError();

event Queue(bytes32 indexed txId, address indexed target,uint value,string func,bytes data,uint _timestamp);
event Execute(bytes32 indexed txId, address indexed target,uint value,string func,bytes data,uint _timestamp);
event Cancel(bytes32 indexed txId);
uint public constant MIN_DELAY = 10; //秒
uint public constant MAX_DELAY = 1000;
uint public constant GRACE_PERIOD = 1000;
address public owner;
mapping(bytes32=>bool) public queued;

constructor(){
owner = msg.sender;
}
receive() external payable{

}
modifier onlyOwner(){
if (msg.sender != owner){
revert NotOwnerError();
}
_;
}

function getTxId(address _target,uint _value,string calldata _func,bytes calldata _data,uint _timestamp) public pure returns (bytes32 txId){
return keccak256(abi.encode(_target,_value,_func,_data,_timestamp));
}

//_target:目标合约地址
//_value:数值,
//_func:要执行的方法名称
//_data:要操作的具体数据
//_timestamp:时间戳
function queue(address _target,uint _value,string calldata _func,bytes calldata _data,uint _timestamp) external onlyOwner{
//当前交易id
bytes32 txId = getTxId(_target,_value,_func,_data,_timestamp);
//检查id是否存在
if (queued[txId]){
revert AlreadyQueuedError(txId);
}
//检查交易时间戳,即交易要执行的时间,单位秒
if(_timestamp<block.timestamp+MIN_DELAY || _timestamp>block.timestamp+MAX_DELAY){
revert TimestampNotInRangeError(block.timestamp,_timestamp);
}
queued[txId] = true;
emit Queue(txId,_target,_value,_func,_data,_timestamp);
}
function execute(address _target,uint _value,string calldata _func,bytes calldata _data,uint _timestamp) external payable onlyOwner returns(bytes memory){
//当前交易id
bytes32 txId = getTxId(_target,_value,_func,_data,_timestamp);
//检查txid是否有效
if (queued[txId]){
revert NotQueuedError(txId);
}
//检查时间戳 block.timestamp >_timestamp
if(block.timestamp<_timestamp){
revert TimestampNotPassedError(block.timestamp,_timestamp);
}
if(block.timestamp>_timestamp+GRACE_PERIOD){ //超时1000秒依旧可以执行
revert TimestampExpiredError(block.timestamp,_timestamp+GRACE_PERIOD);
}
//队列中删除交易
queued[txId] = false;
//执行
bytes memory data;
if (bytes(_func).length>0){
data = abi.encodePacked(bytes4(keccak256(bytes(_func))),_data);
}else{
data = _data;
}
(bool ok,bytes memory res) = _target.call{value:_value}(data);
if(!ok){
revert TxFailedError();
}
emit Execute(txId,_target,_value,_func,_data,_timestamp);
return res;
}

function cancel(bytes32 _txId) external onlyOwner{
if (!queued[_txId]){
revert NotQueuedError(_txId);
}
queued[_txId] = false;
emit Cancel(_txId);
}
}

contract TestTimeLock{
address public timeLock;
constructor(address _timeLock){
timeLock = _timeLock;
}
function test() external{
require(msg.sender == timeLock);
}

function getTimestamp() external view returns(uint){
return block.timestamp +100;
}
}

13. 合约安全

  1. 防重入攻击,比如攻击者利用fallback()反复攻击获取资产。解决方案:
    1. 先变更状态,再执行操作。比如,先改变余额,再执行转账
    2. 利用重入锁:
1
2
3
4
5
6
7
bool internal locked;
modifier noReentrant(){
require(!locked,"No re-entrancy");
locked = true;
_;
locked = false
}
  1. 数学溢出攻击
    为防止上溢或下溢,需要使用安全数学库进行操作,openZepplin有提供

12. 总结

本文编辑完毕

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2017-2023 Jason
  • Visitors: | Views:

谢谢打赏~

微信