From cc8e430cc9ad743265d8c897b855809128798d8f Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Tue, 15 Oct 2024 23:57:58 +0400 Subject: [PATCH] fix: respect `--auth` in `cast call` and `cast estimate` (#9120) * fix: respect --auth in cast call and cast estimate * access list parser --- Cargo.lock | 3 ++ crates/cast/bin/tx.rs | 85 ++++++++++++++---------------- crates/cli/Cargo.toml | 3 ++ crates/cli/src/opts/transaction.rs | 41 +++++++++++--- crates/cli/src/utils/mod.rs | 6 +++ 5 files changed, 87 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5736ded80881..ab47c2ef8cb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3659,9 +3659,11 @@ version = "0.2.0" dependencies = [ "alloy-chains", "alloy-dyn-abi", + "alloy-eips", "alloy-json-abi", "alloy-primitives", "alloy-provider", + "alloy-rlp", "alloy-transport", "clap", "color-eyre", @@ -3678,6 +3680,7 @@ dependencies = [ "indicatif", "regex", "serde", + "serde_json", "strsim", "strum", "tempfile", diff --git a/crates/cast/bin/tx.rs b/crates/cast/bin/tx.rs index 1dc9b0cd149f..88b24a6e33c9 100644 --- a/crates/cast/bin/tx.rs +++ b/crates/cast/bin/tx.rs @@ -5,21 +5,18 @@ use alloy_network::{ }; use alloy_primitives::{hex, Address, Bytes, TxKind, U256}; use alloy_provider::Provider; -use alloy_rlp::Decodable; use alloy_rpc_types::{AccessList, Authorization, TransactionInput, TransactionRequest}; use alloy_serde::WithOtherFields; use alloy_signer::Signer; use alloy_transport::Transport; -use cast::revm::primitives::SignedAuthorization; -use eyre::{Result, WrapErr}; +use eyre::Result; use foundry_cli::{ - opts::TransactionOpts, + opts::{CliAuthorizationList, TransactionOpts}, utils::{self, parse_function_args}, }; use foundry_common::ens::NameOrAddress; use foundry_config::{Chain, Config}; use foundry_wallets::{WalletOpts, WalletSigner}; -use serde_json; /// Different sender kinds used by [`CastTxBuilder`]. pub enum SenderKind<'a> { @@ -134,10 +131,10 @@ pub struct CastTxBuilder { tx: WithOtherFields, legacy: bool, blob: bool, - auth: Option, + auth: Option, chain: Chain, etherscan_api_key: Option, - access_list: Option>, + access_list: Option>, state: S, _t: std::marker::PhantomData, } @@ -319,24 +316,32 @@ where self.tx.set_from(from); self.tx.set_chain_id(self.chain.id()); - if !fill { - return Ok((self.tx, self.state.func)); - } + let tx_nonce = if let Some(nonce) = self.tx.nonce { + nonce + } else { + let nonce = self.provider.get_transaction_count(from).await?; + if fill { + self.tx.nonce = Some(nonce); + } + nonce + }; - if let Some(access_list) = match self.access_list { + self.resolve_auth(sender, tx_nonce).await?; + + if let Some(access_list) = match self.access_list.take() { None => None, // --access-list provided with no value, call the provider to create it Some(None) => Some(self.provider.create_access_list(&self.tx).await?.access_list), // Access list provided as a string, attempt to parse it - Some(Some(ref s)) => Some( - serde_json::from_str::(s) - .map(AccessList::from) - .wrap_err("Failed to parse access list from string")?, - ), + Some(Some(access_list)) => Some(access_list), } { self.tx.set_access_list(access_list); } + if !fill { + return Ok((self.tx, self.state.func)); + } + if self.legacy && self.tx.gas_price.is_none() { self.tx.gas_price = Some(self.provider.get_gas_price().await?); } @@ -361,16 +366,6 @@ where } } - let nonce = if let Some(nonce) = self.tx.nonce { - nonce - } else { - let nonce = self.provider.get_transaction_count(from).await?; - self.tx.nonce = Some(nonce); - nonce - }; - - self.resolve_auth(sender, nonce).await?; - if self.tx.gas.is_none() { self.tx.gas = Some(self.provider.estimate_gas(&self.tx).await?); } @@ -379,25 +374,27 @@ where } /// Parses the passed --auth value and sets the authorization list on the transaction. - async fn resolve_auth(&mut self, sender: SenderKind<'_>, nonce: u64) -> Result<()> { - let Some(auth) = &self.auth else { return Ok(()) }; - - let auth = hex::decode(auth)?; - let auth = if let Ok(address) = Address::try_from(auth.as_slice()) { - let auth = - Authorization { chain_id: U256::from(self.chain.id()), nonce: nonce + 1, address }; - - let Some(signer) = sender.as_signer() else { - eyre::bail!("No signer available to sign authorization"); - }; - let signature = signer.sign_hash(&auth.signature_hash()).await?; - - auth.into_signed(signature) - } else if let Ok(auth) = SignedAuthorization::decode(&mut auth.as_ref()) { - auth - } else { - eyre::bail!("Failed to decode authorization"); + async fn resolve_auth(&mut self, sender: SenderKind<'_>, tx_nonce: u64) -> Result<()> { + let Some(auth) = self.auth.take() else { return Ok(()) }; + + let auth = match auth { + CliAuthorizationList::Address(address) => { + let auth = Authorization { + chain_id: U256::from(self.chain.id()), + nonce: tx_nonce + 1, + address, + }; + + let Some(signer) = sender.as_signer() else { + eyre::bail!("No signer available to sign authorization"); + }; + let signature = signer.sign_hash(&auth.signature_hash()).await?; + + auth.into_signed(signature) + } + CliAuthorizationList::Signed(auth) => auth, }; + self.tx.set_authorization_list(vec![auth]); Ok(()) diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index e8690a0a7d91..bab56495a5b3 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -22,10 +22,12 @@ foundry-wallets.workspace = true foundry-compilers = { workspace = true, features = ["full"] } +alloy-eips.workspace = true alloy-dyn-abi.workspace = true alloy-json-abi.workspace = true alloy-primitives.workspace = true alloy-provider.workspace = true +alloy-rlp.workspace = true alloy-transport.workspace = true alloy-chains.workspace = true @@ -43,6 +45,7 @@ tokio = { workspace = true, features = ["macros"] } tracing-subscriber = { workspace = true, features = ["registry", "env-filter"] } tracing.workspace = true yansi.workspace = true +serde_json.workspace = true tracing-tracy = { version = "0.11", optional = true } diff --git a/crates/cli/src/opts/transaction.rs b/crates/cli/src/opts/transaction.rs index 6730902f92a5..c0e229c35cda 100644 --- a/crates/cli/src/opts/transaction.rs +++ b/crates/cli/src/opts/transaction.rs @@ -1,9 +1,36 @@ -use crate::utils::parse_ether_value; -use alloy_primitives::{U256, U64}; +use std::str::FromStr; + +use crate::utils::{parse_ether_value, parse_json}; +use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization}; +use alloy_primitives::{hex, Address, U256, U64}; +use alloy_rlp::Decodable; use clap::Parser; -use serde::Serialize; -#[derive(Clone, Debug, Serialize, Parser)] +/// CLI helper to parse a EIP-7702 authorization list. +/// Can be either a hex-encoded signed authorization or an address. +#[derive(Clone, Debug)] +pub enum CliAuthorizationList { + /// If an address is provided, we sign the authorization delegating to provided address. + Address(Address), + /// If RLP-encoded authorization is provided, we decode it and attach to transaction. + Signed(SignedAuthorization), +} + +impl FromStr for CliAuthorizationList { + type Err = eyre::Error; + + fn from_str(s: &str) -> Result { + if let Ok(addr) = Address::from_str(s) { + Ok(Self::Address(addr)) + } else if let Ok(auth) = SignedAuthorization::decode(&mut hex::decode(s)?.as_ref()) { + Ok(Self::Signed(auth)) + } else { + eyre::bail!("Failed to decode authorization") + } + } +} + +#[derive(Clone, Debug, Parser)] #[command(next_help_heading = "Transaction options")] pub struct TransactionOpts { /// Gas limit for the transaction. @@ -61,15 +88,15 @@ pub struct TransactionOpts { /// /// Can be either a hex-encoded signed authorization or an address. #[arg(long, conflicts_with_all = &["legacy", "blob"])] - pub auth: Option, + pub auth: Option, /// EIP-2930 access list. /// /// Accepts either a JSON-encoded access list or an empty value to create the access list /// via an RPC call to `eth_createAccessList`. To retrieve only the access list portion, use /// the `cast access-list` command. - #[arg(long)] - pub access_list: Option>, + #[arg(long, value_parser = parse_json::)] + pub access_list: Option>, } #[cfg(test)] diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index af3c5e0a5dd9..5b7523447ec9 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -5,6 +5,7 @@ use alloy_transport::Transport; use eyre::{ContextCompat, Result}; use foundry_common::provider::{ProviderBuilder, RetryProvider}; use foundry_config::{Chain, Config}; +use serde::de::DeserializeOwned; use std::{ ffi::OsStr, future::Future, @@ -133,6 +134,11 @@ pub fn parse_ether_value(value: &str) -> Result { }) } +/// Parses a `T` from a string using [`serde_json::from_str`]. +pub fn parse_json(value: &str) -> serde_json::Result { + serde_json::from_str(value) +} + /// Parses a `Duration` from a &str pub fn parse_delay(delay: &str) -> Result { let delay = if delay.ends_with("ms") {