Solidity全局变量完全测试

Posted MateZero

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Solidity全局变量完全测试相关的知识,希望对你有一定的参考价值。

Solidity全局变量完全测试

我们知道,在Solidity中有很多全局变量,例如我们最常用的msg.sender, block.timestamp等。但是有一些我们平常基本接触不到,例如:type(C).name等。本着凡事最怕认真两字的原则,虽然繁琐,但我们将所有的全局变量全部测试一遍,学习怎么调用和应用在哪些场景,进一步加深理解与记忆。

本文基于Solidity 0.8.9版本与hardhat工具进行,在最新的0.8.13版本增加了两个全局变量abi.encodeCallstring.concat,因当前版本的hardhat暂不支持 Solidity 0.8.13,故没有进行这两项测试。
另外,有少数项目也不方便测试,期待有人能改进完善测试方法。

测试合约

我们的测试合约如下:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.9;

interface IERC721 
    function balanceOf(address _owner) external view returns (uint256);
    function ownerOf(uint256 _tokenId) external view returns (address);
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external payable;
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
    function approve(address _approved, uint256 _tokenId) external payable;
    function setApprovalForAll(address _operator, bool _approved) external;
    function getApproved(uint256 _tokenId) external view returns (address);
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);


contract NoReceiveEth 
    function originTest(address instance) public view returns(address) 
        return GlobalVariables(instance).originTest();
    


contract Base 
    function getUint(uint a) public pure virtual returns(uint) 
        return a;
    


