-
Notifications
You must be signed in to change notification settings - Fork 2.4k
feat(cast): run arbitrary bytecode #1020
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
e888a52
:gear: cast run subcommand
refcell d2800e5
:art: finish up cast run
refcell 415f692
:test_tube: cast run tests and cleaning
refcell 8e0a881
:construction_worker: cast run clippy fixes
refcell e5e963d
:gear: cast run fixes
refcell File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 |
|---|---|---|
|
|
@@ -6,3 +6,4 @@ | |
| //! [`foundry_config::Config`]. | ||
|
|
||
| pub mod find_block; | ||
| pub mod run; | ||
This file contains hidden or 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,164 @@ | ||
| use crate::{ | ||
| cmd::{forge::build::BuildArgs, Cmd}, | ||
| opts::evm::EvmArgs, | ||
| utils, | ||
| }; | ||
|
|
||
| use ansi_term::Colour; | ||
| use clap::Parser; | ||
| use ethers::types::{Address, Bytes, U256}; | ||
|
|
||
| use forge::{ | ||
| executor::{ | ||
| opts::EvmOpts, DatabaseRef, DeployResult, Executor, ExecutorBuilder, RawCallResult, | ||
| }, | ||
| trace::TraceKind, | ||
| CALLER, | ||
| }; | ||
| use foundry_config::{figment::Figment, Config}; | ||
| use foundry_utils::{encode_args, IntoFunction}; | ||
| use hex::ToHex; | ||
|
|
||
| // Loads project's figment and merges the build cli arguments into it | ||
| foundry_config::impl_figment_convert!(RunArgs, opts, evm_opts); | ||
|
|
||
| #[derive(Debug, Clone, Parser)] | ||
| pub struct RunArgs { | ||
| #[clap(help = "the bytecode to execute")] | ||
| pub bytecode: String, | ||
|
|
||
| // Optional Calldata | ||
| #[clap(help = "the calldata to pass to the contract")] | ||
| pub calldata: Option<String>, | ||
|
|
||
| /// Open the bytecode execution in debug mode | ||
| #[clap(long, help = "debug the bytecode execution")] | ||
| pub debug: bool, | ||
|
|
||
| #[clap(flatten)] | ||
| opts: BuildArgs, | ||
|
|
||
| #[clap(flatten)] | ||
| pub evm_opts: EvmArgs, | ||
| } | ||
|
|
||
| impl Cmd for RunArgs { | ||
| type Output = (); | ||
|
|
||
| fn run(self) -> eyre::Result<Self::Output> { | ||
| // Load figment | ||
| let figment: Figment = From::from(&self); | ||
| let evm_opts = figment.extract::<EvmOpts>()?; | ||
| let verbosity = evm_opts.verbosity; | ||
| let config = Config::from_provider(figment).sanitized(); | ||
|
|
||
| // Parse bytecode string | ||
| let parsed_bytecode = self.bytecode.parse::<Bytes>()?; | ||
|
|
||
| // Parse Calldata | ||
| let calldata: Bytes = if let Some(calldata) = | ||
| self.calldata.unwrap_or_else(|| "0x".to_string()).strip_prefix("0x") | ||
| { | ||
| hex::decode(calldata)?.into() | ||
| } else { | ||
| let args: Vec<String> = vec![]; | ||
| encode_args(&IntoFunction::into("".to_string()), &args)?.into() | ||
| }; | ||
|
|
||
| // Create executor | ||
| let mut builder = ExecutorBuilder::new() | ||
| .with_cheatcodes(evm_opts.ffi) | ||
| .with_config(evm_opts.evm_env()) | ||
| .with_spec(crate::utils::evm_spec(&config.evm_version)) | ||
| .with_fork(utils::get_fork(&evm_opts, &config.rpc_storage_caching)); | ||
| if verbosity >= 3 { | ||
| builder = builder.with_tracing(); | ||
| } | ||
| if self.debug { | ||
| builder = builder.with_tracing().with_debugger(); | ||
| } | ||
|
|
||
| // Create the runner | ||
| let mut runner = Runner::new(builder.build(), evm_opts.sender); | ||
|
|
||
| // Deploy the bytecode | ||
| let DeployResult { address, .. } = runner.setup(parsed_bytecode)?; | ||
|
|
||
| // Run the bytecode at the deployed address | ||
| let rcr = runner.run(address, calldata)?; | ||
|
|
||
| // TODO: Waterfall debug | ||
| // Ex: https://twitter.com/danielvf/status/1503756428212936710 | ||
|
|
||
| // Unwrap Traces | ||
| let mut traces = | ||
| rcr.traces.map(|traces| vec![(TraceKind::Execution, traces)]).unwrap_or_default(); | ||
|
|
||
| if verbosity >= 3 { | ||
| if traces.is_empty() { | ||
| eyre::bail!("Unexpected error: No traces despite verbosity level. Please report this as a bug: https://github.com/gakonst/foundry/issues/new?assignees=&labels=T-bug&template=BUG-FORM.yml"); | ||
| } | ||
|
|
||
| if rcr.reverted { | ||
| println!("Traces:"); | ||
| for (kind, trace) in &mut traces { | ||
| let should_include = match kind { | ||
| TraceKind::Setup => (verbosity >= 5) || (verbosity == 4), | ||
| TraceKind::Execution => verbosity > 3, | ||
| _ => false, | ||
| }; | ||
|
|
||
| if should_include { | ||
| // TODO: Create decoder using local fork | ||
| // decoder.decode(trace); | ||
| println!("{}", trace); | ||
| } | ||
| } | ||
| println!(); | ||
| } | ||
| } | ||
|
|
||
| if rcr.reverted { | ||
| println!("{}", Colour::Red.paint("[REVERT]")); | ||
| println!("Gas consumed: {}", rcr.gas); | ||
| } else { | ||
| println!("{}", Colour::Green.paint("[SUCCESS]")); | ||
| let o = rcr.result.encode_hex::<String>(); | ||
| if !o.is_empty() { | ||
| println!("Output: {}", o); | ||
| } else { | ||
| println!("{}", Colour::Yellow.paint("No Output")); | ||
| } | ||
| println!("Gas consumed: {}", rcr.gas); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| struct Runner<DB: DatabaseRef> { | ||
| pub executor: Executor<DB>, | ||
| pub sender: Address, | ||
| } | ||
|
|
||
| impl<DB: DatabaseRef> Runner<DB> { | ||
| pub fn new(executor: Executor<DB>, sender: Address) -> Self { | ||
| Self { executor, sender } | ||
| } | ||
|
|
||
| pub fn setup(&mut self, code: Bytes) -> eyre::Result<DeployResult> { | ||
| // We max out their balance so that they can deploy and make calls. | ||
| self.executor.set_balance(self.sender, U256::MAX); | ||
| self.executor.set_balance(*CALLER, U256::MAX); | ||
|
|
||
| // We set the nonce of the deployer accounts to 1 to get the same addresses as DappTools | ||
| self.executor.set_nonce(self.sender, 1); | ||
|
|
||
| // Deploy an instance of the contract | ||
| Ok(self.executor.deploy(self.sender, code.0, 0u32.into()).expect("couldn't deploy")) | ||
| } | ||
|
|
||
| pub fn run(&mut self, address: Address, calldata: Bytes) -> eyre::Result<RawCallResult> { | ||
| self.executor.call_raw(self.sender, address, calldata.0, 0_u64.into()) | ||
| } | ||
| } |
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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,67 @@ | ||
| //! Contains various tests for checking cast commands | ||
| use foundry_cli_test_utils::{ | ||
| casttest, | ||
| util::{TestCommand, TestProject}, | ||
| }; | ||
|
|
||
| // tests that the `cast run` command works correctly | ||
| casttest!(run_bytecode, |_: TestProject, mut cmd: TestCommand| { | ||
| // Create executable bytecode | ||
| let raw_bytecode = "0x604260005260206000F3".to_string(); | ||
| let raw_calldata = | ||
| "0x70a08231000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84".to_string(); | ||
|
|
||
| // Call `cast run` | ||
| cmd.arg("run").arg(raw_bytecode).arg(raw_calldata); | ||
| let output = cmd.stdout_lossy(); | ||
|
|
||
| // Expect successful bytecode execution | ||
| assert!(output.contains("[SUCCESS]")); | ||
| assert!(output.contains("Gas consumed:")); | ||
| assert!(output.contains("No Output")); | ||
|
|
||
| // Create reverting bytecode | ||
| let reverting_bytecode = "0x610101610102016000526001601ff3".to_string(); | ||
| let revert_calldata = | ||
| "0x70a08231000000000000000000000000b4c79dab8f259c7aee6e5b2aa729821864227e84".to_string(); | ||
|
|
||
| // Call `cast run` | ||
| cmd.cfuse().arg("run").arg(reverting_bytecode).arg(revert_calldata); | ||
| let revert_output = cmd.stdout_lossy(); | ||
|
|
||
| // Expect bytecode execution to revert | ||
| assert!(revert_output.contains("[REVERT]")); | ||
| assert!(revert_output.contains("Gas consumed:")); | ||
|
|
||
| // On revert, we don't have an output | ||
| assert!(!revert_output.contains("Output")); | ||
| }); | ||
|
|
||
| // tests that `cast run` works without calldata | ||
| casttest!(run_bytecode_absent_calldata, |_: TestProject, mut cmd: TestCommand| { | ||
| // Create executable bytecode | ||
| let raw_bytecode = "0x604260005260206000F3".to_string(); | ||
|
|
||
| // Call `cast run` | ||
| cmd.arg("run").arg(raw_bytecode); | ||
| let output = cmd.stdout_lossy(); | ||
|
|
||
| // Expect successful bytecode execution | ||
| assert!(output.contains("[SUCCESS]")); | ||
| assert!(output.contains("Gas consumed:")); | ||
| assert!(output.contains("No Output")); | ||
|
|
||
| // Create reverting bytecode | ||
| let reverting_bytecode = "0x610101610102016000526001601ff3".to_string(); | ||
|
|
||
| // Call `cast run` | ||
| cmd.cfuse().arg("run").arg(reverting_bytecode); | ||
| let revert_output = cmd.stdout_lossy(); | ||
|
|
||
| // Expect bytecode execution to revert | ||
| assert!(revert_output.contains("[REVERT]")); | ||
| assert!(revert_output.contains("Gas consumed:")); | ||
|
|
||
| // On revert, we don't have an output | ||
| assert!(!revert_output.contains("Output")); | ||
| }); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice!