1、MultiCall
一个RPC节点限制了客户端对链的调用,在20秒间隔之内只能调用一次,如果需要多次调用方法,那么可以将多次调用命令打包,仅对RPC节点进行一次调用。
因此,本节使用一种方法实现了使用一个Call同时调用一个或多个合约的多个方法的例子。
- 选择器编码
- 本节采用了选择器编码的方式生成目标方法的机器码(abi.encodeWithSelector)
- abi.encodeWithSelector(this.fun1.selector);
- 优点:不需要以字符串形式输入函数名称及参数
- 静态调用
- 本节在调用目标合约的方法时,采用了静态调用的方法,而不是动态调用call
- Call作为动态调用,在同时调用多个方法时可能会产生bug
- 传入数组参数
- 在传入数组参数时,采用["A","B"]的格式
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;
contract TestMultiCall{
function fun1() external view returns(uint,uint){
return(1,block.timestamp);
}
function fun2() external view returns(uint,uint){
return(2,block.timestamp);
}
function getfun1() external pure returns (bytes memory){
//abi.encodeWithSignature("fun1()")
//获取fun1的机器码
return abi.encodeWithSelector(this.fun1.selector);
}
function getfun2() external pure returns (bytes memory){
return abi.encodeWithSelector(this.fun2.selector);
}
}
contract 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会采用动态的写入调用方法
//这条语句的意思是:对于目标地址target[i],使用静态方式staticcall()传入机器码data,并返回调用是否成功以及调用的结果
(bool success,bytes memory result) = targets[i].staticcall(data[i]);
//在使用底层call调用方法时,可以使用require确认方法结果,如果调用失败终止程序
require(success,"call failed");
results[i] = result;
}
return results;
}
}
2、ABI解码
对已经成为机器码的数据进行解码,在解码时应知道数组类型
abi.decode(data,(uint,address,uint[]))
- 输入结构体数据:
- 结构体数据输入时为一个多维数组
- 例:
- 代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;
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));
}
}
结果:
3、gas优化
(1)内存变量的运算成本低于存储变量
- memory改为calldata
- 对于某个方法内多次调用的变量,设定内存变量,并在最后将内存变量一次写入存储变量中
(2)不进行二次赋值
- 直接使用判断条件,不再进行布尔赋值
(3)小技巧
- 把i++换为++i
(4)缓存数组的长度
在循环中,每次循环都要读取数组长度,因此需要缓存数组的长度以减少gas浪费。
(5)将数组元素提前赋值在内存变量中
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;
contract notGasGolf{
uint public total;
function sumIfEvenAndlessThen99(uint[] memory nums) external {
for (uint i = 0; i<nums.length; i+=1){
bool isEven = nums[i] % 2 == 0;
bool isLessThan99 = nums[i] < 99;
if(isEven && isLessThan99){
total += nums[i];
}
}
}
}
contract GasGolf{
uint public total;
function sumIfEvenAndlessThen99(uint[] memory 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;
}
}
4、时间锁合约
经常用在Dapp和Defi上,用于保护管理员权限,如果针对合约进行重要操作,排在队列中等待48小时或更长时间,如果发现该操作有作恶行为,那么通过时间锁合约能够及时取消。
任何用户部署合约都需要具有锁定期。
交易延迟:在最小的交易延迟之后和最大的交易延迟之前能够执行合约
(1)将交易推入等待队列
(2)到达时间后可执行交易
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;
contract TimeLock{
error NotOwnerError();
error AlreadyQueueError(bytes32 txId);
error timestampNotInRangeError(uint blockTimestamp,uint timestamp);
error NotQueuedError(bytes32 txId);
error timestampNotPassedError(uint blockTimestamp,uint timestamp);
error TimestampExpiredError(uint blockTimestamp,uint timestamp);
error TxFailedError();
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;
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 txId);
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
)
);
}
function queue(
address _target,
uint _value,
string calldata _func,
bytes calldata _data,
uint _timestamp
) external onlyOwner {
//create tx id
bytes32 txId = getTxId(_target,_value,_func,_data,_timestamp);
//check tx id unique
if (queued[txId]){
revert AlreadyQueueError(txId);
}
//check timestamp
if (_timestamp < block.timestamp + MIN_DELAY || _timestamp > block.timestamp + MAX_DELAY){
revert timestampNotInRangeError(block.timestamp,_timestamp);
}
//queue tx
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){
bytes32 txId = getTxId(_target,_value,_func,_data,_timestamp);
//check txId is in queue
if(!queued[txId]){
revert NotQueuedError(txId);
}
//check block.timestamp > _timestamp
if(block.timestamp < _timestamp){
revert timestampNotPassedError(block.timestamp,_timestamp);
}
//block.timestamp < _timestamp+grace
if(block.timestamp > _timestamp + GRACE_PERIOD){
revert TimestampExpiredError(block.timestamp,_timestamp + GRACE_PERIOD);
}
//delete tx from queue
queued[txId] = false;
//execute the tx
bytes memory data;
//判断函数是否是对方合约的回退函数
if(bytes(_func).length > 0){
data = abi.encodePacked(
bytes4(keccak256(bytes(_func)))
);
}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 test{
uint public value;
function Test() public {
value = 10;
}
function getTimestamp() external view returns(uint){
return block.timestamp +100;
}
}