/// 全局变量测试
contract GlobalVariables is Base

    event SendEther(address indexed receipt,uint amount, bool result);
    event ReturnBaseFee(uint fee);
    event GasLeft(uint gas);
    event SelfDestruct(address indexed recipient);

    // 解码数据
    // 这里只适合类型提前知道的情况,因为Solidity类型无法作为参数,所以这里采用了固定类型进行测试
    function decodeTest(bytes memory data) public pure returns(uint a, uint[2] memory b, bytes memory c) 
        (a,b,c) = abi.decode(data,(uint, uint[2], bytes));
    

    //编码数据,这里可以和解码数据联合测试
    function encodeTest(uint a, uint[2] memory b, bytes memory c) public pure returns(bytes memory) 
        return abi.encode(a,b,c);
    

    //压缩编码,注意它会引起混淆,一般用于哈希计算时,也就是不需要解码操作
    function encodePackedTest(uint a, uint[2] memory b, bytes memory c) public pure returns(bytes memory) 
        return abi.encodePacked(a,b,c);
    

    //根据函数选择器编码,其实就是函数选择器加上前面的编码,通常用于底层函数调用,例如SafeTransfer。
    function encodeWithSelectorTest(bytes4 selector,uint a, uint[2] memory b, bytes memory c) public pure returns(bytes memory) 
        return abi.encodeWithSelector(selector, a,b,c);
    

    // 0.8.13版本才有,hardhat暂不支持这么高版本,故skip
    // function(uint, uint[2] memory, bytes memory ) functionPointer;
    // //直接根据函数编码,其结果和encodeWithSelector相同,但会多一个参数类型检查
    // function encodeCallTest(
    //     uint a, 
    //     uint[2] memory b, 
    //     bytes memory c
    // ) public pure returns(bytes memory) 
    //     return abi.encodeCall(functionPointer,a,b,c);
    // 


    //根据函数名称编码,这里其实给出了函数选择器的计算方法: bytes4(keccak256(bytes(signature))
    // 结果和 abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...) 相同
    // 注意signature的写法:例如: "testFunc(uint256,uint256[2],bytes)"
    function encodeWithSignatureTest(
        string memory signature,
        uint a, 
        uint[2] memory b, 
        bytes memory c
    ) public pure returns(bytes memory) 
        return abi.encodeWithSignature(signature,a,b,c);
    

    // 连接bytes 、bytes1 - bytes32,注意,它并不会padding
    function bytesConcatTest(bytes memory a, bytes memory b, bytes memory c)  public pure returns(bytes memory) 
        return bytes.concat(a,b,c);
    


    // //字符串连接,很直观 0.8.13版本才有,hardhat暂不支持这么高版本,故skip
    // function stringConcatTest(string memory a, string memory b, string memory c) public pure returns(string memory) 
    //     return string.concat(a,b,c);
    // 

    // EIP-1559 基础fee 
    function basefeeTest() public returns(uint) 
        emit ReturnBaseFee(block.basefee);
        return block.basefee;
    

    // 以下是获取各种区块信息
    function chainidTest() public view returns(uint) 
        return block.chainid;
    

    function coinbaseTest() public view returns(address payable) 
        return block.coinbase;
    

    function difficultyTest() public view returns(uint) 
        return block.difficulty;
    

    function gaslimitTest() public view returns(uint) 
        return block.gaslimit;
    

    function numberTest() public view returns(uint) 
        return block.number;
    

    function timestampTest() public view returns(uint) 
        return block.timestamp;
    

    // 不是很好测试
    function gasleftTest() public returns(uint) 
        uint gas = gasleft();
        emit GasLeft(gas);
        return gas;
    

    function msgDataTest(uint , uint[2] memory , bytes memory ) public pure returns(bytes memory) 
        return msg.data;
    

    function msgSenderTest() public view returns(address) 
        return msg.sender;
    

    function msgSigTest(uint , uint[2] memory , bytes memory ) public pure returns(bytes4) 
        return msg.sig;
    

    function msgValueTest() payable public returns(uint) 
        emit SendEther(address(this),msg.value,true);
        return msg.value;
    

    function gasPriceTest() public view returns(uint) 
        return tx.gasprice;
    

    function originTest() public view returns(address) 
        require(msg.sender == tx.origin, "Sender is a contract");
        return tx.origin;
    

    // 抛出异常
    function assertTest(uint a) public pure returns(bool) 
        assert(a > 5);
        return true;
    

    function requireTest(uint a) public pure returns(bool) 
        require(a > 5);
        return true;
    

    function requireTest(uint a , string memory reason) public pure returns(bool) 
        require(a > 5 , reason);
        return true;
    

    function revertTest(bool isRevert) public pure returns(bool) 
        if(isRevert) 
            revert();
        
        return true;
    

    function revertTest(bool isRevert, string memory reason) public pure returns(bool) 
        if(isRevert) 
            revert(reason);
        
        return true;
    

    // 加密算法
    function blockhashTest(uint blockNumber) public view returns(bytes32) 
        return blockhash(blockNumber);
    

    function keccak256Test(bytes memory data) public pure returns(bytes32) 
        return keccak256(data);
    

    function sha256Test(bytes memory data) public pure returns(bytes32) 
        return sha256(data);
    

    function ripemd160Test(bytes memory data) public pure returns(bytes20) 
        return ripemd160(data);
    

    // 注意这里第一个参数为messageHash,不是message
    function ecrecoverTest(bytes32 hash, uint8 v, bytes32 r, bytes32 s) public pure returns(address) 
        return ecrecover(hash,v,r,s);
    

    // 注意这里重点是可溢出
    function addmodTest(uint x, uint y, uint k) public pure returns(uint) 
        return addmod(x,y,k);
    

    function mulmodTest(uint x, uint y, uint k) public pure returns(uint) 
        return mulmod(x, y, k);
    

    // this 代表本合约
    function thisTest() public view returns(address) 
        return address(this);
    

    // 注意 this.call代表一个外部调用而不是内部跳转
    function thisCallTest() public view returns(address) 
        return this.msgSenderTest();
    

    // 调用父合约函数
    function superTest(uint a) public pure returns(uint) 
        if(a > 10) 
            return super.getUint(a);
         else 
            return a - 2;
        
    

    // 自杀,注意这里转移ETH会无视合约是否接收ETH
    function selfdestructTest(address payable recipient) public 
        emit SelfDestruct(recipient);
        selfdestruct(recipient);
        // 注意,这后面的语句可能无法执行
    

    function balanceTest() public view returns(uint) 
        return address(this).balance;
    

    //这里的code不知道该怎么测试
    function codeTest() public view returns(bytes memory) 
        return address(this).code;
    

    function codehashTest() public view returns(bytes32) 
        return address(this).codehash;
    

    // 注意发送失败返回falses
    function sendTest(address payable recipient, uint amount) public returns(bool result) 
        result = recipient.send(amount);
        emit SendEther(recipient,amount,result);
    

    // 发送失败会重置
    function transferTest(address payable recipient, uint amount) public 
        recipient.transfer(amount);
        emit SendEther(recipient,amount,true);
    

    // 下面的信息编译后可获取
    function contractNameTest() public pure returns(string memory) 
        return type(GlobalVariables).name;
    

    function creationCodeTest() public pure returns(bytes memory) 
        return type(Base).creationCode;
    

    function runtimeCodeTest() public pure returns(bytes memory) 
        return type(Base).runtimeCode;
    

    /**
    *    0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^
         0xa22cb465 ^ 0xe985e9c ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde = 0x80ac58cd
     */
    function interfaceIdTest() public pure returns(bytes4) 
        return type(IERC721).interfaceId; // 0x80ac58cd
    

    // 最小值,最大值,常用来替代uint(-1)
    function minTest() public pure returns(int256) 
        return type(int256).min;
    

    function maxTest() public pure returns(uint256) 
        return type(uint).max;
    

