Skip to content

Commit

Permalink
Add creation-code flags and creation-args
Browse files Browse the repository at this point in the history
  • Loading branch information
pawurb committed Oct 10, 2024
1 parent 7104639 commit f3641e0
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 8 deletions.
10 changes: 7 additions & 3 deletions crates/cast/bin/args.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::cmd::{
access_list::AccessListArgs, bind::BindArgs, call::CallArgs, create2::Create2Args,
creation_code::CreationCodeArgs, estimate::EstimateArgs, find_block::FindBlockArgs,
interface::InterfaceArgs, logs::LogsArgs, mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs,
send::SendTxArgs, storage::StorageArgs, wallet::WalletSubcommands,
creation_args::CreationArgsArgs, creation_code::CreationCodeArgs, estimate::EstimateArgs,
find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs, mktx::MakeTxArgs,
rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, wallet::WalletSubcommands,
};
use alloy_primitives::{Address, B256, U256};
use alloy_rpc_types::BlockId;
Expand Down Expand Up @@ -902,6 +902,10 @@ pub enum CastSubcommand {
#[command(visible_alias = "cc")]
CreationCode(CreationCodeArgs),

/// Display args used for the contract initialization
#[command(visible_alias = "cra")]
CreationArgs(CreationArgsArgs),

/// Generate a Solidity interface from a given ABI.
///
/// Currently does not support ABI encoder v2.
Expand Down
82 changes: 82 additions & 0 deletions crates/cast/bin/cmd/creation_args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use alloy_primitives::{Address, Bytes};
use clap::{command, Parser};
use eyre::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};

/// CLI arguments for `cast creation-code`.
#[derive(Parser)]
pub struct CreationArgsArgs {
/// An Ethereum address, for which the bytecode will be fetched.
contract: Address,

#[command(flatten)]
etherscan: EtherscanOpts,
#[command(flatten)]
rpc: RpcOpts,
}

impl CreationArgsArgs {
pub async fn run(self) -> Result<()> {
let Self { contract, etherscan, rpc } = self;

let config = Config::from(&etherscan);
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_creation_args(bytecode, contract, &etherscan).await?;

for arg in args_arr {
println!("{arg}");
}

Ok(())
}
}

