21 unstable releases
0.25.7 | May 15, 2024 |
---|---|
0.25.5 | Feb 22, 2024 |
0.25.4 | Nov 24, 2023 |
0.25.0 | Jul 13, 2023 |
0.15.3 | Aug 31, 2021 |
#4 in #expectation
231 downloads per month
410KB
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
~15–22MB
~286K SLoC