Skip to content

Commit

Permalink
feat: move spoof module to ethers_core, add stateoverrides to `Ge…
Browse files Browse the repository at this point in the history
…thDebugTracingCallOptions` (#2406)

* chore: move spoof module to ethers_core

* chore: re-export spoof

---------

Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com>
  • Loading branch information
georgewhewell and DaniPopes committed May 23, 2023
1 parent 18f10e6 commit 58fd3fe
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 282 deletions.
6 changes: 3 additions & 3 deletions book/providers/advanced_usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Lets take a quick look at how to use the `CallBuilder`.

```rust
use ethers::{
providers::{ Http, Provider},
providers::{Http, Provider},
types::{TransactionRequest, H160},
utils::parse_ether,
};
Expand Down Expand Up @@ -75,7 +75,7 @@ Let's look at how to use the state override set. In short, the state override se
```rust
use ethers::{
providers::{
call_raw::{spoof::State, RawCall},
call_raw::RawCall,
Http, Provider,
},
types::{TransactionRequest, H160, U256, U64},
Expand All @@ -98,7 +98,7 @@ async fn main() -> eyre::Result<()> {
.value(val)
.into();

let mut state = State::default();
let mut state = spoof::State::default();

// Set the account balance to max u256
state.account(from_adr).balance(U256::MAX);
Expand Down
266 changes: 264 additions & 2 deletions ethers-core/src/types/trace/geth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ pub use self::{
noop::NoopFrame,
pre_state::{AccountState, DiffMode, PreStateConfig, PreStateFrame, PreStateMode},
};
use crate::types::{serde_helpers::deserialize_stringified_numeric, Bytes, H256, U256};
use crate::types::{
serde_helpers::deserialize_stringified_numeric, Address, Bytes, H256, U256, U64,
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
Expand Down Expand Up @@ -205,5 +207,265 @@ pub struct GethDebugTracingOptions {
pub struct GethDebugTracingCallOptions {
#[serde(flatten)]
pub tracing_options: GethDebugTracingOptions,
// TODO: Add stateoverrides and blockoverrides options
#[serde(default, skip_serializing_if = "Option::is_none")]
pub state_overrides: Option<spoof::State>,
// TODO: Add blockoverrides options
}

/// Provides types and methods for constructing an `eth_call`
/// [state override set](https://geth.ethereum.org/docs/rpc/ns-eth#3-object---state-override-set)
pub mod spoof {
use super::*;
use std::collections::HashMap;

/// The state elements to override for a particular account.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Account {
/// Account nonce
#[serde(skip_serializing_if = "Option::is_none")]
pub nonce: Option<U64>,
/// Account balance
#[serde(skip_serializing_if = "Option::is_none")]
pub balance: Option<U256>,
/// Account code
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<Bytes>,
/// Account storage
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub storage: Option<Storage>,
}

impl Account {
/// Override the account nonce
pub fn nonce(&mut self, nonce: U64) -> &mut Self {
self.nonce = Some(nonce);
self
}
/// Override the account balance
pub fn balance(&mut self, bal: U256) -> &mut Self {
self.balance = Some(bal);
self
}
/// Override the code at the account
pub fn code(&mut self, code: Bytes) -> &mut Self {
self.code = Some(code);
self
}
/// Override the value of the account storage at the given storage `key`
pub fn store(&mut self, key: H256, val: H256) -> &mut Self {
self.storage.get_or_insert_with(Default::default).insert(key, val);
self
}
}

/// Wraps a map from storage slot to the overriden value.
///
/// Storage overrides can either replace the existing state of an account or they can be treated
/// as a diff on the existing state.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Storage {
/// State Diff
#[serde(rename = "stateDiff")]
Diff(HashMap<H256, H256>),
/// State override
#[serde(rename = "state")]
Replace(HashMap<H256, H256>),
}

/// The default storage override is a diff on the existing state of the account.
impl Default for Storage {
fn default() -> Self {
Self::Diff(Default::default())
}
}
impl std::ops::Deref for Storage {
type Target = HashMap<H256, H256>;
fn deref(&self) -> &Self::Target {
match self {
Self::Diff(map) => map,
Self::Replace(map) => map,
}
}
}
impl std::ops::DerefMut for Storage {
fn deref_mut(&mut self) -> &mut Self::Target {
match self {
Self::Diff(map) => map,
Self::Replace(map) => map,
}
}
}

/// A wrapper type that holds a complete state override set.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct State(#[serde(skip_serializing_if = "HashMap::is_empty")] HashMap<Address, Account>);

impl State {
/// Returns a mutable reference to the [`Account`] in the map.
pub fn account(&mut self, adr: Address) -> &mut Account {
self.0.entry(adr).or_default()
}
}

/// Returns an empty state override set.
///
/// # Example
///
/// ```no_run
/// # use ethers_core::{
/// # types::{Address, TransactionRequest, H256, spoof},
/// # utils::{parse_ether, Geth},
/// # };
/// # use ethers_providers::{Provider, Http, Middleware, call_raw::RawCall};
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// let geth = Geth::new().spawn();
/// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
///
/// let adr1: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
/// let adr2: Address = "0x295a70b2de5e3953354a6a8344e616ed314d7251".parse()?;
/// let key = H256::from_low_u64_be(1);
/// let val = H256::from_low_u64_be(17);
///
/// let tx = TransactionRequest::default().to(adr2).from(adr1).into();
///
/// // override the storage at `adr2`
/// let mut state = spoof::state();
/// state.account(adr2).store(key, val);
///
/// // override the nonce at `adr1`
/// state.account(adr1).nonce(2.into());
///
/// provider.call_raw(&tx).state(&state).await?;
/// # Ok(())
/// # }
/// ```
pub fn state() -> State {
Default::default()
}

/// Returns a state override set with a single element setting the balance of the address.
///
/// # Example
/// ```no_run
/// # use ethers_core::{
/// # types::{Address, TransactionRequest, H256},
/// # utils::{parse_ether, Geth},
/// # };
/// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// let geth = Geth::new().spawn();
/// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
///
/// let adr1: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
/// let adr2: Address = "0x295a70b2de5e3953354a6a8344e616ed314d7251".parse()?;
/// let pay_amt = parse_ether(1u64)?;
///
/// // Not enough ether to pay for the transaction
/// let tx = TransactionRequest::pay(adr2, pay_amt).from(adr1).into();
///
/// // override the sender's balance for the call
/// let state = spoof::balance(adr1, pay_amt * 2);
/// provider.call_raw(&tx).state(&state).await?;
/// # Ok(())
/// # }
/// ```
pub fn balance(adr: Address, bal: U256) -> State {
let mut state = State::default();
state.account(adr).balance(bal);
state
}

/// Returns a state override set with a single element setting the nonce of the address.
///
/// # Example
/// ```no_run
/// # use ethers_core::{
/// # types::{Address, TransactionRequest, H256},
/// # utils::{parse_ether, Geth},
/// # };
/// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// let geth = Geth::new().spawn();
/// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
///
/// let adr: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
/// let pay_amt = parse_ether(1u64)?;
///
/// let tx = TransactionRequest::default().from(adr).into();
///
/// // override the sender's nonce for the call
/// let state = spoof::nonce(adr, 72.into());
/// provider.call_raw(&tx).state(&state).await?;
/// # Ok(())
/// # }
/// ```
pub fn nonce(adr: Address, nonce: U64) -> State {
let mut state = State::default();
state.account(adr).nonce(nonce);
state
}

/// Returns a state override set with a single element setting the code at the address.
///
/// # Example
/// ```no_run
/// # use ethers_core::{
/// # types::{Address, TransactionRequest, H256},
/// # utils::{parse_ether, Geth},
/// # };
/// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// let geth = Geth::new().spawn();
/// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
///
/// let adr: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
/// let pay_amt = parse_ether(1u64)?;
///
/// let tx = TransactionRequest::default().to(adr).into();
///
/// // override the code at the target address
/// let state = spoof::code(adr, "0x00".parse()?);
/// provider.call_raw(&tx).state(&state).await?;
/// # Ok(())
/// # }
/// ```
pub fn code(adr: Address, code: Bytes) -> State {
let mut state = State::default();
state.account(adr).code(code);
state
}

/// Returns a state override set with a single element setting the storage at the given address
/// and key.
///
/// # Example
///
/// ```no_run
/// # use ethers_core::{
/// # types::{Address, TransactionRequest, H256},
/// # utils::{parse_ether, Geth},
/// # };
/// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// let geth = Geth::new().spawn();
/// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
///
/// let adr: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
/// let key = H256::from_low_u64_be(1);
/// let val = H256::from_low_u64_be(17);
///
/// let tx = TransactionRequest::default().to(adr).into();
///
/// // override the storage slot `key` at `adr`
/// let state = spoof::storage(adr, key, val);
/// provider.call_raw(&tx).state(&state).await?;
/// # Ok(())
/// # }
/// ```
pub fn storage(adr: Address, key: H256, val: H256) -> State {
let mut state = State::default();
state.account(adr).store(key, val);
state
}
}
9 changes: 5 additions & 4 deletions ethers-providers/src/rpc/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,20 +205,21 @@ impl<P: JsonRpcClient> Provider<P> {
/// Analogous to [`Middleware::call`], but returns a [`CallBuilder`] that can either be
/// `.await`d or used to override the parameters sent to `eth_call`.
///
/// See the [`call_raw::spoof`] for functions to construct state override parameters.
/// See the [`ethers_core::types::spoof`] for functions to construct state override
/// parameters.
///
/// Note: this method _does not_ send a transaction from your account
///
/// [`call_raw::spoof`]: crate::call_raw::spoof
/// [`ethers_core::types::spoof`]: ethers_core::types::spoof
///
/// # Example
///
/// ```no_run
/// # use ethers_core::{
/// # types::{Address, TransactionRequest, H256},
/// # types::{Address, TransactionRequest, H256, spoof},
/// # utils::{parse_ether, Geth},
/// # };
/// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
/// # use ethers_providers::{Provider, Http, Middleware, call_raw::RawCall};
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// let geth = Geth::new().spawn();
/// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
Expand Down
Loading

0 comments on commit 58fd3fe

Please sign in to comment.