async fn parse_creation_args(
bytecode: Bytes,
contract: Address,
etherscan: &EtherscanOpts,
) -> Result<Vec<String>> {
let abi = fetch_abi_from_etherscan(contract, etherscan).await?;
let abi = abi.into_iter().next().ok_or_else(|| eyre::eyre!("No ABI found."))?;
let (abi, _) = abi;

if abi.constructor.is_none() {
return Err(eyre::eyre!("No constructor found."));
}

let constructor = abi.constructor.unwrap();

if constructor.inputs.is_empty() {
return Err(eyre::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)| {
let arg = arg.to_vec();
format!("{} {}", constructor.inputs[i].ty, Bytes::from(arg))
})
.collect();

Ok(display_args)
}
70 changes: 66 additions & 4 deletions crates/cast/bin/cmd/creation_code.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
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 evm_disassembler::{disassemble_bytes, format_operations};
use eyre::Result;
use foundry_block_explorers::Client;
use foundry_cli::{
Expand All @@ -12,6 +12,8 @@ use foundry_cli::{
use foundry_common::provider::RetryProvider;
use foundry_config::Config;

use super::interface::fetch_abi_from_etherscan;

/// CLI arguments for `cast creation-code`.
#[derive(Parser)]
pub struct CreationCodeArgs {
Expand All @@ -22,6 +24,14 @@ pub struct CreationCodeArgs {
#[arg(long)]
disassemble: bool,

/// Return creation bytecode without constructor arguments appended.
#[arg(long)]
without_args: bool,

/// Return only constructor arguments.
#[arg(long)]
only_args: bool,

#[command(flatten)]
etherscan: EtherscanOpts,
#[command(flatten)]
Expand All @@ -30,7 +40,12 @@ pub struct CreationCodeArgs {

impl CreationCodeArgs {
pub async fn run(self) -> Result<()> {
let Self { contract, etherscan, rpc, disassemble } = self;
let Self { contract, etherscan, rpc, disassemble, without_args, only_args } = self;

if without_args && only_args {
return Err(eyre::eyre!("--without-args and --only-args are mutually exclusive."));
}

let config = Config::from(&etherscan);
let chain = config.chain.unwrap_or_default();
let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default();
Expand All @@ -41,8 +56,11 @@ impl CreationCodeArgs {

let bytecode = fetch_creation_code(contract, client, provider).await?;

let bytecode =
parse_code_output(bytecode, contract, &etherscan, without_args, only_args).await?;

if disassemble {
print!("{}", format_operations(disassemble_bytes(bytecode.into())?)?);
println!("{}", SimpleCast::disassemble(&bytecode)?);
} else {
print!("{bytecode}");
}
Expand All @@ -51,10 +69,54 @@ impl CreationCodeArgs {
}
}

async fn parse_code_output(
bytecode: Bytes,
contract: Address,
etherscan: &EtherscanOpts,
without_args: bool,
only_args: bool,
) -> Result<Bytes> {
if !without_args && !only_args {
return Ok(bytecode);
}

let abi = fetch_abi_from_etherscan(contract, etherscan).await?;
let abi = abi.into_iter().next().ok_or_else(|| eyre::eyre!("No ABI found."))?;
let (abi, _) = abi;

if abi.constructor.is_none() {
if only_args {
return Err(eyre::eyre!("No constructor found."));
}
return Ok(bytecode);
}

let constructor = abi.constructor.unwrap();

if constructor.inputs.is_empty() {
if only_args {
return Err(eyre::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 {
panic!("Unreachable.")
};

Ok(bytecode)
}

/// Fetches the creation code of a contract from Etherscan and RPC.
///
/// If present, constructor arguments are appended to the end of the bytecode.
async fn fetch_creation_code(
pub async fn fetch_creation_code(
contract: Address,
client: Client,
provider: RetryProvider,
Expand Down
2 changes: 1 addition & 1 deletion crates/cast/bin/cmd/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ fn load_abi_from_artifact(path_or_contract: &str) -> Result<Vec<(JsonAbi, String
}

/// Fetches the ABI of a contract from Etherscan.
async fn fetch_abi_from_etherscan(
pub async fn fetch_abi_from_etherscan(
address: Address,
etherscan: &EtherscanOpts,
) -> Result<Vec<(JsonAbi, String)>> {
Expand Down
1 change: 1 addition & 0 deletions crates/cast/bin/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod access_list;
pub mod bind;
pub mod call;
pub mod create2;
pub mod creation_args;
pub mod creation_code;
pub mod estimate;
pub mod find_block;
Expand Down
1 change: 1 addition & 0 deletions crates/cast/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ async fn main_args(args: CastArgs) -> Result<()> {
}
CastSubcommand::Interface(cmd) => cmd.run().await?,
CastSubcommand::CreationCode(cmd) => cmd.run().await?,
CastSubcommand::CreationArgs(cmd) => cmd.run().await?,
CastSubcommand::Bind(cmd) => cmd.run().await?,
CastSubcommand::PrettyCalldata { calldata, offline } => {
let calldata = stdin::unwrap_line(calldata)?;
Expand Down
37 changes: 37 additions & 0 deletions crates/cast/tests/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1342,3 +1342,40 @@ casttest!(fetch_creation_code_from_etherscan, |_prj, cmd| {
.assert_success()
.stdout_eq(str![["0x60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122074c61e8e4eefd410ca92eec26e8112ec6e831d0a4bf35718fdd78b45d68220d064736f6c63430008070033"]]);
});

// tests that fetches a sample contract creation args bytes
// <https://etherscan.io/address/0x0923cad07f06b2d0e5e49e63b8b35738d4156b95>
casttest!(fetch_creation_code_only_args_from_etherscan, |_prj, cmd| {
let eth_rpc_url = next_http_rpc_endpoint();
cmd.args([
"creation-code",
"--etherscan-api-key",
&next_mainnet_etherscan_api_key(),
"0x6982508145454ce325ddbe47a25d4ec3d2311933",
"--rpc-url",
eth_rpc_url.as_str(),
"--only-args",
])
.assert_success()
.stdout_eq(str![["0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000"]]);
});

// tests that displays a sample contract creation args
// <https://etherscan.io/address/0x0923cad07f06b2d0e5e49e63b8b35738d4156b95>
casttest!(fetch_creation_args_from_etherscan, |_prj, cmd| {
let eth_rpc_url = next_http_rpc_endpoint();
cmd.args([
"creation-args",
"--etherscan-api-key",
&next_mainnet_etherscan_api_key(),
"0x6982508145454ce325ddbe47a25d4ec3d2311933",
"--rpc-url",
eth_rpc_url.as_str(),
])
.assert_success()
.stdout_eq(str![[
"uint256 0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000
"
]]);
});

0 comments on commit f3641e0

Please sign in to comment.