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.encodeCall
与string.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.js
和GlobalVariables-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 教程系列8 - Solidity API