合约如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
struct User{
uint8 userType;
uint8 age;
}
contract Test {
User[] public users;
mapping(address => User) public user;
function testCall(bytes memory _payload) external {
(bool success, ) = address(this).call(_payload);
require(success, "call fail.");
}
function testCalled(address[] memory _addr, User[] memory _user) external {
require(_addr.length == _user.length, "Len is not the same.");
for (uint8 i = 0; i < _user.length; i++) {
user[_addr[i]] = _user[i];
users.push(_user[i]);
}
}
function getCallBytes() external pure returns (bytes memory){
address[] memory _addrs = new address[](1);
_addrs[0] = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
User memory _user = User(1,25);
User[] memory _users = new User[](1);
_users[0] = _user;
bytes memory payload = abi.encodeWithSignature("testCalled(address[],(uint8,uint8)[])", _addrs, _users);
return payload;
}
}
在Remix中进行测试,通过测试函数 getCallBytes 生成payload,此函数中参数值是固定的
在etherjs 测试合约时,需要生成上面合约函数getCallBytes生成的payload,参数值都相同,写法如下:
const { expect } = require('chai');
const { ethers } = require('hardhat');
describe("测试合约", function () {
let testContract;
let owner;
beforeEach(async () => {
[owner] = await ethers.getSigners();
console.log("owner地址: ", owner.address);
console.log("================ 部署合约 ==================");
const TestContract = await ethers.getContractFactory("Test");
testContract = await TestContract.deploy();
//验证合约地址
expect(testContract).to.not.equal(ethers.constants.AddressZero);
console.log("合约地址: ", testContract.address);
});
it("1、测试指令集", async function () {
console.log("================ 生成指令集 ==================");
//指令集 - 函数参数 - 账户地址
const newAddrs = [owner.address];
//指令集 - 函数参数 - 用户信息
const newUsers = [
{
userType: 1,
age: 25
}
]
//指令集 - Solidity中函数的名称和参数类型
const functionName = "testCalled";
const functionParamType = ["address[]", "(uint8,uint8)[]"];
//指令集 - 用于编码函数名称和参数类型的selector
const selector = ethers.utils.hexDataSlice(ethers.utils.keccak256(ethers.utils.toUtf8Bytes(`${functionName}(${functionParamType.join(',')})`)), 0, 4);
//指令集 - 用于编码函数参数的ABI编码器
const encodedData = ethers.utils.defaultAbiCoder.encode(
["address[]", "(uint8 userType, uint8 age)[]"],
[newAddrs, newUsers]
);
//指令集 - 构造包含函数选择器的payload
const payload = selector + encodedData.substr(2); // 去除前面的'0x'
console.log("payload:", payload);
console.log("================ 调用合约方法 ==================");
//调用合约方法
await testContract.testCall(payload);
console.log("================ 验证执行方法结果 ==================");
//验证用户是否已加
const user = await testContract.user(owner.address);
expect(user.userType).to.be.equal(newUsers[0].userType);
expect(user.age).to.be.equal(newUsers[0].age);
console.log("用户信息", user);
});
});
测试结果如下,打印出payload, 与合约函数getCallBytes生成的payload是一致的。
注:使用call调用的函数需要是external,即上面的testCalled函数是external