Skip to content

Commit

Permalink
🗽 Add address parameter to the mock contract (#815)
Browse files Browse the repository at this point in the history
* 🗽 Add address parameter to the mock contract

Co-authored-by: yivlad <[email protected]>
  • Loading branch information
pawelpolak2 and yivlad authored Jan 10, 2023
1 parent b54c6b9 commit da92375
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 162 deletions.
6 changes: 6 additions & 0 deletions .changeset/blue-numbers-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@ethereum-waffle/hardhat": patch
"@ethereum-waffle/mock-contract": patch
---

Add mock contract deployment at a specified address
9 changes: 9 additions & 0 deletions docs/source/mock-contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ Create an instance of a mock contract providing the :code:`ABI` of the smart con
const mockContract = await deployMockContract(wallet, contractAbi);
You can also choose the deployment address of the mock contract with the options argument:

.. code-block:: ts
const mockContract = await deployMockContract(wallet, contractAbi, {
address: deploymentAddress,
overrride: false // optional, specifies if the contract should be overwritten
})
The mock contract can now be integrated into other contracts by using the :code:`address` attribute.
Return values for mocked functions can be set using:

Expand Down
4 changes: 4 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion waffle-hardhat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@
},
"devDependencies": {
"@ethereum-waffle/chai": "workspace:*",
"@ethereum-waffle/mock-contract": "workspace:^*",
"@ethereum-waffle/provider": "workspace:*",
"@nomiclabs/hardhat-ethers": "2.1.0",
"hardhat-waffle-dev": "2.0.3-dev.c5b5c29",
"@types/node": "^17.0.41",
"eslint": "^7.14.0",
"ethereum-waffle": "workspace:*",
"ethers": "5.6.2",
"hardhat": "2.10.1",
"hardhat-waffle-dev": "2.0.3-dev.c5b5c29",
"mocha": "^8.2.1"
}
}
15 changes: 15 additions & 0 deletions waffle-hardhat/test/mockContract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {waffle} from 'hardhat';
import {MockProvider} from 'ethereum-waffle';
import {mockContractDirectTest} from '@ethereum-waffle/mock-contract/test/directTest';
import {mockContractProxiedTest} from '@ethereum-waffle/mock-contract/test/proxiedTest';