合约中部分知识点有简单注释。现在合约有了,我们要开始调用了,当然单元测试是最适合这项工作的。

单元测试文件

因为测试的内容较多,我们新建了两个单元测试文件
GlobalVariables-01.jsGlobalVariables-02.js

// GlobalVariables-01.js
const  expect, assert  = require("chai");
const  ethers  = require("hardhat");

describe("GlobalVariables Test 01", function () 

    let instance;
    let owner,user,addrs;
    let test_contract;

    const abi = [
        "function testFunc(uint a, uint[2] memory b, bytes memory c) public view returns(uint256)" 
    ]
    const interface = new ethers.utils.Interface(abi);

    const args = [
        10245,
        [2345,1098],
        "0x12345678"
    ]
    const abiCode = new ethers.utils.AbiCoder();

    before( async function() 
        const GlobalVariables = await ethers.getContractFactory("GlobalVariables");
        instance = await GlobalVariables.deploy();

        const NoReceiveEth = await ethers.getContractFactory("NoReceiveEth");
        test_contract = await NoReceiveEth.deploy();

        [owner, user, ...addrs] = await ethers.getSigners();
    );

    describe("Encode and Decode test", function() 
        it("Encode test", async () => 
            let data = abiCode.encode(
                ["uint","uint[2]","bytes"],
                args
            )
            expect(await instance.encodeTest(...args)).to.be.equal(data)
        )

        it("Decode test", async () => 
            let data = abiCode.encode(
                ["uint","uint[2]","bytes"],
                args
            )
            const [a,b,c] = await instance.decodeTest(data);
            assert.equal(a,args[0])
            assert.equal(b[0],args[1][0])
            assert.equal(b[1],args[1][1])
            assert.equal(c,args[2])
        );

        it("EncodePacked test", async () => 
            let data = abiCode.encode(
                ["uint","uint[2]"],
                [args[0],args[1]]
            )
            data += args[2].substring(2,args[2].length);
            expect (await instance.encodePackedTest(...args)).to.be.equal(data);
        )

        it("EncodeWithSelecto

以上是关于Solidity全局变量完全测试的主要内容,如果未能解决你的问题,请参考以下文章

solidity语法3——全局变量,表达式,控制结构

03-Solidity8.0变量

solidity:1. 变量和常量

智能合约语言 Solidity 教程系列8 - Solidity API

智能合约语言 Solidity 教程系列8 - Solidity API

基于以太坊的智能合约开发教程Solidity 继承与权限