【发布时间】:2021-09-07 14:11:22
【问题描述】:
上下文
在尝试设置用于测试 this Chainlink VRF random number contract 的基本自托管单元测试环境(和 CI)时,我在如何在本地模拟任何相关区块链/测试网时遇到了一些困难。
例如,我找到了this repository that tests Chainlinks VRF。但是,对于默认部署,它建议/需要免费的KOVAN_RPC_URL,例如来自 Infura 的网站,甚至对于“本地部署”,它建议/需要来自例如的免费 MAINNET_RPC_URL Alchemy 的网站。
尝试/基线
我采用了来自 waffle 框架的单元测试环境,描述为:
文件结构
src____AmIRichAlready.sol
|____RandomNumberConsumer.sol
|
test____AmIRichAlready.test.ts
|____mocha.opts
package.json
tsconfig.json
waffle.json
yarn.lock
文件内容
AmIRichAlready.sol
pragma solidity ^0.6.2;
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
}
contract AmIRichAlready {
IERC20 private tokenContract;
uint public richness = 1000000 * 10 ** 18;
constructor (IERC20 _tokenContract) public {
tokenContract = _tokenContract;
}
function check() public view returns (bool) {
uint balance = tokenContract.balanceOf(msg.sender);
return balance > richness;
}
// IS THIS NEEDED???
function setRichness(uint256 _richness) public {
richness = _richness;
}
}
RandomNumberConsumer.sol 文件内容已在 stackexange over here 上。
AmIRichAlready.test.ts
import {expect, use} from 'chai';
import {Contract, utils, Wallet} from 'ethers';
import {deployContract, deployMockContract, MockProvider, solidity} from 'ethereum-waffle';
import IERC20 from '../build/IERC20.json';
import AmIRichAlready from '../build/AmIRichAlready.json';
use(solidity);
describe('Am I Rich Already', () => {
let mockERC20: Contract;
let contract: Contract;
let vrfContract: Contract;
let wallet: Wallet;
beforeEach(async () => {
[wallet] = new MockProvider().getWallets();
mockERC20 = await deployMockContract(wallet, IERC20.abi);
contract = await deployContract(wallet, AmIRichAlready, [mockERC20.address]);
vrfContract = await deployContract(wallet, RandomNumberConsumer);
});
it('checks if contract called balanceOf with certain wallet on the ERC20 token', async () => {
await mockERC20.mock.balanceOf
.withArgs(wallet.address)
.returns(utils.parseEther('999999'));
await contract.check();
expect('balanceOf').to.be.calledOnContractWith(mockERC20, [wallet.address]);
});
it('returns false if the wallet has less than 1000000 coins', async () => {
await mockERC20.mock.balanceOf
.withArgs(wallet.address)
.returns(utils.parseEther('999999'));
expect(await contract.check()).to.be.equal(false);
});
it('returns true if the wallet has at least 1000000 coins', async () => {
await mockERC20.mock.balanceOf
.withArgs(wallet.address)
.returns(utils.parseEther('1000000'));
expect(await contract.check()).to.be.equal(false);
});
});
mocha.opts
-r ts-node/register/transpile-only
--timeout 50000
--no-warnings
test/**/*.test.{js,ts}
package.json
{
"name": "example-dynamic-mocking-and-testing-calls",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"test": "export NODE_ENV=test && mocha",
"build": "waffle",
"lint": "eslint '{src,test}/**/*.ts'",
"lint:fix": "eslint --fix '{src,test}/**/*.ts'"
},
"devDependencies": {
"@openzeppelin/contracts": "^4.3.1",
"@types/chai": "^4.2.3",
"@types/mocha": "^5.2.7",
"@typescript-eslint/eslint-plugin": "^2.30.0",
"@typescript-eslint/parser": "^2.30.0",
"chai": "^4.3.4",
"eslint": "^6.8.0",
"eslint-plugin-import": "^2.20.2",
"ethereum-waffle": "^3.4.0",
"ethers": "^5.0.17",
"mocha": "^7.2.0",
"ts-node": "^8.9.1",
"typescript": "^3.8.3"
}
}
tsconfig.json
{
"compilerOptions": {
"declaration": true,
"esModuleInterop": true,
"lib": [
"ES2018"
],
"module": "CommonJS",
"moduleResolution": "node",
"outDir": "dist",
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "ES2018"
}
// custom test in vrfContract
it('Tests if a random number is returned', async () => {
expect(await vrfContract.getRandomNumber()).to.be.equal(7);
});
}
waffle.json
{
"compilerType": "solcjs",
"compilerVersion": "0.6.2",
"sourceDirectory": "./src",
"outputDirectory": "./build"
}
yarn.lock 文件内容有点大,而且是自动生成的,所以你可以找到它on the Waffle framework repository。同样,package.json 可以在同一存储库中找到 here。
命令
也可以简单地克隆具有指定文件结构here 的 repo,并使用以下命令运行测试:
git clone git@github.com:a-t-2/chainlink.git
git clone git@github.com:a-t-2/test_vrf3.git
cd test_vrf3
sudo apt install npm
npm install
npm audit fix
npm install --save-dev ethereum-waffle
npm install @openzeppelin/contracts -D
npm i chai -D
npm i mocha -D
rm -r build
npx waffle
npx mocha
npm test
测试输出
这将测试AmIRichAlready.sol 文件并输出:
Am I Rich Already
✓ checks if contract called balanceOf with certain wallet on the ERC20 token (249ms)
✓ returns false if the wallet has less than 1000000 coins (190ms)
✓ returns true if the wallet has at least 1000000 coins (159ms)
Tests if a random number is returned:
Error: cannot estimate gas; transaction may fail or may require manual gas limit (error={"name":"RuntimeError","results":{"0x0a0b028de6cf6e8446853a300061305501136cefa5f5eb3e96afd95dbd73dd92":{"error":"revert","program_counter":609,"return":"0x"}},"hashes":["0x0a0b028de6cf6e8446853a300061305501136cefa5f5eb3e96afd95dbd73dd92"],"message":"VM Exception while processing transaction: revert"}, tx={"data":"0xdbdff2c1","to":{},"from":"0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff","gasPrice":{"type":"BigNumber","hex":"0x77359400"},"type":0,"nonce":{},"gasLimit":{},"chainId":{}}, code=UNPREDICTABLE_GAS_LIMIT, version=abstract-signer/5.4.1)
at Logger.makeError (node_modules/@ethersproject/logger/src.ts/index.ts:225:28)
at Logger.throwError (node_modules/@ethersproject/logger/src.ts/index.ts:237:20)
at /home/name/git/trucol/tested/new_test/test_vrf3/node_modules/@ethersproject/abstract-signer/src.ts/index.ts:301:31
at process._tickCallback (internal/process/next_tick.js:68:7)
3 passing (4s)
问题
如果提供了足够的“gas”,我需要哪些文件、文件结构和命令来自动测试getRandomNumber() 合约是否返回整数,否则返回错误?
【问题讨论】:
标签: unit-testing random solidity chainlink