Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ alloy-trie = "0.9"

## op-alloy
op-alloy-consensus = "0.24.0"
op-alloy-network = "0.24.0"
op-alloy-rpc-types = "0.24.0"
op-alloy-flz = "0.13.1"

Expand Down Expand Up @@ -501,6 +502,7 @@ alloy-evm = { git = "https://github.com/alloy-rs/evm.git", branch = "alloy-2.0"

## op-alloy / alloy-op-evm
op-alloy-consensus = { git = "https://github.com/foundry-rs/optimism", branch = "alloy-2.0" }
op-alloy-network = { git = "https://github.com/foundry-rs/optimism", branch = "alloy-2.0" }
op-alloy-rpc-types = { git = "https://github.com/foundry-rs/optimism", branch = "alloy-2.0" }
op-alloy = { git = "https://github.com/foundry-rs/optimism", branch = "alloy-2.0" }
alloy-op-evm = { git = "https://github.com/foundry-rs/optimism", branch = "alloy-2.0" }
Expand Down
1 change: 1 addition & 0 deletions crates/cast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ tempo-alloy.workspace = true
alloy-evm.workspace = true

op-alloy-flz.workspace = true
op-alloy-network.workspace = true

chrono.workspace = true
eyre.workspace = true
Expand Down
45 changes: 31 additions & 14 deletions crates/cast/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,25 @@ use alloy_rpc_types::{BlockId, BlockNumberOrTag::Latest};
use clap::{CommandFactory, Parser};
use clap_complete::generate;
use eyre::Result;
use foundry_cli::{utils, utils::LoadConfig};
use foundry_cli::{
opts::NetworkVariant,
utils::{self, LoadConfig},
};
use foundry_common::{
abi::{get_error, get_event},
fmt::{format_tokens, format_uint_exp, serialize_value_as_json},
fs,
provider::ProviderBuilder,
selectors::{
ParsedSignatures, SelectorImportData, SelectorKind, decode_calldata, decode_event_topic,
decode_function_selector, decode_selectors, import_selectors, parse_signatures,
pretty_calldata,
},
shell, stdin,
};
use op_alloy_network::Optimism;
use std::time::Instant;
use tempo_alloy::TempoNetwork;

/// Run the `cast` command-line interface.
pub fn run() -> Result<()> {
Expand Down Expand Up @@ -533,23 +539,34 @@ pub async fn run_command(args: CastArgs) -> Result<()> {
}
CastSubcommand::Run(cmd) => cmd.run().await?,
CastSubcommand::SendTx(cmd) => cmd.run().await?,
CastSubcommand::Tx { tx_hash, from, nonce, field, raw, rpc, to_request } => {
CastSubcommand::Tx { tx_hash, from, nonce, field, raw, rpc, to_request, network } => {
let config = rpc.load_config()?;
// Can use either --raw or specify raw as a field
if raw || field.as_ref().is_some_and(|f| f == "raw") {
// Temporary workaround to handle tx raw encoding through FoundryNetwork
// TODO: Once the Network selection UI will be finalized, bring back --raw
// handling to `Cast::transaction`
sh_println!("{}", crate::transaction_raw(&config, tx_hash, from, nonce).await?)?
} else {
let provider = utils::get_provider(&config)?;
sh_println!(
"{}",
let is_raw = raw || field.as_ref().is_some_and(|f| f == "raw");
let output = match network {
Some(NetworkVariant::Optimism) => {
let provider = ProviderBuilder::<Optimism>::from_config(&config)?.build()?;

Cast::new(&provider)
.transaction(tx_hash, from, nonce, field, to_request)
.transaction(tx_hash, from, nonce, field, is_raw, to_request)
.await?
)?
}
}
Some(NetworkVariant::Tempo) => {
let provider =
ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
Cast::new(&provider)
.transaction(tx_hash, from, nonce, field, is_raw, to_request)
.await?
}
// Ethereum (default) or no --raw flag
_ => {
let provider = utils::get_provider(&config)?;
Cast::new(&provider)
.transaction(tx_hash, from, nonce, field, is_raw, to_request)
.await?
}
};
sh_println!("{}", output)?
}

// 4Byte
Expand Down
54 changes: 9 additions & 45 deletions crates/cast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,11 @@ use foundry_common::{
compile::etherscan_project,
flatten,
fmt::*,
fs,
provider::ProviderBuilder,
shell,
fs, shell,
};
use foundry_config::{Chain, Config};
use foundry_config::Chain;
use foundry_evm::core::bytecode::InstIter;
use foundry_primitives::{FoundryNetwork, FoundryTxEnvelope};
use foundry_primitives::FoundryTxEnvelope;
use futures::{FutureExt, StreamExt, future::Either};

use rayon::prelude::*;
Expand Down Expand Up @@ -721,7 +719,7 @@ where
/// ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?;
/// let cast = Cast::new(provider);
/// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc";
/// let tx = cast.transaction(Some(tx_hash.to_string()), None, None, None, false).await?;
/// let tx = cast.transaction(Some(tx_hash.to_string()), None, None, None, false, false).await?;
/// println!("{}", tx);
/// # Ok(())
/// # }
Expand All @@ -732,6 +730,7 @@ where
from: Option<NameOrAddress>,
nonce: Option<u64>,
field: Option<String>,
raw: bool,
to_request: bool,
) -> Result<String> {
let tx = if let Some(tx_hash) = tx_hash {
Expand All @@ -758,7 +757,10 @@ where
eyre::bail!("tx hash or from address is required")
};

Ok(if let Some(ref field) = field {
Ok(if raw {
let encoded = tx.as_ref().encoded_2718();
format!("0x{}", hex::encode(encoded))
} else if let Some(ref field) = field {
get_pretty_tx_attr::<N>(&tx, field.as_str())
.ok_or_else(|| eyre::eyre!("invalid tx field: {}", field.to_string()))?
} else if shell::is_json() {
Expand Down Expand Up @@ -2358,44 +2360,6 @@ fn explorer_client(
builder.build().map_err(Into::into)
}

// Temporary workaround to handle tx raw encoding through FoundryNetwork
// TODO: Once the Network selection UI will be finalized, bring back --raw
// handling to `Cast::transaction`
pub async fn transaction_raw(
config: &Config,
tx_hash: Option<String>,
from: Option<NameOrAddress>,
nonce: Option<u64>,
) -> Result<String> {
let provider = ProviderBuilder::<FoundryNetwork>::from_config(config)?.build()?;
let tx = if let Some(tx_hash) = tx_hash {
let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?;
provider
.get_transaction_by_hash(tx_hash)
.await?
.ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))?
} else if let Some(from) = from {
// If nonce is not provided, uses 0.
let nonce = U64::from(nonce.unwrap_or_default());
let from = from.resolve(provider.root()).await?;

provider
.raw_request::<_, Option<<FoundryNetwork as Network>::TransactionResponse>>(
"eth_getTransactionBySenderAndNonce".into(),
(from, nonce),
)
.await?
.ok_or_else(|| {
eyre::eyre!("tx not found for sender {from} and nonce {:?}", nonce.to::<u64>())
})?
} else {
eyre::bail!("tx hash or from address is required")
};

let encoded = tx.as_ref().encoded_2718();
Ok(format!("0x{}", hex::encode(encoded)))
}

#[cfg(test)]
mod tests {
use super::{DynSolValue, SimpleCast as Cast, serialize_value_as_json};
Expand Down
6 changes: 5 additions & 1 deletion crates/cast/src/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use alloy_primitives::{Address, B256, Selector, U256};
use alloy_rpc_types::BlockId;
use clap::{ArgAction, Parser, Subcommand, ValueHint};
use eyre::Result;
use foundry_cli::opts::{EtherscanOpts, GlobalArgs, RpcOpts};
use foundry_cli::opts::{EtherscanOpts, GlobalArgs, NetworkVariant, RpcOpts};
use foundry_common::version::{LONG_VERSION, SHORT_VERSION};
use std::{path::PathBuf, str::FromStr};
/// A Swiss Army knife for interacting with Ethereum applications from the command line.
Expand Down Expand Up @@ -519,6 +519,10 @@ pub enum CastSubcommand {
/// If specified, the transaction will be converted to a TransactionRequest JSON format.
#[arg(long)]
to_request: bool,

/// Specify the Network for correct encoding.
#[arg(long, short, num_args = 1, value_name = "NETWORK")]
network: Option<NetworkVariant>,
},

/// Get the transaction receipt for a transaction.
Expand Down
18 changes: 18 additions & 0 deletions crates/cast/tests/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3978,6 +3978,8 @@ casttest!(tx_raw_opstack_deposit, |_prj, cmd| {
"tx",
"0xf403cba612d1c01c027455c0d97427ccd5f7f99aac30017e065f81d1e30244ea",
"--raw",
"-n",
"optimism",
"--rpc-url",
"https://sepolia.base.org",
]).assert_success()
Expand All @@ -3987,6 +3989,22 @@ casttest!(tx_raw_opstack_deposit, |_prj, cmd| {
"#]]);
});

casttest!(tx_raw_tempo, |_prj, cmd| {
cmd.args([
"tx",
"0xa24c6bbeea629a80be79e970a9749d0cbc6ee31625a0b75f585c173ab15a18ec",
"--raw",
"-n",
"tempo",
"--rpc-url",
"https://rpc.moderato.tempo.xyz",
]).assert_success()
.stdout_eq(str![[r#"
0x76f8cf82a5bf1485059682f018830494e5f85ef85c9420c0000000000000000000007d9cc57068833ea780b84440c10f190000000000000000000000008a871f4189067637cfc4cc1500abd6244bf1df740000000000000000000000000000000000000000000000000000000005f5e100c08082057e80809420c000000000000000000000000000000000000080c0b841eb100c4cbd96903bf9e97968c0982670bb90fc191ee4544c7ff32d44e901dbea3f6fbdd58255051135c2fe1aa81583a270d96009cbe375f4605ef15971273a4f1b

"#]]);
});

// Test that cast send --create works correctly with constructor arguments
// <https://github.com/foundry-rs/foundry/issues/10947>
forgetest_async!(cast_send_create_with_constructor_args, |prj, cmd| {
Expand Down
2 changes: 2 additions & 0 deletions crates/cli/src/opts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod chain;
mod dependency;
mod evm;
mod global;
mod network;
mod rpc;
mod tempo;
mod transaction;
Expand All @@ -12,6 +13,7 @@ pub use chain::*;
pub use dependency::*;
pub use evm::*;
pub use global::*;
pub use network::*;
pub use rpc::*;
pub use tempo::*;
pub use transaction::*;
11 changes: 11 additions & 0 deletions crates/cli/src/opts/network.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/// Network selection, defaulting to Ethereum
#[derive(Clone, Debug, Default, clap::ValueEnum)]
pub enum NetworkVariant {
/// Ethereum (default)
#[default]
Ethereum,
/// Optimism / OP-stack
Optimism,
/// Tempo
Tempo,
}
Comment on lines +1 to +11
Copy link
Copy Markdown
Member

@zerosnacks zerosnacks Mar 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, would like to get @onbjerg's input & OK on this as we had a prior conversation about this when working on tempo-foundry; what is preferable is if we somehow have a central location where this is maintained; I don't have an issue with the current proposal.

Context:

#[derive(Clone, Debug, Default, Parser, Copy, Serialize, Deserialize, PartialEq)]
pub struct NetworkConfigs {
/// Enable Optimism network features.
#[arg(help_heading = "Networks", long, conflicts_with = "celo")]
// Skipped from configs (forge) as there is no feature to be added yet.
#[serde(skip)]
optimism: bool,
/// Enable Celo network features.
#[arg(help_heading = "Networks", long, conflicts_with = "optimism")]
#[serde(default)]
celo: bool,
/// Whether to bypass prevrandao.
#[arg(skip)]
#[serde(default)]
bypass_prevrandao: bool,
}

Loading