describe('INTEGRATION: Mock Contract', () => {
const provider = waffle.provider as MockProvider;

before(async () => {
await provider.send('hardhat_reset', []);
});

mockContractDirectTest(provider);
mockContractProxiedTest(provider);
});
20 changes: 10 additions & 10 deletions waffle-mock-contract/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,32 +32,32 @@
"module": "dist/esm/src/index.ts",
"types": "dist/esm/src/index.d.ts",
"scripts": {
"test": "export NODE_ENV=test && yarn test:build && mocha",
"test": "export NODE_ENV=test && mocha",
"lint": "eslint '{src,test}/**/*.ts'",
"lint:fix": "eslint --fix '{src,test}/**/*.ts'",
"build": "rimraf ./dist && yarn build:sol && yarn build:esm && yarn build:cjs",
"build": "rimraf ./dist && yarn build:sol && yarn build:esm && yarn build:cjs && ts-node ./test/helpers/buildTestContracts.ts",
"build:sol": "ts-node compile.ts",
"build:esm": "tsc -p tsconfig.build.json --outDir dist/esm --module ES6",
"build:cjs": "tsc -p tsconfig.build.json --outDir dist/cjs",
"test:build": "ts-node ./test/helpers/buildTestContracts.ts",
"clean": "rimraf ./dist ./test/example/build"
},
"engines": {
"node": ">=10.0"
},
"devDependencies": {
"ethers": "5.6.2",
"@ethersproject/abi": "^5.6.1",
"@ethereum-waffle/chai": "workspace:*",
"@ethereum-waffle/compiler": "workspace:*",
"solc": "0.8.15",
"@ethereum-waffle/provider": "workspace:*",
"typechain": "^8.0.0",
"@ethersproject/abi": "^5.6.1",
"@ethersproject/providers": "5.6.2",
"eslint": "^7.14.0",
"ethers": "5.6.2",
"mocha": "^8.2.1",
"rimraf": "^3.0.2",
"typescript": "^4.6.2",
"eslint": "^7.14.0",
"ts-node": "^9.0.0"
"solc": "0.8.15",
"ts-node": "^9.0.0",
"typechain": "^8.0.0",
"typescript": "^4.6.2"
},
"peerDependencies": {
"ethers": "*"
Expand Down
36 changes: 33 additions & 3 deletions waffle-mock-contract/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ import {Contract, ContractFactory, Signer, utils} from 'ethers';
import type {JsonFragment} from '@ethersproject/abi';

import DoppelgangerContract from './Doppelganger.json';
import type {JsonRpcProvider} from '@ethersproject/providers';

type ABI = string | Array<utils.Fragment | JsonFragment | string>

export type Stub = ReturnType<typeof stub>;

type DeployOptions = {
address: string;
override?: boolean;
}

export interface MockContract extends Contract {
mock: {
[key: string]: Stub;
Expand All @@ -15,7 +21,31 @@ export interface MockContract extends Contract {
staticcall (contract: Contract, functionName: string, ...params: any[]): Promise<any>;
}

async function deploy(signer: Signer) {
async function deploy(signer: Signer, options?: DeployOptions) {
if (options) {
const {address, override} = options;
const provider = signer.provider as JsonRpcProvider;
if (!override && await provider.getCode(address) !== '0x') {
throw new Error(
`${address} already contains a contract. ` +
'If you want to override it, set the override parameter.');
}
if ((provider as any)._hardhatNetwork) {
if (await provider.send('hardhat_setCode', [
address,
'0x' + DoppelgangerContract.evm.deployedBytecode.object
])) {
return new Contract(address, DoppelgangerContract.abi, signer);
} else throw new Error(`Couldn't deploy at ${address}`);
} else {
if (await provider.send('evm_setAccountCode', [
address,
'0x' + DoppelgangerContract.evm.deployedBytecode.object
])) {
return new Contract(address, DoppelgangerContract.abi, signer);
} else throw new Error(`Couldn't deploy at ${address}`);
}
}
const factory = new ContractFactory(DoppelgangerContract.abi, DoppelgangerContract.bytecode, signer);
return factory.deploy();
}
Expand Down Expand Up @@ -53,8 +83,8 @@ function createMock(abi: ABI, mockContractInstance: Contract) {
return mockedAbi;
}

export async function deployMockContract(signer: Signer, abi: ABI): Promise<MockContract> {
const mockContractInstance = await deploy(signer);
export async function deployMockContract(signer: Signer, abi: ABI, options?: DeployOptions): Promise<MockContract> {
const mockContractInstance = await deploy(signer, options);

const mock = createMock(abi, mockContractInstance);
const mockedContract = new Contract(mockContractInstance.address, abi, signer) as MockContract;
Expand Down
102 changes: 2 additions & 100 deletions waffle-mock-contract/test/direct.test.ts
Original file line number Diff line number Diff line change
@@ -1,102 +1,4 @@
import {use, expect} from 'chai';
import chaiAsPromised from 'chai-as-promised';
import {MockProvider} from '@ethereum-waffle/provider';
import {waffleChai} from '@ethereum-waffle/chai';
import {ContractFactory} from 'ethers';
import {mockContractDirectTest} from './directTest';

import {deployMockContract} from '../src';
import Counter from './helpers/interfaces/Counter.json';

use(chaiAsPromised);
use(waffleChai);

describe('Mock Contract - Integration (called directly)', () => {
const [wallet] = new MockProvider().getWallets();

it('throws readable error if mock was not set up for a method', async () => {
const mockCounter = await deployMockContract(wallet, Counter.abi);

await expect(mockCounter.read()).to.be.revertedWith('Mock on the method is not initialized');
});

it('mocking returned values', async () => {
const mockCounter = await deployMockContract(wallet, Counter.abi);
await mockCounter.mock.read.returns(45291);

expect(await mockCounter.read()).to.equal(45291);
});

it('mocking revert', async () => {
const mockCounter = await deployMockContract(wallet, Counter.abi);
await mockCounter.mock.read.reverts();

await expect(mockCounter.read()).to.be.revertedWith('Mock revert');
});

it('mock with call arguments', async () => {
const mockCounter = await deployMockContract(wallet, Counter.abi);
await mockCounter.mock.add.returns(1);
await mockCounter.mock.add.withArgs(1).returns(2);
await mockCounter.mock.add.withArgs(2).reverts();

expect(await mockCounter.add(0)).to.equal(1);
expect(await mockCounter.add(1)).to.equal(2);
await expect(mockCounter.add(2)).to.be.revertedWith('Mock revert');
expect(await mockCounter.add(3)).to.equal(1);
});

it('should be able to call to another contract', async () => {
const counterFactory = new ContractFactory(Counter.abi, Counter.bytecode, wallet);
const counter = await counterFactory.deploy();
const mockCounter = await deployMockContract(wallet, Counter.abi);

expect(await mockCounter.staticcall(counter, 'read()')).to.equal('0');
expect(await mockCounter.staticcall(counter, 'read')).to.equal('0');
});

it('should be able to call another contract with a parameter', async () => {
const counterFactory = new ContractFactory(Counter.abi, Counter.bytecode, wallet);
const counter = await counterFactory.deploy();
const mockCounter = await deployMockContract(wallet, Counter.abi);

expect(await mockCounter.staticcall(counter, 'add', 1)).to.equal('1');
});

it('should be able to call another contract with many parameters', async () => {
const counterFactory = new ContractFactory(Counter.abi, Counter.bytecode, wallet);
const counter = await counterFactory.deploy();
const mockCounter = await deployMockContract(wallet, Counter.abi);

expect(await mockCounter.staticcall(counter, 'addThree', 1, 2, 3)).to.equal('6');
});

it('should be able to execute another contract', async () => {
const counterFactory = new ContractFactory(Counter.abi, Counter.bytecode, wallet);
const counter = await counterFactory.deploy();
const mockCounter = await deployMockContract(wallet, Counter.abi);

await mockCounter.call(counter, 'increment()');
expect(await counter.read()).to.equal('1');

await mockCounter.call(counter, 'increment');
expect(await counter.read()).to.equal('2');
});

it('should be able to execute another contract with a parameter', async () => {
const counterFactory = new ContractFactory(Counter.abi, Counter.bytecode, wallet);
const counter = await counterFactory.deploy();
const mockCounter = await deployMockContract(wallet, Counter.abi);

await mockCounter.call(counter, 'increaseBy', 2);
expect(await counter.read()).to.equal('2');
});

it('should be able to execute another contract with many parameters', async () => {
const counterFactory = new ContractFactory(Counter.abi, Counter.bytecode, wallet);
const counter = await counterFactory.deploy();
const mockCounter = await deployMockContract(wallet, Counter.abi);

await mockCounter.call(counter, 'increaseByThreeValues', 1, 2, 3);
expect(await counter.read()).to.equal('6');
});
});
mockContractDirectTest(new MockProvider());
Loading

0 comments on commit da92375

Please sign in to comment.