-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* feat(cast) add creation-code method [#8973] * Fix typo * Fix CI * Code review fixes * Add creation-code flags and creation-args * Update comments * eyre style fixes * typo * use r#".."# for snapbox * Apply suggestions from code review * fix test regression * tag arguments as mutually exclusive * use unreachable! * Rename and add abi_path param * Decode constructor args * Update crates/cast/bin/cmd/constructor_args.rs * fix test * Update crates/cast/bin/args.rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Update crates/cast/bin/cmd/creation_code.rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Update crates/cast/bin/cmd/creation_code.rs Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com> * Fix formatting * Code review fixes --------- Co-authored-by: zerosnacks <zerosnacks@protonmail.com> Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com>
- Loading branch information
1 parent
dd443c6
commit 3e901af
Showing
8 changed files
with
348 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
use alloy_dyn_abi::DynSolType; | ||
use alloy_primitives::{Address, Bytes}; | ||
use clap::{command, Parser}; | ||
use eyre::{eyre, OptionExt, Result}; | ||
use foundry_block_explorers::Client; | ||
use foundry_cli::{ | ||
opts::{EtherscanOpts, RpcOpts}, | ||
utils, | ||
}; | ||
use foundry_config::Config; | ||
|
||
use super::{ | ||
creation_code::fetch_creation_code, | ||
interface::{fetch_abi_from_etherscan, load_abi_from_file}, | ||
}; | ||
|
||
/// CLI arguments for `cast creation-args`. | ||
#[derive(Parser)] | ||
pub struct ConstructorArgsArgs { | ||
/// An Ethereum address, for which the bytecode will be fetched. | ||
contract: Address, | ||
|
||
/// Path to file containing the contract's JSON ABI. It's necessary if the target contract is | ||
/// not verified on Etherscan | ||
#[arg(long)] | ||
abi_path: Option<String>, | ||
|
||
#[command(flatten)] | ||
etherscan: EtherscanOpts, | ||
|
||
#[command(flatten)] | ||
rpc: RpcOpts, | ||
} | ||
|
||
impl ConstructorArgsArgs { | ||
pub async fn run(self) -> Result<()> { | ||
let Self { contract, etherscan, rpc, abi_path } = self; | ||
|
||
let config = Config::from(ðerscan); | ||
let chain = config.chain.unwrap_or_default(); | ||
let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); | ||
let client = Client::new(chain, api_key)?; | ||
|
||
let config = Config::from(&rpc); | ||
let provider = utils::get_provider(&config)?; | ||
|
||
let bytecode = fetch_creation_code(contract, client, provider).await?; | ||
|
||
let args_arr = parse_constructor_args(bytecode, contract, ðerscan, abi_path).await?; | ||
for arg in args_arr { | ||
let _ = sh_println!("{arg}"); | ||
} | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
/// Fetches the constructor arguments values and types from the creation bytecode and ABI. | ||
async fn parse_constructor_args( | ||
bytecode: Bytes, | ||
contract: Address, | ||
etherscan: &EtherscanOpts, | ||
abi_path: Option<String>, | ||
) -> Result<Vec<String>> { | ||
let abi = if let Some(abi_path) = abi_path { | ||
load_abi_from_file(&abi_path, None)? | ||
} else { | ||
fetch_abi_from_etherscan(contract, etherscan).await? | ||
}; | ||
|
||
let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?; | ||
let (abi, _) = abi; | ||
|
||
let constructor = abi.constructor.ok_or_else(|| eyre!("No constructor found."))?; | ||
|
||
if constructor.inputs.is_empty() { | ||
return Err(eyre!("No constructor arguments found.")); | ||
} | ||
|
||
let args_size = constructor.inputs.len() * 32; | ||
let args_bytes = Bytes::from(bytecode[bytecode.len() - args_size..].to_vec()); | ||
|
||
let display_args: Vec<String> = args_bytes | ||
.chunks(32) | ||
.enumerate() | ||
.map(|(i, arg)| { | ||
format_arg(&constructor.inputs[i].ty, arg).expect("Failed to format argument.") | ||
}) | ||
.collect(); | ||
|
||
Ok(display_args) | ||
} | ||
|
||
fn format_arg(ty: &str, arg: &[u8]) -> Result<String> { | ||
let arg_type: DynSolType = ty.parse().expect("Invalid ABI type."); | ||
let decoded = arg_type.abi_decode(arg)?; | ||
let bytes = Bytes::from(arg.to_vec()); | ||
|
||
Ok(format!("{bytes} → {decoded:?}")) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
use alloy_primitives::{Address, Bytes}; | ||
use alloy_provider::{ext::TraceApi, Provider}; | ||
use alloy_rpc_types::trace::parity::{Action, CreateAction, CreateOutput, TraceOutput}; | ||
use cast::SimpleCast; | ||
use clap::{command, Parser}; | ||
use eyre::{eyre, OptionExt, Result}; | ||
use foundry_block_explorers::Client; | ||
use foundry_cli::{ | ||
opts::{EtherscanOpts, RpcOpts}, | ||
utils, | ||
}; | ||
use foundry_common::provider::RetryProvider; | ||
use foundry_config::Config; | ||
|
||
use super::interface::{fetch_abi_from_etherscan, load_abi_from_file}; | ||
|
||
/// CLI arguments for `cast creation-code`. | ||
#[derive(Parser)] | ||
pub struct CreationCodeArgs { | ||
/// An Ethereum address, for which the bytecode will be fetched. | ||
contract: Address, | ||
|
||
/// Path to file containing the contract's JSON ABI. It's necessary if the target contract is | ||
/// not verified on Etherscan. | ||
#[arg(long)] | ||
abi_path: Option<String>, | ||
|
||
/// Disassemble bytecodes into individual opcodes. | ||
#[arg(long)] | ||
disassemble: bool, | ||
|
||
/// Return creation bytecode without constructor arguments appended. | ||
#[arg(long, conflicts_with = "only_args")] | ||
without_args: bool, | ||
|
||
/// Return only constructor arguments. | ||
#[arg(long)] | ||
only_args: bool, | ||
|
||
#[command(flatten)] | ||
etherscan: EtherscanOpts, | ||
|
||
#[command(flatten)] | ||
rpc: RpcOpts, | ||
} | ||
|
||
impl CreationCodeArgs { | ||
pub async fn run(self) -> Result<()> { | ||
let Self { contract, etherscan, rpc, disassemble, without_args, only_args, abi_path } = | ||
self; | ||
|
||
let config = Config::from(ðerscan); | ||
let chain = config.chain.unwrap_or_default(); | ||
let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); | ||
let client = Client::new(chain, api_key)?; | ||
|
||
let config = Config::from(&rpc); | ||
let provider = utils::get_provider(&config)?; | ||
|
||
let bytecode = fetch_creation_code(contract, client, provider).await?; | ||
|
||
let bytecode = | ||
parse_code_output(bytecode, contract, ðerscan, abi_path, without_args, only_args) | ||
.await?; | ||
|
||
if disassemble { | ||
let _ = sh_println!("{}", SimpleCast::disassemble(&bytecode)?); | ||
} else { | ||
let _ = sh_println!("{bytecode}"); | ||
} | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
/// Parses the creation bytecode and returns one of the following: | ||
/// - The complete bytecode | ||
/// - The bytecode without constructor arguments | ||
/// - Only the constructor arguments | ||
async fn parse_code_output( | ||
bytecode: Bytes, | ||
contract: Address, | ||
etherscan: &EtherscanOpts, | ||
abi_path: Option<String>, | ||
without_args: bool, | ||
only_args: bool, | ||
) -> Result<Bytes> { | ||
if !without_args && !only_args { | ||
return Ok(bytecode); | ||
} | ||
|
||
let abi = if let Some(abi_path) = abi_path { | ||
load_abi_from_file(&abi_path, None)? | ||
} else { | ||
fetch_abi_from_etherscan(contract, etherscan).await? | ||
}; | ||
|
||
let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?; | ||
let (abi, _) = abi; | ||
|
||
if abi.constructor.is_none() { | ||
if only_args { | ||
return Err(eyre!("No constructor found.")); | ||
} | ||
return Ok(bytecode); | ||
} | ||
|
||
let constructor = abi.constructor.unwrap(); | ||
if constructor.inputs.is_empty() { | ||
if only_args { | ||
return Err(eyre!("No constructor arguments found.")); | ||
} | ||
return Ok(bytecode); | ||
} | ||
|
||
let args_size = constructor.inputs.len() * 32; | ||
|
||
let bytecode = if without_args { | ||
Bytes::from(bytecode[..bytecode.len() - args_size].to_vec()) | ||
} else if only_args { | ||
Bytes::from(bytecode[bytecode.len() - args_size..].to_vec()) | ||
} else { | ||
unreachable!(); | ||
}; | ||
|
||
Ok(bytecode) | ||
} | ||
|
||
/// Fetches the creation code of a contract from Etherscan and RPC. | ||
pub async fn fetch_creation_code( | ||
contract: Address, | ||
client: Client, | ||
provider: RetryProvider, | ||
) -> Result<Bytes> { | ||
let creation_data = client.contract_creation_data(contract).await?; | ||
let creation_tx_hash = creation_data.transaction_hash; | ||
let tx_data = provider.get_transaction_by_hash(creation_tx_hash).await?; | ||
let tx_data = tx_data.ok_or_eyre("Could not find creation tx data.")?; | ||
|
||
let bytecode = if tx_data.inner.to.is_none() { | ||
// Contract was created using a standard transaction | ||
tx_data.inner.input | ||
} else { | ||
// Contract was created using a factory pattern or create2 | ||
// Extract creation code from tx traces | ||
let mut creation_bytecode = None; | ||
|
||
let traces = provider.trace_transaction(creation_tx_hash).await.map_err(|e| { | ||
eyre!("Could not fetch traces for transaction {}: {}", creation_tx_hash, e) | ||
})?; | ||
|
||
for trace in traces { | ||
if let Some(TraceOutput::Create(CreateOutput { address, .. })) = trace.trace.result { | ||
if address == contract { | ||
creation_bytecode = match trace.trace.action { | ||
Action::Create(CreateAction { init, .. }) => Some(init), | ||
_ => None, | ||
}; | ||
} | ||
} | ||
} | ||
|
||
creation_bytecode.ok_or_else(|| eyre!("Could not find contract creation trace."))? | ||
}; | ||
|
||
Ok(bytecode) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.