一种非常简单的模拟Solidity智能合约交易的方法
我们知道,在MetaMask调用合约时,会模拟执行一次,如果调用失败,会提前显示失败并问你是否要强制执行。这个功能很有用的,那么我们自己能不能实现类似的功能呢?
答案是肯定的,并且也相当简单。
示例合约我们先看测试合约:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MockTest {
uint public x = 5;
address public owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner, "only owner");
_;
}
function changeX(uint _x) external onlyOwner returns(uint value, address sender) {
x = _x;
return (_x * _x,msg.sender);
}
}
合约很简单,只有管理员能改变x的值并且进行相关计算(操作)。这里只是简单计算了x
的平方来模拟合约中复杂的操作过程。
那我们有时候想自己模拟运行一下,并得到相应的操作结果,能否实现呢?
答案就在下方 -_- !
模拟方法请看下面的单元测试文件:
const { expect,assert } = require("chai");
const { ethers } = require("hardhat");
describe("Mock test", function () {
let instance;
beforeEach(async () => {
const MockTest = await ethers.getContractFactory("MockTest");
instance = await MockTest.deploy();
});
it("Call without owner will be failed" , async () => {
let data = instance.interface.encodeFunctionData("changeX",[9]);
let AddressOne = ethers.utils.getAddress("0x" + "0".repeat(39) + "1");
let transaction = {
from:AddressOne,
to:instance.address,
data
};
try {
await ethers.provider.call(transaction);
}catch(e) {
assert.equal(e.message,"VM Exception while processing transaction: reverted with reason string 'only owner'");
return;
}
throw("Test Failed");
});
it("Call with owner will be successful", async () => {
let data = instance.interface.encodeFunctionData("changeX",[9]);
let owner = await instance.owner();
let transaction = {
from:owner,
to:instance.address,
data
};
let result = await ethers.provider.call(transaction);
const {value,sender} = instance.interface.decodeFunctionResult("changeX",result);
expect(value).to.be.equal(81);
expect(sender).to.be.equal(owner);
expect(await instance.x()).to.be.equal(5);
});
});
运行单元测试的结果为:
Mock test
✔ Call without owner will be failed (40ms)
✔ Call with owner will be successful
2 passing (619ms)
那么,有的小伙伴要问了,如果别人把这个owner
设置为私有变量,我怎么去获取这个owner
呢?
记住,私有变量只是对链上合约不可见,对链下是没有办法隐藏的。以太坊一切可是公开透明的。
例如我们可以通过观察合约部署者,平常具有管理员权限的函数的调用,直接读取相应插槽等方法来获取这个owner
。