Skip to content

Commit

Permalink
feat: added support for call value (#142)
Browse files Browse the repository at this point in the history
Added functionality:
- `expect(myFake.myFunction).to.have.been.calledWithValue(1234);`
- `expect(myFake.myFunction.getCall(0).value).to.eq(1);`
  • Loading branch information
0xGorilla authored Jul 17, 2022
1 parent 466d944 commit 3c2b80b
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 5 deletions.
18 changes: 16 additions & 2 deletions docs/source/fakes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,8 @@ Called N times
expect(myFake.myFunction).to.have.callCount(123);
Asserting call arguments
************************
Asserting call arguments or value
*********************************

Called with specific arguments
##############################
Expand Down Expand Up @@ -313,6 +313,13 @@ Called once with specific arguments
expect(myFake.myFunction).to.have.been.calledOnceWith(1234, false);
Called with an specific call value
###################################

.. code-block:: typescript
expect(myFake.myFunction).to.have.been.calledWithValue(1234);
Asserting call order
********************

Expand Down Expand Up @@ -355,6 +362,13 @@ Getting arguments at a specific call index
expect(myFake.myFunction.getCall(0).args[0]).to.be.gt(50);
Getting call value at a specific call index
##########################################

.. code-block:: typescript
expect(myFake.myFunction.getCall(0).value).to.eq(1);
Manipulating fallback functions
*******************************

Expand Down
1 change: 1 addition & 0 deletions src/chai-plugin/matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export const matchers: Chai.ChaiPlugin = (chai: Chai.ChaiStatic, utils: Chai.Cha
smockMethodWithWatchableContractArg('calledImmediatelyBefore', 'been called immediately before %1');
smockMethodWithWatchableContractArg('calledImmediatelyAfter', 'been called immediately after %1');
smockMethod('calledWith', 'been called with arguments %*', '%D');
smockMethod('calledWithValue', 'been called with value %*', '%D');
smockMethod('calledOnceWith', 'been called exactly once with arguments %*', '%D');
smockMethod('delegatedFrom', 'been called via a delegated call by %*', '');
};
5 changes: 5 additions & 0 deletions src/chai-plugin/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BigNumber } from 'ethers';
import { WatchableContractFunction } from '../index';

