19 releases (10 breaking)

0.25.5 Feb 22, 2024
0.25.4 Nov 24, 2023
0.25.2 Sep 27, 2023
0.25.0 Jul 13, 2023
0.15.3 Aug 31, 2021

#5 in #expectation

Download history 4/week @ 2023-12-04 69/week @ 2023-12-11 17/week @ 2023-12-18 4/week @ 2023-12-25 6/week @ 2024-01-01 40/week @ 2024-01-08 23/week @ 2024-01-22 7/week @ 2024-01-29 20/week @ 2024-02-05 26/week @ 2024-02-12 316/week @ 2024-02-19 60/week @ 2024-02-26 33/week @ 2024-03-04 88/week @ 2024-03-11 57/week @ 2024-03-18

263 downloads per month

MIT/Apache

415KB
8K SLoC

This crate allows emulating ethereum node with a limited number of supported RPC calls, enabling you to mock ethereum contracts.

Create a new deployment using the Mock::deploy function.

Configure contract's behaviour using Contract::expect_transaction and Contract::expect_call.

Finally, create an ethcontract's Instance by calling Contract::instance, then use said instance in your tests.

Example

Let's mock voting contract from solidity examples.

First, we create a mock node and deploy a new mocked contract:

let mock = Mock::new(/* chain_id = */ 1337);
let contract = mock.deploy(abi);

Then we set up expectations for method calls:

// We'll need to know method signatures and types.
let vote: Signature<(U256,), ()> = [1, 33, 185, 63].into();
let winning_proposal: Signature<(), U256> = [96, 159, 241, 189].into();

// We expect some transactions calling the `vote` method.
contract
    .expect_transaction(vote);

// We also expect calls to `winning_proposal` that will return
// a value of `1`.
contract
    .expect_call(winning_proposal)
    .returns(1.into());

Finally, we create a dynamic instance and work with it as usual:

let instance = contract.instance();

instance
    .method(vote, (1.into(),))?
    .from(account)
    .send()
    .await?;

let winning_proposal_index = instance
    .view_method(winning_proposal, ())?
    .call()
    .await?;
assert_eq!(winning_proposal_index, 1.into());

Describing expectations

The mocked contracts have an interface similar to the one of the mockall crate.

For each contract's method that you expect to be called during a test, call Contract::expect_transaction or Contract::expect_call and set up the created Expectation with functions such as returns, times, in_sequence. For greater flexibility, you can have multiple expectations attached to the same method.

See Expectation for more info and examples.

Interacting with mocked contracts

After contract's behaviour is programmed, you can call Contract::instance to create an ethcontract's Instance.

You can also get contract's address and send RPC calls directly through web3.

Specifically, mock node supports eth_call, eth_sendRawTransaction, and eth_getTransactionReceipt.

At the moment, mock node can't sign transactions on its own, so eth_sendTransaction is not supported. Also, deploying contracts via eth_sendRawTransaction is not possible yet.

Mocking generated contracts

Overall, generated contracts are similar to the dynamic ones: they are deployed with Mock::deploy and configured with Contract::expect_call and Contract::expect_transaction.

You can get generated contract's ABI using the raw_contract function.

Generated method signatures are available through the signatures function.

Finally, type-safe instance can be created using the at method.

Here's an example of mocking an ERC20-compatible contract.

First, we create a mock node and deploy a new mocked contract:

ethcontract::contract!("ERC20.json");

let mock = Mock::new(/* chain_id = */ 1337);
let contract = mock.deploy(ERC20::raw_contract().abi.clone());

Then we set up expectations using the generated method signatures:

contract
    .expect_transaction(ERC20::signatures().transfer())
    .once()
    .returns(true);

Finally, we use mock contract's address to interact with the mock node:

let instance = ERC20::at(&mock.web3(), contract.address());
instance
    .transfer(recipient, 100.into())
    .from(account)
    .send()
    .await?;

Mocking gas and gas estimation

Mock node allows you to customize value returned from eth_gasPrice RPC call. Use Mock::update_gas_price to set a new gas price.

Estimating gas consumption with eth_estimateGas is not supported at the moment. For now, calls to eth_estimateGas always return 1.

Dependencies

~14–23MB
~290K SLoC