Skip to content

Commit c057a6e

Browse files
authored
Call with state overrides (#983)
Using `eth_call` together with state overrides is pretty useful and widely supported thing. Currently we need to use very ugly workaround in https://github.com/cowprotocol/services to use state overrides with `ethcontract-rs`. In order to make everyone's lives easier it makes the most sense to make `call_with_state_overrides` a first class citizen in `ethcontract-rs`. - I added the `StateOverride` struct which defines the format of things that can be overriden - I added `call_with_state_overrides` on `MethodCallBuilder` and `ViewCallBuilder` - unfortunately does the `web3` crate not have any nice helper functions for using state overrides so I implemented the call with the underlying logic that the `web3` crate would have called ### Test Plan I tested this by pointing the ethcontract dependency in the cowprotocol repo to a local checkout with this patch and did a manual test to make sure things work. Given that we are about to completely abandon this crate I think it's fine to check in a test specific for this.
1 parent 8817f4b commit c057a6e

File tree

3 files changed

+63
-1
lines changed

3 files changed

+63
-1
lines changed

ethcontract/src/contract/method.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
//! intended to be used directly but to be used by a contract `Instance` with
33
//! [Instance::method](ethcontract::contract::Instance::method).
44
5+
use crate::state_overrides::StateOverrides;
56
use crate::transaction::{Account, GasPrice, TransactionBuilder, TransactionResult};
67
use crate::{batch::CallBatch, errors::MethodError, tokens::Tokenize};
78
use ethcontract_common::abi::{Function, Token};
89
use std::marker::PhantomData;
9-
use web3::types::{AccessList, Address, BlockId, Bytes, CallRequest, U256};
10+
use web3::helpers::CallFuture;
11+
use web3::types::{AccessList, Address, BlockId, BlockNumber, Bytes, CallRequest, U256};
1012
use web3::Transport;
1113
use web3::{api::Web3, BatchTransport};
1214

@@ -152,6 +154,14 @@ impl<T: Transport, R: Tokenize> MethodBuilder<T, R> {
152154
pub async fn call(self) -> Result<R, MethodError> {
153155
self.view().call().await
154156
}
157+
158+
/// Same as [`call`] but allows to also specify state overrides with the call.
159+
pub async fn call_with_state_overrides(
160+
self,
161+
overrides: &StateOverrides,
162+
) -> Result<R, MethodError> {
163+
self.view().call_with_state_overrides(overrides).await
164+
}
155165
}
156166

157167
/// Data used for building a contract method call. The view method builder can't
@@ -243,6 +253,20 @@ impl<T: Transport, R: Tokenize> ViewMethodBuilder<T, R> {
243253
convert_response::<_, R>(future, function).await
244254
}
245255

256+
/// Same as [`call`] but also allows to specify state overrides for the call.
257+
pub async fn call_with_state_overrides(
258+
self,
259+
overrides: &StateOverrides,
260+
) -> Result<R, MethodError> {
261+
let transport = &self.m.web3.transport().clone();
262+
let (function, call, block) = self.decompose();
263+
let req = web3::helpers::serialize(&call);
264+
let block = web3::helpers::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into()));
265+
let overrides = web3::helpers::serialize(overrides);
266+
let future = CallFuture::new(transport.execute("eth_call", vec![req, block, overrides]));
267+
convert_response::<_, R>(future, function).await
268+
}
269+
246270
/// Adds this view method to a batch. Allows execution with other contract calls in one roundtrip
247271
/// The returned future only resolve once `batch` is resolved. Panics, if `batch` is dropped before
248272
/// executing

ethcontract/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ pub mod errors;
9999
mod int;
100100
pub mod log;
101101
pub mod secret;
102+
pub mod state_overrides;
102103
pub mod tokens;
103104
pub mod transaction;
104105
pub mod transport;

ethcontract/src/state_overrides.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//! Contains data structures needed to specify
2+
//! state overrides for `eth_call`s.
3+
4+
use primitive_types::{H160, H256, U256};
5+
use serde::Serialize;
6+
use std::collections::HashMap;
7+
use web3::types::{Bytes, U64};
8+
9+
/// State overrides.
10+
pub type StateOverrides = HashMap<H160, StateOverride>;
11+
12+
/// State override object.
13+
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
14+
#[serde(rename_all = "camelCase")]
15+
pub struct StateOverride {
16+
/// Fake balance to set for the account before executing the call.
17+
#[serde(skip_serializing_if = "Option::is_none")]
18+
pub balance: Option<U256>,
19+
20+
/// Fake nonce to set for the account before executing the call.
21+
#[serde(skip_serializing_if = "Option::is_none")]
22+
pub nonce: Option<U64>,
23+
24+
/// Fake EVM bytecode to inject into the account before executing the call.
25+
#[serde(skip_serializing_if = "Option::is_none")]
26+
pub code: Option<Bytes>,
27+
28+
/// Fake key-value mapping to override **all** slots in the account storage
29+
/// before executing the call.
30+
#[serde(skip_serializing_if = "Option::is_none")]
31+
pub state: Option<HashMap<H256, H256>>,
32+
33+
/// Fake key-value mapping to override **individual** slots in the account
34+
/// storage before executing the call.
35+
#[serde(skip_serializing_if = "Option::is_none")]
36+
pub state_diff: Option<HashMap<H256, H256>>,
37+
}

0 commit comments

Comments
 (0)