diff --git a/crates/cast/src/cmd/call.rs b/crates/cast/src/cmd/call.rs index 053bad5d2c664..65ee4c068c17b 100644 --- a/crates/cast/src/cmd/call.rs +++ b/crates/cast/src/cmd/call.rs @@ -3,8 +3,11 @@ use crate::{ tx::{CastTxBuilder, SenderKind}, Cast, }; -use alloy_primitives::{TxKind, U256}; -use alloy_rpc_types::{BlockId, BlockNumberOrTag}; +use alloy_primitives::{Address, Bytes, TxKind, U256}; +use alloy_rpc_types::{ + state::{StateOverride, StateOverridesBuilder}, + BlockId, BlockNumberOrTag, +}; use clap::Parser; use eyre::Result; use foundry_cli::{ @@ -26,9 +29,35 @@ use foundry_evm::{ opts::EvmOpts, traces::{InternalTraceMode, TraceMode}, }; -use std::str::FromStr; +use regex::Regex; +use std::{str::FromStr, sync::LazyLock}; + +// matches override pattern
:` - Override the code of an account
+/// * `--override-state ::` - Override a storage slot of an account
+///
+/// Multiple overrides can be specified for the same account. For example:
+///
+/// ```bash
+/// cast call 0x... "transfer(address,uint256)" 0x... 100 \
+/// --override-balance 0x123:0x1234 \
+/// --override-nonce 0x123:1 \
+/// --override-code 0x123:0x1234 \
+/// --override-state 0x123:0x1:0x1234
+/// --override-state-diff 0x123:0x1:0x1234
+/// ```
#[derive(Debug, Parser)]
pub struct CallArgs {
/// The destination of the transaction.
@@ -92,6 +121,31 @@ pub struct CallArgs {
/// Use current project artifacts for trace decoding.
#[arg(long, visible_alias = "la")]
pub with_local_artifacts: bool,
+
+ /// Override the balance of an account.
+ /// Format: address:balance
+ #[arg(long = "override-balance", value_name = "ADDRESS:BALANCE")]
+ pub balance_overrides: Option>,
+
+ /// Override the nonce of an account.
+ /// Format: address:nonce
+ #[arg(long = "override-nonce", value_name = "ADDRESS:NONCE")]
+ pub nonce_overrides: Option>,
+
+ /// Override the code of an account.
+ /// Format: address:code
+ #[arg(long = "override-code", value_name = "ADDRESS:CODE")]
+ pub code_overrides: Option>,
+
+ /// Override the state of an account.
+ /// Format: address:slot:value
+ #[arg(long = "override-state", value_name = "ADDRESS:SLOT:VALUE")]
+ pub state_overrides: Option>,
+
+ /// Override the state diff of an account.
+ /// Format: address:slot:value
+ #[arg(long = "override-state-diff", value_name = "ADDRESS:SLOT:VALUE")]
+ pub state_diff_overrides: Option>,
}
#[derive(Debug, Parser)]
@@ -123,6 +177,7 @@ impl CallArgs {
let figment = Into::::into(&self.eth).merge(&self);
let evm_opts = figment.extract::()?;
let mut config = Config::from_provider(figment)?.sanitized();
+ let state_overrides = self.get_state_overrides()?;
let Self {
to,
@@ -236,10 +291,54 @@ impl CallArgs {
return Ok(());
}
- sh_println!("{}", Cast::new(provider).call(&tx, func.as_ref(), block).await?)?;
+ sh_println!(
+ "{}",
+ Cast::new(provider).call(&tx, func.as_ref(), block, state_overrides).await?
+ )?;
Ok(())
}
+ /// Parse state overrides from command line arguments
+ pub fn get_state_overrides(&self) -> eyre::Result {
+ let mut state_overrides_builder = StateOverridesBuilder::default();
+
+ // Parse balance overrides
+ for override_str in self.balance_overrides.iter().flatten() {
+ let (addr, balance) = address_value_override(override_str)?;
+ state_overrides_builder =
+ state_overrides_builder.with_balance(addr.parse()?, balance.parse()?);
+ }
+
+ // Parse nonce overrides
+ for override_str in self.nonce_overrides.iter().flatten() {
+ let (addr, nonce) = address_value_override(override_str)?;
+ state_overrides_builder =
+ state_overrides_builder.with_nonce(addr.parse()?, nonce.parse()?);
+ }
+
+ // Parse code overrides
+ for override_str in self.code_overrides.iter().flatten() {
+ let (addr, code_str) = address_value_override(override_str)?;
+ state_overrides_builder =
+ state_overrides_builder.with_code(addr.parse()?, Bytes::from_str(code_str)?);
+ }
+
+ // Parse state overrides
+ for override_str in self.state_overrides.iter().flatten() {
+ let (addr, slot, value) = address_slot_value_override(override_str)?;
+ state_overrides_builder =
+ state_overrides_builder.with_state(addr, [(slot.into(), value.into())]);
+ }
+
+ // Parse state diff overrides
+ for override_str in self.state_diff_overrides.iter().flatten() {
+ let (addr, slot, value) = address_slot_value_override(override_str)?;
+ state_overrides_builder =
+ state_overrides_builder.with_state_diff(addr, [(slot.into(), value.into())]);
+ }
+
+ Ok(state_overrides_builder.build())
+ }
}
impl figment::Provider for CallArgs {
@@ -262,10 +361,30 @@ impl figment::Provider for CallArgs {
}
}
+/// Parse an override string in the format address:value.
+fn address_value_override(address_override: &str) -> Result<(&str, &str)> {
+ address_override.split_once(':').ok_or_else(|| {
+ eyre::eyre!("Invalid override {address_override}. Expected :")
+ })
+}
+
+/// Parse an override string in the format address:slot:value.
+fn address_slot_value_override(address_override: &str) -> Result<(Address, U256, U256)> {
+ let captures = OVERRIDE_PATTERN.captures(address_override).ok_or_else(|| {
+ eyre::eyre!("Invalid override {address_override}. Expected ::")
+ })?;
+
+ Ok((
+ captures[1].parse()?, // Address
+ captures[2].parse()?, // Slot (U256)
+ captures[3].parse()?, // Value (U256)
+ ))
+}
+
#[cfg(test)]
mod tests {
use super::*;
- use alloy_primitives::{hex, Address};
+ use alloy_primitives::hex;
#[test]
fn can_parse_call_data() {
@@ -279,17 +398,59 @@ mod tests {
}
#[test]
- fn call_sig_and_data_exclusive() {
- let data = hex::encode("hello");
- let to = Address::ZERO;
- let args = CallArgs::try_parse_from([
+ fn can_parse_state_overrides() {
+ let args = CallArgs::parse_from([
+ "foundry-cli",
+ "--override-balance",
+ "0x123:0x1234",
+ "--override-nonce",
+ "0x123:1",
+ "--override-code",
+ "0x123:0x1234",
+ "--override-state",
+ "0x123:0x1:0x1234",
+ ]);
+
+ assert_eq!(args.balance_overrides, Some(vec!["0x123:0x1234".to_string()]));
+ assert_eq!(args.nonce_overrides, Some(vec!["0x123:1".to_string()]));
+ assert_eq!(args.code_overrides, Some(vec!["0x123:0x1234".to_string()]));
+ assert_eq!(args.state_overrides, Some(vec!["0x123:0x1:0x1234".to_string()]));
+ }
+
+ #[test]
+ fn can_parse_multiple_state_overrides() {
+ let args = CallArgs::parse_from([
"foundry-cli",
- to.to_string().as_str(),
- "signature",
- "--data",
- format!("0x{data}").as_str(),
+ "--override-balance",
+ "0x123:0x1234",
+ "--override-balance",
+ "0x456:0x5678",
+ "--override-nonce",
+ "0x123:1",
+ "--override-nonce",
+ "0x456:2",
+ "--override-code",
+ "0x123:0x1234",
+ "--override-code",
+ "0x456:0x5678",
+ "--override-state",
+ "0x123:0x1:0x1234",
+ "--override-state",
+ "0x456:0x2:0x5678",
]);
- assert!(args.is_err());
+ assert_eq!(
+ args.balance_overrides,
+ Some(vec!["0x123:0x1234".to_string(), "0x456:0x5678".to_string()])
+ );
+ assert_eq!(args.nonce_overrides, Some(vec!["0x123:1".to_string(), "0x456:2".to_string()]));
+ assert_eq!(
+ args.code_overrides,
+ Some(vec!["0x123:0x1234".to_string(), "0x456:0x5678".to_string()])
+ );
+ assert_eq!(
+ args.state_overrides,
+ Some(vec!["0x123:0x1:0x1234".to_string(), "0x456:0x2:0x5678".to_string()])
+ );
}
}
diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs
index a83e67ef2183f..66a0abc75f78f 100644
--- a/crates/cast/src/lib.rs
+++ b/crates/cast/src/lib.rs
@@ -16,7 +16,9 @@ use alloy_provider::{
PendingTransactionBuilder, Provider,
};
use alloy_rlp::Decodable;
-use alloy_rpc_types::{BlockId, BlockNumberOrTag, Filter, TransactionRequest};
+use alloy_rpc_types::{
+ state::StateOverride, BlockId, BlockNumberOrTag, Filter, TransactionRequest,
+};
use alloy_serde::WithOtherFields;
use alloy_sol_types::sol;
use base::{Base, NumberWithBase, ToBase};
@@ -107,11 +109,12 @@ impl> Cast {
///
/// ```
/// use alloy_primitives::{Address, U256, Bytes};
- /// use alloy_rpc_types::{TransactionRequest};
+ /// use alloy_rpc_types::{TransactionRequest, state::{StateOverride, AccountOverride}};
/// use alloy_serde::WithOtherFields;
/// use cast::Cast;
/// use alloy_provider::{RootProvider, ProviderBuilder, network::AnyNetwork};
- /// use std::str::FromStr;
+ /// use std::{str::FromStr, collections::HashMap};
+ /// use alloy_rpc_types::state::StateOverridesBuilder;
/// use alloy_sol_types::{sol, SolCall};
///
/// sol!(
@@ -119,14 +122,22 @@ impl> Cast {
/// );
///
/// # async fn foo() -> eyre::Result<()> {
- /// let alloy_provider = ProviderBuilder::<_,_, AnyNetwork>::default().on_builtin("http://localhost:8545").await?;;
+ /// let alloy_provider = ProviderBuilder::<_,_, AnyNetwork>::default().connect("http://localhost:8545").await?;;
/// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?;
/// let greeting = greetingCall { i: U256::from(5) }.abi_encode();
/// let bytes = Bytes::from_iter(greeting.iter());
/// let tx = TransactionRequest::default().to(to).input(bytes.into());
/// let tx = WithOtherFields::new(tx);
+ ///
+ /// // Create state overrides
+ /// let mut state_override = StateOverride::default();
+ /// let mut account_override = AccountOverride::default();
+ /// account_override.balance = Some(U256::from(1000));
+ /// state_override.insert(to, account_override);
+ /// let state_override_object = StateOverridesBuilder::default().build();
+ ///
/// let cast = Cast::new(alloy_provider);
- /// let data = cast.call(&tx, None, None).await?;
+ /// let data = cast.call(&tx, None, None, state_override_object).await?;
/// println!("{}", data);
/// # Ok(())
/// # }
@@ -136,8 +147,14 @@ impl> Cast {
req: &WithOtherFields,
func: Option<&Function>,
block: Option,
+ state_override: StateOverride,
) -> Result {
- let res = self.provider.call(req.clone()).block(block.unwrap_or_default()).await?;
+ let res = self
+ .provider
+ .call(req.clone())
+ .block(block.unwrap_or_default())
+ .overrides(state_override)
+ .await?;
let mut decoded = vec![];
diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs
index c37ea2bbed221..be2e9a7ed7101 100644
--- a/crates/cast/tests/cli/main.rs
+++ b/crates/cast/tests/cli/main.rs
@@ -2257,6 +2257,132 @@ forgetest_async!(cast_call_custom_chain_id, |_prj, cmd| {
.assert_success();
});
+// https://github.com/foundry-rs/foundry/issues/10189
+forgetest_async!(cast_call_custom_override, |prj, cmd| {
+ let (_, handle) = anvil::spawn(NodeConfig::test()).await;
+
+ foundry_test_utils::util::initialize(prj.root());
+ prj.add_source(
+ "Counter",
+ r#"
+contract Counter {
+ uint256 public number;
+
+ function getBalance(address target) public returns (uint256) {
+ return target.balance;
+ }
+}
+ "#,
+ )
+ .unwrap();
+
+ // Deploy counter contract.
+ cmd.args([
+ "script",
+ "--private-key",
+ "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
+ "--rpc-url",
+ &handle.http_endpoint(),
+ "--broadcast",
+ "CounterScript",
+ ])
+ .assert_success();
+
+ // Override state, `number()` should return overridden value.
+ cmd.cast_fuse()
+ .args([
+ "call",
+ "0x5FbDB2315678afecb367f032d93F642f64180aa3",
+ "--rpc-url",
+ &handle.http_endpoint(),
+ "--override-state",
+ "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x1234",
+ "number()(uint256)",
+ ])
+ .assert_success()
+ .stdout_eq(str![[r#"
+4660
+
+"#]]);
+
+ // Override balance, `getBalance()` should return overridden value.
+ cmd.cast_fuse()
+ .args([
+ "call",
+ "0x5FbDB2315678afecb367f032d93F642f64180aa3",
+ "--rpc-url",
+ &handle.http_endpoint(),
+ "--override-balance",
+ "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x1111",
+ "getBalance(address)(uint256)",
+ "0x5FbDB2315678afecb367f032d93F642f64180aa3",
+ ])
+ .assert_success()
+ .stdout_eq(str![[r#"
+4369
+
+"#]]);
+
+ // Override code with
+ // contract Counter {
+ // uint256 public number1;
+ // }
+ // Calling `number()` should fail.
+ cmd.cast_fuse()
+ .args([
+ "call",
+ "0x5FbDB2315678afecb367f032d93F642f64180aa3",
+ "--rpc-url",
+ &handle.http_endpoint(),
+ "--override-code",
+ "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x6080604052348015600e575f5ffd5b50600436106026575f3560e01c8063c223a39e14602a575b5f5ffd5b60306044565b604051603b9190605f565b60405180910390f35b5f5481565b5f819050919050565b6059816049565b82525050565b5f60208201905060705f8301846052565b9291505056fea26469706673582212202a0acfb9083efed3e0e9f27177b090731d4392cf196d58e27e05088f59008d0964736f6c634300081d0033",
+ "number()(uint256)",
+ ])
+ .assert_failure()
+ .stderr_eq(str![[r#"
+Error: server returned an error response: error code 3: execution reverted, data: "0x"
+
+"#]]);
+
+ // Calling `number1()` with overridden state should return new value.
+ cmd.cast_fuse()
+ .args([
+ "call",
+ "0x5FbDB2315678afecb367f032d93F642f64180aa3",
+ "--rpc-url",
+ &handle.http_endpoint(),
+ "--override-code",
+ "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x6080604052348015600e575f5ffd5b50600436106026575f3560e01c8063c223a39e14602a575b5f5ffd5b60306044565b604051603b9190605f565b60405180910390f35b5f5481565b5f819050919050565b6059816049565b82525050565b5f60208201905060705f8301846052565b9291505056fea26469706673582212202a0acfb9083efed3e0e9f27177b090731d4392cf196d58e27e05088f59008d0964736f6c634300081d0033",
+ "--override-state",
+ "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x2222",
+ "number1()(uint256)",
+ ])
+ .assert_success()
+ .stdout_eq(str![[r#"
+8738
+
+"#]]);
+
+ // Calling `number1()` with overridden state should return new value.
+ cmd.cast_fuse()
+ .args([
+ "call",
+ "0x5FbDB2315678afecb367f032d93F642f64180aa3",
+ "--rpc-url",
+ &handle.http_endpoint(),
+ "--override-code",
+ "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x6080604052348015600e575f5ffd5b50600436106026575f3560e01c8063c223a39e14602a575b5f5ffd5b60306044565b604051603b9190605f565b60405180910390f35b5f5481565b5f819050919050565b6059816049565b82525050565b5f60208201905060705f8301846052565b9291505056fea26469706673582212202a0acfb9083efed3e0e9f27177b090731d4392cf196d58e27e05088f59008d0964736f6c634300081d0033",
+ "--override-state-diff",
+ "0x5FbDB2315678afecb367f032d93F642f64180aa3:0x0:0x2222",
+ "number1()(uint256)",
+ ])
+ .assert_success()
+ .stdout_eq(str![[r#"
+8738
+
+"#]]);
+});
+
// https://github.com/foundry-rs/foundry/issues/9541
forgetest_async!(cast_run_impersonated_tx, |_prj, cmd| {
let (_api, handle) = anvil::spawn(