Skip to content

Commit

Permalink
fix: respect --auth in cast call and cast estimate (#9120)
Browse files Browse the repository at this point in the history
* fix: respect --auth in cast call and cast estimate

* access list parser
  • Loading branch information
klkvr authored Oct 15, 2024
1 parent f5aa05e commit cc8e430
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 51 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 41 additions & 44 deletions crates/cast/bin/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -134,10 +131,10 @@ pub struct CastTxBuilder<T, P, S> {
tx: WithOtherFields<TransactionRequest>,
legacy: bool,
blob: bool,
auth: Option<String>,
auth: Option<CliAuthorizationList>,
chain: Chain,
etherscan_api_key: Option<String>,
access_list: Option<Option<String>>,
access_list: Option<Option<AccessList>>,
state: S,
_t: std::marker::PhantomData<T>,
}
Expand Down Expand Up @@ -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::<AccessList>(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?);
}
Expand All @@ -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?);
}
Expand All @@ -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(())
Expand Down
3 changes: 3 additions & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 }

Expand Down
41 changes: 34 additions & 7 deletions crates/cli/src/opts/transaction.rs
Original file line number Diff line number Diff line change
@@ -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<Self, Self::Err> {
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.
Expand Down Expand Up @@ -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<String>,
pub auth: Option<CliAuthorizationList>,

/// 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<Option<String>>,
#[arg(long, value_parser = parse_json::<AccessList>)]
pub access_list: Option<Option<AccessList>>,
}

#[cfg(test)]
Expand Down
6 changes: 6 additions & 0 deletions crates/cli/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -133,6 +134,11 @@ pub fn parse_ether_value(value: &str) -> Result<U256> {
})
}

/// Parses a `T` from a string using [`serde_json::from_str`].
pub fn parse_json<T: DeserializeOwned>(value: &str) -> serde_json::Result<T> {
serde_json::from_str(value)
}

/// Parses a `Duration` from a &str
pub fn parse_delay(delay: &str) -> Result<Duration> {
let delay = if delay.ends_with("ms") {
Expand Down

0 comments on commit cc8e430

Please sign in to comment.