solidity基础语法与简单案例20221204

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;
    }
}

Guess you like

Origin blog.csdn.net/H_Roger/article/details/128169321