declare global {
Expand Down Expand Up @@ -50,6 +51,10 @@ declare global {
* Returns true if call received provided arguments.
*/
calledWith(...args: any[]): Assertion;
/**
* Returns true if call received the provided value.
*/
calledWithValue(value: BigNumber): Assertion;
/**
* Returns true when called at exactly once with the provided arguments.
*/
Expand Down
3 changes: 2 additions & 1 deletion src/factories/smock-contract.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Message from '@nomiclabs/ethereumjs-vm/dist/evm/message';
import { FactoryOptions } from '@nomiclabs/hardhat-ethers/types';
import { BaseContract, ContractFactory, ethers } from 'ethers';
import { BaseContract, BigNumber, ContractFactory, ethers } from 'ethers';
import { Interface } from 'ethers/lib/utils';
import { ethers as hardhatEthers } from 'hardhat';
import { Observable } from 'rxjs';
Expand Down Expand Up @@ -205,6 +205,7 @@ function parseMessage(message: Message, contractInterface: Interface, sighash: s
return {
args: sighash === null ? toHexString(message.data) : getMessageArgs(message.data, contractInterface, sighash),
nonce: Sandbox.getNextNonce(),
value: BigNumber.from(message.value.toString()),
target: fromFancyAddress(message.delegatecall ? message.codeAddress : message.to),
delegatedFrom: message.delegatecall ? fromFancyAddress(message.to) : undefined,
};
Expand Down
4 changes: 4 additions & 0 deletions src/logic/watchable-function-logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export class WatchableFunctionLogic {
return !!this.callHistory.find((call) => this.isDeepEqual(call.args, expectedCallArgs));
}

calledWithValue(value: BigNumber): boolean {
return !!this.callHistory.find((call) => call.value.eq(value));
}

alwaysCalledWith(...expectedCallArgs: unknown[]): boolean {
const callWithOtherArgs = this.callHistory.find((call) => !this.isDeepEqual(call.args, expectedCallArgs));
return this.getCalled() && !callWithOtherArgs;
Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { Fragment, Interface, JsonFragment } from '@ethersproject/abi';
import { Provider } from '@ethersproject/abstract-provider';
import { Signer } from '@ethersproject/abstract-signer';
import { BaseContract, ContractFactory, ethers } from 'ethers';
import { BaseContract, BigNumber, ContractFactory, ethers } from 'ethers';
import { EditableStorageLogic } from './logic/editable-storage-logic';
import { ReadableStorageLogic } from './logic/readable-storage-logic';
import { WatchableFunctionLogic } from './logic/watchable-function-logic';
Expand Down Expand Up @@ -35,6 +35,7 @@ export interface ContractCall {
args: unknown[] | string;
nonce: number;
target: string;
value: BigNumber;
delegatedFrom?: string;
}

Expand Down
2 changes: 1 addition & 1 deletion test/contracts/watchable-function-logic/Receiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct StructNested {
contract Receiver {
fallback() external {}

function receiveEmpty() public {}
function receiveEmpty() public payable {}

function receiveBoolean(bool) public {}

Expand Down
36 changes: 36 additions & 0 deletions test/unit/watchable-function-logic/call-arguments.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { FakeContract, smock } from '@src';
import { Caller, Caller__factory, Receiver } from '@typechained';
import chai, { AssertionError, expect } from 'chai';
import { BigNumber } from 'ethers';
import { ethers } from 'hardhat';

chai.should();
Expand All @@ -9,8 +11,11 @@ chai.use(smock.matchers);
describe('WatchableFunctionLogic: Call arguments', () => {
let fake: FakeContract<Receiver>;
let caller: Caller;
let signer: SignerWithAddress;

before(async () => {
[, signer] = await ethers.getSigners();

const callerFactory = (await ethers.getContractFactory('Caller')) as Caller__factory;
caller = await callerFactory.deploy();
});
Expand Down Expand Up @@ -48,6 +53,37 @@ describe('WatchableFunctionLogic: Call arguments', () => {
});
});

describe('calledWithValue', async () => {
const value = BigNumber.from(123);

it('should throw when the watchablecontract is not called', async () => {
expect(() => {
fake.receiveEmpty.should.have.been.calledWithValue(value);
}).to.throw(AssertionError);
});

it('should throw when the watchablecontract is called with incorrect arguments', async () => {
await fake.connect(signer).receiveEmpty({ value: value.sub(1) });

expect(() => {
fake.receiveEmpty.should.have.been.calledWithValue(value);
}).to.throw(AssertionError);
});

it('should not throw when the watchablecontract is called with the correct arguments', async () => {
await fake.connect(signer).receiveEmpty({ value });
fake.receiveEmpty.should.have.been.calledWithValue(value);
});

it('should not throw when the watchablecontract is called with incorrect arguments but the correct ones as well', async () => {
await fake.connect(signer).receiveEmpty({ value: value.sub(1) });
await fake.connect(signer).receiveEmpty({ value: value });
await fake.connect(signer).receiveEmpty({ value: value.add(1) });

fake.receiveEmpty.should.have.been.calledWithValue(value);
});
});

describe('always.calledWith', async () => {
it('should throw when the watchablecontract is not called', async () => {
expect(() => {
Expand Down
15 changes: 15 additions & 0 deletions test/unit/watchable-function-logic/type-handling.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { FakeContract, smock } from '@src';
import { BYTES32_EXAMPLE, BYTES_EXAMPLE, STRUCT_DYNAMIC_SIZE_EXAMPLE, STRUCT_FIXED_SIZE_EXAMPLE } from '@test-utils';
import { Caller, Caller__factory, Receiver } from '@typechained';
Expand All @@ -11,8 +12,11 @@ chai.use(smock.matchers);
describe('WatchableFunctionLogic: Type handling', () => {
let fake: FakeContract<Receiver>;
let caller: Caller;
let signer: SignerWithAddress;

before(async () => {
[, signer] = await ethers.getSigners();

const callerFactory = (await ethers.getContractFactory('Caller')) as Caller__factory;
caller = await callerFactory.deploy();
});
Expand Down Expand Up @@ -123,4 +127,15 @@ describe('WatchableFunctionLogic: Type handling', () => {
fake['receiveOverload(bool)'].should.have.been.calledWith(true);
fake['receiveOverload(bool,bool)'].should.have.been.calledWith(true, false);
});

it('should handle msg.value', async () => {
const value = BigNumber.from(123);
await fake.connect(signer).receiveEmpty({ value });
fake.receiveEmpty.getCall(0).value.should.equal(value);
});

it('should handle empty msg.value', async () => {
await fake.connect(signer).receiveEmpty();
fake.receiveEmpty.getCall(0).value.should.equal(BigNumber.from(0));
});
});

0 comments on commit 3c2b80b

Please sign in to comment.