From a6f631f5d48bd1a93b891a33ca841a0ffe626ae7 Mon Sep 17 00:00:00 2001 From: clabby Date: Sat, 2 Dec 2023 00:19:35 -0500 Subject: [PATCH 1/5] Add EIP-3074 support --- Cargo.lock | 34 +++++++++--------- Cargo.toml | 26 +++++++------- crates/anvil/core/src/eth/transaction/mod.rs | 6 ++-- crates/anvil/src/eth/util.rs | 1 + crates/anvil/src/genesis.rs | 2 ++ crates/anvil/src/hardfork.rs | 8 +++++ crates/cast/bin/cmd/create2.rs | 4 +-- crates/cast/bin/cmd/storage.rs | 2 +- crates/cast/src/rlp_converter.rs | 2 +- crates/cheatcodes/spec/src/vm.rs | 2 ++ crates/cheatcodes/src/inspector.rs | 1 + crates/cheatcodes/src/string.rs | 2 +- crates/cheatcodes/src/test/expect.rs | 2 +- crates/common/src/fmt/dynamic.rs | 2 +- crates/common/src/fmt/mod.rs | 2 +- crates/common/src/provider.rs | 2 +- crates/config/src/lib.rs | 8 ++++- crates/debugger/src/tui.rs | 4 ++- crates/doc/src/builder.rs | 4 +-- .../src/preprocessor/contract_inheritance.rs | 2 +- crates/doc/src/preprocessor/git_source.rs | 2 +- crates/evm/core/src/decode.rs | 18 +++++----- crates/evm/core/src/opts.rs | 4 +-- crates/evm/core/src/utils.rs | 12 +++++-- crates/evm/traces/src/decoder/mod.rs | 2 +- crates/evm/traces/src/lib.rs | 1 + crates/evm/traces/src/node.rs | 36 ++++++++++--------- crates/forge/bin/cmd/script/multi.rs | 2 +- crates/forge/tests/cli/config.rs | 1 + 29 files changed, 113 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6725991000549..88eb5b9d11837 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2082,7 +2082,7 @@ dependencies = [ [[package]] name = "ethers" version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -2097,7 +2097,7 @@ dependencies = [ [[package]] name = "ethers-addressbook" version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" dependencies = [ "ethers-core", "once_cell", @@ -2108,7 +2108,7 @@ dependencies = [ [[package]] name = "ethers-contract" version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" dependencies = [ "const-hex", "ethers-contract-abigen", @@ -2126,7 +2126,7 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" dependencies = [ "Inflector", "const-hex", @@ -2149,7 +2149,7 @@ dependencies = [ [[package]] name = "ethers-contract-derive" version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" dependencies = [ "Inflector", "const-hex", @@ -2164,7 +2164,7 @@ dependencies = [ [[package]] name = "ethers-core" version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" dependencies = [ "arrayvec", "bytes", @@ -2193,7 +2193,7 @@ dependencies = [ [[package]] name = "ethers-etherscan" version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" dependencies = [ "chrono", "ethers-core", @@ -2208,7 +2208,7 @@ dependencies = [ [[package]] name = "ethers-middleware" version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" dependencies = [ "async-trait", "auto_impl", @@ -2233,7 +2233,7 @@ dependencies = [ [[package]] name = "ethers-providers" version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" dependencies = [ "async-trait", "auto_impl", @@ -2271,7 +2271,7 @@ dependencies = [ [[package]] name = "ethers-signers" version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" dependencies = [ "async-trait", "coins-bip32", @@ -2299,7 +2299,7 @@ dependencies = [ [[package]] name = "ethers-solc" version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" dependencies = [ "cfg-if", "const-hex", @@ -5706,7 +5706,7 @@ dependencies = [ [[package]] name = "revm" version = "3.5.0" -source = "git+https://github.com/bluealloy/revm?branch=reth_freeze#b00ebab8b3477f87e3d876a11b8f18d00a8f4103" +source = "git+https://github.com/clabby/revm?branch=cl/eip-3074#74c9d447d8865650ea8296517362b7341a45a0dc" dependencies = [ "auto_impl", "revm-interpreter", @@ -5718,7 +5718,7 @@ dependencies = [ [[package]] name = "revm-interpreter" version = "1.3.0" -source = "git+https://github.com/bluealloy/revm?branch=reth_freeze#b00ebab8b3477f87e3d876a11b8f18d00a8f4103" +source = "git+https://github.com/clabby/revm?branch=cl/eip-3074#74c9d447d8865650ea8296517362b7341a45a0dc" dependencies = [ "revm-primitives", "serde", @@ -5727,15 +5727,13 @@ dependencies = [ [[package]] name = "revm-precompile" version = "2.2.0" -source = "git+https://github.com/bluealloy/revm?branch=reth_freeze#b00ebab8b3477f87e3d876a11b8f18d00a8f4103" +source = "git+https://github.com/clabby/revm?branch=cl/eip-3074#74c9d447d8865650ea8296517362b7341a45a0dc" dependencies = [ "aurora-engine-modexp", "c-kzg", - "k256", "once_cell", "revm-primitives", "ripemd", - "secp256k1", "sha2 0.10.8", "substrate-bn", ] @@ -5743,7 +5741,7 @@ dependencies = [ [[package]] name = "revm-primitives" version = "1.3.0" -source = "git+https://github.com/bluealloy/revm?branch=reth_freeze#b00ebab8b3477f87e3d876a11b8f18d00a8f4103" +source = "git+https://github.com/clabby/revm?branch=cl/eip-3074#74c9d447d8865650ea8296517362b7341a45a0dc" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -5754,6 +5752,8 @@ dependencies = [ "enumn", "hashbrown 0.14.3", "hex", + "k256", + "secp256k1", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index 19a26c63bb2ed..6db993d054bcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -187,17 +187,17 @@ tower-http = "0.4" #ethers-solc = { path = "../ethers-rs/ethers-solc" } [patch.crates-io] -ethers = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } -ethers-core = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } -ethers-contract = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } -ethers-contract-abigen = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } -ethers-providers = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } -ethers-signers = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } -ethers-middleware = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } -ethers-solc = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } - -revm = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" } -revm-interpreter = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" } -revm-precompile = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" } -revm-primitives = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" } +ethers = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } +ethers-core = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } +ethers-contract = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } +ethers-contract-abigen = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } +ethers-providers = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } +ethers-signers = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } +ethers-middleware = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } +ethers-solc = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } + +revm = { git = "https://github.com/clabby/revm", branch = "cl/eip-3074" } +revm-interpreter = { git = "https://github.com/clabby/revm", branch = "cl/eip-3074" } +revm-precompile = { git = "https://github.com/clabby/revm", branch = "cl/eip-3074" } +revm-primitives = { git = "https://github.com/clabby/revm", branch = "cl/eip-3074" } diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index 68cd9b48fa8dc..b07914480f8a3 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -137,7 +137,7 @@ impl EthTransactionRequest { gas_limit: gas.unwrap_or_default(), is_system_tx: optimism_fields.clone()?.is_system_tx, input: data.clone().unwrap_or_default(), - })); + })) } match ( transaction_type, @@ -860,9 +860,7 @@ impl Decodable for TypedTransaction { if rlp.is_list() { return Ok(TypedTransaction::Legacy(rlp.as_val()?)) } - let [first, s @ ..] = rlp.data()? else { - return Err(DecoderError::Custom("empty slice")); - }; + let [first, s @ ..] = rlp.data()? else { return Err(DecoderError::Custom("empty slice")) }; // "advance" the header, see comments in fastrlp impl below let s = if s.is_empty() { &rlp.as_raw()[1..] } else { s }; diff --git a/crates/anvil/src/eth/util.rs b/crates/anvil/src/eth/util.rs index 4181df4e8d447..d3c6e9d7e97f5 100644 --- a/crates/anvil/src/eth/util.rs +++ b/crates/anvil/src/eth/util.rs @@ -79,6 +79,7 @@ pub fn to_precompile_id(spec_id: SpecId) -> revm::precompile::SpecId { SpecId::MERGE | SpecId::SHANGHAI | SpecId::CANCUN | + SpecId::PRAGUE | SpecId::BEDROCK | SpecId::REGOLITH | SpecId::CANYON | diff --git a/crates/anvil/src/genesis.rs b/crates/anvil/src/genesis.rs index 23afd5fc3c9a4..43ba7030b8013 100644 --- a/crates/anvil/src/genesis.rs +++ b/crates/anvil/src/genesis.rs @@ -199,6 +199,8 @@ pub struct Config { #[serde(default, skip_serializing_if = "Option::is_none")] pub cancun_block: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub prague_block: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub terminal_total_difficulty: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub terminal_total_difficulty_passed: Option, diff --git a/crates/anvil/src/hardfork.rs b/crates/anvil/src/hardfork.rs index 42745fb80ce84..62e20c3e9e5d6 100644 --- a/crates/anvil/src/hardfork.rs +++ b/crates/anvil/src/hardfork.rs @@ -22,6 +22,7 @@ pub enum Hardfork { Paris, Shanghai, Cancun, + Prague, #[default] Latest, } @@ -48,6 +49,7 @@ impl Hardfork { // TODO: set block number after activation Hardfork::Cancun => unreachable!(), + Hardfork::Prague => unreachable!(), } } @@ -100,6 +102,10 @@ impl Hardfork { // TODO: set fork hash once known ForkId { hash: ForkHash([0xc1, 0xfd, 0xf1, 0x81]), next: 0 } } + Hardfork::Prague => { + // TODO: set fork hash once known + ForkId { hash: ForkHash([0xc1, 0xfd, 0xf1, 0x81]), next: 0 } + } } } } @@ -127,6 +133,7 @@ impl FromStr for Hardfork { "paris" | "merge" | "15" => Hardfork::Paris, "shanghai" | "16" => Hardfork::Shanghai, "cancun" | "17" => Hardfork::Cancun, + "prague" | "18" => Hardfork::Prague, "latest" => Hardfork::Latest, _ => return Err(format!("Unknown hardfork {s}")), }; @@ -156,6 +163,7 @@ impl From for SpecId { // TODO: switch to latest after activation Hardfork::Cancun => SpecId::CANCUN, + Hardfork::Prague => SpecId::PRAGUE, } } } diff --git a/crates/cast/bin/cmd/create2.rs b/crates/cast/bin/cmd/create2.rs index 661ea3cb18569..44f9bef1bf143 100644 --- a/crates/cast/bin/cmd/create2.rs +++ b/crates/cast/bin/cmd/create2.rs @@ -196,7 +196,7 @@ impl Create2Args { loop { // Stop if a result was found in another thread. if found.load(Ordering::Relaxed) { - break None; + break None } // Calculate the `CREATE2` address. @@ -211,7 +211,7 @@ impl Create2Args { if regex.matches(s).into_iter().count() == regex_len { // Notify other threads that we found a result. found.store(true, Ordering::Relaxed); - break Some((salt.0, addr)); + break Some((salt.0, addr)) } // Increment the salt for the next iteration. diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index 676d9e4db1f24..94efe374ce038 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -217,7 +217,7 @@ async fn fetch_storage_slots( fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) -> Result<()> { if !pretty { println!("{}", serde_json::to_string_pretty(&serde_json::to_value(layout)?)?); - return Ok(()); + return Ok(()) } let mut table = Table::new(); diff --git a/crates/cast/src/rlp_converter.rs b/crates/cast/src/rlp_converter.rs index 3a520844d76b1..c67c99cf52048 100644 --- a/crates/cast/src/rlp_converter.rs +++ b/crates/cast/src/rlp_converter.rs @@ -24,7 +24,7 @@ impl Decodable for Item { fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { let h = Header::decode(buf)?; if buf.len() < h.payload_length { - return Err(alloy_rlp::Error::InputTooShort); + return Err(alloy_rlp::Error::InputTooShort) } let mut d = &buf[..h.payload_length]; let r = if h.list { diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index a9421d087daa8..676640a2e3052 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -47,6 +47,8 @@ interface Vm { CallCode, /// The account was called via staticcall. StaticCall, + /// The account was called via authcall. + AuthCall, /// The account was created. Create, /// The account was selfdestructed. diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 2eef1975502b1..9151c1d8b7064 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -826,6 +826,7 @@ impl Inspector for Cheatcodes { CallScheme::CallCode => crate::Vm::AccountAccessKind::CallCode, CallScheme::DelegateCall => crate::Vm::AccountAccessKind::DelegateCall, CallScheme::StaticCall => crate::Vm::AccountAccessKind::StaticCall, + CallScheme::AuthCall => crate::Vm::AccountAccessKind::AuthCall, }; // Record this call by pushing it to a new pending vector; all subsequent calls at // that depth will be pushed to the same vector. When the call ends, the diff --git a/crates/cheatcodes/src/string.rs b/crates/cheatcodes/src/string.rs index f55350592ea10..d40e1d8d2f159 100644 --- a/crates/cheatcodes/src/string.rs +++ b/crates/cheatcodes/src/string.rs @@ -138,7 +138,7 @@ fn parse_value_fallback(s: &str, ty: &DynSolType) -> Option false, _ => return None, }; - return Some(Ok(DynSolValue::Bool(b))); + return Some(Ok(DynSolValue::Bool(b))) } DynSolType::Int(_) | DynSolType::Uint(_) | diff --git a/crates/cheatcodes/src/test/expect.rs b/crates/cheatcodes/src/test/expect.rs index bdecef14204a3..64266ae319ea2 100644 --- a/crates/cheatcodes/src/test/expect.rs +++ b/crates/cheatcodes/src/test/expect.rs @@ -457,7 +457,7 @@ pub(crate) fn handle_expect_revert( // If None, accept any revert let Some(expected_revert) = expected_revert else { - return Ok(success_return()); + return Ok(success_return()) }; if !expected_revert.is_empty() && retdata.is_empty() { diff --git a/crates/common/src/fmt/dynamic.rs b/crates/common/src/fmt/dynamic.rs index 2e30f2f7e5ee5..d8a8d2072ec9e 100644 --- a/crates/common/src/fmt/dynamic.rs +++ b/crates/common/src/fmt/dynamic.rs @@ -41,7 +41,7 @@ impl DynValueFormatter { DynSolValue::Bool(inner) => write!(f, "{inner}"), DynSolValue::CustomStruct { name, prop_names, tuple } => { if self.raw { - return self.tuple(tuple, f); + return self.tuple(tuple, f) } f.write_str(name)?; diff --git a/crates/common/src/fmt/mod.rs b/crates/common/src/fmt/mod.rs index fbe1670fc6cbc..ea03a98274d9a 100644 --- a/crates/common/src/fmt/mod.rs +++ b/crates/common/src/fmt/mod.rs @@ -68,7 +68,7 @@ pub fn format_uint_exp(num: U256) -> String { pub fn format_int_exp(num: I256) -> String { let (sign, abs) = num.into_sign_and_abs(); if abs < U256::from(10_000) { - return format!("{sign}{abs}"); + return format!("{sign}{abs}") } let exp = to_exp_notation(abs, 4, true, sign); diff --git a/crates/common/src/provider.rs b/crates/common/src/provider.rs index 730260d9ab4d7..bea5e18bc58b6 100644 --- a/crates/common/src/provider.rs +++ b/crates/common/src/provider.rs @@ -302,7 +302,7 @@ fn resolve_path(path: &Path) -> Result { fn resolve_path(path: &Path) -> Result { if let Some(s) = path.to_str() { if s.starts_with(r"\\.\pipe\") { - return Ok(path.to_path_buf()); + return Ok(path.to_path_buf()) } } Err(()) diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 5175775315d5c..31b1bd423cf65 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -369,6 +369,9 @@ pub struct Config { /// Should be removed once EvmVersion Cancun is supported by solc pub cancun: bool, + /// Temporary config to enable [SpecId::PRAGUE] + pub prague: bool, + /// The root path where the config detection started from, `Config::with_root` #[doc(hidden)] // We're skipping serialization here, so it won't be included in the [`Config::to_string()`] @@ -694,7 +697,9 @@ impl Config { /// Returns the [SpecId] derived from the configured [EvmVersion] #[inline] pub fn evm_spec_id(&self) -> SpecId { - if self.cancun { + if self.prague { + return SpecId::PRAGUE + } else if self.cancun { return SpecId::CANCUN } evm_spec_id(&self.evm_version) @@ -1752,6 +1757,7 @@ impl Default for Config { profile: Self::DEFAULT_PROFILE, fs_permissions: FsPermissions::new([PathPermission::read("out")]), cancun: false, + prague: false, __root: Default::default(), src: "src".into(), test: "test".into(), diff --git a/crates/debugger/src/tui.rs b/crates/debugger/src/tui.rs index 7cd2b67efd0ce..9d9fbfcab8297 100644 --- a/crates/debugger/src/tui.rs +++ b/crates/debugger/src/tui.rs @@ -677,6 +677,7 @@ Line::from(Span::styled("[t]: stack labels | [m]: memory decoding | [shift + j/k CallKind::StaticCall => "Contract staticcall", CallKind::CallCode => "Contract callcode", CallKind::DelegateCall => "Contract delegatecall", + CallKind::AuthCall => "Contract authcall", }) .borders(Borders::ALL); @@ -1257,7 +1258,8 @@ Line::from(Span::styled("[t]: stack labels | [m]: memory decoding | [shift + j/k (i == (offset + size - 1) / 32 && j <= (offset + size - 1) % 32) { - // [offset, offset + size] is the memory region to be colored. + // [offset, offset + size] is the memory region to be + // colored. // If a byte at row i and column j in the memory panel // falls in this region, set the color. Style::default().fg(color) diff --git a/crates/doc/src/builder.rs b/crates/doc/src/builder.rs index 692e69b82e1dd..1826f3518f642 100644 --- a/crates/doc/src/builder.rs +++ b/crates/doc/src/builder.rs @@ -133,13 +133,13 @@ impl DocBuilder { Err(err) => { if from_library { // Ignore failures for library files - return Ok(Vec::new()); + return Ok(Vec::new()) } else { return Err(eyre::eyre!( "Failed to parse Solidity code for {}\nDebug info: {:?}", path.display(), err - )); + )) } } }; diff --git a/crates/doc/src/preprocessor/contract_inheritance.rs b/crates/doc/src/preprocessor/contract_inheritance.rs index 7b4b28e10e060..b4972ac901777 100644 --- a/crates/doc/src/preprocessor/contract_inheritance.rs +++ b/crates/doc/src/preprocessor/contract_inheritance.rs @@ -54,7 +54,7 @@ impl ContractInheritance { fn try_link_base(&self, base: &str, documents: &Vec) -> Option { for candidate in documents { if candidate.from_library && !self.include_libraries { - continue; + continue } if let DocumentContent::Single(ref item) = candidate.content { if let ParseSource::Contract(ref contract) = item.source { diff --git a/crates/doc/src/preprocessor/git_source.rs b/crates/doc/src/preprocessor/git_source.rs index eaa53e6268d5e..a72764c65482a 100644 --- a/crates/doc/src/preprocessor/git_source.rs +++ b/crates/doc/src/preprocessor/git_source.rs @@ -29,7 +29,7 @@ impl Preprocessor for GitSource { let commit = self.commit.clone().unwrap_or("master".to_owned()); for document in documents.iter() { if document.from_library { - continue; + continue } let git_url = format!( "{repo}/blob/{commit}/{}", diff --git a/crates/evm/core/src/decode.rs b/crates/evm/core/src/decode.rs index 6812cfd4f5032..3d09bb51b7d74 100644 --- a/crates/evm/core/src/decode.rs +++ b/crates/evm/core/src/decode.rs @@ -57,7 +57,7 @@ pub fn maybe_decode_revert( if err.len() < SELECTOR_LEN { if let Some(status) = status { if !status.is_ok() { - return Some(format!("EvmError: {status:?}")); + return Some(format!("EvmError: {status:?}")) } } return if err.is_empty() { @@ -69,12 +69,12 @@ pub fn maybe_decode_revert( if err == crate::constants::MAGIC_SKIP { // Also used in forge fuzz runner - return Some("SKIPPED".to_string()); + return Some("SKIPPED".to_string()) } // Solidity's `Error(string)` or `Panic(uint256)` if let Ok(e) = alloy_sol_types::GenericContractError::abi_decode(err, false) { - return Some(e.to_string()); + return Some(e.to_string()) } let (selector, data) = err.split_at(SELECTOR_LEN); @@ -84,17 +84,17 @@ pub fn maybe_decode_revert( // `CheatcodeError(string)` Vm::CheatcodeError::SELECTOR => { let e = Vm::CheatcodeError::abi_decode_raw(data, false).ok()?; - return Some(e.message); + return Some(e.message) } // `expectRevert(bytes)` Vm::expectRevert_2Call::SELECTOR => { let e = Vm::expectRevert_2Call::abi_decode_raw(data, false).ok()?; - return maybe_decode_revert(&e.revertData[..], maybe_abi, status); + return maybe_decode_revert(&e.revertData[..], maybe_abi, status) } // `expectRevert(bytes4)` Vm::expectRevert_1Call::SELECTOR => { let e = Vm::expectRevert_1Call::abi_decode_raw(data, false).ok()?; - return maybe_decode_revert(&e.revertData[..], maybe_abi, status); + return maybe_decode_revert(&e.revertData[..], maybe_abi, status) } _ => {} } @@ -108,19 +108,19 @@ pub fn maybe_decode_revert( "{}({})", abi_error.name, decoded.iter().map(foundry_common::fmt::format_token).format(", ") - )); + )) } } } // ABI-encoded `string` if let Ok(s) = String::abi_decode(err, false) { - return Some(s); + return Some(s) } // UTF-8-encoded string if let Ok(s) = std::str::from_utf8(err) { - return Some(s.to_string()); + return Some(s.to_string()) } // Generic custom error diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index 1ccac43d42257..dd99a391fb7f0 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -182,14 +182,14 @@ impl EvmOpts { if let Some(ref url) = self.fork_url { if url.contains("mainnet") { trace!(?url, "auto detected mainnet chain"); - return Some(Chain::mainnet()); + return Some(Chain::mainnet()) } trace!(?url, "retrieving chain via eth_chainId"); let provider = Provider::try_from(url.as_str()) .unwrap_or_else(|_| panic!("Failed to establish provider to {url}")); if let Ok(id) = RuntimeOrHandle::new().block_on(provider.get_chainid()) { - return Some(Chain::from(id.as_u64())); + return Some(Chain::from(id.as_u64())) } } diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index 8edaa4e21e0e4..879d09774e43e 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -22,6 +22,7 @@ pub enum CallKind { StaticCall, CallCode, DelegateCall, + AuthCall, Create, Create2, } @@ -33,6 +34,7 @@ impl From for CallKind { CallScheme::StaticCall => CallKind::StaticCall, CallScheme::CallCode => CallKind::CallCode, CallScheme::DelegateCall => CallKind::DelegateCall, + CallScheme::AuthCall => CallKind::AuthCall, } } } @@ -49,9 +51,11 @@ impl From for CallKind { impl From for ActionType { fn from(kind: CallKind) -> Self { match kind { - CallKind::Call | CallKind::StaticCall | CallKind::DelegateCall | CallKind::CallCode => { - ActionType::Call - } + CallKind::Call | + CallKind::StaticCall | + CallKind::DelegateCall | + CallKind::CallCode | + CallKind::AuthCall => ActionType::Call, CallKind::Create => ActionType::Create, CallKind::Create2 => ActionType::Create, } @@ -65,6 +69,7 @@ impl From for CallType { CallKind::StaticCall => CallType::StaticCall, CallKind::CallCode => CallType::CallCode, CallKind::DelegateCall => CallType::DelegateCall, + CallKind::AuthCall => CallType::AuthCall, CallKind::Create => CallType::None, CallKind::Create2 => CallType::None, } @@ -129,6 +134,7 @@ pub fn halt_to_instruction_result(halt: Halt) -> InstructionResult { Halt::CreateInitcodeSizeLimit => InstructionResult::CreateInitcodeSizeLimit, Halt::OverflowPayment => InstructionResult::OverflowPayment, Halt::StateChangeDuringStaticCall => InstructionResult::StateChangeDuringStaticCall, + Halt::ActiveAccountUnsetAuthCall => InstructionResult::ActiveAccountUnsetAuthCall, Halt::CallNotAllowedInsideStatic => InstructionResult::CallNotAllowedInsideStatic, Halt::OutOfFund => InstructionResult::OutOfFund, Halt::CallTooDeep => InstructionResult::CallTooDeep, diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index 2866d8319a396..ece2f13b41bce 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -460,7 +460,7 @@ impl CallTraceDecoder { fn apply_label(&self, value: &DynSolValue) -> String { if let DynSolValue::Address(addr) = value { if let Some(label) = self.labels.get(addr) { - return format!("{label}: [{addr}]"); + return format!("{label}: [{addr}]") } } format_token(value) diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index 3d88ee23ff1eb..4dd1e5ae6e771 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -535,6 +535,7 @@ impl fmt::Display for CallTrace { CallKind::StaticCall => " [staticcall]", CallKind::CallCode => " [callcode]", CallKind::DelegateCall => " [delegatecall]", + CallKind::AuthCall => " [authcall]", CallKind::Create | CallKind::Create2 => unreachable!(), }; diff --git a/crates/evm/traces/src/node.rs b/crates/evm/traces/src/node.rs index e68840b69e962..b753902706978 100644 --- a/crates/evm/traces/src/node.rs +++ b/crates/evm/traces/src/node.rs @@ -37,12 +37,14 @@ impl CallTraceNode { /// Returns the `Res` for a parity trace pub fn parity_result(&self) -> Res { match self.kind() { - CallKind::Call | CallKind::StaticCall | CallKind::CallCode | CallKind::DelegateCall => { - Res::Call(CallResult { - gas_used: self.trace.gas_cost.into(), - output: self.trace.output.to_raw().into(), - }) - } + CallKind::Call | + CallKind::StaticCall | + CallKind::CallCode | + CallKind::DelegateCall | + CallKind::AuthCall => Res::Call(CallResult { + gas_used: self.trace.gas_cost.into(), + output: self.trace.output.to_raw().into(), + }), CallKind::Create | CallKind::Create2 => Res::Create(CreateResult { gas_used: self.trace.gas_cost.into(), code: self.trace.output.to_raw().into(), @@ -62,16 +64,18 @@ impl CallTraceNode { }) } match self.kind() { - CallKind::Call | CallKind::StaticCall | CallKind::CallCode | CallKind::DelegateCall => { - Action::Call(Call { - from: self.trace.caller.to_ethers(), - to: self.trace.address.to_ethers(), - value: self.trace.value.to_ethers(), - gas: self.trace.gas_cost.into(), - input: self.trace.data.as_bytes().to_vec().into(), - call_type: self.kind().into(), - }) - } + CallKind::Call | + CallKind::StaticCall | + CallKind::CallCode | + CallKind::DelegateCall | + CallKind::AuthCall => Action::Call(Call { + from: self.trace.caller.to_ethers(), + to: self.trace.address.to_ethers(), + value: self.trace.value.to_ethers(), + gas: self.trace.gas_cost.into(), + input: self.trace.data.as_bytes().to_vec().into(), + call_type: self.kind().into(), + }), CallKind::Create | CallKind::Create2 => Action::Create(Create { from: self.trace.caller.to_ethers(), value: self.trace.value.to_ethers(), diff --git a/crates/forge/bin/cmd/script/multi.rs b/crates/forge/bin/cmd/script/multi.rs index 7fa4709a96340..2eb5e2b4ef7cd 100644 --- a/crates/forge/bin/cmd/script/multi.rs +++ b/crates/forge/bin/cmd/script/multi.rs @@ -144,7 +144,7 @@ impl ScriptArgs { join_all(futs).await.into_iter().filter(|res| res.is_err()).collect::>(); if !errors.is_empty() { - return Err(eyre::eyre!("{errors:?}")); + return Err(eyre::eyre!("{errors:?}")) } } diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 27001b7321720..30fa8f4d94e94 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -112,6 +112,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { doc: Default::default(), fs_permissions: Default::default(), cancun: true, + prague: true, __non_exhaustive: (), __warnings: vec![], }; From 63c201b7ec8fcb8820fc11b3bf7971fd7373e792 Mon Sep 17 00:00:00 2001 From: clabby Date: Sat, 2 Dec 2023 11:35:25 -0500 Subject: [PATCH 2/5] bump revm --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 88eb5b9d11837..ee7bc8a322712 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5706,7 +5706,7 @@ dependencies = [ [[package]] name = "revm" version = "3.5.0" -source = "git+https://github.com/clabby/revm?branch=cl/eip-3074#74c9d447d8865650ea8296517362b7341a45a0dc" +source = "git+https://github.com/clabby/revm?branch=cl/eip-3074#7b6365ab030f94b88fde70c934af736a58ae2e04" dependencies = [ "auto_impl", "revm-interpreter", @@ -5718,7 +5718,7 @@ dependencies = [ [[package]] name = "revm-interpreter" version = "1.3.0" -source = "git+https://github.com/clabby/revm?branch=cl/eip-3074#74c9d447d8865650ea8296517362b7341a45a0dc" +source = "git+https://github.com/clabby/revm?branch=cl/eip-3074#7b6365ab030f94b88fde70c934af736a58ae2e04" dependencies = [ "revm-primitives", "serde", @@ -5727,7 +5727,7 @@ dependencies = [ [[package]] name = "revm-precompile" version = "2.2.0" -source = "git+https://github.com/clabby/revm?branch=cl/eip-3074#74c9d447d8865650ea8296517362b7341a45a0dc" +source = "git+https://github.com/clabby/revm?branch=cl/eip-3074#7b6365ab030f94b88fde70c934af736a58ae2e04" dependencies = [ "aurora-engine-modexp", "c-kzg", @@ -5741,7 +5741,7 @@ dependencies = [ [[package]] name = "revm-primitives" version = "1.3.0" -source = "git+https://github.com/clabby/revm?branch=cl/eip-3074#74c9d447d8865650ea8296517362b7341a45a0dc" +source = "git+https://github.com/clabby/revm?branch=cl/eip-3074#7b6365ab030f94b88fde70c934af736a58ae2e04" dependencies = [ "alloy-primitives", "alloy-rlp", From 5046b1f96bc5b36da34deedffdf4ea7ee876ad4d Mon Sep 17 00:00:00 2001 From: "moxey.eth" Date: Thu, 18 Apr 2024 08:30:54 +1000 Subject: [PATCH 3/5] chore: up --- Cargo.lock | 8 ++++---- Cargo.toml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee7bc8a322712..f238abdf2652f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5706,7 +5706,7 @@ dependencies = [ [[package]] name = "revm" version = "3.5.0" -source = "git+https://github.com/clabby/revm?branch=cl/eip-3074#7b6365ab030f94b88fde70c934af736a58ae2e04" +source = "git+https://github.com/jxom/revm?branch=jxom/eip-3074#695b3ae3dd7872e8b43f3521af28a43ca033802f" dependencies = [ "auto_impl", "revm-interpreter", @@ -5718,7 +5718,7 @@ dependencies = [ [[package]] name = "revm-interpreter" version = "1.3.0" -source = "git+https://github.com/clabby/revm?branch=cl/eip-3074#7b6365ab030f94b88fde70c934af736a58ae2e04" +source = "git+https://github.com/jxom/revm?branch=jxom/eip-3074#695b3ae3dd7872e8b43f3521af28a43ca033802f" dependencies = [ "revm-primitives", "serde", @@ -5727,7 +5727,7 @@ dependencies = [ [[package]] name = "revm-precompile" version = "2.2.0" -source = "git+https://github.com/clabby/revm?branch=cl/eip-3074#7b6365ab030f94b88fde70c934af736a58ae2e04" +source = "git+https://github.com/jxom/revm?branch=jxom/eip-3074#695b3ae3dd7872e8b43f3521af28a43ca033802f" dependencies = [ "aurora-engine-modexp", "c-kzg", @@ -5741,7 +5741,7 @@ dependencies = [ [[package]] name = "revm-primitives" version = "1.3.0" -source = "git+https://github.com/clabby/revm?branch=cl/eip-3074#7b6365ab030f94b88fde70c934af736a58ae2e04" +source = "git+https://github.com/jxom/revm?branch=jxom/eip-3074#695b3ae3dd7872e8b43f3521af28a43ca033802f" dependencies = [ "alloy-primitives", "alloy-rlp", diff --git a/Cargo.toml b/Cargo.toml index 6db993d054bcb..38ff559aeb13e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -196,8 +196,8 @@ ethers-signers = { git = "https://github.com/clabby/ethers-rs", branch = "cl/cal ethers-middleware = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } ethers-solc = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } -revm = { git = "https://github.com/clabby/revm", branch = "cl/eip-3074" } -revm-interpreter = { git = "https://github.com/clabby/revm", branch = "cl/eip-3074" } -revm-precompile = { git = "https://github.com/clabby/revm", branch = "cl/eip-3074" } -revm-primitives = { git = "https://github.com/clabby/revm", branch = "cl/eip-3074" } +revm = { git = "https://github.com/jxom/revm", branch = "jxom/eip-3074" } +revm-interpreter = { git = "https://github.com/jxom/revm", branch = "jxom/eip-3074" } +revm-precompile = { git = "https://github.com/jxom/revm", branch = "jxom/eip-3074" } +revm-primitives = { git = "https://github.com/jxom/revm", branch = "jxom/eip-3074" } From 18e25da78e5add65eab165e598f607c92a283c01 Mon Sep 17 00:00:00 2001 From: "moxey.eth" Date: Sun, 28 Apr 2024 10:35:26 +1000 Subject: [PATCH 4/5] chore: update revm --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f238abdf2652f..c330d8d194e42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5706,7 +5706,7 @@ dependencies = [ [[package]] name = "revm" version = "3.5.0" -source = "git+https://github.com/jxom/revm?branch=jxom/eip-3074#695b3ae3dd7872e8b43f3521af28a43ca033802f" +source = "git+https://github.com/jxom/revm?branch=jxom/eip-3074#1ddd520c938bb50ddf4f8493a640f73b0924691c" dependencies = [ "auto_impl", "revm-interpreter", @@ -5718,7 +5718,7 @@ dependencies = [ [[package]] name = "revm-interpreter" version = "1.3.0" -source = "git+https://github.com/jxom/revm?branch=jxom/eip-3074#695b3ae3dd7872e8b43f3521af28a43ca033802f" +source = "git+https://github.com/jxom/revm?branch=jxom/eip-3074#1ddd520c938bb50ddf4f8493a640f73b0924691c" dependencies = [ "revm-primitives", "serde", @@ -5727,7 +5727,7 @@ dependencies = [ [[package]] name = "revm-precompile" version = "2.2.0" -source = "git+https://github.com/jxom/revm?branch=jxom/eip-3074#695b3ae3dd7872e8b43f3521af28a43ca033802f" +source = "git+https://github.com/jxom/revm?branch=jxom/eip-3074#1ddd520c938bb50ddf4f8493a640f73b0924691c" dependencies = [ "aurora-engine-modexp", "c-kzg", @@ -5741,7 +5741,7 @@ dependencies = [ [[package]] name = "revm-primitives" version = "1.3.0" -source = "git+https://github.com/jxom/revm?branch=jxom/eip-3074#695b3ae3dd7872e8b43f3521af28a43ca033802f" +source = "git+https://github.com/jxom/revm?branch=jxom/eip-3074#1ddd520c938bb50ddf4f8493a640f73b0924691c" dependencies = [ "alloy-primitives", "alloy-rlp", From dda394c46e484b41ff5654355401474c7b06b228 Mon Sep 17 00:00:00 2001 From: "moxey.eth" Date: Fri, 3 May 2024 10:09:24 +1000 Subject: [PATCH 5/5] chore: merge --- .cargo/config.toml | 15 +- .config/nextest.toml | 6 +- .gitattributes | 4 - .github/CODEOWNERS | 10 +- .github/scripts/matrices.py | 24 +- .github/scripts/prune-prereleases.js | 43 +- .github/workflows/docker-publish.yml | 2 +- .github/workflows/heavy-integration.yml | 59 - .github/workflows/project.yml | 16 - .github/workflows/release.yml | 46 +- .github/workflows/test.yml | 302 +- .gitignore | 1 + CONTRIBUTING.md | 9 +- Cargo.lock | 4249 +++++++---- Cargo.toml | 166 +- FUNDING.json | 7 + README.md | 5 + clippy.toml | 5 +- crates/anvil/Cargo.toml | 66 +- crates/anvil/README.md | 2 +- crates/anvil/core/Cargo.toml | 35 +- crates/anvil/core/src/eth/block.rs | 540 +- crates/anvil/core/src/eth/mod.rs | 252 +- crates/anvil/core/src/eth/proof.rs | 49 +- crates/anvil/core/src/eth/receipt.rs | 376 - crates/anvil/core/src/eth/serde_helpers.rs | 86 + crates/anvil/core/src/eth/state.rs | 16 - crates/anvil/core/src/eth/subscription.rs | 76 +- .../core/src/eth/transaction/ethers_compat.rs | 287 - crates/anvil/core/src/eth/transaction/mod.rs | 2741 ++++--- .../core/src/eth/transaction/optimism.rs | 335 + crates/anvil/core/src/eth/trie.rs | 50 +- crates/anvil/core/src/eth/utils.rs | 37 +- crates/anvil/core/src/types.rs | 47 +- crates/anvil/rpc/src/error.rs | 4 +- crates/anvil/rpc/src/request.rs | 14 +- crates/anvil/rpc/src/response.rs | 6 +- crates/anvil/server/Cargo.toml | 1 - crates/anvil/server/src/config.rs | 11 +- crates/anvil/server/src/ipc.rs | 4 +- crates/anvil/server/src/lib.rs | 95 +- crates/anvil/src/anvil.rs | 56 +- crates/anvil/src/cmd.rs | 181 +- crates/anvil/src/config.rs | 356 +- crates/anvil/src/eth/api.rs | 986 +-- crates/anvil/src/eth/backend/cheats.rs | 13 +- crates/anvil/src/eth/backend/db.rs | 117 +- crates/anvil/src/eth/backend/executor.rs | 283 +- crates/anvil/src/eth/backend/fork.rs | 425 +- crates/anvil/src/eth/backend/genesis.rs | 64 +- crates/anvil/src/eth/backend/info.rs | 11 +- crates/anvil/src/eth/backend/mem/cache.rs | 10 +- crates/anvil/src/eth/backend/mem/fork_db.rs | 46 +- .../anvil/src/eth/backend/mem/in_memory_db.rs | 119 +- crates/anvil/src/eth/backend/mem/inspector.rs | 122 +- crates/anvil/src/eth/backend/mem/mod.rs | 1376 ++-- crates/anvil/src/eth/backend/mem/state.rs | 144 +- crates/anvil/src/eth/backend/mem/storage.rs | 176 +- crates/anvil/src/eth/backend/notifications.rs | 6 +- crates/anvil/src/eth/backend/time.rs | 9 +- crates/anvil/src/eth/backend/validate.rs | 6 +- crates/anvil/src/eth/error.rs | 108 +- crates/anvil/src/eth/fees.rs | 282 +- crates/anvil/src/eth/miner.rs | 4 +- crates/anvil/src/eth/mod.rs | 2 +- crates/anvil/src/eth/otterscan/api.rs | 103 +- crates/anvil/src/eth/otterscan/types.rs | 177 +- crates/anvil/src/eth/pool/mod.rs | 80 +- crates/anvil/src/eth/pool/transactions.rs | 62 +- crates/anvil/src/eth/sign.rs | 148 +- crates/anvil/src/eth/util.rs | 44 +- crates/anvil/src/evm.rs | 92 + crates/anvil/src/filter.rs | 12 +- crates/anvil/src/genesis.rs | 300 - crates/anvil/src/hardfork.rs | 29 +- crates/anvil/src/lib.rs | 119 +- crates/anvil/src/logging.rs | 4 +- crates/anvil/src/pubsub.rs | 99 +- crates/anvil/src/server/handler.rs | 19 +- crates/anvil/src/server/mod.rs | 57 +- crates/anvil/src/service.rs | 40 +- crates/anvil/src/tasks/mod.rs | 67 +- crates/anvil/test-data/SimpleStorage.json | 118 +- crates/anvil/test-data/emit_logs.json | 68 +- crates/anvil/test-data/greeter.json | 45 +- crates/anvil/test-data/multicall.json | 145 +- crates/anvil/test-data/storage_sample.json | 1 + crates/anvil/tests/it/abi.rs | 96 +- crates/anvil/tests/it/anvil.rs | 19 +- crates/anvil/tests/it/anvil_api.rs | 357 +- crates/anvil/tests/it/api.rs | 306 +- crates/anvil/tests/it/eip4844.rs | 192 + crates/anvil/tests/it/fork.rs | 790 +- crates/anvil/tests/it/ganache.rs | 219 +- crates/anvil/tests/it/gas.rs | 180 +- crates/anvil/tests/it/genesis.rs | 16 +- crates/anvil/tests/it/geth.rs | 106 +- crates/anvil/tests/it/ipc.rs | 16 +- crates/anvil/tests/it/logs.rs | 261 +- crates/anvil/tests/it/main.rs | 6 +- crates/anvil/tests/it/optimism.rs | 214 +- crates/anvil/tests/it/otterscan.rs | 502 +- crates/anvil/tests/it/proof.rs | 133 + crates/anvil/tests/it/proof/eip1186.rs | 297 - crates/anvil/tests/it/proof/mod.rs | 81 - crates/anvil/tests/it/pubsub.rs | 243 +- crates/anvil/tests/it/revert.rs | 249 +- crates/anvil/tests/it/sign.rs | 61 +- crates/anvil/tests/it/state.rs | 23 + crates/anvil/tests/it/traces.rs | 216 +- crates/anvil/tests/it/transaction.rs | 941 ++- crates/anvil/tests/it/txpool.rs | 33 +- crates/anvil/tests/it/utils.rs | 71 +- crates/anvil/tests/it/wsapi.rs | 10 +- crates/cast/Cargo.toml | 42 +- crates/cast/bin/cmd/access_list.rs | 108 +- crates/cast/bin/cmd/bind.rs | 12 +- crates/cast/bin/cmd/call.rs | 214 +- crates/cast/bin/cmd/create2.rs | 56 +- crates/cast/bin/cmd/estimate.rs | 60 +- crates/cast/bin/cmd/find_block.rs | 38 +- crates/cast/bin/cmd/interface.rs | 12 +- crates/cast/bin/cmd/logs.rs | 310 +- crates/cast/bin/cmd/mktx.rs | 109 + crates/cast/bin/cmd/mod.rs | 1 + crates/cast/bin/cmd/rpc.rs | 6 +- crates/cast/bin/cmd/run.rs | 115 +- crates/cast/bin/cmd/send.rs | 149 +- crates/cast/bin/cmd/storage.rs | 170 +- crates/cast/bin/cmd/wallet/list.rs | 108 + crates/cast/bin/cmd/wallet/mod.rs | 279 +- crates/cast/bin/cmd/wallet/vanity.rs | 25 +- crates/cast/bin/main.rs | 283 +- crates/cast/bin/opts.rs | 423 +- crates/cast/bin/tx.rs | 124 + crates/cast/src/base.rs | 18 +- crates/cast/src/errors.rs | 2 +- crates/cast/src/lib.rs | 879 +-- crates/cast/src/rlp_converter.rs | 2 +- crates/cast/src/tx.rs | 402 -- crates/cast/tests/cli/main.rs | 278 +- crates/cast/tests/fixtures/ERC20Artifact.json | 386 +- crates/cast/tests/fixtures/interface.json | 1 + .../cast/tests/fixtures/sign_typed_data.json | 39 +- crates/cheatcodes/Cargo.toml | 22 +- crates/cheatcodes/assets/cheatcodes.json | 6370 +++++++++++++---- .../cheatcodes/assets/cheatcodes.schema.json | 14 + crates/cheatcodes/spec/src/cheatcode.rs | 13 + crates/cheatcodes/spec/src/lib.rs | 11 +- crates/cheatcodes/spec/src/vm.rs | 993 ++- crates/cheatcodes/src/base64.rs | 31 + crates/cheatcodes/src/config.rs | 67 +- crates/cheatcodes/src/env.rs | 55 +- crates/cheatcodes/src/error.rs | 18 +- crates/cheatcodes/src/evm.rs | 302 +- crates/cheatcodes/src/evm/fork.rs | 140 +- crates/cheatcodes/src/evm/mock.rs | 11 +- crates/cheatcodes/src/evm/prank.rs | 4 +- crates/cheatcodes/src/fs.rs | 222 +- crates/cheatcodes/src/inspector.rs | 914 ++- crates/cheatcodes/src/json.rs | 75 +- crates/cheatcodes/src/lib.rs | 89 +- crates/cheatcodes/src/script.rs | 87 +- crates/cheatcodes/src/string.rs | 56 +- crates/cheatcodes/src/test.rs | 4 +- crates/cheatcodes/src/test/assert.rs | 1221 ++++ crates/cheatcodes/src/test/expect.rs | 108 +- crates/cheatcodes/src/toml.rs | 243 + crates/cheatcodes/src/utils.rs | 182 +- crates/chisel/Cargo.toml | 22 +- crates/chisel/benches/session_source.rs | 13 +- crates/chisel/bin/main.rs | 107 +- crates/chisel/src/dispatcher.rs | 690 +- crates/chisel/src/executor.rs | 220 +- crates/chisel/src/runner.rs | 16 +- crates/chisel/src/session.rs | 11 +- crates/chisel/src/session_source.rs | 156 +- crates/chisel/src/solidity_helper.rs | 32 +- crates/chisel/tests/cache.rs | 42 +- crates/cli/Cargo.toml | 28 +- crates/cli/src/handler.rs | 14 +- crates/cli/src/opts/build/core.rs | 53 +- crates/cli/src/opts/build/mod.rs | 19 +- crates/cli/src/opts/build/paths.rs | 28 +- crates/cli/src/opts/dependency.rs | 2 +- crates/cli/src/opts/ethereum.rs | 78 +- crates/cli/src/opts/mod.rs | 2 - crates/cli/src/opts/transaction.rs | 20 +- crates/cli/src/opts/wallet/error.rs | 11 - crates/cli/src/opts/wallet/mod.rs | 616 -- crates/cli/src/opts/wallet/multi_wallet.rs | 520 -- crates/cli/src/utils/abi.rs | 61 + crates/cli/src/utils/cmd.rs | 101 +- crates/cli/src/utils/mod.rs | 99 +- crates/common/Cargo.toml | 41 +- crates/common/src/abi.rs | 81 +- crates/common/src/calc.rs | 56 +- crates/common/src/clap_helpers.rs | 6 - crates/common/src/compile.rs | 654 +- crates/common/src/constants.rs | 2 +- crates/common/src/contracts.rs | 312 +- crates/common/src/ens.rs | 237 + crates/common/src/evm.rs | 96 +- crates/common/src/fmt/mod.rs | 8 +- crates/common/src/fmt/ui.rs | 348 +- crates/common/src/glob.rs | 42 +- crates/common/src/lib.rs | 8 +- .../src/{provider.rs => provider/mod.rs} | 165 +- crates/common/src/provider/retry.rs | 141 + .../common/src/provider/runtime_transport.rs | 323 + crates/common/src/provider/tower.rs | 196 + crates/common/src/retry.rs | 2 +- crates/common/src/runtime_client.rs | 338 - crates/common/src/selectors.rs | 149 +- crates/common/src/serde_helpers.rs | 129 + crates/common/src/shell.rs | 4 +- crates/common/src/term.rs | 9 +- crates/common/src/traits.rs | 53 +- crates/common/src/transactions.rs | 84 +- crates/common/src/types.rs | 250 - crates/common/src/units.rs | 329 - crates/config/Cargo.toml | 15 +- crates/config/README.md | 9 +- crates/config/src/cache.rs | 6 +- crates/config/src/doc.rs | 2 +- crates/config/src/endpoints.rs | 252 +- crates/config/src/error.rs | 17 +- crates/config/src/etherscan.rs | 128 +- crates/config/src/fix.rs | 14 +- crates/config/src/fmt.rs | 17 +- crates/config/src/fs_permissions.rs | 53 +- crates/config/src/fuzz.rs | 35 +- crates/config/src/inline/conf_parser.rs | 4 +- crates/config/src/inline/error.rs | 4 +- crates/config/src/inline/mod.rs | 32 +- crates/config/src/inline/natspec.rs | 356 +- crates/config/src/invariant.rs | 19 +- crates/config/src/lib.rs | 701 +- crates/config/src/macros.rs | 30 +- crates/config/src/providers/mod.rs | 15 +- crates/config/src/providers/remappings.rs | 2 +- crates/config/src/resolve.rs | 2 +- crates/config/src/utils.rs | 9 +- crates/config/src/warning.rs | 47 +- crates/debugger/Cargo.toml | 6 +- crates/debugger/src/lib.rs | 7 +- crates/debugger/src/op.rs | 8 +- crates/debugger/src/tui.rs | 1402 ---- crates/debugger/src/{ => tui}/builder.rs | 14 +- crates/debugger/src/tui/context.rs | 347 + crates/debugger/src/tui/draw.rs | 761 ++ crates/debugger/src/tui/mod.rs | 215 + crates/doc/Cargo.toml | 1 + crates/doc/src/builder.rs | 2 +- crates/doc/src/document.rs | 112 +- crates/doc/src/parser/comment.rs | 8 +- crates/doc/src/parser/error.rs | 2 +- crates/doc/src/parser/item.rs | 4 +- crates/doc/src/parser/mod.rs | 20 +- .../src/preprocessor/contract_inheritance.rs | 2 +- crates/doc/src/preprocessor/deployments.rs | 30 +- .../doc/src/preprocessor/infer_hyperlinks.rs | 324 + crates/doc/src/preprocessor/inheritdoc.rs | 2 +- crates/doc/src/preprocessor/mod.rs | 7 +- crates/doc/src/writer/as_doc.rs | 101 +- crates/doc/src/writer/buf_writer.rs | 2 +- crates/evm/core/Cargo.toml | 18 +- crates/evm/core/src/abi/HardhatConsole.json | 2 +- crates/evm/core/src/abi/console.rs | 12 +- crates/evm/core/src/abi/hardhat_console.rs | 42 +- crates/evm/core/src/abi/mod.rs | 7 +- .../evm/core/src/backend/{fuzz.rs => cow.rs} | 121 +- crates/evm/core/src/backend/diagnostic.rs | 2 +- crates/evm/core/src/backend/error.rs | 22 +- crates/evm/core/src/backend/in_memory_db.rs | 14 +- crates/evm/core/src/backend/mod.rs | 462 +- crates/evm/core/src/backend/snapshot.rs | 14 +- crates/evm/core/src/debug.rs | 149 +- crates/evm/core/src/decode.rs | 258 +- crates/evm/core/src/fork/backend.rs | 212 +- crates/evm/core/src/fork/cache.rs | 130 +- crates/evm/core/src/fork/database.rs | 8 +- crates/evm/core/src/fork/init.rs | 61 +- crates/evm/core/src/fork/mod.rs | 2 +- crates/evm/core/src/fork/multi.rs | 153 +- crates/evm/core/src/ic.rs | 70 + crates/evm/core/src/lib.rs | 27 + crates/evm/core/src/opcodes.rs | 25 + crates/evm/core/src/opts.rs | 47 +- crates/evm/core/src/snapshot.rs | 7 +- crates/evm/core/src/utils.rs | 452 +- crates/evm/core/test-data/storage.json | 2 +- crates/evm/coverage/Cargo.toml | 1 + crates/evm/coverage/src/analysis.rs | 255 +- crates/evm/coverage/src/anchors.rs | 25 +- crates/evm/coverage/src/inspector.rs | 23 +- crates/evm/coverage/src/lib.rs | 78 +- crates/evm/evm/Cargo.toml | 14 +- crates/evm/evm/src/executors/builder.rs | 14 +- crates/evm/evm/src/executors/fuzz/mod.rs | 86 +- .../evm/evm/src/executors/invariant/error.rs | 271 +- .../evm/evm/src/executors/invariant/funcs.rs | 128 - crates/evm/evm/src/executors/invariant/mod.rs | 567 +- .../evm/evm/src/executors/invariant/replay.rs | 134 + .../evm/evm/src/executors/invariant/result.rs | 156 + .../evm/evm/src/executors/invariant/shrink.rs | 152 + crates/evm/evm/src/executors/mod.rs | 642 +- crates/evm/evm/src/executors/tracing.rs | 11 +- crates/evm/evm/src/inspectors/access_list.rs | 85 - crates/evm/evm/src/inspectors/chisel_state.rs | 11 +- crates/evm/evm/src/inspectors/debugger.rs | 150 +- crates/evm/evm/src/inspectors/logs.rs | 59 +- crates/evm/evm/src/inspectors/mod.rs | 8 +- crates/evm/evm/src/inspectors/printer.rs | 65 - crates/evm/evm/src/inspectors/stack.rs | 621 +- crates/evm/evm/src/lib.rs | 10 +- crates/evm/fuzz/Cargo.toml | 7 +- crates/evm/fuzz/src/error.rs | 2 - crates/evm/fuzz/src/inspector.rs | 44 +- .../evm/fuzz/src/invariant/call_override.rs | 2 +- crates/evm/fuzz/src/invariant/filters.rs | 4 +- crates/evm/fuzz/src/invariant/mod.rs | 28 +- crates/evm/fuzz/src/lib.rs | 73 +- crates/evm/fuzz/src/strategies/calldata.rs | 78 +- crates/evm/fuzz/src/strategies/int.rs | 79 +- crates/evm/fuzz/src/strategies/invariants.rs | 172 +- crates/evm/fuzz/src/strategies/mod.rs | 35 +- crates/evm/fuzz/src/strategies/param.rs | 261 +- crates/evm/fuzz/src/strategies/state.rs | 307 +- crates/evm/fuzz/src/strategies/uint.rs | 76 +- crates/evm/traces/Cargo.toml | 10 +- crates/evm/traces/src/decoder/mod.rs | 515 +- crates/evm/traces/src/decoder/precompiles.rs | 19 +- crates/evm/traces/src/identifier/etherscan.rs | 111 +- crates/evm/traces/src/identifier/local.rs | 122 +- crates/evm/traces/src/identifier/mod.rs | 62 +- .../evm/traces/src/identifier/signatures.rs | 100 +- crates/evm/traces/src/inspector.rs | 250 - crates/evm/traces/src/lib.rs | 679 +- crates/evm/traces/src/node.rs | 87 - crates/fmt/Cargo.toml | 2 +- crates/fmt/src/buffer.rs | 2 +- crates/fmt/src/comments.rs | 22 +- crates/fmt/src/formatter.rs | 1993 +++--- crates/fmt/src/helpers.rs | 21 +- crates/fmt/src/inline_config.rs | 4 +- crates/fmt/src/string.rs | 4 +- crates/fmt/testdata/BlockComments/fmt.sol | 25 + .../fmt/testdata/BlockComments/original.sol | 26 + .../testdata/BlockCommentsFunction/fmt.sol | 20 + .../BlockCommentsFunction/original.sol | 20 + .../testdata/ConstructorModifierStyle/fmt.sol | 13 + .../ConstructorModifierStyle/original.sol | 13 + crates/fmt/testdata/EnumVariants/fmt.sol | 19 + crates/fmt/testdata/EnumVariants/original.sol | 23 + crates/fmt/testdata/Repros/fmt.sol | 12 + crates/fmt/testdata/Repros/original.sol | 12 + crates/fmt/testdata/SortedImports/fmt.sol | 34 + .../fmt/testdata/SortedImports/original.sol | 23 + crates/fmt/tests/formatter.rs | 47 +- crates/forge/Cargo.toml | 61 +- crates/forge/README.md | 2 +- crates/forge/assets/CounterTemplate.s.sol | 2 +- crates/forge/assets/CounterTemplate.t.sol | 2 +- .../forge/assets/generated/TestTemplate.t.sol | 4 +- crates/forge/benches/test.rs | 2 +- crates/forge/bin/cmd/bind.rs | 63 +- crates/forge/bin/cmd/build.rs | 46 +- crates/forge/bin/cmd/cache.rs | 15 +- crates/forge/bin/cmd/clone.rs | 803 +++ crates/forge/bin/cmd/config.rs | 17 +- crates/forge/bin/cmd/coverage.rs | 253 +- crates/forge/bin/cmd/create.rs | 346 +- crates/forge/bin/cmd/debug.rs | 19 +- crates/forge/bin/cmd/doc/mod.rs | 29 +- crates/forge/bin/cmd/doc/server.rs | 13 +- crates/forge/bin/cmd/flatten.rs | 35 +- crates/forge/bin/cmd/fmt.rs | 26 +- crates/forge/bin/cmd/geiger/find.rs | 17 +- crates/forge/bin/cmd/geiger/mod.rs | 14 +- crates/forge/bin/cmd/generate/mod.rs | 6 +- crates/forge/bin/cmd/init.rs | 39 +- crates/forge/bin/cmd/inspect.rs | 138 +- crates/forge/bin/cmd/install.rs | 68 +- crates/forge/bin/cmd/mod.rs | 10 +- crates/forge/bin/cmd/remappings.rs | 28 +- crates/forge/bin/cmd/remove.rs | 7 +- crates/forge/bin/cmd/script/artifacts.rs | 9 - crates/forge/bin/cmd/script/broadcast.rs | 668 -- crates/forge/bin/cmd/script/build.rs | 291 - crates/forge/bin/cmd/script/cmd.rs | 361 - crates/forge/bin/cmd/script/executor.rs | 328 - crates/forge/bin/cmd/script/mod.rs | 971 --- crates/forge/bin/cmd/script/multi.rs | 179 - crates/forge/bin/cmd/script/transaction.rs | 462 -- crates/forge/bin/cmd/selectors.rs | 99 +- crates/forge/bin/cmd/snapshot.rs | 55 +- crates/forge/bin/cmd/test/filter.rs | 146 +- crates/forge/bin/cmd/test/mod.rs | 995 ++- crates/forge/bin/cmd/test/summary.rs | 76 +- crates/forge/bin/cmd/tree.rs | 8 +- crates/forge/bin/cmd/update.rs | 10 +- crates/forge/bin/cmd/watch.rs | 23 +- crates/forge/bin/main.rs | 106 +- crates/forge/bin/opts.rs | 114 +- crates/forge/src/coverage.rs | 145 +- crates/forge/src/gas_report.rs | 166 +- crates/forge/src/lib.rs | 89 +- crates/forge/src/link.rs | 722 -- crates/forge/src/multi_runner.rs | 522 +- crates/forge/src/result.rs | 324 +- crates/forge/src/runner.rs | 624 +- crates/forge/tests/cli/build.rs | 16 + crates/forge/tests/cli/cmd.rs | 273 +- crates/forge/tests/cli/config.rs | 516 +- crates/forge/tests/cli/context.rs | 81 + crates/forge/tests/cli/create.rs | 312 +- crates/forge/tests/cli/debug.rs | 94 + crates/forge/tests/cli/ext_integration.rs | 153 +- crates/forge/tests/cli/heavy_integration.rs | 3 - crates/forge/tests/cli/main.rs | 5 +- crates/forge/tests/cli/multi_script.rs | 39 +- crates/forge/tests/cli/script.rs | 627 +- crates/forge/tests/cli/svm.rs | 7 +- crates/forge/tests/cli/test_cmd.rs | 235 +- crates/forge/tests/cli/utils.rs | 38 +- crates/forge/tests/cli/verify.rs | 174 +- .../tests/fixtures/can_check_snapshot.stdout | 8 +- .../can_create_template_contract.stdout | 2 +- .../fixtures/can_create_using_unlocked.stdout | 2 +- .../can_create_with_constructor_args.stdout | 2 +- .../can_run_test_in_custom_test_folder.stdout | 8 +- .../fixtures/can_set_yul_optimizer.stderr | 3 +- .../tests/fixtures/can_test_repeatedly.stdout | 10 +- .../can_use_libs_in_multi_fork.stdout | 8 +- .../include_custom_types_in_traces.stdout | 25 + crates/forge/tests/fixtures/repro_6531.stdout | 17 + ...xactly_once_with_changed_versions.1.stdout | 8 +- ...xactly_once_with_changed_versions.2.stdout | 8 +- .../suggest_when_no_tests_match.stdout | 4 - .../forge/tests/fixtures/warn_no_tests.stdout | 4 - .../tests/fixtures/warn_no_tests_match.stdout | 4 - crates/forge/tests/it/cheats.rs | 64 +- crates/forge/tests/it/config.rs | 162 +- crates/forge/tests/it/core.rs | 84 +- crates/forge/tests/it/fork.rs | 57 +- crates/forge/tests/it/fs.rs | 16 +- crates/forge/tests/it/fuzz.rs | 133 +- crates/forge/tests/it/inline.rs | 87 +- crates/forge/tests/it/invariant.rs | 503 +- crates/forge/tests/it/repros.rs | 168 +- crates/forge/tests/it/spec.rs | 9 +- crates/forge/tests/it/test_helpers.rs | 340 +- crates/linking/Cargo.toml | 17 + crates/linking/src/lib.rs | 520 ++ crates/script/Cargo.toml | 55 + crates/script/src/broadcast.rs | 417 ++ crates/script/src/build.rs | 323 + crates/script/src/execute.rs | 518 ++ crates/script/src/lib.rs | 870 +++ crates/script/src/multi_sequence.rs | 154 + .../cmd/script => script/src}/providers.rs | 36 +- .../bin/cmd/script => script/src}/receipts.rs | 82 +- .../bin/cmd/script => script/src}/runner.rs | 125 +- .../bin/cmd/script => script/src}/sequence.rs | 280 +- crates/script/src/simulate.rs | 430 ++ crates/script/src/transaction.rs | 223 + .../bin/cmd/script => script/src}/verify.rs | 51 +- crates/test-utils/Cargo.toml | 6 +- crates/test-utils/src/fd_lock.rs | 2 +- crates/test-utils/src/filter.rs | 40 +- crates/test-utils/src/lib.rs | 2 + crates/test-utils/src/macros.rs | 79 - crates/{common => test-utils}/src/rpc.rs | 22 + crates/test-utils/src/script.rs | 54 +- crates/test-utils/src/util.rs | 291 +- crates/verify/Cargo.toml | 43 + crates/verify/src/bytecode.rs | 654 ++ .../src}/etherscan/flatten.rs | 10 +- .../verify => verify/src}/etherscan/mod.rs | 214 +- .../src}/etherscan/standard_json.rs | 0 .../cmd/verify/mod.rs => verify/src/lib.rs} | 128 +- .../bin/cmd/verify => verify/src}/provider.rs | 2 +- crates/{forge/bin/cmd => verify/src}/retry.rs | 10 +- .../bin/cmd/verify => verify/src}/sourcify.rs | 85 +- crates/wallets/Cargo.toml | 46 + crates/wallets/src/error.rs | 34 + crates/wallets/src/lib.rs | 14 + crates/wallets/src/multi_wallet.rs | 474 ++ crates/wallets/src/raw_wallet.rs | 62 + crates/wallets/src/utils.rs | 152 + crates/wallets/src/wallet.rs | 215 + crates/wallets/src/wallet_signer.rs | 209 + deny.toml | 24 +- docs/dev/cheatcodes.md | 8 +- flake.lock | 109 + flake.nix | 56 + foundryup/foundryup | 12 +- foundryup/install | 30 +- rustfmt.toml | 3 - testdata/cancun/cheats/BlobBaseFee.t.sol | 14 + testdata/cancun/cheats/Blobhashes.t.sol | 20 + testdata/cheats/Vm.sol | 217 +- testdata/{ => default}/cheats/Addr.t.sol | 2 +- testdata/default/cheats/Assert.t.sol | 829 +++ testdata/{ => default}/cheats/Assume.t.sol | 2 +- testdata/{ => default}/cheats/Bank.t.sol | 2 +- testdata/default/cheats/Base64.t.sol | 24 + testdata/{ => default}/cheats/Broadcast.t.sol | 44 +- testdata/{ => default}/cheats/ChainId.t.sol | 2 +- testdata/{ => default}/cheats/Cool.t.sol | 4 +- testdata/{ => default}/cheats/Deal.t.sol | 2 +- testdata/{ => default}/cheats/Derive.t.sol | 2 +- testdata/{ => default}/cheats/Env.t.sol | 10 +- testdata/{ => default}/cheats/Etch.t.sol | 4 +- .../{ => default}/cheats/ExpectCall.t.sol | 2 +- .../{ => default}/cheats/ExpectEmit.t.sol | 2 +- .../{ => default}/cheats/ExpectRevert.t.sol | 18 +- testdata/{ => default}/cheats/Fee.t.sol | 2 +- testdata/{ => default}/cheats/Ffi.t.sol | 2 +- testdata/{ => default}/cheats/Fork.t.sol | 8 +- testdata/{ => default}/cheats/Fork2.t.sol | 2 +- testdata/{ => default}/cheats/Fs.t.sol | 48 +- .../{ => default}/cheats/GasMetering.t.sol | 2 +- .../default/cheats/GetBlockTimestamp.t.sol | 26 + testdata/{ => default}/cheats/GetCode.t.sol | 24 +- .../cheats/GetDeployedCode.t.sol | 14 +- testdata/{ => default}/cheats/GetLabel.t.sol | 2 +- testdata/{ => default}/cheats/GetNonce.t.sol | 2 +- testdata/{ => default}/cheats/Json.t.sol | 165 +- testdata/{ => default}/cheats/Label.t.sol | 2 +- testdata/default/cheats/LastCallGas.t.sol | 125 + testdata/{ => default}/cheats/Load.t.sol | 2 +- testdata/{ => default}/cheats/Mapping.t.sol | 2 +- testdata/{ => default}/cheats/MemSafety.t.sol | 88 +- testdata/{ => default}/cheats/MockCall.t.sol | 2 +- testdata/{ => default}/cheats/Parse.t.sol | 2 +- testdata/{ => default}/cheats/Prank.t.sol | 2 +- .../{ => default}/cheats/Prevrandao.t.sol | 8 +- .../{ => default}/cheats/ProjectRoot.t.sol | 2 +- testdata/default/cheats/Prompt.t.sol | 29 + .../{ => default}/cheats/ReadCallers.t.sol | 2 +- testdata/{ => default}/cheats/Record.t.sol | 2 +- .../cheats/RecordAccountAccesses.t.sol | 186 +- .../{ => default}/cheats/RecordLogs.t.sol | 2 +- testdata/{ => default}/cheats/Remember.t.sol | 2 +- .../{ => default}/cheats/ResetNonce.t.sol | 2 +- testdata/{ => default}/cheats/Roll.t.sol | 2 +- testdata/{ => default}/cheats/RpcUrls.t.sol | 23 +- testdata/{ => default}/cheats/SetNonce.t.sol | 2 +- .../{ => default}/cheats/SetNonceUnsafe.t.sol | 2 +- testdata/{ => default}/cheats/Setup.t.sol | 4 +- testdata/{ => default}/cheats/Sign.t.sol | 2 +- testdata/default/cheats/SignP256.t.sol | 18 + testdata/{ => default}/cheats/Skip.t.sol | 2 +- testdata/{ => default}/cheats/Sleep.t.sol | 2 +- testdata/{ => default}/cheats/Snapshots.t.sol | 51 +- testdata/{ => default}/cheats/Store.t.sol | 2 +- testdata/default/cheats/StringUtils.t.sol | 54 + testdata/{ => default}/cheats/ToString.t.sol | 2 +- testdata/default/cheats/Toml.t.sol | 318 + testdata/{ => default}/cheats/Travel.t.sol | 2 +- testdata/{ => default}/cheats/TryFfi.sol | 2 +- testdata/{ => default}/cheats/UnixTime.t.sol | 2 +- testdata/{ => default}/cheats/Wallet.t.sol | 2 +- testdata/{ => default}/cheats/Warp.t.sol | 2 +- testdata/default/cheats/dumpState.t.sol | 139 + testdata/default/cheats/getBlockNumber.t.sol | 25 + .../{ => default}/cheats/loadAllocs.t.sol | 2 +- testdata/{ => default}/core/Abstract.t.sol | 0 .../core/ContractEnvironment.t.sol | 0 testdata/{ => default}/core/DSStyle.t.sol | 0 .../{ => default}/core/FailingSetup.t.sol | 0 .../core/FailingTestAfterFailedSetup.t.sol | 0 .../{ => default}/core/MultipleSetup.t.sol | 0 .../{ => default}/core/PaymentFailure.t.sol | 2 +- testdata/{ => default}/core/Reverting.t.sol | 0 .../{ => default}/core/SetupConsistency.t.sol | 0 testdata/{ => default}/fork/DssExecLib.sol | 0 testdata/{ => default}/fork/ForkSame_1.t.sol | 2 +- testdata/{ => default}/fork/ForkSame_2.t.sol | 2 +- testdata/{ => default}/fork/LaunchFork.t.sol | 0 testdata/{ => default}/fork/Transact.t.sol | 2 +- testdata/{ => default}/fs/Default.t.sol | 10 +- testdata/{ => default}/fs/Disabled.t.sol | 12 +- testdata/{ => default}/fuzz/Fuzz.t.sol | 0 .../{ => default}/fuzz/FuzzCollection.t.sol | 0 .../default/fuzz/FuzzFailurePersist.t.sol | 29 + testdata/{ => default}/fuzz/FuzzInt.t.sol | 17 +- testdata/default/fuzz/FuzzPositive.t.sol | 18 + testdata/{ => default}/fuzz/FuzzUint.t.sol | 11 +- .../invariant/common/InvariantAssume.t.sol | 23 + .../common/InvariantCalldataDictionary.t.sol | 95 + .../common/InvariantCustomError.t.sol | 35 + .../invariant/common/InvariantFixtures.t.sol | 77 + .../common/InvariantHandlerFailure.t.sol | 0 .../common/InvariantInnerContract.t.sol | 0 .../common/InvariantPreserveState.t.sol | 49 + .../common/InvariantReentrancy.t.sol | 12 +- .../common/InvariantShrinkBigSequence.t.sol | 31 + .../common/InvariantShrinkFailOnRevert.t.sol | 26 + .../common/InvariantShrinkWithAssert.t.sol | 115 + .../invariant/common/InvariantTest1.t.sol | 0 .../storage/InvariantStorageTest.t.sol | 0 .../invariant/target/ExcludeContracts.t.sol | 0 .../invariant/target/ExcludeSenders.t.sol | 0 .../target/FuzzedTargetContracts.t.sol | 66 + .../invariant/target/TargetContracts.t.sol | 0 .../invariant/target/TargetInterfaces.t.sol | 0 .../invariant/target/TargetSelectors.t.sol | 0 .../fuzz/invariant/target/TargetSenders.t.sol | 0 .../targetAbi/ExcludeArtifacts.t.sol | 2 +- .../targetAbi/TargetArtifactSelectors.t.sol | 2 +- .../targetAbi/TargetArtifactSelectors2.t.sol | 6 +- .../invariant/targetAbi/TargetArtifacts.t.sol | 2 +- .../{ => default}/inline/FuzzInlineConf.t.sol | 0 .../inline/InvariantInlineConf.t.sol | 0 testdata/default/linking/cycle/Cycle.t.sol | 16 + .../linking/duplicate/Duplicate.t.sol | 0 .../{ => default}/linking/nested/Nested.t.sol | 0 .../{ => default}/linking/simple/Simple.t.sol | 0 testdata/{ => default}/logs/DebugLogs.t.sol | 0 testdata/{ => default}/logs/HardhatLogs.t.sol | 0 testdata/{ => default}/logs/console.sol | 0 testdata/{ => default}/repros/Issue2623.t.sol | 2 +- testdata/{ => default}/repros/Issue2629.t.sol | 2 +- testdata/{ => default}/repros/Issue2723.t.sol | 2 +- testdata/{ => default}/repros/Issue2898.t.sol | 2 +- testdata/{ => default}/repros/Issue2956.t.sol | 6 +- testdata/{ => default}/repros/Issue2984.t.sol | 2 +- testdata/{ => default}/repros/Issue3055.t.sol | 2 +- testdata/{ => default}/repros/Issue3077.t.sol | 2 +- testdata/{ => default}/repros/Issue3110.t.sol | 2 +- testdata/{ => default}/repros/Issue3119.t.sol | 2 +- testdata/{ => default}/repros/Issue3189.t.sol | 2 +- testdata/{ => default}/repros/Issue3190.t.sol | 2 +- testdata/{ => default}/repros/Issue3192.t.sol | 2 +- testdata/{ => default}/repros/Issue3220.t.sol | 2 +- testdata/{ => default}/repros/Issue3221.t.sol | 6 +- testdata/{ => default}/repros/Issue3223.t.sol | 8 +- testdata/{ => default}/repros/Issue3347.t.sol | 2 +- testdata/{ => default}/repros/Issue3437.t.sol | 2 +- testdata/{ => default}/repros/Issue3596.t.sol | 2 +- testdata/{ => default}/repros/Issue3653.t.sol | 6 +- testdata/{ => default}/repros/Issue3661.t.sol | 0 testdata/{ => default}/repros/Issue3674.t.sol | 4 +- testdata/{ => default}/repros/Issue3685.t.sol | 2 +- testdata/{ => default}/repros/Issue3703.t.sol | 2 +- testdata/{ => default}/repros/Issue3708.t.sol | 2 +- testdata/{ => default}/repros/Issue3723.t.sol | 2 +- testdata/{ => default}/repros/Issue3753.t.sol | 2 +- testdata/{ => default}/repros/Issue3792.t.sol | 2 +- testdata/default/repros/Issue4402.t.sol | 64 + testdata/{ => default}/repros/Issue4586.t.sol | 2 +- testdata/{ => default}/repros/Issue4630.t.sol | 10 +- testdata/{ => default}/repros/Issue4640.t.sol | 2 +- testdata/{ => default}/repros/Issue4832.t.sol | 2 +- testdata/{ => default}/repros/Issue5038.t.sol | 2 +- testdata/default/repros/Issue5529.t.sol | 37 + testdata/{ => default}/repros/Issue5808.t.sol | 4 +- testdata/default/repros/Issue5929.t.sol | 16 + testdata/{ => default}/repros/Issue5935.t.sol | 2 +- testdata/{ => default}/repros/Issue5948.t.sol | 2 +- testdata/{ => default}/repros/Issue6006.t.sol | 2 +- testdata/default/repros/Issue6032.t.sol | 48 + testdata/{ => default}/repros/Issue6070.t.sol | 4 +- testdata/{ => default}/repros/Issue6115.t.sol | 0 testdata/{ => default}/repros/Issue6170.t.sol | 2 +- testdata/{ => default}/repros/Issue6180.t.sol | 2 +- testdata/default/repros/Issue6293.t.sol | 19 + testdata/{ => default}/repros/Issue6355.t.sol | 2 +- testdata/{ => default}/repros/Issue6437.t.sol | 2 +- testdata/default/repros/Issue6501.t.sol | 14 + testdata/default/repros/Issue6538.t.sol | 17 + testdata/default/repros/Issue6554.t.sol | 16 + testdata/default/repros/Issue6616.t.sol | 22 + testdata/default/repros/Issue6634.t.sol | 95 + testdata/default/repros/Issue6759.t.sol | 19 + testdata/default/repros/Issue6966.t.sol | 16 + testdata/default/repros/Issue7481.t.sol | 22 + .../deploy.sol/31337/run-latest.json | 1 + testdata/{ => default}/script/deploy.sol | 4 +- .../{ => default}/spec/ShanghaiCompat.t.sol | 2 +- .../trace/ConflictingSignatures.t.sol | 2 +- testdata/{ => default}/trace/Trace.t.sol | 2 +- .../creation_data.json | 1 + .../metadata.json | 1 + .../creation_data.json | 1 + .../metadata.json | 1 + .../creation_data.json | 1 + .../metadata.json | 1 + .../creation_data.json | 1 + .../metadata.json | 1 + .../creation_data.json | 1 + .../metadata.json | 1 + .../creation_data.json | 1 + .../metadata.json | 1 + .../creation_data.json | 5 + .../metadata.json | 96 + .../creation_data.json | 1 + .../metadata.json | 1 + testdata/fixtures/Dir/depth1 | 2 +- testdata/fixtures/Json/Issue4402.json | 4 + testdata/fixtures/Json/test.json | 33 +- .../Json/{wholeJson.json => whole_json.json} | 0 testdata/fixtures/Toml/Issue4402.toml | 2 + testdata/fixtures/Toml/test.toml | 50 + testdata/fixtures/Toml/whole_toml.toml | 3 + .../fixtures/Toml/write_complex_test.toml | 6 + testdata/fixtures/Toml/write_test.toml | 2 + testdata/foundry.toml | 1 + testdata/multi-version/Counter.sol | 11 + testdata/multi-version/Importer.sol | 7 + testdata/multi-version/cheats/GetCode.t.sol | 25 + testdata/multi-version/cheats/GetCode17.t.sol | 26 + .../deploy.sol/31337/run-latest.json | 63 - 716 files changed, 52647 insertions(+), 34454 deletions(-) delete mode 100644 .github/workflows/heavy-integration.yml delete mode 100644 .github/workflows/project.yml create mode 100644 FUNDING.json delete mode 100644 crates/anvil/core/src/eth/receipt.rs delete mode 100644 crates/anvil/core/src/eth/state.rs delete mode 100644 crates/anvil/core/src/eth/transaction/ethers_compat.rs create mode 100644 crates/anvil/core/src/eth/transaction/optimism.rs create mode 100644 crates/anvil/src/evm.rs delete mode 100644 crates/anvil/src/genesis.rs create mode 100644 crates/anvil/test-data/storage_sample.json create mode 100644 crates/anvil/tests/it/eip4844.rs create mode 100644 crates/anvil/tests/it/proof.rs delete mode 100644 crates/anvil/tests/it/proof/eip1186.rs delete mode 100644 crates/anvil/tests/it/proof/mod.rs create mode 100644 crates/anvil/tests/it/state.rs create mode 100644 crates/cast/bin/cmd/mktx.rs create mode 100644 crates/cast/bin/cmd/wallet/list.rs create mode 100644 crates/cast/bin/tx.rs delete mode 100644 crates/cast/src/tx.rs create mode 100644 crates/cast/tests/fixtures/interface.json create mode 100644 crates/cheatcodes/src/base64.rs create mode 100644 crates/cheatcodes/src/test/assert.rs create mode 100644 crates/cheatcodes/src/toml.rs delete mode 100644 crates/cli/src/opts/wallet/error.rs delete mode 100644 crates/cli/src/opts/wallet/mod.rs delete mode 100644 crates/cli/src/opts/wallet/multi_wallet.rs create mode 100644 crates/cli/src/utils/abi.rs delete mode 100644 crates/common/src/clap_helpers.rs create mode 100644 crates/common/src/ens.rs rename crates/common/src/{provider.rs => provider/mod.rs} (68%) create mode 100644 crates/common/src/provider/retry.rs create mode 100644 crates/common/src/provider/runtime_transport.rs create mode 100644 crates/common/src/provider/tower.rs delete mode 100644 crates/common/src/runtime_client.rs create mode 100644 crates/common/src/serde_helpers.rs delete mode 100644 crates/common/src/types.rs delete mode 100644 crates/common/src/units.rs delete mode 100644 crates/debugger/src/tui.rs rename crates/debugger/src/{ => tui}/builder.rs (89%) create mode 100644 crates/debugger/src/tui/context.rs create mode 100644 crates/debugger/src/tui/draw.rs create mode 100644 crates/debugger/src/tui/mod.rs create mode 100644 crates/doc/src/preprocessor/infer_hyperlinks.rs rename crates/evm/core/src/backend/{fuzz.rs => cow.rs} (70%) create mode 100644 crates/evm/core/src/ic.rs create mode 100644 crates/evm/core/src/opcodes.rs delete mode 100644 crates/evm/evm/src/executors/invariant/funcs.rs create mode 100644 crates/evm/evm/src/executors/invariant/replay.rs create mode 100644 crates/evm/evm/src/executors/invariant/result.rs create mode 100644 crates/evm/evm/src/executors/invariant/shrink.rs delete mode 100644 crates/evm/evm/src/inspectors/access_list.rs delete mode 100644 crates/evm/evm/src/inspectors/printer.rs delete mode 100644 crates/evm/traces/src/inspector.rs delete mode 100644 crates/evm/traces/src/node.rs create mode 100644 crates/fmt/testdata/BlockComments/fmt.sol create mode 100644 crates/fmt/testdata/BlockComments/original.sol create mode 100644 crates/fmt/testdata/BlockCommentsFunction/fmt.sol create mode 100644 crates/fmt/testdata/BlockCommentsFunction/original.sol create mode 100644 crates/fmt/testdata/ConstructorModifierStyle/fmt.sol create mode 100644 crates/fmt/testdata/ConstructorModifierStyle/original.sol create mode 100644 crates/fmt/testdata/EnumVariants/fmt.sol create mode 100644 crates/fmt/testdata/EnumVariants/original.sol create mode 100644 crates/fmt/testdata/SortedImports/fmt.sol create mode 100644 crates/fmt/testdata/SortedImports/original.sol create mode 100644 crates/forge/bin/cmd/clone.rs delete mode 100644 crates/forge/bin/cmd/script/artifacts.rs delete mode 100644 crates/forge/bin/cmd/script/broadcast.rs delete mode 100644 crates/forge/bin/cmd/script/build.rs delete mode 100644 crates/forge/bin/cmd/script/cmd.rs delete mode 100644 crates/forge/bin/cmd/script/executor.rs delete mode 100644 crates/forge/bin/cmd/script/mod.rs delete mode 100644 crates/forge/bin/cmd/script/multi.rs delete mode 100644 crates/forge/bin/cmd/script/transaction.rs delete mode 100644 crates/forge/src/link.rs create mode 100644 crates/forge/tests/cli/context.rs create mode 100644 crates/forge/tests/cli/debug.rs delete mode 100644 crates/forge/tests/cli/heavy_integration.rs create mode 100644 crates/forge/tests/fixtures/include_custom_types_in_traces.stdout create mode 100644 crates/forge/tests/fixtures/repro_6531.stdout create mode 100644 crates/linking/Cargo.toml create mode 100644 crates/linking/src/lib.rs create mode 100644 crates/script/Cargo.toml create mode 100644 crates/script/src/broadcast.rs create mode 100644 crates/script/src/build.rs create mode 100644 crates/script/src/execute.rs create mode 100644 crates/script/src/lib.rs create mode 100644 crates/script/src/multi_sequence.rs rename crates/{forge/bin/cmd/script => script/src}/providers.rs (65%) rename crates/{forge/bin/cmd/script => script/src}/receipts.rs (66%) rename crates/{forge/bin/cmd/script => script/src}/runner.rs (78%) rename crates/{forge/bin/cmd/script => script/src}/sequence.rs (64%) create mode 100644 crates/script/src/simulate.rs create mode 100644 crates/script/src/transaction.rs rename crates/{forge/bin/cmd/script => script/src}/verify.rs (73%) rename crates/{common => test-utils}/src/rpc.rs (85%) create mode 100644 crates/verify/Cargo.toml create mode 100644 crates/verify/src/bytecode.rs rename crates/{forge/bin/cmd/verify => verify/src}/etherscan/flatten.rs (95%) rename crates/{forge/bin/cmd/verify => verify/src}/etherscan/mod.rs (73%) rename crates/{forge/bin/cmd/verify => verify/src}/etherscan/standard_json.rs (100%) rename crates/{forge/bin/cmd/verify/mod.rs => verify/src/lib.rs} (66%) rename crates/{forge/bin/cmd/verify => verify/src}/provider.rs (97%) rename crates/{forge/bin/cmd => verify/src}/retry.rs (91%) rename crates/{forge/bin/cmd/verify => verify/src}/sourcify.rs (75%) create mode 100644 crates/wallets/Cargo.toml create mode 100644 crates/wallets/src/error.rs create mode 100644 crates/wallets/src/lib.rs create mode 100644 crates/wallets/src/multi_wallet.rs create mode 100644 crates/wallets/src/raw_wallet.rs create mode 100644 crates/wallets/src/utils.rs create mode 100644 crates/wallets/src/wallet.rs create mode 100644 crates/wallets/src/wallet_signer.rs create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 testdata/cancun/cheats/BlobBaseFee.t.sol create mode 100644 testdata/cancun/cheats/Blobhashes.t.sol rename testdata/{ => default}/cheats/Addr.t.sol (95%) create mode 100644 testdata/default/cheats/Assert.t.sol rename testdata/{ => default}/cheats/Assume.t.sol (92%) rename testdata/{ => default}/cheats/Bank.t.sol (95%) create mode 100644 testdata/default/cheats/Base64.t.sol rename testdata/{ => default}/cheats/Broadcast.t.sol (91%) rename testdata/{ => default}/cheats/ChainId.t.sol (93%) rename testdata/{ => default}/cheats/Cool.t.sol (95%) rename testdata/{ => default}/cheats/Deal.t.sol (96%) rename testdata/{ => default}/cheats/Derive.t.sol (96%) rename testdata/{ => default}/cheats/Env.t.sol (99%) rename testdata/{ => default}/cheats/Etch.t.sol (78%) rename testdata/{ => default}/cheats/ExpectCall.t.sol (99%) rename testdata/{ => default}/cheats/ExpectEmit.t.sol (99%) rename testdata/{ => default}/cheats/ExpectRevert.t.sol (90%) rename testdata/{ => default}/cheats/Fee.t.sol (94%) rename testdata/{ => default}/cheats/Ffi.t.sol (97%) rename testdata/{ => default}/cheats/Fork.t.sol (93%) rename testdata/{ => default}/cheats/Fork2.t.sol (99%) rename testdata/{ => default}/cheats/Fs.t.sol (89%) rename testdata/{ => default}/cheats/GasMetering.t.sol (98%) create mode 100644 testdata/default/cheats/GetBlockTimestamp.t.sol rename testdata/{ => default}/cheats/GetCode.t.sol (80%) rename testdata/{ => default}/cheats/GetDeployedCode.t.sol (87%) rename testdata/{ => default}/cheats/GetLabel.t.sol (94%) rename testdata/{ => default}/cheats/GetNonce.t.sol (94%) rename testdata/{ => default}/cheats/Json.t.sol (75%) rename testdata/{ => default}/cheats/Label.t.sol (91%) create mode 100644 testdata/default/cheats/LastCallGas.t.sol rename testdata/{ => default}/cheats/Load.t.sol (97%) rename testdata/{ => default}/cheats/Mapping.t.sol (99%) rename testdata/{ => default}/cheats/MemSafety.t.sol (91%) rename testdata/{ => default}/cheats/MockCall.t.sol (99%) rename testdata/{ => default}/cheats/Parse.t.sol (99%) rename testdata/{ => default}/cheats/Prank.t.sol (99%) rename testdata/{ => default}/cheats/Prevrandao.t.sol (85%) rename testdata/{ => default}/cheats/ProjectRoot.t.sol (97%) create mode 100644 testdata/default/cheats/Prompt.t.sol rename testdata/{ => default}/cheats/ReadCallers.t.sol (99%) rename testdata/{ => default}/cheats/Record.t.sol (98%) rename testdata/{ => default}/cheats/RecordAccountAccesses.t.sol (86%) rename testdata/{ => default}/cheats/RecordLogs.t.sol (99%) rename testdata/{ => default}/cheats/Remember.t.sol (96%) rename testdata/{ => default}/cheats/ResetNonce.t.sol (97%) rename testdata/{ => default}/cheats/Roll.t.sol (97%) rename testdata/{ => default}/cheats/RpcUrls.t.sol (70%) rename testdata/{ => default}/cheats/SetNonce.t.sol (96%) rename testdata/{ => default}/cheats/SetNonceUnsafe.t.sol (97%) rename testdata/{ => default}/cheats/Setup.t.sol (93%) rename testdata/{ => default}/cheats/Sign.t.sol (96%) create mode 100644 testdata/default/cheats/SignP256.t.sol rename testdata/{ => default}/cheats/Skip.t.sol (96%) rename testdata/{ => default}/cheats/Sleep.t.sol (98%) rename testdata/{ => default}/cheats/Snapshots.t.sol (50%) rename testdata/{ => default}/cheats/Store.t.sol (98%) create mode 100644 testdata/default/cheats/StringUtils.t.sol rename testdata/{ => default}/cheats/ToString.t.sol (98%) create mode 100644 testdata/default/cheats/Toml.t.sol rename testdata/{ => default}/cheats/Travel.t.sol (95%) rename testdata/{ => default}/cheats/TryFfi.sol (97%) rename testdata/{ => default}/cheats/UnixTime.t.sol (97%) rename testdata/{ => default}/cheats/Wallet.t.sol (99%) rename testdata/{ => default}/cheats/Warp.t.sol (96%) create mode 100644 testdata/default/cheats/dumpState.t.sol create mode 100644 testdata/default/cheats/getBlockNumber.t.sol rename testdata/{ => default}/cheats/loadAllocs.t.sol (99%) rename testdata/{ => default}/core/Abstract.t.sol (100%) rename testdata/{ => default}/core/ContractEnvironment.t.sol (100%) rename testdata/{ => default}/core/DSStyle.t.sol (100%) rename testdata/{ => default}/core/FailingSetup.t.sol (100%) rename testdata/{ => default}/core/FailingTestAfterFailedSetup.t.sol (100%) rename testdata/{ => default}/core/MultipleSetup.t.sol (100%) rename testdata/{ => default}/core/PaymentFailure.t.sol (93%) rename testdata/{ => default}/core/Reverting.t.sol (100%) rename testdata/{ => default}/core/SetupConsistency.t.sol (100%) rename testdata/{ => default}/fork/DssExecLib.sol (100%) rename testdata/{ => default}/fork/ForkSame_1.t.sol (95%) rename testdata/{ => default}/fork/ForkSame_2.t.sol (95%) rename testdata/{ => default}/fork/LaunchFork.t.sol (100%) rename testdata/{ => default}/fork/Transact.t.sol (99%) rename testdata/{ => default}/fs/Default.t.sol (85%) rename testdata/{ => default}/fs/Disabled.t.sol (81%) rename testdata/{ => default}/fuzz/Fuzz.t.sol (100%) rename testdata/{ => default}/fuzz/FuzzCollection.t.sol (100%) create mode 100644 testdata/default/fuzz/FuzzFailurePersist.t.sol rename testdata/{ => default}/fuzz/FuzzInt.t.sol (67%) create mode 100644 testdata/default/fuzz/FuzzPositive.t.sol rename testdata/{ => default}/fuzz/FuzzUint.t.sol (69%) create mode 100644 testdata/default/fuzz/invariant/common/InvariantAssume.t.sol create mode 100644 testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol create mode 100644 testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol create mode 100644 testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol rename testdata/{ => default}/fuzz/invariant/common/InvariantHandlerFailure.t.sol (100%) rename testdata/{ => default}/fuzz/invariant/common/InvariantInnerContract.t.sol (100%) create mode 100644 testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol rename testdata/{ => default}/fuzz/invariant/common/InvariantReentrancy.t.sol (64%) create mode 100644 testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol create mode 100644 testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol create mode 100644 testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol rename testdata/{ => default}/fuzz/invariant/common/InvariantTest1.t.sol (100%) rename testdata/{ => default}/fuzz/invariant/storage/InvariantStorageTest.t.sol (100%) rename testdata/{ => default}/fuzz/invariant/target/ExcludeContracts.t.sol (100%) rename testdata/{ => default}/fuzz/invariant/target/ExcludeSenders.t.sol (100%) create mode 100644 testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol rename testdata/{ => default}/fuzz/invariant/target/TargetContracts.t.sol (100%) rename testdata/{ => default}/fuzz/invariant/target/TargetInterfaces.t.sol (100%) rename testdata/{ => default}/fuzz/invariant/target/TargetSelectors.t.sol (100%) rename testdata/{ => default}/fuzz/invariant/target/TargetSenders.t.sol (100%) rename testdata/{ => default}/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol (91%) rename testdata/{ => default}/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol (87%) rename testdata/{ => default}/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol (84%) rename testdata/{ => default}/fuzz/invariant/targetAbi/TargetArtifacts.t.sol (91%) rename testdata/{ => default}/inline/FuzzInlineConf.t.sol (100%) rename testdata/{ => default}/inline/InvariantInlineConf.t.sol (100%) create mode 100644 testdata/default/linking/cycle/Cycle.t.sol rename testdata/{ => default}/linking/duplicate/Duplicate.t.sol (100%) rename testdata/{ => default}/linking/nested/Nested.t.sol (100%) rename testdata/{ => default}/linking/simple/Simple.t.sol (100%) rename testdata/{ => default}/logs/DebugLogs.t.sol (100%) rename testdata/{ => default}/logs/HardhatLogs.t.sol (100%) rename testdata/{ => default}/logs/console.sol (100%) rename testdata/{ => default}/repros/Issue2623.t.sol (95%) rename testdata/{ => default}/repros/Issue2629.t.sol (96%) rename testdata/{ => default}/repros/Issue2723.t.sol (95%) rename testdata/{ => default}/repros/Issue2898.t.sol (96%) rename testdata/{ => default}/repros/Issue2956.t.sol (82%) rename testdata/{ => default}/repros/Issue2984.t.sol (96%) rename testdata/{ => default}/repros/Issue3055.t.sol (97%) rename testdata/{ => default}/repros/Issue3077.t.sol (97%) rename testdata/{ => default}/repros/Issue3110.t.sol (97%) rename testdata/{ => default}/repros/Issue3119.t.sol (96%) rename testdata/{ => default}/repros/Issue3189.t.sol (96%) rename testdata/{ => default}/repros/Issue3190.t.sol (94%) rename testdata/{ => default}/repros/Issue3192.t.sol (95%) rename testdata/{ => default}/repros/Issue3220.t.sol (97%) rename testdata/{ => default}/repros/Issue3221.t.sol (82%) rename testdata/{ => default}/repros/Issue3223.t.sol (76%) rename testdata/{ => default}/repros/Issue3347.t.sol (91%) rename testdata/{ => default}/repros/Issue3437.t.sol (93%) rename testdata/{ => default}/repros/Issue3596.t.sol (96%) rename testdata/{ => default}/repros/Issue3653.t.sol (77%) rename testdata/{ => default}/repros/Issue3661.t.sol (100%) rename testdata/{ => default}/repros/Issue3674.t.sol (77%) rename testdata/{ => default}/repros/Issue3685.t.sol (97%) rename testdata/{ => default}/repros/Issue3703.t.sol (98%) rename testdata/{ => default}/repros/Issue3708.t.sol (97%) rename testdata/{ => default}/repros/Issue3723.t.sol (93%) rename testdata/{ => default}/repros/Issue3753.t.sol (94%) rename testdata/{ => default}/repros/Issue3792.t.sol (96%) create mode 100644 testdata/default/repros/Issue4402.t.sol rename testdata/{ => default}/repros/Issue4586.t.sol (97%) rename testdata/{ => default}/repros/Issue4630.t.sol (70%) rename testdata/{ => default}/repros/Issue4640.t.sol (94%) rename testdata/{ => default}/repros/Issue4832.t.sol (92%) rename testdata/{ => default}/repros/Issue5038.t.sol (99%) create mode 100644 testdata/default/repros/Issue5529.t.sol rename testdata/{ => default}/repros/Issue5808.t.sol (90%) create mode 100644 testdata/default/repros/Issue5929.t.sol rename testdata/{ => default}/repros/Issue5935.t.sol (97%) rename testdata/{ => default}/repros/Issue5948.t.sol (97%) rename testdata/{ => default}/repros/Issue6006.t.sol (97%) create mode 100644 testdata/default/repros/Issue6032.t.sol rename testdata/{ => default}/repros/Issue6070.t.sol (89%) rename testdata/{ => default}/repros/Issue6115.t.sol (100%) rename testdata/{ => default}/repros/Issue6170.t.sol (95%) rename testdata/{ => default}/repros/Issue6180.t.sol (95%) create mode 100644 testdata/default/repros/Issue6293.t.sol rename testdata/{ => default}/repros/Issue6355.t.sol (96%) rename testdata/{ => default}/repros/Issue6437.t.sol (97%) create mode 100644 testdata/default/repros/Issue6501.t.sol create mode 100644 testdata/default/repros/Issue6538.t.sol create mode 100644 testdata/default/repros/Issue6554.t.sol create mode 100644 testdata/default/repros/Issue6616.t.sol create mode 100644 testdata/default/repros/Issue6634.t.sol create mode 100644 testdata/default/repros/Issue6759.t.sol create mode 100644 testdata/default/repros/Issue6966.t.sol create mode 100644 testdata/default/repros/Issue7481.t.sol create mode 100644 testdata/default/script/broadcast/deploy.sol/31337/run-latest.json rename testdata/{ => default}/script/deploy.sol (89%) rename testdata/{ => default}/spec/ShanghaiCompat.t.sol (96%) rename testdata/{ => default}/trace/ConflictingSignatures.t.sol (97%) rename testdata/{ => default}/trace/Trace.t.sol (98%) create mode 100644 testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/creation_data.json create mode 100644 testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/metadata.json create mode 100644 testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/creation_data.json create mode 100644 testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/metadata.json create mode 100644 testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/creation_data.json create mode 100644 testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/metadata.json create mode 100644 testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/creation_data.json create mode 100644 testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/metadata.json create mode 100644 testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/creation_data.json create mode 100644 testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/metadata.json create mode 100644 testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/creation_data.json create mode 100644 testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/metadata.json create mode 100644 testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/creation_data.json create mode 100644 testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/metadata.json create mode 100644 testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/creation_data.json create mode 100644 testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/metadata.json create mode 100644 testdata/fixtures/Json/Issue4402.json rename testdata/fixtures/Json/{wholeJson.json => whole_json.json} (100%) create mode 100644 testdata/fixtures/Toml/Issue4402.toml create mode 100644 testdata/fixtures/Toml/test.toml create mode 100644 testdata/fixtures/Toml/whole_toml.toml create mode 100644 testdata/fixtures/Toml/write_complex_test.toml create mode 100644 testdata/fixtures/Toml/write_test.toml create mode 100644 testdata/multi-version/Counter.sol create mode 100644 testdata/multi-version/Importer.sol create mode 100644 testdata/multi-version/cheats/GetCode.t.sol create mode 100644 testdata/multi-version/cheats/GetCode17.t.sol delete mode 100644 testdata/script/broadcast/deploy.sol/31337/run-latest.json diff --git a/.cargo/config.toml b/.cargo/config.toml index 45bca53ae5eb9..76cf725f9e2e2 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,16 +1,11 @@ [alias] cheats = "test -p foundry-cheatcodes-spec --features schema tests::" +test-debugger = "test -p forge --test cli manual_debug_setup -- --include-ignored --nocapture" +# Increase the stack size to 10MB for Windows targets, which is in line with Linux +# (whereas default for Windows is 1MB). [target.x86_64-pc-windows-msvc] -rustflags = [ - # Increases the stack size to 10MB, which is - # in line with Linux (whereas default for Windows is 1MB) - "-Clink-arg=/STACK:10000000", -] +rustflags = ["-Clink-arg=/STACK:10000000"] [target.i686-pc-windows-msvc] -rustflags = [ - # Increases the stack size to 10MB, which is - # in line with Linux (whereas default for Windows is 1MB) - "-Clink-arg=/STACK:10000000", -] +rustflags = ["-Clink-arg=/STACK:10000000"] diff --git a/.config/nextest.toml b/.config/nextest.toml index 4629c63f29c3f..56f40c364142e 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -6,6 +6,6 @@ slow-timeout = { period = "1m", terminate-after = 3 } filter = "test(/ext_integration|can_test_forge_std/)" slow-timeout = { period = "5m", terminate-after = 4 } -[profile.heavy] -retries = 1 -slow-timeout = { period = "120m", terminate-after = 1} \ No newline at end of file +[[profile.default.overrides]] +filter = "package(foundry-cheatcodes-spec)" +retries = 0 diff --git a/.gitattributes b/.gitattributes index f492456d97b17..0e34b8632d97e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,2 @@ -# auto-detection may fail for human-readable files, like the ones in abi/abi/*.sol -**/*.sol linguist-language=Solidity - -crates/abi/src/bindings/*.rs linguist-generated crates/cheatcodes/assets/*.json linguist-generated testdata/cheats/Vm.sol linguist-generated diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 99659760a2b6a..4eea49b388b79 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,10 @@ * @danipopes @evalir @mattsse -crates/anvil/ @evalir @mattsse -crates/evm/coverage/ @evalir @onbjerg +crates/anvil/ @danipopes @mattsse @evalir +crates/cheatcodes/ @danipopes @mattsse @klkvr @evalir +crates/evm/coverage/ @onbjerg crates/fmt/ @rkrasiuk -crates/macros/impls/ @danipopes +crates/linking/ @klkvr +crates/macros/ @danipopes +crates/script/ @danipopes @mattsse @klkvr +crates/wallets/ @klkvr diff --git a/.github/scripts/matrices.py b/.github/scripts/matrices.py index 9cd1eb5ebfa3c..3fdcea1154211 100755 --- a/.github/scripts/matrices.py +++ b/.github/scripts/matrices.py @@ -6,15 +6,15 @@ # A runner target class Target: - # GitHub runner OS - os_id: str + # GHA runner + runner_label: str # Rust target triple target: str # SVM Solc target svm_target_platform: str - def __init__(self, os_id: str, target: str, svm_target_platform: str): - self.os_id = os_id + def __init__(self, runner_label: str, target: str, svm_target_platform: str): + self.runner_label = runner_label self.target = target self.svm_target_platform = svm_target_platform @@ -42,7 +42,7 @@ def __init__( # GHA matrix entry class Expanded: name: str - os: str + runner_label: str target: str svm_target_platform: str flags: str @@ -51,14 +51,14 @@ class Expanded: def __init__( self, name: str, - os: str, + runner_label: str, target: str, svm_target_platform: str, flags: str, partition: int, ): self.name = name - self.os = os + self.runner_label = runner_label self.target = target self.svm_target_platform = svm_target_platform self.flags = flags @@ -76,7 +76,7 @@ def __init__( config = [ Case( name="unit", - filter="kind(lib) | kind(bench) | kind(proc-macro)", + filter="!kind(test)", n_partitions=1, pr_cross_platform=True, ), @@ -92,12 +92,6 @@ def __init__( n_partitions=2, pr_cross_platform=False, ), - Case( - name="integration / forge-std", - filter="package(=forge) & test(~forge_std)", - n_partitions=1, - pr_cross_platform=False, - ), Case( name="integration / external", filter="package(=forge) & test(~ext_integration)", @@ -129,7 +123,7 @@ def main(): obj = Expanded( name=name, - os=target.os_id, + runner_label=target.runner_label, target=target.target, svm_target_platform=target.svm_target_platform, flags=flags, diff --git a/.github/scripts/prune-prereleases.js b/.github/scripts/prune-prereleases.js index 0537fbf1380ae..bbdb0696f516e 100644 --- a/.github/scripts/prune-prereleases.js +++ b/.github/scripts/prune-prereleases.js @@ -1,3 +1,23 @@ +// In case node 21 is not used. +function groupBy(array, keyOrIterator) { + var iterator; + + // use the function passed in, or create one + if(typeof keyOrIterator !== 'function') { + const key = String(keyOrIterator); + iterator = function (item) { return item[key]; }; + } else { + iterator = keyOrIterator; + } + + return array.reduce(function (memo, item) { + const key = iterator(item); + memo[key] = memo[key] || []; + memo[key].push(item); + return memo; + }, {}); +} + module.exports = async ({ github, context }) => { console.log("Pruning old prereleases"); @@ -11,16 +31,25 @@ module.exports = async ({ github, context }) => { release => // Only consider releases tagged `nightly-${SHA}` for deletion release.tag_name.includes("nightly") && - release.tag_name !== "nightly" && - // ref: https://github.com/foundry-rs/foundry/issues/3881 - // Skipping pruning the build on 1st day of each month - !release.created_at.includes("-01T") + release.tag_name !== "nightly" ); - // Keep newest 3 nightlies - nightlies = nightlies.slice(3); + // Pruning rules: + // 1. only keep the earliest (by created_at) release of the month + // 2. to keep the newest 3 nightlies + // Notes: + // - This addresses https://github.com/foundry-rs/foundry/issues/6732 + // - Name of the release may deviate from created_at due to the usage of different timezones. + + // Group releases by months. + // Per doc: + // > The latest release is the most recent non-prerelease, non-draft release, sorted by the created_at attribute. + const groups = groupBy(nightlies, i => i.created_at.slice(0, 7)); + const nightliesToPrune = Object.values(groups) + .reduce((acc, cur) => acc.concat(cur.slice(0, -1)), []) // rule 1 + .slice(3); // rule 2 - for (const nightly of nightlies) { + for (const nightly of nightliesToPrune) { console.log(`Deleting nightly: ${nightly.tag_name}`); await github.rest.repos.deleteRelease({ owner: context.repo.owner, diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 835fa8b9c8ef6..a4d2543c20846 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -17,7 +17,7 @@ env: jobs: container: - runs-on: ubuntu-20.04 + runs-on: Linux-20.04 # https://docs.github.com/en/actions/reference/authentication-in-a-workflow permissions: id-token: write diff --git a/.github/workflows/heavy-integration.yml b/.github/workflows/heavy-integration.yml deleted file mode 100644 index 8583f7bae237a..0000000000000 --- a/.github/workflows/heavy-integration.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: heavy integration - -on: - schedule: - # Runs at 10PM utc - - cron: "0 22 * * *" - workflow_dispatch: - -env: - CARGO_TERM_COLOR: always - -jobs: - heavy-integration: - name: heavy (long-running) integration tests - runs-on: ubuntu-latest - timeout-minutes: 120 - env: - ETH_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/C3JEvfW6VgtqZQa-Qp1E-2srEiIc02sD - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: taiki-e/install-action@nextest - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Forge RPC cache - uses: actions/cache@v3 - with: - path: | - ~/.foundry/cache - ~/.config/.foundry/cache - key: rpc-cache-${{ hashFiles('crates/forge/tests/rpc-cache-keyfile') }} - - name: Setup git config - run: | - git config --global user.name "GitHub Actions Bot" - git config --global user.email "<>" - - name: Force use of HTTPS for submodules - run: git config --global url."https://github.com/".insteadOf "git@github.com:" - - name: test - run: | - cargo nextest run --profile heavy -p forge \ - --test cli --features heavy-integration-tests -E "test(~heavy_integration)" - - # If any of the jobs fail, this will create a high-priority issue to signal so. - issue: - name: Open an issue - runs-on: ubuntu-latest - needs: heavy-integration - if: ${{ failure() }} - steps: - - uses: actions/checkout@v4 - - uses: JasonEtco/create-an-issue@v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WORKFLOW_URL: | - ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - with: - update_existing: true - filename: .github/INTEGRATION_FAILURE.md diff --git a/.github/workflows/project.yml b/.github/workflows/project.yml deleted file mode 100644 index dd2899dd36972..0000000000000 --- a/.github/workflows/project.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: project - -on: - issues: - types: [opened, transferred] - -jobs: - add-to-project: - name: add issue - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/add-to-project@main - with: - project-url: https://github.com/orgs/foundry-rs/projects/2 - github-token: ${{ secrets.GH_PROJECTS_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3934a8970f992..c7422bfb67f88 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,7 +43,7 @@ jobs: # the changelog. - name: Create build-specific nightly tag if: ${{ env.IS_NIGHTLY }} - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: TAG_NAME: ${{ steps.release_info.outputs.tag_name }} with: @@ -66,39 +66,39 @@ jobs: uses: ./.github/workflows/docker-publish.yml release: - name: ${{ matrix.target }} (${{ matrix.os }}) - runs-on: ${{ matrix.os }} + name: ${{ matrix.target }} (${{ matrix.runner }}) + runs-on: ${{ matrix.runner }} timeout-minutes: 240 needs: prepare strategy: fail-fast: false matrix: include: - # `os`: GHA runner + # `runner`: GHA runner label # `target`: Rust build target triple # `platform` and `arch`: Used in tarball names # `svm`: target platform to use for the Solc binary: https://github.com/roynalnaruto/svm-rs/blob/84cbe0ac705becabdc13168bae28a45ad2299749/svm-builds/build.rs#L4-L24 - - os: ubuntu-20.04 + - runner: Linux-20.04 target: x86_64-unknown-linux-gnu svm_target_platform: linux-amd64 platform: linux arch: amd64 - - os: ubuntu-20.04 + - runner: Linux-20.04 target: aarch64-unknown-linux-gnu svm_target_platform: linux-aarch64 platform: linux arch: arm64 - - os: macos-latest + - runner: macos-latest-large target: x86_64-apple-darwin svm_target_platform: macosx-amd64 platform: darwin arch: amd64 - - os: macos-latest + - runner: macos-latest-large target: aarch64-apple-darwin svm_target_platform: macosx-aarch64 platform: darwin arch: arm64 - - os: windows-latest + - runner: Windows target: x86_64-pc-windows-msvc svm_target_platform: windows-amd64 platform: win32 @@ -130,11 +130,27 @@ jobs: env: SVM_TARGET_PLATFORM: ${{ matrix.svm_target_platform }} shell: bash - # Windows runs out of RAM when building binaries with LLVM run: | - flags=() - [[ "${{ matrix.target }}" == *windows* ]] && flags+=(-j1) - cargo build --release --bins --target ${{ matrix.target }} "${flags[@]}" + set -eo pipefail + target="${{ matrix.target }}" + flags=() + + # `jemalloc` and `keccak-asm` are not supported on MSVC or aarch64 Linux. + if [[ "$target" != *msvc* && "$target" != "aarch64-unknown-linux-gnu" ]]; then + flags+=(--features asm-keccak,jemalloc) + fi + + [[ "$target" == *windows* ]] && exe=".exe" + + cargo build --release --bins --target "$target" "${flags[@]}" + + bins=(anvil cast chisel forge) + for name in "${bins[@]}"; do + bin=./target/$target/release/$name$exe + file "$bin" || true + ldd "$bin" || true + $bin --version || true + done - name: Archive binaries id: artifacts @@ -221,14 +237,14 @@ jobs: # Moves the `nightly` tag to `HEAD` - name: Move nightly tag if: ${{ env.IS_NIGHTLY }} - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | const moveTag = require('./.github/scripts/move-tag.js') await moveTag({ github, context }, 'nightly') - name: Delete old nightlies - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | const prunePrereleases = require('./.github/scripts/prune-prereleases.js') diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d816ea2e5d376..2914175065c3d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,155 +1,181 @@ name: test on: - push: - branches: - - master - pull_request: + push: + branches: + - master + pull_request: concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true env: - CARGO_TERM_COLOR: always + CARGO_TERM_COLOR: always jobs: - matrices: - name: build matrices - runs-on: ubuntu-latest - outputs: - test-matrix: ${{ steps.gen.outputs.test-matrix }} - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 - with: - python-version: "3.11" - - name: Generate matrices - id: gen - env: - EVENT_NAME: ${{ github.event_name }} - run: | - output=$(python3 .github/scripts/matrices.py) - echo "::debug::test-matrix=$output" - echo "test-matrix=$output" >> $GITHUB_OUTPUT - - test: - name: test ${{ matrix.name }} - runs-on: ${{ matrix.os }} - timeout-minutes: 60 - needs: matrices - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.matrices.outputs.test-matrix) }} + matrices: + name: build matrices + runs-on: ubuntu-latest + outputs: + test-matrix: ${{ steps.gen.outputs.test-matrix }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: "3.11" + - name: Generate matrices + id: gen env: - ETH_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/C3JEvfW6VgtqZQa-Qp1E-2srEiIc02sD - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - with: - target: ${{ matrix.target }} - - uses: taiki-e/install-action@nextest + EVENT_NAME: ${{ github.event_name }} + run: | + output=$(python3 .github/scripts/matrices.py) + echo "::debug::test-matrix=$output" + echo "test-matrix=$output" >> $GITHUB_OUTPUT + + test: + name: test ${{ matrix.name }} + runs-on: ${{ matrix.runner_label }} + timeout-minutes: 60 + needs: matrices + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.matrices.outputs.test-matrix) }} + env: + ETH_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/C3JEvfW6VgtqZQa-Qp1E-2srEiIc02sD + CARGO_PROFILE_DEV_DEBUG: 0 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ matrix.target }} + - uses: taiki-e/install-action@nextest + + # External tests dependencies + - name: Setup Node.js + if: contains(matrix.name, 'external') + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install Bun + if: contains(matrix.name, 'external') && !contains(matrix.runner_label, 'windows') + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + - name: Setup Python + if: contains(matrix.name, 'external') + uses: actions/setup-python@v4 + with: + python-version: 3.11 + - name: Install Vyper + if: contains(matrix.name, 'external') + run: pip install vyper - # External tests dependencies - - name: Setup Python - if: contains(matrix.name, 'external') - uses: actions/setup-python@v4 - with: - python-version: 3.11 - - name: Setup Node.js - if: contains(matrix.name, 'external') - uses: actions/setup-node@v4 - with: - node-version: 20 - - name: Install pnpm - if: contains(matrix.name, 'external') - uses: pnpm/action-setup@v2 - with: - version: latest - run_install: false - - name: Install Vyper - if: contains(matrix.name, 'external') - run: pip install vyper + - name: Forge RPC cache + uses: actions/cache@v3 + with: + path: | + ~/.foundry/cache + ~/.config/.foundry/cache + key: rpc-cache-${{ hashFiles('crates/forge/tests/rpc-cache-keyfile') }} + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: Setup Git config + run: | + git config --global user.name "GitHub Actions Bot" + git config --global user.email "<>" + git config --global url."https://github.com/".insteadOf "git@github.com:" + - name: Test + env: + SVM_TARGET_PLATFORM: ${{ matrix.svm_target_platform }} + run: cargo nextest run ${{ matrix.flags }} - - name: Forge RPC cache - uses: actions/cache@v3 - with: - path: | - ~/.foundry/cache - ~/.config/.foundry/cache - key: rpc-cache-${{ hashFiles('crates/forge/tests/rpc-cache-keyfile') }} - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Setup Git config - run: | - git config --global user.name "GitHub Actions Bot" - git config --global user.email "<>" - git config --global url."https://github.com/".insteadOf "git@github.com:" - - name: Test - env: - SVM_TARGET_PLATFORM: ${{ matrix.svm_target_platform }} - run: cargo nextest run ${{ matrix.flags }} + docs: + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: write + pages: write + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - run: cargo doc --workspace --all-features --no-deps --document-private-items + env: + RUSTDOCFLAGS: + --cfg docsrs --show-type-layout --generate-link-to-definition --enable-index-page + -Zunstable-options + - name: Deploy documentation + uses: peaceiris/actions-gh-pages@v3 + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: target/doc + force_orphan: true - doctest: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - run: cargo test --workspace --doc + doctest: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - run: cargo test --workspace --doc - clippy: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@clippy - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - run: cargo clippy --workspace --all-targets --all-features - env: - RUSTFLAGS: -Dwarnings + clippy: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@clippy + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - run: cargo clippy --workspace --all-targets --all-features + env: + RUSTFLAGS: -Dwarnings - rustfmt: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - with: - components: rustfmt - - run: cargo fmt --all --check + rustfmt: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + with: + toolchain: nightly-2024-02-03 + components: rustfmt + - run: cargo fmt --all --check - forge-fmt: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: forge fmt - shell: bash - # We have to ignore at shell level because testdata/ is not a valid Foundry project, - # so running `forge fmt` with `--root testdata` won't actually check anything - run: | - shopt -s extglob - cargo run --bin forge -- fmt --check testdata/**/!(Vm).sol + forge-fmt: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: forge fmt + shell: bash + # We have to ignore at shell level because testdata/ is not a valid Foundry project, + # so running `forge fmt` with `--root testdata` won't actually check anything + run: | + shopt -s extglob + cargo run --bin forge -- fmt --check $(find testdata -name '*.sol' ! -name Vm.sol) - crate-checks: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: taiki-e/install-action@cargo-hack - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - run: cargo hack check + crate-checks: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: taiki-e/install-action@cargo-hack + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - run: cargo hack check diff --git a/.gitignore b/.gitignore index 19f666e451bf9..14e811f2d511f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ out/ out.json .idea .vscode +bloat* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b2bf43604dfc4..98b36e8539a05 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,8 +4,6 @@ Thanks for your interest in improving Foundry! There are multiple opportunities to contribute at any level. It doesn't matter if you are just getting started with Rust or are the most weathered expert, we can use your help. -**No contribution is too small and all contributions are valued.** - This document will help you get started. **Do not let the document intimidate you**. It should be considered as a guide to help you navigate the process. @@ -33,6 +31,11 @@ There are fundamentally four ways an individual can contribute: **Anybody can participate in any stage of contribution**. We urge you to participate in the discussion around bugs and participate in reviewing PRs. +### Contributions Related to Spelling and Grammar + +At this time, we will not be accepting contributions that only fix spelling or grammatical errors in documentation, code or +elsewhere. + ### Asking for help If you have reviewed existing documentation and still have questions, or you are having problems, you can get help in the following ways: @@ -83,7 +86,7 @@ Please also make sure that the following commands pass if you have changed the c cargo check --all cargo test --all --all-features cargo +nightly fmt -- --check -cargo +nightly clippy --all --all-features -- -D warnings +cargo +nightly clippy --all --all-targets --all-features -- -D warnings ``` If you are working in VSCode, we recommend you install the [rust-analyzer](https://rust-analyzer.github.io/) extension, and use the following VSCode user settings: diff --git a/Cargo.lock b/Cargo.lock index c330d8d194e42..276ac2d0dcfe5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,9 +29,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -40,20 +40,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" -dependencies = [ - "getrandom 0.2.11", - "once_cell", - "version_check", -] - -[[package]] -name = "ahash" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", @@ -63,35 +52,69 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alloy-chains" -version = "0.1.4" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f12f8177eddf9275fa9ae5fd73b50cee062f9b1eb95ef435f28354c79ba386" +checksum = "fe6c2674230e94ea98767550b02853bf7024b46f784827be95acfc5f5f1a445f" dependencies = [ "num_enum", "serde", "strum", ] +[[package]] +name = "alloy-consensus" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "c-kzg", + "serde", + "sha2", + "thiserror", +] + +[[package]] +name = "alloy-contract" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-types", + "alloy-sol-types", + "alloy-transport", + "futures", + "futures-util", + "thiserror", +] + [[package]] name = "alloy-dyn-abi" -version = "0.5.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fafc3b20c6d069d9db47037f34acfb0e079c050fa5c1ff9e79855609b359b92b" +checksum = "22ab339ca7b4ea9115f0578c941abc80a171edf8e5eadd01e6c4237b68db8083" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -105,14 +128,39 @@ dependencies = [ "proptest", "serde", "serde_json", - "winnow", + "winnow 0.6.7", +] + +[[package]] +name = "alloy-eips" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "c-kzg", + "derive_more", + "once_cell", + "serde", +] + +[[package]] +name = "alloy-genesis" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-primitives", + "alloy-serde", + "serde", + "serde_json", ] [[package]] name = "alloy-json-abi" -version = "0.5.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d32061da2f184e5defab8e65a3057f88b7017cfe1ea9e2d6b413edb5ca76a54" +checksum = "44294729c145cf7ae65feab544b5b81fb2bb7e2fd060214842eb3989a1e9d882" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -120,11 +168,40 @@ dependencies = [ "serde_json", ] +[[package]] +name = "alloy-json-rpc" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-network" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-primitives", + "alloy-rpc-types", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "futures-utils-wasm", + "thiserror", +] + [[package]] name = "alloy-primitives" -version = "0.5.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08ca2c09d5911548a5cb620382ea0e1af99d3c898ce0efecbbd274a4676cf53e" +checksum = "50c715249705afa1e32be79dabfd35e2ef0f1cc02ad2cf48c9d1e20026ee637b" dependencies = [ "alloy-rlp", "arbitrary", @@ -134,9 +211,11 @@ dependencies = [ "derive_arbitrary", "derive_more", "ethereum_ssz", - "getrandom 0.2.11", + "getrandom 0.2.14", "hex-literal", "itoa", + "k256", + "keccak-asm", "proptest", "proptest-derive", "rand 0.8.5", @@ -145,69 +224,381 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "alloy-provider" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-eips", + "alloy-json-rpc", + "alloy-network", + "alloy-primitives", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-rpc-types-trace", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "async-stream", + "async-trait", + "auto_impl", + "dashmap", + "futures", + "futures-utils-wasm", + "lru", + "reqwest 0.12.4", + "serde_json", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "alloy-pubsub" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-transport", + "bimap", + "futures", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", +] + [[package]] name = "alloy-rlp" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc0fac0fc16baf1f63f78b47c3d24718f3619b0714076f6a02957d808d52cbef" +checksum = "8d58d9f5da7b40e9bfff0b7e7816700be4019db97d4b6359fe7f94a9e22e42ac" dependencies = [ "alloy-rlp-derive", "arrayvec", "bytes", - "smol_str", ] [[package]] name = "alloy-rlp-derive" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0391754c09fab4eae3404d19d0d297aa1c670c1775ab51d8a5312afeca23157" +checksum = "1a047897373be4bbb0224c1afdabca92648dc57a9c9ef6e7b0be3aff7a859c83" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", +] + +[[package]] +name = "alloy-rpc-client" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-pubsub", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "futures", + "pin-project", + "reqwest 0.12.4", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", + "url", +] + +[[package]] +name = "alloy-rpc-types" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "itertools 0.12.1", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "alloy-rpc-types-engine" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types", + "alloy-serde", + "jsonwebtoken 9.3.0", + "rand 0.8.5", + "serde", + "thiserror", +] + +[[package]] +name = "alloy-rpc-types-trace" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types", + "alloy-serde", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-serde" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-signer" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-dyn-abi", + "alloy-primitives", + "alloy-sol-types", + "async-trait", + "auto_impl", + "elliptic-curve", + "k256", + "thiserror", +] + +[[package]] +name = "alloy-signer-aws" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "aws-sdk-kms", + "k256", + "spki", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-signer-ledger" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "coins-ledger", + "futures-util", + "semver 1.0.22", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-signer-trezor" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "semver 1.0.22", + "thiserror", + "tracing", + "trezor-client", +] + +[[package]] +name = "alloy-signer-wallet" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "coins-bip32", + "coins-bip39", + "elliptic-curve", + "eth-keystore", + "k256", + "rand 0.8.5", + "thiserror", ] [[package]] name = "alloy-sol-macro" -version = "0.5.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e40cea54ac58080a1b88ea6556866eac1902b321186c77d53ad2b5ebbbf0e038" +checksum = "bef9a94a27345fb31e3fcb5f5e9f592bb4847493b07fa1e47dd9fde2222f2e28" dependencies = [ "alloy-json-abi", + "alloy-sol-macro-input", "const-hex", - "dunce", - "heck", + "heck 0.4.1", "indexmap", "proc-macro-error", "proc-macro2", "quote", + "syn 2.0.60", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31fe73cd259527e24dc2dbfe64bc95e5ddfcd2b2731f670a11ff72b2be2c25b" +dependencies = [ + "alloy-json-abi", + "const-hex", + "dunce", + "heck 0.5.0", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.60", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c8d6e74e4feeaa2bcfdecfd3da247ab53c67bd654ba1907270c32e02b142331" +dependencies = [ + "winnow 0.6.7", +] + +[[package]] +name = "alloy-sol-types" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afaffed78bfb17526375754931e045f96018aa810844b29c7aef823266dd4b4b" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] + +[[package]] +name = "alloy-transport" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-json-rpc", + "base64 0.22.0", + "futures-util", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower", + "url", + "wasm-bindgen-futures", +] + +[[package]] +name = "alloy-transport-http" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-json-rpc", + "alloy-transport", + "reqwest 0.12.4", + "serde_json", + "tower", + "url", +] + +[[package]] +name = "alloy-transport-ipc" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" +dependencies = [ + "alloy-json-rpc", + "alloy-pubsub", + "alloy-transport", + "bytes", + "futures", + "interprocess", + "pin-project", + "serde", "serde_json", - "syn 2.0.39", - "syn-solidity", - "tiny-keccak", + "tempfile", + "tokio", + "tokio-util", + "tracing", ] [[package]] -name = "alloy-sol-type-parser" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23cb462613b2046da46dbf69ebaee458b7bfd3e9d7fe05adcce38a8d4b8a14f" +name = "alloy-transport-ws" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=ca54552#ca54552075da02339f678e5b591877ff6c2939db" dependencies = [ - "winnow", + "alloy-pubsub", + "alloy-transport", + "futures", + "http 0.2.12", + "serde_json", + "tokio", + "tokio-tungstenite 0.20.1", + "tracing", + "ws_stream_wasm", ] [[package]] -name = "alloy-sol-types" -version = "0.5.2" +name = "alloy-trie" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81aa34725607be118c395d62c1d8d97c8a343dd1ada5370ed508ed5232eab6a" +checksum = "beb28aa4ecd32fdfa1b1bdd111ff7357dd562c6b2372694cf9e613434fcba659" dependencies = [ - "alloy-json-abi", "alloy-primitives", - "alloy-sol-macro", - "const-hex", + "alloy-rlp", + "derive_more", + "hashbrown", + "nybbles", "serde", + "smallvec", + "tracing", ] [[package]] @@ -246,9 +637,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -260,43 +651,65 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anvil" version = "0.2.0" dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-contract", + "alloy-dyn-abi", + "alloy-eips", + "alloy-genesis", + "alloy-json-abi", + "alloy-json-rpc", + "alloy-network", "alloy-primitives", + "alloy-provider", + "alloy-pubsub", + "alloy-rlp", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-rpc-types-trace", + "alloy-signer", + "alloy-signer-wallet", + "alloy-sol-types", + "alloy-transport", + "alloy-transport-ipc", + "alloy-transport-ws", + "alloy-trie", "anvil-core", "anvil-rpc", "anvil-server", @@ -308,62 +721,64 @@ dependencies = [ "clap", "clap_complete", "clap_complete_fig", - "crc 3.0.1", + "crc 3.2.1", "ctrlc", "ethereum-forkid", "ethers", "ethers-core", - "ethers-solc", + "eyre", "fdlimit", "flate2", + "foundry-cli", "foundry-common", + "foundry-compilers", "foundry-config", "foundry-evm", + "foundry-test-utils", "futures", - "hash-db", - "hyper", - "itertools 0.11.0", + "hyper 1.3.1", + "itertools 0.12.1", "k256", - "memory-db", "parking_lot", "pretty_assertions", "rand 0.8.5", + "revm", "serde", "serde_json", "serde_repr", "tempfile", "thiserror", + "tikv-jemallocator", "tokio", "tower", "tracing", "tracing-subscriber", - "trie-db", "vergen", - "yansi 0.5.1", + "yansi 1.0.1", ] [[package]] name = "anvil-core" version = "0.2.0" dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-eips", + "alloy-network", "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types", + "alloy-rpc-types-trace", + "alloy-trie", "anvil-core", "bytes", - "ethers-contract", - "ethers-core", - "ethers-middleware", - "ethers-providers", + "c-kzg", "foundry-common", "foundry-evm", - "hash-db", - "hash256-std-hasher", - "keccak-hasher", - "open-fastrlp", - "reference-trie", + "rand 0.8.5", "revm", "serde", "serde_json", - "triehash", ] [[package]] @@ -385,7 +800,6 @@ dependencies = [ "bytes", "clap", "futures", - "hyper", "parity-tokio-ipc", "parking_lot", "pin-project", @@ -399,24 +813,27 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "arbitrary" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "ariadne" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72fe02fc62033df9ba41cba57ee19acf5e742511a140c7dbc3a873e19a19a1bd" +checksum = "44055e597c674aef7cb903b2b9f6e4cba1277ed0d2d61dae7cd52d7ffa81f8e2" dependencies = [ "unicode-width", - "yansi 0.5.1", + "yansi 1.0.1", ] [[package]] @@ -513,151 +930,527 @@ dependencies = [ ] [[package]] -name = "ark-serialize" -version = "0.4.2" +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "async-channel" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" +dependencies = [ + "concurrent-queue", + "event-listener 5.3.0", + "event-listener-strategy 0.5.2", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-lock" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +dependencies = [ + "event-listener 4.0.3", + "event-listener-strategy 0.4.0", + "pin-project-lite", +] + +[[package]] +name = "async-priority-channel" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c21678992e1b21bebfe2bc53ab5f5f68c106eddab31b24e0bb06e9b715a86640" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version 0.4.0", +] + +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "atomic-take" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "aurora-engine-modexp" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aef7712851e524f35fbbb74fa6599c5cd8692056a1c36f9ca0d2001b670e7e5" +dependencies = [ + "hex", + "num", +] + +[[package]] +name = "auto_impl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "autocfg" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" + +[[package]] +name = "aws-config" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4707646259764ab59fd9a50e9de2e92c637b28b36285d6f6fa030e915fbd9" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 0.2.12", + "hyper 0.14.28", + "ring 0.17.8", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16838e6c9e12125face1c1eff1343c75e3ff540de98ff7ebd61874a89bcfeb9" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-runtime" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4963ac9ff2d33a4231b3806c1c69f578f221a9cabb89ad2bde62ce2b442c8a7" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid 1.8.0", +] + +[[package]] +name = "aws-sdk-kms" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +checksum = "4453868f71232e0baf5947972972e87813c8bbb311351f669a6ad6aa5b86ee63" dependencies = [ - "ark-std 0.4.0", - "digest 0.10.7", - "num-bigint", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", ] [[package]] -name = "ark-std" -version = "0.3.0" +name = "aws-sdk-sso" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +checksum = "3d70fb493f4183f5102d8a8d0cc9b57aec29a762f55c0e7bf527e0f7177bb408" dependencies = [ - "num-traits", - "rand 0.8.5", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", ] [[package]] -name = "ark-std" -version = "0.4.0" +name = "aws-sdk-ssooidc" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +checksum = "de3f37549b3e38b7ea5efd419d4d7add6ea1e55223506eb0b4fef9d25e7cc90d" dependencies = [ - "num-traits", - "rand 0.8.5", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", ] [[package]] -name = "arrayvec" -version = "0.7.4" +name = "aws-sdk-sts" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "3b2ff219a5d4b795cd33251c19dbe9c4b401f2b2cbe513e07c76ada644eaf34e" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] [[package]] -name = "ascii-canvas" -version = "3.0.0" +name = "aws-sigv4" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +checksum = "58b56f1cbe6fd4d0c2573df72868f20ab1c125ca9c9dbce17927a463433a2e57" dependencies = [ - "term", + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.1.0", + "once_cell", + "percent-encoding", + "sha2", + "time", + "tracing", ] [[package]] -name = "async-priority-channel" -version = "0.1.0" +name = "aws-smithy-async" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c21678992e1b21bebfe2bc53ab5f5f68c106eddab31b24e0bb06e9b715a86640" +checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c" dependencies = [ - "event-listener", + "futures-util", + "pin-project-lite", + "tokio", ] [[package]] -name = "async-recursion" -version = "1.0.5" +name = "aws-smithy-http" +version = "0.60.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +checksum = "4a7de001a1b9a25601016d8057ea16e31a45fdca3751304c8edf4ad72e706c08" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.39", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", ] [[package]] -name = "async-trait" -version = "0.1.74" +name = "aws-smithy-json" +version = "0.60.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.39", + "aws-smithy-types", ] [[package]] -name = "async_io_stream" -version = "0.3.3" +name = "aws-smithy-query" +version = "0.60.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" dependencies = [ - "futures", - "pharos", - "rustc_version 0.4.0", + "aws-smithy-types", + "urlencoding", ] [[package]] -name = "atomic" -version = "0.6.0" +name = "aws-smithy-runtime" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +checksum = "44e7945379821074549168917e89e60630647e186a69243248f08c6d168b975a" dependencies = [ - "bytemuck", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "http-body 1.0.0", + "hyper 0.14.28", + "hyper-rustls 0.24.2", + "once_cell", + "pin-project-lite", + "pin-utils", + "rustls 0.21.12", + "tokio", + "tracing", ] [[package]] -name = "atomic-take" -version = "1.1.0" +name = "aws-smithy-runtime-api" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" +checksum = "4cc56a5c96ec741de6c5e6bf1ce6948be969d6506dfa9c39cffc284e31e4979b" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.1.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] [[package]] -name = "aurora-engine-modexp" -version = "1.0.0" +name = "aws-smithy-types" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfacad86e9e138fca0670949eb8ed4ffdf73a55bded8887efe0863cd1a3a6f70" +checksum = "abe14dceea1e70101d38fbf2a99e6a34159477c0fb95e68e05c66bd7ae4c3729" dependencies = [ - "hex", - "num", + "base64-simd", + "bytes", + "bytes-utils", + "http 0.2.12", + "http 1.1.0", + "http-body 0.4.6", + "http-body 1.0.0", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", ] [[package]] -name = "auto_impl" -version = "1.1.0" +name = "aws-smithy-xml" +version = "0.60.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +checksum = "d123fbc2a4adc3c301652ba8e149bf4bc1d1725affb9784eb20c953ace06bf55" dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", + "xmlparser", ] [[package]] -name = "autocfg" -version = "1.1.0" +name = "aws-types" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "5a43b56df2c529fe44cb4d92bd64d0479883fb9608ff62daede4df5405381814" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "http 0.2.12", + "rustc_version 0.4.0", + "tracing", +] [[package]] name = "axum" -version = "0.6.20" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" dependencies = [ "async-trait", "axum-core", - "base64 0.21.5", - "bitflags 1.3.2", + "base64 0.21.7", "bytes", "futures-util", - "http", - "http-body", - "hyper", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", + "hyper-util", "itoa", "matchit", "memchr", @@ -670,36 +1463,41 @@ dependencies = [ "serde_path_to_error", "serde_urlencoded", "sha1", - "sync_wrapper", + "sync_wrapper 1.0.1", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.21.0", "tower", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "axum-core" -version = "0.3.4" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", "mime", + "pin-project-lite", "rustversion", + "sync_wrapper 0.1.2", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -724,9 +1522,25 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + +[[package]] +name = "base64-simd" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] [[package]] name = "base64ct" @@ -741,27 +1555,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" [[package]] -name = "bindgen" -version = "0.66.1" +name = "bimap" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" -dependencies = [ - "bitflags 2.4.1", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.39", - "which", -] +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" [[package]] name = "bit-set" @@ -786,9 +1583,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ "arbitrary", "serde", @@ -809,20 +1606,25 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.9.0" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] -name = "block-buffer" -version = "0.10.4" +name = "blocking" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "495f7104e962b7356f0aeb34247aca1fe7d2e783b346582db7f2904cb5717e88" dependencies = [ - "generic-array", + "async-channel", + "async-lock", + "async-task", + "futures-io", + "futures-lite", + "piper", ] [[package]] @@ -839,22 +1641,22 @@ dependencies = [ [[package]] name = "bs58" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ - "sha2 0.10.8", + "sha2", "tinyvec", ] [[package]] name = "bstr" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", - "regex-automata 0.4.3", + "regex-automata 0.4.6", "serde", ] @@ -875,9 +1677,9 @@ checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byte-slice-cast" @@ -887,9 +1689,9 @@ checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "bytemuck" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" [[package]] name = "byteorder" @@ -899,13 +1701,23 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" dependencies = [ "serde", ] +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "bzip2" version = "0.4.4" @@ -929,11 +1741,10 @@ dependencies = [ [[package]] name = "c-kzg" -version = "0.4.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32700dc7904064bb64e857d38a1766607372928e2466ee5f02a869829b3297d7" +checksum = "3130f3d8717cc02e668a896af24984d5d5d4e8bf12e278e982e0f1bd88a0f9af" dependencies = [ - "bindgen", "blst", "cc", "glob", @@ -953,9 +1764,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34637b3140142bdf929fb439e8aa4ebad7651ebf7b1080b3930aa16ac1459ff" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" dependencies = [ "serde", ] @@ -968,7 +1779,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.20", + "semver 1.0.22", "serde", "serde_json", "thiserror", @@ -984,11 +1795,23 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" name = "cast" version = "0.2.0" dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-contract", "alloy-dyn-abi", "alloy-json-abi", + "alloy-json-rpc", + "alloy-network", "alloy-primitives", + "alloy-provider", "alloy-rlp", + "alloy-rpc-types", + "alloy-signer", + "alloy-signer-wallet", + "alloy-sol-types", + "alloy-transport", "async-trait", + "aws-sdk-kms", "chrono", "clap", "clap_complete", @@ -997,13 +1820,10 @@ dependencies = [ "const-hex", "criterion", "dunce", - "eth-keystore", "ethers-contract", "ethers-core", - "ethers-middleware", - "ethers-providers", - "ethers-signers", "evm-disassembler", + "evmole", "eyre", "foundry-block-explorers", "foundry-cli", @@ -1012,23 +1832,23 @@ dependencies = [ "foundry-config", "foundry-evm", "foundry-test-utils", + "foundry-wallets", "futures", "indicatif", - "itertools 0.11.0", + "itertools 0.12.1", "rand 0.8.5", "rayon", "regex", "rpassword", - "rusoto_core", - "rusoto_kms", - "semver 1.0.20", + "semver 1.0.22", "serde", "serde_json", "tempfile", + "tikv-jemallocator", "tokio", "tracing", "vergen", - "yansi 0.5.1", + "yansi 1.0.1", ] [[package]] @@ -1038,22 +1858,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] -name = "cc" -version = "1.0.83" +name = "castaway" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" dependencies = [ - "jobserver", - "libc", + "rustversion", ] [[package]] -name = "cexpr" -version = "0.6.0" +name = "cc" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" dependencies = [ - "nom", + "jobserver", + "libc", + "once_cell", ] [[package]] @@ -1062,6 +1883,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "chisel" version = "0.2.0" @@ -1069,12 +1896,13 @@ dependencies = [ "alloy-dyn-abi", "alloy-json-abi", "alloy-primitives", + "alloy-rpc-types", "clap", "criterion", "dirs 5.0.1", - "ethers-core", "eyre", "forge-fmt", + "foundry-block-explorers", "foundry-cli", "foundry-common", "foundry-compilers", @@ -1082,39 +1910,41 @@ dependencies = [ "foundry-evm", "once_cell", "regex", - "reqwest", + "reqwest 0.12.4", "revm", "rustyline", - "semver 1.0.20", + "semver 1.0.22", "serde", "serde_json", "serial_test", "solang-parser", "strum", + "tikv-jemallocator", "time", "tokio", + "tracing", + "tracing-subscriber", "vergen", - "yansi 0.5.1", + "yansi 1.0.1", ] [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "serde", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] name = "ciborium" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", @@ -1123,15 +1953,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", @@ -1147,22 +1977,11 @@ dependencies = [ "inout", ] -[[package]] -name = "clang-sys" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" -version = "4.4.10" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -1170,14 +1989,14 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.9" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.1", "terminal_size", "unicase", "unicode-width", @@ -1185,18 +2004,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.4.4" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffe91f06a11b4b9420f62103854e90867812cd5d01557f853c5ee8e791b12ae" +checksum = "dd79504325bf38b10165b02e89b4347300f855f273c4cb30c4a3209e6583275e" dependencies = [ "clap", ] [[package]] name = "clap_complete_fig" -version = "4.4.2" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e571d70e22ec91d34e1c5317c8308035a2280d925167646bf094fc5de1737c" +checksum = "54b3e65f91fabdd23cac3d57d39d5d938b4daabd070c335c006dccb866a61110" dependencies = [ "clap", "clap_complete", @@ -1204,21 +2023,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "clearscreen" @@ -1253,10 +2072,10 @@ dependencies = [ "bs58", "coins-core", "digest 0.10.7", - "hmac 0.12.1", + "hmac", "k256", "serde", - "sha2 0.10.8", + "sha2", "thiserror", ] @@ -1268,11 +2087,11 @@ checksum = "3db8fba409ce3dc04f7d804074039eb68b960b0829161f8e06c95fea3f122528" dependencies = [ "bitvec", "coins-bip32", - "hmac 0.12.1", + "hmac", "once_cell", "pbkdf2 0.12.2", "rand 0.8.5", - "sha2 0.10.8", + "sha2", "thiserror", ] @@ -1282,7 +2101,7 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "bech32", "bs58", "digest 0.10.7", @@ -1291,31 +2110,27 @@ dependencies = [ "ripemd", "serde", "serde_derive", - "sha2 0.10.8", + "sha2", "sha3", "thiserror", ] [[package]] name = "coins-ledger" -version = "0.9.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12adb2f4914ec6d8659cd9d1630463ca13ac90f5be18133cb9a70921f15fc145" +checksum = "9e076e6e5d9708f0b90afe2dbe5a8ba406b5c794347661e6e44618388c7e3a31" dependencies = [ "async-trait", "byteorder", "cfg-if", - "getrandom 0.2.11", + "getrandom 0.2.14", "hex", "hidapi-rusb", "js-sys", - "lazy_static", - "libc", "log", - "matches", "nix 0.26.4", - "serde", - "tap", + "once_cell", "thiserror", "tokio", "tracing", @@ -1325,9 +2140,9 @@ dependencies = [ [[package]] name = "color-eyre" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" dependencies = [ "backtrace", "color-spantrace", @@ -1358,9 +2173,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "comfy-table" -version = "7.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686" +checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" dependencies = [ "crossterm", "strum", @@ -1380,24 +2195,46 @@ dependencies = [ "winapi", ] +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "console" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] name = "const-hex" -version = "1.10.0" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5104de16b218eddf8e34ffe2f86f74bfa4e61e95a1b89732fccf6325efd0557" +checksum = "5ba00838774b4ab0233e355d26710fbfc8327a05c017f6dc4873f876d1f79f78" dependencies = [ "cfg-if", "cpufeatures", @@ -1408,9 +2245,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "constant_time_eq" @@ -1442,9 +2279,9 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -1460,9 +2297,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.0.1" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] @@ -1475,9 +2312,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] @@ -1522,46 +2359,37 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset 0.9.0", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crossterm" @@ -1569,7 +2397,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "crossterm_winapi", "libc", "mio", @@ -1616,16 +2444,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "ctr" version = "0.9.2" @@ -1637,12 +2455,47 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.1" +version = "3.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" dependencies = [ - "nix 0.27.1", - "windows-sys 0.48.0", + "nix 0.28.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 2.0.60", +] + +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.60", ] [[package]] @@ -1652,7 +2505,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.3", + "hashbrown", "lock_api", "once_cell", "parking_lot_core", @@ -1666,19 +2519,20 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] @@ -1702,7 +2556,38 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", +] + +[[package]] +name = "derive_builder" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" +dependencies = [ + "derive_builder_core", + "syn 2.0.60", ] [[package]] @@ -1726,7 +2611,9 @@ checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" dependencies = [ "console", "shell-words", + "tempfile", "thiserror", + "zeroize", ] [[package]] @@ -1750,7 +2637,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", + "block-buffer", "const-oid", "crypto-common", "subtle", @@ -1824,6 +2711,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "dunce" version = "1.0.4" @@ -1832,9 +2725,9 @@ checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "dyn-clone" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "ecdsa" @@ -1852,9 +2745,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "elasticlunr-rs" @@ -1880,6 +2773,7 @@ dependencies = [ "ff", "generic-array", "group", + "pem-rfc7468", "pkcs8", "rand_core 0.6.4", "sec1", @@ -1904,9 +2798,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -1919,11 +2813,11 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "enr" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe81b5c06ecfdbc71dd845216f225f53b62a10cb8a16c946836a3467f701d05b" +checksum = "2a3d8dc56e02f954cac8eb489772c552c473346fc34f67412bb6244fd647f7e4" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "bytes", "hex", "k256", @@ -1937,26 +2831,36 @@ dependencies = [ [[package]] name = "enumn" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ad8cef1d801a4686bfd8919f0b30eac4c8e48968c437a6405ded4fb5272d2b" +checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", +] + +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", ] [[package]] name = "env_logger" -version = "0.10.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ + "anstream", + "anstyle", + "env_filter", "humantime", - "is-terminal", "log", - "regex", - "termcolor", ] [[package]] @@ -1995,16 +2899,16 @@ dependencies = [ "ctr", "digest 0.10.7", "hex", - "hmac 0.12.1", + "hmac", "pbkdf2 0.11.0", "rand 0.8.5", "scrypt", "serde", "serde_json", - "sha2 0.10.8", + "sha2", "sha3", "thiserror", - "uuid", + "uuid 0.8.2", ] [[package]] @@ -2081,8 +2985,9 @@ dependencies = [ [[package]] name = "ethers" -version = "2.0.11" -source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "816841ea989f0c69e459af1cf23a6b0033b19a55424a1ea3a30099becdb8dec0" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -2096,8 +3001,9 @@ dependencies = [ [[package]] name = "ethers-addressbook" -version = "2.0.11" -source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5495afd16b4faa556c3bba1f21b98b4983e53c1755022377051a975c3b021759" dependencies = [ "ethers-core", "once_cell", @@ -2107,8 +3013,9 @@ dependencies = [ [[package]] name = "ethers-contract" -version = "2.0.11" -source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fceafa3578c836eeb874af87abacfb041f92b4da0a78a5edd042564b8ecdaaa" dependencies = [ "const-hex", "ethers-contract-abigen", @@ -2125,8 +3032,9 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" -version = "2.0.11" -source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04ba01fbc2331a38c429eb95d4a570166781f14290ef9fdb144278a90b5a739b" dependencies = [ "Inflector", "const-hex", @@ -2138,18 +3046,19 @@ dependencies = [ "proc-macro2", "quote", "regex", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", - "syn 2.0.39", - "toml 0.8.8", + "syn 2.0.60", + "toml 0.8.12", "walkdir", ] [[package]] name = "ethers-contract-derive" -version = "2.0.11" -source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87689dcabc0051cde10caaade298f9e9093d65f6125c14575db3fd8c669a168f" dependencies = [ "Inflector", "const-hex", @@ -2158,13 +3067,14 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "ethers-core" -version = "2.0.11" -source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d80cc6ad30b14a48ab786523af33b37f28a8623fc06afd55324816ef18fb1f" dependencies = [ "arrayvec", "bytes", @@ -2183,7 +3093,7 @@ dependencies = [ "serde", "serde_json", "strum", - "syn 2.0.39", + "syn 2.0.60", "tempfile", "thiserror", "tiny-keccak", @@ -2192,13 +3102,14 @@ dependencies = [ [[package]] name = "ethers-etherscan" -version = "2.0.11" -source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79e5973c26d4baf0ce55520bd732314328cabe53193286671b47144145b9649" dependencies = [ "chrono", "ethers-core", - "reqwest", - "semver 1.0.20", + "reqwest 0.11.27", + "semver 1.0.22", "serde", "serde_json", "thiserror", @@ -2207,8 +3118,9 @@ dependencies = [ [[package]] name = "ethers-middleware" -version = "2.0.11" -source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f9fdf09aec667c099909d91908d5eaf9be1bd0e2500ba4172c1d28bfaa43de" dependencies = [ "async-trait", "auto_impl", @@ -2220,7 +3132,7 @@ dependencies = [ "futures-locks", "futures-util", "instant", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "thiserror", @@ -2232,12 +3144,13 @@ dependencies = [ [[package]] name = "ethers-providers" -version = "2.0.11" -source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6434c9a33891f1effc9c75472e12666db2fa5a0fec4b29af6221680a6fe83ab2" dependencies = [ "async-trait", "auto_impl", - "base64 0.21.5", + "base64 0.21.7", "bytes", "const-hex", "enr", @@ -2247,17 +3160,17 @@ dependencies = [ "futures-timer", "futures-util", "hashers", - "http", + "http 0.2.12", "instant", - "jsonwebtoken", + "jsonwebtoken 8.3.0", "once_cell", "pin-project", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "thiserror", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.20.1", "tracing", "tracing-futures", "url", @@ -2270,61 +3183,47 @@ dependencies = [ [[package]] name = "ethers-signers" -version = "2.0.11" -source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228875491c782ad851773b652dd8ecac62cda8571d3bc32a5853644dd26766c2" dependencies = [ "async-trait", "coins-bip32", "coins-bip39", - "coins-ledger", "const-hex", "elliptic-curve", "eth-keystore", "ethers-core", - "futures-executor", - "futures-util", - "home", - "protobuf", "rand 0.8.5", - "rusoto_core", - "rusoto_kms", - "semver 1.0.20", - "sha2 0.10.8", - "spki", + "sha2", "thiserror", "tracing", - "trezor-client", ] [[package]] name = "ethers-solc" -version = "2.0.11" -source = "git+https://github.com/clabby/ethers-rs?branch=cl/call-type-3074#2ffcdc579fac2032c706ee81c5697aec1a731db6" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66244a771d9163282646dbeffe0e6eca4dda4146b6498644e678ac6089b11edd" dependencies = [ "cfg-if", "const-hex", "dirs 5.0.1", "dunce", "ethers-core", - "fs_extra", - "futures-util", "glob", "home", - "md-5 0.10.6", + "md-5", "num_cpus", "once_cell", "path-slash", - "rand 0.8.5", "rayon", "regex", - "semver 1.0.20", + "semver 1.0.22", "serde", "serde_json", - "sha2 0.10.8", "solang-parser", - "svm-rs", - "svm-rs-builds", - "tempfile", + "svm-rs 0.3.5", "thiserror", "tiny-keccak", "tokio", @@ -2339,21 +3238,72 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.0", + "pin-project-lite", +] + [[package]] name = "evm-disassembler" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef8b778f0f7ba24aaa7c1d8fa7ec75db869f8a8508907be49eac899865ea52d" +checksum = "ded685d9f07315ff689ba56e7d84e6f1e782db19b531a46c34061a733bba7258" dependencies = [ "eyre", "hex", ] +[[package]] +name = "evmole" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd4e05af4c306bcba507bd358feac33ec73f4314a89bd93758d035c629f2f5fe" +dependencies = [ + "ruint", +] + [[package]] name = "eyre" -version = "0.6.9" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", @@ -2361,9 +3311,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fastrlp" @@ -2386,7 +3336,7 @@ dependencies = [ "bytes", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -2402,9 +3352,9 @@ dependencies = [ [[package]] name = "fd-lock" -version = "4.0.1" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b93f7a0db71c99f68398f80653ed05afb0b00e062e1a20c7ff849c4edfabbbcc" +checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" dependencies = [ "cfg-if", "rustix", @@ -2433,30 +3383,30 @@ dependencies = [ [[package]] name = "figment" -version = "0.10.12" +version = "0.10.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "649f3e5d826594057e9a519626304d8da859ea8a0b18ce99500c586b8d45faee" +checksum = "d032832d74006f99547004d49410a4b4218e4c33382d56ca3ff89df74f86b953" dependencies = [ "atomic", "parking_lot", "pear", "serde", "tempfile", - "toml 0.8.8", + "toml 0.8.12", "uncased", "version_check", ] [[package]] name = "filetime" -version = "0.2.22" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", - "windows-sys 0.48.0", + "redox_syscall 0.4.1", + "windows-sys 0.52.0", ] [[package]] @@ -2480,9 +3430,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "4556222738635b7a3417ae6130d8f52201e45a0c4d1a907f0826383adb5f85e7" dependencies = [ "crc32fast", "miniz_oxide", @@ -2513,9 +3463,17 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" name = "forge" version = "0.2.0" dependencies = [ + "alloy-chains", + "alloy-consensus", "alloy-dyn-abi", "alloy-json-abi", + "alloy-network", "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-signer", + "alloy-signer-wallet", + "alloy-transport", "anvil", "async-trait", "axum", @@ -2528,13 +3486,12 @@ dependencies = [ "dialoguer", "dunce", "ethers-contract", - "ethers-core", - "ethers-middleware", - "ethers-providers", - "ethers-signers", + "evm-disassembler", "eyre", "forge-doc", "forge-fmt", + "forge-script", + "forge-verify", "foundry-block-explorers", "foundry-cli", "foundry-common", @@ -2542,12 +3499,16 @@ dependencies = [ "foundry-config", "foundry-debugger", "foundry-evm", + "foundry-linking", "foundry-test-utils", + "foundry-wallets", "futures", "globset", - "hyper", + "humantime-serde", + "hyper 1.3.1", "indicatif", - "itertools 0.11.0", + "itertools 0.12.1", + "mockall", "once_cell", "opener", "parking_lot", @@ -2557,24 +3518,28 @@ dependencies = [ "proptest", "rayon", "regex", - "reqwest", - "semver 1.0.20", + "reqwest 0.12.4", + "revm-inspectors", + "rustc-hash", + "semver 1.0.22", "serde", "serde_json", - "serial_test", "similar", "solang-parser", "strum", - "svm-rs", + "svm-rs 0.5.2", "tempfile", "thiserror", + "tikv-jemallocator", "tokio", + "toml 0.8.12", + "toml_edit 0.22.12", "tower-http", "tracing", "tracing-subscriber", "vergen", "watchexec", - "yansi 0.5.1", + "yansi 1.0.1", ] [[package]] @@ -2589,15 +3554,16 @@ dependencies = [ "foundry-common", "foundry-compilers", "foundry-config", - "itertools 0.11.0", + "itertools 0.12.1", "mdbook", "once_cell", "rayon", + "regex", "serde", "serde_json", "solang-parser", "thiserror", - "toml 0.8.8", + "toml 0.8.12", "tracing", ] @@ -2608,15 +3574,91 @@ dependencies = [ "alloy-primitives", "ariadne", "foundry-config", - "itertools 0.11.0", + "itertools 0.12.1", "pretty_assertions", "solang-parser", "thiserror", - "toml 0.8.8", + "toml 0.8.12", "tracing", "tracing-subscriber", ] +[[package]] +name = "forge-script" +version = "0.2.0" +dependencies = [ + "alloy-chains", + "alloy-dyn-abi", + "alloy-eips", + "alloy-json-abi", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-signer", + "alloy-transport", + "async-recursion", + "clap", + "const-hex", + "dialoguer", + "dunce", + "eyre", + "forge-verify", + "foundry-cheatcodes", + "foundry-cli", + "foundry-common", + "foundry-compilers", + "foundry-config", + "foundry-debugger", + "foundry-evm", + "foundry-linking", + "foundry-wallets", + "futures", + "indicatif", + "itertools 0.12.1", + "parking_lot", + "revm-inspectors", + "semver 1.0.22", + "serde", + "serde_json", + "tempfile", + "tracing", + "yansi 1.0.1", +] + +[[package]] +name = "forge-verify" +version = "0.2.0" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "async-trait", + "clap", + "const-hex", + "eyre", + "foundry-block-explorers", + "foundry-cli", + "foundry-common", + "foundry-compilers", + "foundry-config", + "foundry-evm", + "foundry-test-utils", + "futures", + "once_cell", + "regex", + "reqwest 0.12.4", + "revm-primitives", + "semver 1.0.22", + "serde", + "serde_json", + "tempfile", + "tokio", + "tracing", + "yansi 1.0.1", +] + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -2628,16 +3670,16 @@ dependencies = [ [[package]] name = "foundry-block-explorers" -version = "0.1.1" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47331ff0955a60df66db753f5dcfe3ca0275abe192e58c9eb6d0d8c1a131e604" +checksum = "b64cb03e297eb85b9f84a195fec7390a5e9805d9b82b11b2af57f65fc6d4ceb7" dependencies = [ "alloy-chains", "alloy-json-abi", "alloy-primitives", "foundry-compilers", - "reqwest", - "semver 1.0.20", + "reqwest 0.12.4", + "semver 1.0.22", "serde", "serde_json", "thiserror", @@ -2649,23 +3691,35 @@ name = "foundry-cheatcodes" version = "0.2.0" dependencies = [ "alloy-dyn-abi", + "alloy-genesis", "alloy-json-abi", "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-signer", + "alloy-signer-wallet", "alloy-sol-types", + "base64 0.22.0", "const-hex", - "ethers-core", - "ethers-providers", - "ethers-signers", + "dialoguer", "eyre", "foundry-cheatcodes-spec", "foundry-common", "foundry-compilers", "foundry-config", "foundry-evm-core", - "itertools 0.11.0", + "foundry-wallets", + "itertools 0.12.1", "jsonpath_lib", + "k256", + "p256", + "parking_lot", "revm", + "rustc-hash", + "semver 1.0.22", "serde_json", + "thiserror", + "toml 0.8.12", "tracing", "walkdir", ] @@ -2685,17 +3739,16 @@ dependencies = [ name = "foundry-cli" version = "0.2.0" dependencies = [ + "alloy-chains", "alloy-dyn-abi", "alloy-json-abi", "alloy-primitives", - "async-trait", + "alloy-provider", + "alloy-transport", "clap", "color-eyre", "const-hex", "dotenvy", - "ethers-core", - "ethers-providers", - "ethers-signers", "eyre", "forge-fmt", "foundry-common", @@ -2703,104 +3756,107 @@ dependencies = [ "foundry-config", "foundry-debugger", "foundry-evm", + "foundry-wallets", + "futures", "indicatif", - "itertools 0.11.0", "once_cell", "regex", - "rpassword", - "rusoto_core", - "rusoto_kms", "serde", - "strsim", + "strsim 0.10.0", "strum", "tempfile", - "thiserror", "tokio", "tracing", "tracing-error", "tracing-subscriber", - "yansi 0.5.1", + "yansi 1.0.1", ] [[package]] name = "foundry-common" version = "0.2.0" dependencies = [ + "alloy-consensus", + "alloy-contract", "alloy-dyn-abi", "alloy-json-abi", + "alloy-json-rpc", "alloy-primitives", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-rpc-types-engine", "alloy-sol-types", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", "async-trait", - "auto_impl", "clap", "comfy-table", - "const-hex", - "dunce", - "ethers-core", - "ethers-middleware", - "ethers-providers", + "dunce", "eyre", "foundry-block-explorers", "foundry-compilers", "foundry-config", + "foundry-linking", "foundry-macros", "glob", "globset", + "num-format", "once_cell", "pretty_assertions", - "rand 0.8.5", - "regex", - "reqwest", - "semver 1.0.20", + "reqwest 0.12.4", + "rustc-hash", + "semver 1.0.22", "serde", "serde_json", "tempfile", "thiserror", "tokio", + "tower", "tracing", "url", "walkdir", - "yansi 0.5.1", + "yansi 1.0.1", ] [[package]] name = "foundry-compilers" -version = "0.1.1" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eec9fd9df7ad0509ae45e2ea8a595cf13b1f35d69980e985b3be00ffabcc01a" +checksum = "e5421772f768d43f81052159c5175e7d10941e0f0416dafd768f353ed9c554fd" dependencies = [ "alloy-json-abi", "alloy-primitives", "cfg-if", - "const-hex", "dirs 5.0.1", "dunce", "fs_extra", "futures-util", - "glob", "home", - "md-5 0.10.6", - "memmap2 0.9.0", - "num_cpus", + "itertools 0.12.1", + "md-5", + "memmap2 0.9.4", "once_cell", "path-slash", "rand 0.8.5", "rayon", "regex", - "semver 1.0.20", + "semver 1.0.22", "serde", "serde_json", - "sha2 0.10.8", + "sha2", "solang-parser", - "svm-rs", + "svm-rs 0.5.2", "svm-rs-builds", "tempfile", "thiserror", - "tiny-keccak", "tokio", "tracing", "walkdir", - "yansi 0.5.1", + "yansi 1.0.1", ] [[package]] @@ -2811,6 +3867,7 @@ dependencies = [ "alloy-chains", "alloy-primitives", "dirs-next", + "dunce", "eyre", "figment", "foundry-block-explorers", @@ -2821,16 +3878,17 @@ dependencies = [ "path-slash", "pretty_assertions", "regex", - "reqwest", + "reqwest 0.12.4", "revm-primitives", - "semver 1.0.20", + "semver 1.0.22", "serde", "serde_json", "serde_regex", + "solang-parser", "tempfile", "thiserror", - "toml 0.8.8", - "toml_edit 0.21.0", + "toml 0.8.12", + "toml_edit 0.22.12", "tracing", "walkdir", ] @@ -2843,10 +3901,12 @@ dependencies = [ "crossterm", "eyre", "foundry-common", + "foundry-compilers", "foundry-evm-core", "foundry-evm-traces", "ratatui", "revm", + "revm-inspectors", "tracing", ] @@ -2858,9 +3918,8 @@ dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-sol-types", + "arrayvec", "const-hex", - "ethers-core", - "ethers-signers", "eyre", "foundry-cheatcodes", "foundry-common", @@ -2870,10 +3929,10 @@ dependencies = [ "foundry-evm-coverage", "foundry-evm-fuzz", "foundry-evm-traces", - "hashbrown 0.14.3", "parking_lot", "proptest", "revm", + "revm-inspectors", "thiserror", "tracing", ] @@ -2883,24 +3942,31 @@ name = "foundry-evm-core" version = "0.2.0" dependencies = [ "alloy-dyn-abi", + "alloy-genesis", "alloy-json-abi", "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", "alloy-sol-types", + "alloy-transport", + "arrayvec", + "auto_impl", "const-hex", "derive_more", - "ethers-core", - "ethers-providers", "eyre", "foundry-cheatcodes-spec", "foundry-common", "foundry-compilers", "foundry-config", "foundry-macros", + "foundry-test-utils", "futures", - "itertools 0.11.0", + "itertools 0.12.1", "once_cell", "parking_lot", "revm", + "revm-inspectors", + "rustc-hash", "serde", "serde_json", "thiserror", @@ -2919,7 +3985,8 @@ dependencies = [ "foundry-compilers", "foundry-evm-core", "revm", - "semver 1.0.20", + "rustc-hash", + "semver 1.0.22", "tracing", ] @@ -2930,8 +3997,6 @@ dependencies = [ "alloy-dyn-abi", "alloy-json-abi", "alloy-primitives", - "arbitrary", - "ethers-core", "eyre", "foundry-common", "foundry-compilers", @@ -2939,8 +4004,8 @@ dependencies = [ "foundry-evm-core", "foundry-evm-coverage", "foundry-evm-traces", - "hashbrown 0.14.3", - "itertools 0.11.0", + "indexmap", + "itertools 0.12.1", "parking_lot", "proptest", "rand 0.8.5", @@ -2959,7 +4024,6 @@ dependencies = [ "alloy-primitives", "alloy-sol-types", "const-hex", - "ethers-core", "eyre", "foundry-block-explorers", "foundry-common", @@ -2967,16 +4031,24 @@ dependencies = [ "foundry-config", "foundry-evm-core", "futures", - "hashbrown 0.14.3", - "itertools 0.11.0", + "itertools 0.12.1", "once_cell", - "ordered-float", - "revm", + "revm-inspectors", "serde", "tempfile", "tokio", "tracing", - "yansi 0.5.1", + "yansi 1.0.1", +] + +[[package]] +name = "foundry-linking" +version = "0.2.0" +dependencies = [ + "alloy-primitives", + "foundry-compilers", + "semver 1.0.22", + "thiserror", ] [[package]] @@ -2986,7 +4058,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -2994,16 +4066,17 @@ name = "foundry-test-utils" version = "0.2.0" dependencies = [ "alloy-primitives", - "ethers-core", - "ethers-providers", + "alloy-provider", + "alloy-rpc-types", "eyre", - "fd-lock 4.0.1", + "fd-lock 4.0.2", "foundry-common", "foundry-compilers", "foundry-config", "once_cell", "parking_lot", "pretty_assertions", + "rand 0.8.5", "regex", "serde_json", "tracing", @@ -3011,6 +4084,43 @@ dependencies = [ "walkdir", ] +[[package]] +name = "foundry-wallets" +version = "0.2.0" +dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "alloy-signer-aws", + "alloy-signer-ledger", + "alloy-signer-trezor", + "alloy-signer-wallet", + "alloy-sol-types", + "async-trait", + "aws-config", + "aws-sdk-kms", + "clap", + "const-hex", + "derive_builder", + "eyre", + "foundry-common", + "foundry-config", + "itertools 0.12.1", + "rpassword", + "serde", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + [[package]] name = "fs2" version = "0.4.3" @@ -3021,6 +4131,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "fs4" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dabded2e32cd57ded879041205c60a4a4c4bab47bd0fd2fa8b01f30849f02b" +dependencies = [ + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -3054,9 +4174,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -3069,9 +4189,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -3079,15 +4199,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -3096,9 +4216,19 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "futures-core", + "pin-project-lite", +] [[package]] name = "futures-locks" @@ -3112,32 +4242,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ "gloo-timers", "send_wrapper 0.4.0", @@ -3145,9 +4275,9 @@ dependencies = [ [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -3161,6 +4291,12 @@ dependencies = [ "slab", ] +[[package]] +name = "futures-utils-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + [[package]] name = "fxhash" version = "0.2.1" @@ -3194,9 +4330,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "js-sys", @@ -3211,19 +4347,6 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" -[[package]] -name = "git2" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf97ba92db08df386e10c8ede66a2a0369bd277090afd8710e19e38de9ec0cd" -dependencies = [ - "bitflags 2.4.1", - "libc", - "libgit2-sys", - "log", - "url", -] - [[package]] name = "gix-actor" version = "0.20.0" @@ -3266,7 +4389,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e874f41437441c02991dcea76990b9058fadfc54b02ab4dd06ab2218af43897" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "bstr", "gix-path", "libc", @@ -3312,7 +4435,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07c98204529ac3f24b34754540a852593d2a4c7349008df389240266627a72a" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "bstr", "gix-features", "gix-path", @@ -3397,7 +4520,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9615cbd6b456898aeb942cd75e5810c382fbfc48dbbff2fa23ebd2d33dcbe9c7" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "gix-path", "libc", "windows", @@ -3418,17 +4541,18 @@ dependencies = [ [[package]] name = "gix-trace" -version = "0.1.3" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b6d623a1152c3facb79067d6e2ecdae48130030cf27d6eb21109f13bd7b836" +checksum = "f924267408915fddcd558e3f37295cc7d6a3e50f8bd8b606cee0808c3915157e" [[package]] name = "gix-utils" -version = "0.1.5" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b85d89dc728613e26e0ed952a19583744e7f5240fcd4aa30d6c824ffd8b52f0f" +checksum = "35192df7fd0fa112263bad8021e2df7167df4cc2a6e6d15892e1e55621d3d4dc" dependencies = [ "fastrand", + "unicode-normalization", ] [[package]] @@ -3456,8 +4580,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.3", - "regex-syntax 0.8.2", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", ] [[package]] @@ -3485,16 +4609,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.22" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", "indexmap", "slab", "tokio", @@ -3504,15 +4628,19 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] [[package]] name = "handlebars" -version = "4.5.0" +version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa67bab9ff362228eb3d00bd024a4965d8231bbb7921167f0cfa66c6626b225" +checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" dependencies = [ "log", "pest", @@ -3522,37 +4650,13 @@ dependencies = [ "thiserror", ] -[[package]] -name = "hash-db" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" - -[[package]] -name = "hash256-std-hasher" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" -dependencies = [ - "crunchy", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.7", -] - [[package]] name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.6", + "ahash", "allocator-api2", "serde", ] @@ -3572,11 +4676,17 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -3605,16 +4715,6 @@ dependencies = [ "rusb", ] -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac", - "digest 0.9.0", -] - [[package]] name = "hmac" version = "0.12.1" @@ -3626,11 +4726,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3649,9 +4749,20 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -3660,20 +4771,43 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ "bytes", - "http", + "futures-core", + "http 1.1.0", + "http-body 1.0.0", "pin-project-lite", ] [[package]] name = "http-range-header" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +checksum = "3ce4ef31cda248bbdb6e6820603b82dfcd9e833db65a43e997a0ccec777d11fe" [[package]] name = "httparse" @@ -3693,24 +4827,34 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2", "tokio", "tower-service", "tracing", @@ -3718,18 +4862,23 @@ dependencies = [ ] [[package]] -name = "hyper-rustls" -version = "0.23.2" +name = "hyper" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ - "http", - "hyper", - "log", - "rustls 0.20.9", - "rustls-native-certs", + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", "tokio", - "tokio-rustls 0.23.4", + "want", ] [[package]] @@ -3739,31 +4888,73 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", - "hyper", - "rustls 0.21.9", + "http 0.2.12", + "hyper 0.14.28", + "log", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", ] +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.3.1", + "hyper-util", + "rustls 0.22.4", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tower-service", +] + [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", - "hyper", + "http-body-util", + "hyper 1.3.1", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.3.1", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.58" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -3782,6 +4973,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -3794,15 +4991,15 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" dependencies = [ "crossbeam-deque", "globset", "log", "memchr", - "regex-automata 0.4.3", + "regex-automata 0.4.6", "same-file", "walkdir", "winapi-util", @@ -3872,19 +5069,19 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown", ] [[package]] name = "indicatif" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" dependencies = [ "console", "instant", @@ -3895,9 +5092,9 @@ dependencies = [ [[package]] name = "indoc" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "inlinable_string" @@ -3943,6 +5140,33 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "interprocess" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f2533f3be42fffe3b5e63b71aeca416c1c3bc33e4e27be018521e76b1f38fb" +dependencies = [ + "blocking", + "cfg-if", + "futures-core", + "futures-io", + "intmap", + "libc", + "once_cell", + "rustc_version 0.4.0", + "spinning", + "thiserror", + "to_method", + "tokio", + "winapi", +] + +[[package]] +name = "intmap" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae52f28f45ac2bc96edb7714de995cffc174a395fb0abf5bff453587c980d7b9" + [[package]] name = "ipnet" version = "2.9.0" @@ -3951,13 +5175,13 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", - "rustix", - "windows-sys 0.48.0", + "libc", + "windows-sys 0.52.0", ] [[package]] @@ -3978,26 +5202,35 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -4019,46 +5252,60 @@ version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ - "base64 0.21.5", - "pem", + "base64 0.21.7", + "pem 1.1.1", "ring 0.16.20", "serde", "serde_json", "simple_asn1", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64 0.21.7", + "js-sys", + "pem 3.0.4", + "ring 0.17.8", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "k256" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", "once_cell", - "sha2 0.10.8", + "sha2", "signature", ] [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] [[package]] -name = "keccak-hasher" -version = "0.15.3" +name = "keccak-asm" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711adba9940a039f4374fc5724c0a5eaca84a2d558cce62256bfe26f0dbef05e" +checksum = "bb8515fff80ed850aea4a1595f2e519c003e2a00a82fe168ebf5269196caf444" dependencies = [ - "hash-db", - "hash256-std-hasher", - "tiny-keccak", + "digest 0.10.7", + "sha3-asm", ] [[package]] @@ -4083,31 +5330,33 @@ dependencies = [ [[package]] name = "lalrpop" -version = "0.20.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" dependencies = [ "ascii-canvas", "bit-set", - "diff", "ena", - "is-terminal", - "itertools 0.10.5", + "itertools 0.11.0", "lalrpop-util", "petgraph", "regex", - "regex-syntax 0.7.5", + "regex-syntax 0.8.3", "string_cache", "term", "tiny-keccak", "unicode-xid", + "walkdir", ] [[package]] name = "lalrpop-util" -version = "0.20.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +dependencies = [ + "regex-automata 0.4.6", +] [[package]] name = "lazy_static" @@ -4118,39 +5367,11 @@ dependencies = [ "spin 0.5.2", ] -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" -version = "0.2.150" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" - -[[package]] -name = "libgit2-sys" -version = "0.16.1+1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2a2bb3680b094add03bb3732ec520ece34da31a8cd2d633d1389d0f0fb60d0c" -dependencies = [ - "cc", - "libc", - "libz-sys", - "pkg-config", -] - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libm" @@ -4160,32 +5381,19 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "libc", - "redox_syscall 0.4.1", ] [[package]] name = "libusb1-sys" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d0e2afce4245f2c9a418511e5af8718bcaf2fa408aefb259504d1a9cb25f27" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libz-sys" -version = "1.1.12" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +checksum = "da050ade7ac4ff1ba5379af847a10a10a8e284181e060105bf8d86960ce9ce0f" dependencies = [ "cc", "libc", @@ -4195,15 +5403,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -4211,17 +5419,17 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "lru" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2994eeba8ed550fd9b47a0b38f0242bc3344e496483c6180b69139cc2fa5d1d7" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" dependencies = [ - "hashbrown 0.14.3", + "hashbrown", ] [[package]] @@ -4259,29 +5467,12 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "matchit" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" -[[package]] -name = "md-5" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "md-5" version = "0.10.6" @@ -4294,9 +5485,9 @@ dependencies = [ [[package]] name = "mdbook" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80992cb0e05f22cc052c99f8e883f1593b891014b96a8b4637fd274d7030c85e" +checksum = "0c33564061c3c640bed5ace7d6a2a1b65f2c64257d1ac930c15e94ed0fb561d3" dependencies = [ "ammonia", "anyhow", @@ -4310,7 +5501,6 @@ dependencies = [ "memchr", "once_cell", "opener", - "pathdiff", "pulldown-cmark", "regex", "serde", @@ -4323,9 +5513,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memmap2" @@ -4338,9 +5528,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.0" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" dependencies = [ "libc", ] @@ -4354,26 +5544,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memory-db" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6566c70c1016f525ced45d7b7f97730a2bafb037c788211d0c186ef5b2189f0a" -dependencies = [ - "hash-db", - "hashbrown 0.12.3", - "parity-util-mem", -] - [[package]] name = "miette" version = "5.10.0" @@ -4394,7 +5564,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -4421,18 +5591,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.9" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", @@ -4440,6 +5610,33 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mockall" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43766c2b5203b10de348ffe19f7e54564b64f3d6018ff7648d1e2d6d3a0f0a48" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cbce79ec385a1d4f54baa90a76401eb15d9cab93685f62e7e9f942aa00ae2" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -4460,9 +5657,9 @@ dependencies = [ [[package]] name = "new_debug_unreachable" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nibble_vec" @@ -4482,18 +5679,19 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset 0.7.1", + "memoffset", "pin-utils", ] [[package]] name = "nix" -version = "0.27.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "cfg-if", + "cfg_aliases", "libc", ] @@ -4515,11 +5713,11 @@ checksum = "f5438dd2b2ff4c6df6e1ce22d825ed2fa93ee2922235cc45186991717f0a892d" [[package]] name = "normpath" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec60c60a693226186f5d6edf073232bfb6464ed97eb22cf3b01c1e8198fd97f5" +checksum = "5831952a9476f2fed74b77d74182fa5ddc4d21c72ec45a333b250e3ed0272804" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -4552,9 +5750,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "3135b08af27d103b0a51f2ae0f8632117b7b185ccf931445affa8df530576a41" dependencies = [ "num-bigint", "num-complex", @@ -4577,28 +5775,43 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" dependencies = [ "autocfg", "num-integer", @@ -4619,9 +5832,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", "libm", @@ -4639,30 +5852,30 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate 2.0.0", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "num_threads" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ "libc", ] @@ -4673,20 +5886,33 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "nybbles" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95f06be0417d97f81fe4e5c86d7d01b392655a9cac9c19a848aa033e18937b23" +dependencies = [ + "alloy-rlp", + "const-hex", + "proptest", + "serde", + "smallvec", +] + [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" @@ -4694,12 +5920,6 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - [[package]] name = "open-fastrlp" version = "0.1.4" @@ -4738,11 +5958,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.60" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "cfg-if", "foreign-types", "libc", @@ -4759,7 +5979,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -4770,9 +5990,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.96" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -4787,13 +6007,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] -name = "ordered-float" -version = "4.1.1" +name = "outref" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "536900a8093134cf9ccf00a27deb3532421099e958d9dd431135d0c7543ca1e8" -dependencies = [ - "num-traits", -] +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" [[package]] name = "overload" @@ -4807,6 +6024,18 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parity-scale-codec" version = "3.6.9" @@ -4848,35 +6077,16 @@ dependencies = [ ] [[package]] -name = "parity-util-mem" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c32561d248d352148124f036cac253a644685a21dc9fea383eb4907d7bd35a8f" -dependencies = [ - "cfg-if", - "hashbrown 0.12.3", - "impl-trait-for-tuples", - "parity-util-mem-derive", - "parking_lot", - "winapi", -] - -[[package]] -name = "parity-util-mem-derive" -version = "0.1.0" +name = "parking" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" -dependencies = [ - "proc-macro2", - "syn 1.0.109", - "synstructure", -] +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -4884,15 +6094,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -4918,12 +6128,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - [[package]] name = "pbkdf2" version = "0.11.0" @@ -4931,9 +6135,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ "digest 0.10.7", - "hmac 0.12.1", + "hmac", "password-hash", - "sha2 0.10.8", + "sha2", ] [[package]] @@ -4943,38 +6147,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac 0.12.1", + "hmac", ] [[package]] name = "pear" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a386cd715229d399604b50d1361683fe687066f42d56f54be995bc6868f71c" +checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" dependencies = [ "inlinable_string", "pear_codegen", - "yansi 1.0.0-rc.1", + "yansi 1.0.1", ] [[package]] name = "pear_codegen" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f0f13dac8069c139e8300a6510e3f4143ecf5259c60b116a9b271b4ca0d54" +checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.39", + "syn 2.0.60", ] -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "pem" version = "1.1.1" @@ -4984,6 +6182,25 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.0", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -4992,9 +6209,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.5" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" +checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95" dependencies = [ "memchr", "thiserror", @@ -5003,9 +6220,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.5" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" +checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c" dependencies = [ "pest", "pest_generator", @@ -5013,26 +6230,26 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.5" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" +checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "pest_meta" -version = "2.7.5" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" +checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca" dependencies = [ "once_cell", "pest", - "sha2 0.10.8", + "sha2", ] [[package]] @@ -5124,7 +6341,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -5147,29 +6364,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -5177,6 +6394,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -5189,9 +6417,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plotters" @@ -5223,9 +6451,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" [[package]] name = "powerfmt" @@ -5237,13 +6465,39 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "predicates" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" [[package]] -name = "precomputed-hash" -version = "0.1.1" +name = "predicates-tree" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] [[package]] name = "pretty_assertions" @@ -5257,12 +6511,21 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.15" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +checksum = "5ac2cf0f2e4f42b49f5ffd07dae8d746508ef7526c13940e5f524012ae6c6550" dependencies = [ "proc-macro2", - "syn 2.0.39", + "syn 2.0.60", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", ] [[package]] @@ -5298,6 +6561,15 @@ dependencies = [ "toml_edit 0.20.7", ] +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -5324,9 +6596,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -5339,9 +6611,9 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", "version_check", - "yansi 1.0.0-rc.1", + "yansi 1.0.1", ] [[package]] @@ -5363,13 +6635,13 @@ checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.4.1", + "bitflags 2.5.0", "lazy_static", "num-traits", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", "rusty-fork", "tempfile", "unarray", @@ -5388,9 +6660,9 @@ dependencies = [ [[package]] name = "protobuf" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55bad9126f378a853655831eb7363b7b01b81d19f8cb1218861086ca4a1a61e" +checksum = "b65f4a8ec18723a734e5dc09c173e0abf9690432da5340285d536edcb4dac190" dependencies = [ "once_cell", "protobuf-support", @@ -5399,24 +6671,31 @@ dependencies = [ [[package]] name = "protobuf-support" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d4d7b8601c814cfb36bcebb79f0e61e45e1e93640cf778837833bbed05c372" +checksum = "6872f4d4f4b98303239a2b5838f5bbbb77b01ffc892d627957f37a22d7cfe69c" dependencies = [ "thiserror", ] [[package]] name = "pulldown-cmark" -version = "0.9.3" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" +checksum = "76979bea66e7875e7509c4ec5300112b316af87fa7a252ca91c448b32dfe3993" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "memchr", + "pulldown-cmark-escape", "unicase", ] +[[package]] +name = "pulldown-cmark-escape" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd348ff538bc9caeda7ee8cad2d1d48236a1f443c1fa3913c6a02fe0043b1dd3" + [[package]] name = "quick-error" version = "1.2.3" @@ -5425,9 +6704,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -5507,7 +6786,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.14", ] [[package]] @@ -5530,17 +6809,19 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.24.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ebc917cfb527a566c37ecb94c7e3fd098353516fb4eb6bea17015ade0182425" +checksum = "a564a852040e82671dc50a37d88f3aa83bbc690dfc6844cfe7a2591620206a80" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "cassowary", + "compact_str", "crossterm", "indoc", - "itertools 0.11.0", + "itertools 0.12.1", "lru", "paste", + "stability", "strum", "unicode-segmentation", "unicode-width", @@ -5548,9 +6829,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -5558,9 +6839,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -5568,57 +6849,43 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", ] [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.14", "libredox", "thiserror", ] -[[package]] -name = "reference-trie" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f63dfce83d1e0e80cf2dc5222df5c2bc30992b30c44d1e66e7c9793d3a418e4" -dependencies = [ - "hash-db", - "hash256-std-hasher", - "keccak-hasher", - "parity-scale-codec", - "trie-db", - "trie-root", -] - [[package]] name = "regex" -version = "1.10.2" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.3", - "regex-syntax 0.8.2", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", ] [[package]] @@ -5632,50 +6899,92 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] -name = "regex-syntax" -version = "0.6.29" +name = "regex-lite" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e" [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.11.22" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", - "http", - "http-body", - "hyper", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", "hyper-rustls 0.24.2", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tokio-rustls 0.24.1", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.25.4", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +dependencies = [ + "base64 0.22.0", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", + "hyper-rustls 0.26.0", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", @@ -5684,41 +6993,61 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.9", - "rustls-native-certs", - "rustls-pemfile", + "rustls 0.22.4", + "rustls-native-certs 0.7.0", + "rustls-pemfile 2.1.2", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "system-configuration", + "sync_wrapper 0.1.2", "tokio", "tokio-native-tls", - "tokio-rustls 0.24.1", + "tokio-rustls 0.25.0", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", - "winreg", + "webpki-roots 0.26.1", + "winreg 0.52.0", ] [[package]] name = "revm" -version = "3.5.0" -source = "git+https://github.com/jxom/revm?branch=jxom/eip-3074#1ddd520c938bb50ddf4f8493a640f73b0924691c" +version = "8.0.0" +source = "git+https://github.com/wevm/revm?branch=jxom/eip-3074-v8#66a499fc281b493a34ee42069309cf7aed363052" dependencies = [ "auto_impl", + "cfg-if", + "dyn-clone", "revm-interpreter", "revm-precompile", "serde", "serde_json", ] +[[package]] +name = "revm-inspectors" +version = "0.1.0" +source = "git+https://github.com/jxom/evm-inspectors?branch=jxom/eip-3074#8cbdfe5e78381c4ac8d7e7fc050a11d2b5bddabb" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types", + "alloy-rpc-types-trace", + "alloy-sol-types", + "anstyle", + "colorchoice", + "revm", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "revm-interpreter" -version = "1.3.0" -source = "git+https://github.com/jxom/revm?branch=jxom/eip-3074#1ddd520c938bb50ddf4f8493a640f73b0924691c" +version = "4.0.0" +source = "git+https://github.com/wevm/revm?branch=jxom/eip-3074-v8#66a499fc281b493a34ee42069309cf7aed363052" dependencies = [ "revm-primitives", "serde", @@ -5726,33 +7055,36 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "2.2.0" -source = "git+https://github.com/jxom/revm?branch=jxom/eip-3074#1ddd520c938bb50ddf4f8493a640f73b0924691c" +version = "6.0.0" +source = "git+https://github.com/wevm/revm?branch=jxom/eip-3074-v8#66a499fc281b493a34ee42069309cf7aed363052" dependencies = [ "aurora-engine-modexp", "c-kzg", "once_cell", "revm-primitives", "ripemd", - "sha2 0.10.8", + "sha2", "substrate-bn", ] [[package]] name = "revm-primitives" -version = "1.3.0" -source = "git+https://github.com/jxom/revm?branch=jxom/eip-3074#1ddd520c938bb50ddf4f8493a640f73b0924691c" +version = "3.1.1" +source = "git+https://github.com/wevm/revm?branch=jxom/eip-3074-v8#66a499fc281b493a34ee42069309cf7aed363052" dependencies = [ "alloy-primitives", - "alloy-rlp", "auto_impl", - "bitflags 2.4.1", + "bitflags 2.5.0", "bitvec", "c-kzg", + "cfg-if", + "derive_more", + "dyn-clone", "enumn", - "hashbrown 0.14.3", + "hashbrown", "hex", "k256", + "once_cell", "secp256k1", "serde", ] @@ -5763,7 +7095,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac 0.12.1", + "hmac", "subtle", ] @@ -5784,16 +7116,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.6" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", - "getrandom 0.2.11", + "cfg-if", + "getrandom 0.2.14", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -5850,9 +7183,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.11.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608a5726529f2f0ef81b8fde9873c4bb829d6b5b5ca6be4d97345ddf0749c825" +checksum = "8f308135fef9fc398342da5472ce7c484529df23743fb7c734e0f3d472971e62" dependencies = [ "alloy-rlp", "arbitrary", @@ -5875,103 +7208,20 @@ dependencies = [ [[package]] name = "ruint-macro" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" +checksum = "f86854cf50259291520509879a5c294c3c9a4c334e9ff65071c51e42ef1e2343" [[package]] name = "rusb" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45fff149b6033f25e825cbb7b2c625a11ee8e6dac09264d49beb125e39aa97bf" +checksum = "ab9f9ff05b63a786553a4c02943b74b34a988448671001e9a27e2f0565cc05a4" dependencies = [ "libc", "libusb1-sys", ] -[[package]] -name = "rusoto_core" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1db30db44ea73551326269adcf7a2169428a054f14faf9e1768f2163494f2fa2" -dependencies = [ - "async-trait", - "base64 0.13.1", - "bytes", - "crc32fast", - "futures", - "http", - "hyper", - "hyper-rustls 0.23.2", - "lazy_static", - "log", - "rusoto_credential", - "rusoto_signature", - "rustc_version 0.4.0", - "serde", - "serde_json", - "tokio", - "xml-rs", -] - -[[package]] -name = "rusoto_credential" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee0a6c13db5aad6047b6a44ef023dbbc21a056b6dab5be3b79ce4283d5c02d05" -dependencies = [ - "async-trait", - "chrono", - "dirs-next", - "futures", - "hyper", - "serde", - "serde_json", - "shlex", - "tokio", - "zeroize", -] - -[[package]] -name = "rusoto_kms" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1fc19cfcfd9f6b2f96e36d5b0dddda9004d2cbfc2d17543e3b9f10cc38fce8" -dependencies = [ - "async-trait", - "bytes", - "futures", - "rusoto_core", - "serde", - "serde_json", -] - -[[package]] -name = "rusoto_signature" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ae95491c8b4847931e291b151127eccd6ff8ca13f33603eb3d0035ecb05272" -dependencies = [ - "base64 0.13.1", - "bytes", - "chrono", - "digest 0.9.0", - "futures", - "hex", - "hmac 0.11.0", - "http", - "hyper", - "log", - "md-5 0.9.1", - "percent-encoding", - "pin-project-lite", - "rusoto_credential", - "rustc_version 0.4.0", - "serde", - "sha2 0.9.9", - "tokio", -] - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -6005,16 +7255,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.20", + "semver 1.0.22", ] [[package]] name = "rustix" -version = "0.38.26" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -6023,26 +7273,28 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.9" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.16.20", + "ring 0.17.8", + "rustls-webpki 0.101.7", "sct", - "webpki", ] [[package]] name = "rustls" -version = "0.21.9" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", - "ring 0.17.6", - "rustls-webpki", - "sct", + "ring 0.17.8", + "rustls-pki-types", + "rustls-webpki 0.102.3", + "subtle", + "zeroize", ] [[package]] @@ -6052,7 +7304,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.2", + "rustls-pki-types", "schannel", "security-framework", ] @@ -6063,24 +7328,51 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.0", + "rustls-pki-types", ] +[[package]] +name = "rustls-pki-types" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" + [[package]] name = "rustls-webpki" version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.6", + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", "untrusted 0.9.0", ] [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "rusty-fork" @@ -6100,7 +7392,7 @@ version = "12.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "994eca4bca05c87e86e15d90fc7a91d1be64b4482b38cb2d27474568fe7c9db9" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "cfg-if", "clipboard-win", "fd-lock 3.0.13", @@ -6119,9 +7411,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "salsa20" @@ -6143,9 +7435,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.10.0" +version = "2.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7d66a1128282b7ef025a8ead62a4a9fcf017382ec53b8ffbf4d7bf77bd3c60" +checksum = "7c453e59a955f81fb62ee5d596b450383d699f152d350e9d23a0db2adb78e4c0" dependencies = [ "cfg-if", "derive_more", @@ -6155,9 +7447,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.10.0" +version = "2.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf2c68b89cafb3b8d918dd07b42be0da66ff202cf1155c5739a4e0c1ea0dc19" +checksum = "18cf6c6447f813ef19eb450e985bcce6705f9ce7660db221b59093d15c79c4b7" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", @@ -6165,13 +7457,22 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "scc" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96560eea317a9cc4e0bb1f6a2c93c09a19b8c4fc5cb3fcc0ec1c094cd783e2" +dependencies = [ + "sdd", +] + [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -6210,10 +7511,10 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" dependencies = [ - "hmac 0.12.1", + "hmac", "pbkdf2 0.11.0", "salsa20", - "sha2 0.10.8", + "sha2", ] [[package]] @@ -6222,10 +7523,16 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.6", + "ring 0.17.8", "untrusted 0.9.0", ] +[[package]] +name = "sdd" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d" + [[package]] name = "sec1" version = "0.7.3" @@ -6242,27 +7549,27 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.28.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acea373acb8c21ecb5a23741452acd2593ed44ee3d343e72baaa143bc89d0d5" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" dependencies = [ "secp256k1-sys", ] [[package]] name = "secp256k1-sys" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e67c467c38fd24bd5499dc9a18183b31575c12ee549197e3e20d57aa4fe3b7" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" dependencies = [ "cc", ] [[package]] name = "security-framework" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -6273,9 +7580,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", @@ -6292,9 +7599,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" dependencies = [ "serde", ] @@ -6322,22 +7629,22 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -6353,9 +7660,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "indexmap", "itoa", @@ -6365,9 +7672,9 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" dependencies = [ "itoa", "serde", @@ -6385,20 +7692,20 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.17" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "serde_spanned" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] @@ -6417,27 +7724,27 @@ dependencies = [ [[package]] name = "serial_test" -version = "2.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" +checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" dependencies = [ - "dashmap", "futures", - "lazy_static", "log", + "once_cell", "parking_lot", + "scc", "serial_test_derive", ] [[package]] name = "serial_test_derive" -version = "2.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" +checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -6457,19 +7764,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha2" version = "0.10.8" @@ -6491,6 +7785,16 @@ dependencies = [ "keccak", ] +[[package]] +name = "sha3-asm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac61da6b35ad76b195eb4771210f947734321a8d81d7738e1580d953bc7a15e" +dependencies = [ + "cc", + "cfg-if", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -6508,9 +7812,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "shlex" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" @@ -6535,9 +7839,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -6554,9 +7858,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] name = "simple_asn1" @@ -6587,37 +7891,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" - -[[package]] -name = "smol_str" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" -dependencies = [ - "serde", -] - -[[package]] -name = "socket2" -version = "0.4.10" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -6646,6 +7931,15 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spinning" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d4f0e86297cad2658d92a707320d87bf4e6ae1050287f51d19b67ef3f153a7b" +dependencies = [ + "lock_api", +] + [[package]] name = "spki" version = "0.7.3" @@ -6656,6 +7950,16 @@ dependencies = [ "der", ] +[[package]] +name = "stability" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a" +dependencies = [ + "quote", + "syn 2.0.60", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -6700,26 +8004,32 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.25.3" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -6737,41 +8047,61 @@ dependencies = [ [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "svm-rs" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20689c7d03b6461b502d0b95d6c24874c7d24dea2688af80486a130a06af3b07" +checksum = "11297baafe5fa0c99d5722458eac6a5e25c01eb1b8e5cd137f54079093daa7a4" dependencies = [ "dirs 5.0.1", "fs2", "hex", "once_cell", - "reqwest", - "semver 1.0.20", + "reqwest 0.11.27", + "semver 1.0.22", + "serde", + "serde_json", + "sha2", + "thiserror", + "url", + "zip 0.6.6", +] + +[[package]] +name = "svm-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c7a55b859b986daa02c731cd07758d84b1db852665e45c5cfa6698c41d17cb" +dependencies = [ + "const-hex", + "dirs 5.0.1", + "fs4", + "once_cell", + "reqwest 0.12.4", + "semver 1.0.22", "serde", "serde_json", - "sha2 0.10.8", + "sha2", "thiserror", "url", - "zip", + "zip 1.1.1", ] [[package]] name = "svm-rs-builds" -version = "0.2.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa64b5e8eecd3a8af7cfc311e29db31a268a62d5953233d3e8243ec77a71c4e3" +checksum = "f1bb4806c96207e7cde40fc238f9a1d570f3090f850a742e1e96665440615a31" dependencies = [ "build_const", - "hex", - "semver 1.0.20", + "const-hex", + "semver 1.0.22", "serde_json", - "svm-rs", + "svm-rs 0.5.2", ] [[package]] @@ -6787,9 +8117,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -6798,14 +8128,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.5.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2c7ad08db24862d5b787a94714ff6b047935c3e3f60af944ac969404debd7ff" +checksum = "70aba06097b6eda3c15f6eebab8a6339e121475bcf08bbe6758807e716c372a1" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -6815,16 +8145,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] -name = "synstructure" -version = "0.12.6" +name = "sync_wrapper" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "unicode-xid", -] +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" [[package]] name = "system-configuration" @@ -6855,15 +8179,14 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.8.1" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.4.1", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -6888,15 +8211,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "termcolor" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" -dependencies = [ - "winapi-util", -] - [[package]] name = "terminal_size" version = "0.3.0" @@ -6920,31 +8234,37 @@ dependencies = [ "phf_codegen 0.11.2", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -6959,15 +8279,36 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "tikv-jemalloc-sys" +version = "0.5.4+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9402443cb8fd499b6f327e40565234ff34dbda27460c5b47db0db77443dd85d1" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965fe0c26be5c56c94e38ba547249074803efd52adfb66de62107d95aab3eaca" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + [[package]] name = "time" -version = "0.3.30" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", "libc", + "num-conv", "num_threads", "powerfmt", "serde", @@ -6983,10 +8324,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] @@ -7024,11 +8366,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "to_method" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" + [[package]] name = "tokio" -version = "1.34.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -7038,7 +8386,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2", "tokio-macros", "windows-sys 0.48.0", ] @@ -7051,7 +8399,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -7066,34 +8414,35 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.4" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.20.9", + "rustls 0.21.12", "tokio", - "webpki", ] [[package]] name = "tokio-rustls" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.21.9", + "rustls 0.22.4", + "rustls-pki-types", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] @@ -7104,13 +8453,23 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", - "native-tls", - "rustls 0.21.9", + "rustls 0.21.12", "tokio", - "tokio-native-tls", "tokio-rustls 0.24.1", - "tungstenite", - "webpki-roots", + "tungstenite 0.20.1", + "webpki-roots 0.25.4", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.21.0", ] [[package]] @@ -7121,6 +8480,7 @@ checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", @@ -7138,15 +8498,15 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.8" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.21.0", + "toml_edit 0.22.12", ] [[package]] @@ -7166,7 +8526,7 @@ checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] @@ -7177,20 +8537,31 @@ checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ "indexmap", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.7", ] [[package]] @@ -7217,16 +8588,16 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "bytes", - "futures-core", "futures-util", - "http", - "http-body", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", "http-range-header", "httpdate", "mime", @@ -7272,7 +8643,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -7336,13 +8707,12 @@ dependencies = [ [[package]] name = "trezor-client" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cddb76a030b141d9639470eca2a236f3057a651bba78227cfa77830037a8286" +checksum = "f62c95b37f6c769bd65a0d0beb8b2b003e72998003b896a616a6777c645c05ed" dependencies = [ "byteorder", "hex", - "primitive-types", "protobuf", "rusb", "thiserror", @@ -7350,58 +8720,44 @@ dependencies = [ ] [[package]] -name = "trie-db" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32d034c0d3db64b43c31de38e945f15b40cd4ca6d2dcfc26d4798ce8de4ab83" -dependencies = [ - "hash-db", - "hashbrown 0.12.3", - "log", - "rustc-hex", - "smallvec", -] - -[[package]] -name = "trie-root" -version = "0.17.0" +name = "try-lock" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a36c5ca3911ed3c9a5416ee6c679042064b93fc637ded67e25f92e68d783891" -dependencies = [ - "hash-db", -] +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "triehash" -version = "0.8.4" +name = "tungstenite" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1631b201eb031b563d2e85ca18ec8092508e262a3196ce9bd10a67ec87b9f5c" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ - "hash-db", - "rlp", + "byteorder", + "bytes", + "data-encoding", + "http 0.2.12", + "httparse", + "log", + "rand 0.8.5", + "rustls 0.21.12", + "sha1", + "thiserror", + "url", + "utf-8", ] -[[package]] -name = "try-lock" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" - [[package]] name = "tungstenite" -version = "0.20.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 1.1.0", "httparse", "log", - "native-tls", "rand 0.8.5", - "rustls 0.21.9", "sha1", "thiserror", "url", @@ -7441,9 +8797,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "uncased" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" dependencies = [ "version_check", ] @@ -7459,9 +8815,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-bom" @@ -7477,24 +8833,24 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "unicode-xid" @@ -7525,6 +8881,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -7543,10 +8905,16 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.14", "serde", ] +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" + [[package]] name = "valuable" version = "0.1.0" @@ -7561,12 +8929,12 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "8.2.6" +version = "8.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1290fd64cc4e7d3c9b07d7f333ce0ce0007253e32870e632624835cc80b83939" +checksum = "e27d6bdd219887a9eadd19e1c34f32e47fa332301184935c6d9bca26f3cca525" dependencies = [ "anyhow", - "git2", + "cfg-if", "rustversion", "time", ] @@ -7577,6 +8945,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -7588,9 +8962,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -7619,9 +8993,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -7629,24 +9003,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.39" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -7656,9 +9030,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7666,22 +9040,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "watchexec" @@ -7733,29 +9107,28 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "webpki" -version = "0.22.4" +name = "webpki-roots" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" -dependencies = [ - "ring 0.17.6", - "untrusted 0.9.0", -] +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.25.3" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "which" @@ -7787,11 +9160,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -7811,11 +9184,11 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -7842,7 +9215,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.5", ] [[package]] @@ -7877,17 +9250,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -7904,9 +9278,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -7922,9 +9296,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -7940,9 +9314,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -7958,9 +9338,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -7976,9 +9356,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -7994,9 +9374,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -8012,15 +9392,24 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winnow" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] [[package]] name = "winnow" -version = "0.5.19" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" dependencies = [ "memchr", ] @@ -8035,6 +9424,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "ws_stream_wasm" version = "0.7.4" @@ -8064,10 +9463,10 @@ dependencies = [ ] [[package]] -name = "xml-rs" -version = "0.8.19" +name = "xmlparser" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" [[package]] name = "yansi" @@ -8077,28 +9476,31 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "yansi" -version = "1.0.0-rc.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +dependencies = [ + "is-terminal", +] [[package]] name = "zerocopy" -version = "0.7.28" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d6f15f7ade05d2a4935e34a457b936c23dc70a05cc1d97133dc99e7a3fe0f0e" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.28" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbbad221e3f78500350ecbd7dfa4e63ef945c05f4c61cb7f4d3f84cd0bba649b" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -8118,7 +9520,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.60", ] [[package]] @@ -8134,13 +9536,26 @@ dependencies = [ "crc32fast", "crossbeam-utils", "flate2", - "hmac 0.12.1", + "hmac", "pbkdf2 0.11.0", "sha1", "time", "zstd", ] +[[package]] +name = "zip" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2655979068a1f8fa91cb9e8e5b9d3ee54d18e0ddc358f2f4a395afc0929a84b" +dependencies = [ + "arbitrary", + "byteorder", + "crc32fast", + "crossbeam-utils", + "flate2", +] + [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" @@ -8162,9 +9577,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 38ff559aeb13e..a1f972b14b3e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,27 +28,33 @@ resolver = "2" [workspace.package] version = "0.2.0" edition = "2021" -rust-version = "1.74" # Remember to update clippy.toml as well +# Remember to update clippy.toml as well +rust-version = "1.76" authors = ["Foundry Contributors"] license = "MIT OR Apache-2.0" homepage = "https://github.com/foundry-rs/foundry" repository = "https://github.com/foundry-rs/foundry" exclude = ["benches/", "tests/", "test-data/", "testdata/"] +# Speed up compilation time for dev builds by reducing emitted debug info. +# NOTE: Debuggers may provide less useful information with this setting. +# Uncomment this section if you're using a debugger. [profile.dev] -# Disabling debug info speeds up builds a bunch, -# and we don't rely on it for debugging that much -debug = 0 +debug = 1 -# Speed up tests and dev build +# Speed up tests and dev build. [profile.dev.package] -# solc +# Solc and artifacts foundry-compilers.opt-level = 3 solang-parser.opt-level = 3 +lalrpop-util.opt-level = 3 serde_json.opt-level = 3 -# evm +# EVM +alloy-dyn-abi.opt-level = 3 +alloy-json-abi.opt-level = 3 alloy-primitives.opt-level = 3 +alloy-sol-type-parser.opt-level = 3 alloy-sol-types.opt-level = 3 hashbrown.opt-level = 3 keccak.opt-level = 3 @@ -60,18 +66,23 @@ ruint.opt-level = 3 sha2.opt-level = 3 sha3.opt-level = 3 tiny-keccak.opt-level = 3 +bitvec.opt-level = 3 -# keystores -scrypt.opt-level = 3 +# fuzzing +proptest.opt-level = 3 +foundry-evm-fuzz.opt-level = 3 # forking axum.opt-level = 3 -# Local "release" mode, more optimized than dev but much faster to compile than release +# keystores +scrypt.opt-level = 3 + +# Local "release" mode, more optimized than dev but much faster to compile than release. [profile.local] inherits = "dev" opt-level = 1 -strip = true +strip = "debuginfo" panic = "abort" codegen-units = 16 @@ -83,21 +94,19 @@ strip = "none" panic = "unwind" incremental = false -# Optimized release profile +# Optimized release profile. [profile.release] opt-level = 3 +debug = "line-tables-only" lto = "fat" -strip = true +strip = "debuginfo" panic = "abort" codegen-units = 1 -# Override packages which aren't perf-sensitive for faster compilation speed +# Override packages which aren't perf-sensitive for faster compilation speed. [profile.release.package] mdbook.opt-level = 1 protobuf.opt-level = 1 -rusoto_core.opt-level = 1 -rusoto_credential.opt-level = 1 -rusoto_kms.opt-level = 1 toml_edit.opt-level = 1 trezor-client.opt-level = 1 @@ -109,6 +118,8 @@ forge = { path = "crates/forge" } forge-doc = { path = "crates/doc" } forge-fmt = { path = "crates/fmt" } +forge-verify = { path = "crates/verify" } +forge-script = { path = "crates/script" } foundry-cheatcodes = { path = "crates/cheatcodes" } foundry-cheatcodes-spec = { path = "crates/cheatcodes/spec" } foundry-cli = { path = "crates/cli" } @@ -122,82 +133,95 @@ foundry-evm-fuzz = { path = "crates/evm/fuzz" } foundry-evm-traces = { path = "crates/evm/traces" } foundry-macros = { path = "crates/macros" } foundry-test-utils = { path = "crates/test-utils" } +foundry-wallets = { path = "crates/wallets" } +foundry-linking = { path = "crates/linking" } -foundry-block-explorers = { version = "0.1.1", default-features = false } -foundry-compilers = { version = "0.1.1", default-features = false } +# solc & compilation utilities +foundry-block-explorers = { version = "0.2.6", default-features = false } +foundry-compilers = { version = "0.3.18", default-features = false } ## revm # no default features to avoid c-kzg -revm = { version = "3", default-features = false } -revm-primitives = { version = "1", default-features = false } +revm = { git = "https://github.com/wevm/revm", branch = "jxom/eip-3074-v8", default-features = false } +revm-primitives = { git = "https://github.com/wevm/revm", branch = "jxom/eip-3074-v8", default-features = false } +revm-inspectors = { git = "https://github.com/jxom/evm-inspectors", branch = "jxom/eip-3074", features = [ + "serde", +] } ## ethers -ethers = { version = "2.0", default-features = false } -ethers-core = { version = "2.0", default-features = false } -ethers-contract = { version = "2.0", default-features = false } -ethers-contract-abigen = { version = "2.0", default-features = false } -ethers-providers = { version = "2.0", default-features = false } -ethers-signers = { version = "2.0", default-features = false } -ethers-middleware = { version = "2.0", default-features = false } -ethers-solc = { version = "2.0", default-features = false } +ethers = { version = "2.0.14", default-features = false } +ethers-core = { version = "2.0.14", default-features = false } +ethers-contract = { version = "2.0.14", default-features = false } +ethers-contract-abigen = { version = "2.0.14", default-features = false } +ethers-providers = { version = "2.0.14", default-features = false } +ethers-signers = { version = "2.0.14", default-features = false } ## alloy -alloy-primitives = "0.5.0" -alloy-dyn-abi = "0.5.0" -alloy-json-abi = "0.5.0" -alloy-sol-types = "0.5.0" -syn-solidity = "0.5.0" - -alloy-chains = "0.1.4" +alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-signer-wallet = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-signer-aws = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-signer-ledger = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-signer-trezor = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "ca54552", default-features = false } +alloy-primitives = { version = "0.7.1", features = ["getrandom"] } +alloy-dyn-abi = "0.7.1" +alloy-json-abi = "0.7.1" +alloy-sol-types = "0.7.1" +syn-solidity = "0.7.1" +alloy-chains = "0.1" +alloy-trie = "0.3.1" alloy-rlp = "0.3.3" solang-parser = "=0.3.3" ## misc -chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } +arrayvec = "0.7" +base64 = "0.22" +chrono = { version = "0.4", default-features = false, features = [ + "clock", + "std", +] } color-eyre = "0.6" derive_more = "0.99" +evm-disassembler = "0.5" eyre = "0.6" hex = { package = "const-hex", version = "1.6", features = ["hex"] } -itertools = "0.11" +itertools = "0.12" jsonpath_lib = "0.3" +k256 = "0.13" pretty_assertions = "1.4" -protobuf = "=3.2.0" rand = "0.8" +rustc-hash = "1.1" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["arbitrary_precision"] } +strum = "0.26" toml = "0.8" tracing = "0.1" tracing-subscriber = "0.3" - -axum = "0.6" -hyper = "0.14" +vergen = { version = "8", default-features = false } +indexmap = "2.2" +tikv-jemallocator = "0.5.4" +num-format = "0.4.4" +yansi = { version = "1.0", features = ["detect-tty", "detect-env"] } +tempfile = "3.10" + +axum = "0.7" +hyper = "1.0" +reqwest = { version = "0.12", default-features = false } tower = "0.4" -tower-http = "0.4" - -#[patch."https://github.com/gakonst/ethers-rs"] -#ethers = { path = "../ethers-rs/ethers" } -#ethers-addressbook = { path = "../ethers-rs/ethers-addressbook" } -#ethers-contract = { path = "../ethers-rs/ethers-contract" } -#ethers-contract-abigen = { path = "../ethers-rs/ethers-contract/ethers-contract-abigen" } -#ethers-core = { path = "../ethers-rs/ethers-core" } -#ethers-etherscan = { path = "../ethers-rs/ethers-etherscan" } -#ethers-middleware = { path = "../ethers-rs/ethers-middleware" } -#ethers-providers = { path = "../ethers-rs/ethers-providers" } -#ethers-signers = { path = "../ethers-rs/ethers-signers" } -#ethers-solc = { path = "../ethers-rs/ethers-solc" } - -[patch.crates-io] -ethers = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } -ethers-core = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } -ethers-contract = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } -ethers-contract-abigen = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } -ethers-providers = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } -ethers-signers = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } -ethers-middleware = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } -ethers-solc = { git = "https://github.com/clabby/ethers-rs", branch = "cl/call-type-3074" } - -revm = { git = "https://github.com/jxom/revm", branch = "jxom/eip-3074" } -revm-interpreter = { git = "https://github.com/jxom/revm", branch = "jxom/eip-3074" } -revm-precompile = { git = "https://github.com/jxom/revm", branch = "jxom/eip-3074" } -revm-primitives = { git = "https://github.com/jxom/revm", branch = "jxom/eip-3074" } - +tower-http = "0.5" \ No newline at end of file diff --git a/FUNDING.json b/FUNDING.json new file mode 100644 index 0000000000000..52baedf3bfc3c --- /dev/null +++ b/FUNDING.json @@ -0,0 +1,7 @@ +{ + "drips": { + "ethereum": { + "ownedBy": "0x86308c59a6005d012C51Eef104bBc21786aC5D2E" + } + } +} diff --git a/README.md b/README.md index 8f205f13d9222..b6f4f8640a1ab 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,11 @@ [tg-support-badge]: https://img.shields.io/endpoint?color=neon&logo=telegram&label=support&style=flat-square&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Ffoundry_support [tg-support-url]: https://t.me/foundry_support +**[Install](https://book.getfoundry.sh/getting-started/installation)** +| [User Book](https://book.getfoundry.sh) +| [Developer Docs](./docs/dev/) +| [Crate Docs](https://foundry-rs.github.io/foundry) + **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** Foundry consists of: diff --git a/clippy.toml b/clippy.toml index 866add684a962..09acb653d14ac 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1,4 @@ -msrv = "1.74" +msrv = "1.76" +# bytes::Bytes is included by default and alloy_primitives::Bytes is a wrapper around it, +# so it is safe to ignore it as well +ignore-interior-mutability = ["bytes::Bytes", "alloy_primitives::Bytes"] diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index dbced4c69d71f..7739e8fa2f3f0 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -16,26 +16,49 @@ path = "src/anvil.rs" required-features = ["cli"] [build-dependencies] -vergen = { version = "8", default-features = false, features = ["build", "git", "git2"] } +vergen = { workspace = true, default-features = false, features = [ + "build", + "git", + "gitcl", +] } [dependencies] # foundry internal -anvil-core = { path = "core", features = ["fastrlp", "serde", "impersonated-tx"] } +anvil-core = { path = "core", features = ["serde", "impersonated-tx"] } anvil-rpc = { path = "rpc" } anvil-server = { path = "server" } +foundry-cli.workspace = true foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true # evm support bytes = "1.4.0" -# needed as documented in https://github.com/foundry-rs/foundry/pull/6358 -k256 = "=0.13.1" +k256.workspace = true ethers = { workspace = true, features = ["rustls", "ws", "ipc", "optimism"] } -trie-db = "0.23" -hash-db = "0.15" -memory-db = "0.29" +revm = { workspace = true, features = [ + "std", + "serde", + "memory_limit", + "c-kzg", +] } alloy-primitives = { workspace = true, features = ["serde"] } +alloy-consensus = { workspace = true, features = ["k256", "kzg"] } +alloy-contract = { workspace = true, features = ["pubsub"] } +alloy-network.workspace = true +alloy-eips.workspace = true +alloy-rlp.workspace = true +alloy-signer = { workspace = true, features = ["eip712"] } +alloy-signer-wallet = { workspace = true, features = ["mnemonic"] } +alloy-sol-types = { workspace = true, features = ["std"] } +alloy-dyn-abi = { workspace = true, features = ["std", "eip712"] } +alloy-rpc-types.workspace = true +alloy-rpc-types-trace.workspace = true +alloy-provider = { workspace = true, features = ["reqwest", "ws", "ipc"] } +alloy-transport.workspace = true +alloy-chains.workspace = true +alloy-genesis.workspace = true +alloy-trie.workspace = true # axum related axum.workspace = true @@ -58,13 +81,18 @@ serde_repr = "0.1" serde_json.workspace = true serde.workspace = true thiserror = "1" -yansi = "0.5" -tempfile = "3" +yansi.workspace = true +tempfile.workspace = true itertools.workspace = true rand = "0.8" +eyre.workspace = true # cli -clap = { version = "4", features = ["derive", "env", "wrap_help"], optional = true } +clap = { version = "4", features = [ + "derive", + "env", + "wrap_help", +], optional = true } clap_complete = { version = "4", optional = true } chrono.workspace = true auto_impl = "1" @@ -73,11 +101,21 @@ fdlimit = { version = "0.3", optional = true } clap_complete_fig = "4" ethereum-forkid = "0.12" +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = { workspace = true, optional = true } + [dev-dependencies] -ethers = { workspace = true, features = ["abigen"] } +alloy-json-abi.workspace = true +ethers = { workspace = true, features = ["abigen", "optimism"] } ethers-core = { workspace = true, features = ["optimism"] } -ethers-solc = { workspace = true, features = ["project-util", "full"] } -pretty_assertions = "1.3.0" +foundry-compilers = { workspace = true, features = ["project-util", "full"] } +alloy-rpc-client = { workspace = true, features = ["pubsub"] } +alloy-transport-ipc = { workspace = true, features = ["mock"] } +alloy-transport-ws.workspace = true +alloy-json-rpc.workspace = true +alloy-pubsub.workspace = true +foundry-test-utils.workspace = true +pretty_assertions.workspace = true tokio = { version = "1", features = ["full"] } crc = "3.0.1" @@ -85,3 +123,5 @@ crc = "3.0.1" default = ["cli"] cmd = ["clap", "clap_complete", "ctrlc", "anvil-server/clap"] cli = ["tokio/full", "cmd", "fdlimit"] +asm-keccak = ["alloy-primitives/asm-keccak"] +jemalloc = ["dep:tikv-jemallocator"] diff --git a/crates/anvil/README.md b/crates/anvil/README.md index 165f6285b8999..cb62354fbc519 100644 --- a/crates/anvil/README.md +++ b/crates/anvil/README.md @@ -20,7 +20,7 @@ A local Ethereum node, akin to Ganache, designed for development with [**Forge** ```sh git clone https://github.com/foundry-rs/foundry cd foundry -cargo install --path ./crates/anvil --profile local --force +cargo install --path ./crates/anvil --profile local --locked --force ``` ## Getting started diff --git a/crates/anvil/core/Cargo.toml b/crates/anvil/core/Cargo.toml index 91d86986da824..90738112e6402 100644 --- a/crates/anvil/core/Cargo.toml +++ b/crates/anvil/core/Cargo.toml @@ -12,26 +12,30 @@ repository.workspace = true [dependencies] foundry-common.workspace = true foundry-evm.workspace = true +revm = { workspace = true, default-features = false, features = [ + "std", + "serde", + "memory_limit", + "c-kzg", +] } alloy-primitives = { workspace = true, features = ["serde"] } -revm = { workspace = true, default-features = false, features = ["std", "serde", "memory_limit"] } -ethers-core = { workspace = true, features = ["optimism"] } -# theses are not used by anvil-core, but are required by ethers, because pulled in via foundry-common -ethers-contract = { workspace = true, features = ["optimism"] } -ethers-providers = { workspace = true, features = ["optimism"] } -ethers-middleware = { workspace = true, features = ["optimism"] } +alloy-rpc-types = { workspace = true } +alloy-rpc-types-trace.workspace = true +alloy-rlp.workspace = true +alloy-eips.workspace = true +alloy-network = { workspace = true, features = ["k256"] } +alloy-consensus = { workspace = true, features = ["k256", "kzg"] } +alloy-dyn-abi = { workspace = true, features = ["std", "eip712"] } +alloy-trie.workspace = true serde = { workspace = true, optional = true } serde_json.workspace = true -bytes = { version = "1.4" } -open-fastrlp = { version = "0.1.4", optional = true } +bytes = "1.4" +c-kzg = { version = "1", features = ["serde"] } -# trie -hash-db = { version = "0.15", default-features = false } -hash256-std-hasher = { version = "0.15", default-features = false } -triehash = { version = "0.8", default-features = false } -reference-trie = "0.25" -keccak-hasher = "0.15" +# misc +rand = "0.8" [dev-dependencies] anvil-core = { path = ".", features = ["serde"] } @@ -39,5 +43,4 @@ anvil-core = { path = ".", features = ["serde"] } [features] default = ["serde"] impersonated-tx = [] -fastrlp = ["dep:open-fastrlp"] -serde = ["dep:serde"] \ No newline at end of file +serde = ["dep:serde"] diff --git a/crates/anvil/core/src/eth/block.rs b/crates/anvil/core/src/eth/block.rs index 36956f9c4c0be..a05c2cae45c6f 100644 --- a/crates/anvil/core/src/eth/block.rs +++ b/crates/anvil/core/src/eth/block.rs @@ -1,39 +1,34 @@ -use crate::eth::{receipt::TypedReceipt, transaction::TransactionInfo, trie}; -use ethers_core::{ - types::{Address, Bloom, Bytes, H256, H64, U256}, - utils::{ - keccak256, rlp, - rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}, - }, +use super::{ + transaction::{TransactionInfo, TypedReceipt}, + trie, }; +use alloy_consensus::Header; +use alloy_eips::eip2718::Encodable2718; +use alloy_primitives::{Address, Bloom, Bytes, B256, B64, U256}; +use alloy_rlp::{RlpDecodable, RlpEncodable}; + +// Type alias to optionally support impersonated transactions +#[cfg(not(feature = "impersonated-tx"))] +type Transaction = crate::eth::transaction::TypedTransaction; +#[cfg(feature = "impersonated-tx")] +type Transaction = crate::eth::transaction::MaybeImpersonatedTransaction; /// Container type that gathers all block data -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct BlockInfo { pub block: Block, pub transactions: Vec, pub receipts: Vec, } -// Type alias to optionally support impersonated transactions -#[cfg(not(feature = "impersonated-tx"))] -type Transaction = crate::eth::transaction::TypedTransaction; -#[cfg(feature = "impersonated-tx")] -type Transaction = crate::eth::transaction::MaybeImpersonatedTransaction; - -/// An Ethereum block -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +/// An Ethereum Block +#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable)] pub struct Block { pub header: Header, - /// Note: this supports impersonated transactions pub transactions: Vec, pub ommers: Vec
, } -// == impl Block == - impl Block { /// Creates a new block /// @@ -48,285 +43,97 @@ impl Block { T: Into, { let transactions: Vec<_> = transactions.into_iter().map(Into::into).collect(); - let ommers_hash = H256::from_slice(keccak256(&rlp::encode_list(&ommers)[..]).as_slice()); + let mut encoded_ommers: Vec = Vec::new(); + alloy_rlp::encode_list(&ommers, &mut encoded_ommers); + let ommers_hash = + B256::from_slice(alloy_primitives::utils::keccak256(encoded_ommers).as_slice()); let transactions_root = - trie::ordered_trie_root(transactions.iter().map(|r| rlp::encode(r).freeze())); + trie::ordered_trie_root(transactions.iter().map(|r| r.encoded_2718())); Self { - header: Header::new(partial_header, ommers_hash, transactions_root), + header: Header { + parent_hash: partial_header.parent_hash, + beneficiary: partial_header.beneficiary, + ommers_hash, + state_root: partial_header.state_root, + transactions_root, + receipts_root: partial_header.receipts_root, + logs_bloom: partial_header.logs_bloom, + difficulty: partial_header.difficulty, + number: partial_header.number, + gas_limit: partial_header.gas_limit, + gas_used: partial_header.gas_used, + timestamp: partial_header.timestamp, + extra_data: partial_header.extra_data, + mix_hash: partial_header.mix_hash, + withdrawals_root: None, + blob_gas_used: partial_header.blob_gas_used, + excess_blob_gas: partial_header.excess_blob_gas, + parent_beacon_block_root: partial_header.parent_beacon_block_root, + nonce: partial_header.nonce, + base_fee_per_gas: partial_header.base_fee, + }, transactions, ommers, } } } -impl Encodable for Block { - fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(3); - s.append(&self.header); - s.append_list(&self.transactions); - s.append_list(&self.ommers); - } -} - -impl Decodable for Block { - fn decode(rlp: &Rlp) -> Result { - Ok(Self { header: rlp.val_at(0)?, transactions: rlp.list_at(1)?, ommers: rlp.list_at(2)? }) - } -} - -/// ethereum block header -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct Header { - pub parent_hash: H256, - pub ommers_hash: H256, - pub beneficiary: Address, - pub state_root: H256, - pub transactions_root: H256, - pub receipts_root: H256, - pub logs_bloom: Bloom, - pub difficulty: U256, - pub number: U256, - pub gas_limit: U256, - pub gas_used: U256, - pub timestamp: u64, - pub extra_data: Bytes, - pub mix_hash: H256, - pub nonce: H64, - /// BaseFee was added by EIP-1559 and is ignored in legacy headers. - pub base_fee_per_gas: Option, -} - -// == impl Header == - -impl Header { - pub fn new(partial_header: PartialHeader, ommers_hash: H256, transactions_root: H256) -> Self { - Self { - parent_hash: partial_header.parent_hash, - ommers_hash, - beneficiary: partial_header.beneficiary, - state_root: partial_header.state_root, - transactions_root, - receipts_root: partial_header.receipts_root, - logs_bloom: partial_header.logs_bloom, - difficulty: partial_header.difficulty, - number: partial_header.number, - gas_limit: partial_header.gas_limit, - gas_used: partial_header.gas_used, - timestamp: partial_header.timestamp, - extra_data: partial_header.extra_data, - mix_hash: partial_header.mix_hash, - nonce: partial_header.nonce, - base_fee_per_gas: partial_header.base_fee, - } - } - - pub fn hash(&self) -> H256 { - H256::from_slice(keccak256(&rlp::encode(self)).as_slice()) - } - - /// Returns the rlp length of the Header body, _not including_ trailing EIP155 fields or the - /// rlp list header - /// To get the length including the rlp list header, refer to the Encodable implementation. - #[cfg(feature = "fastrlp")] - pub(crate) fn header_payload_length(&self) -> usize { - use open_fastrlp::Encodable; - - let mut length = 0; - length += self.parent_hash.length(); - length += self.ommers_hash.length(); - length += self.beneficiary.length(); - length += self.state_root.length(); - length += self.transactions_root.length(); - length += self.receipts_root.length(); - length += self.logs_bloom.length(); - length += self.difficulty.length(); - length += self.number.length(); - length += self.gas_limit.length(); - length += self.gas_used.length(); - length += self.timestamp.length(); - length += self.extra_data.length(); - length += self.mix_hash.length(); - length += self.nonce.length(); - length += self.base_fee_per_gas.map(|fee| fee.length()).unwrap_or_default(); - length - } -} - -impl rlp::Encodable for Header { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - if self.base_fee_per_gas.is_none() { - s.begin_list(15); - } else { - s.begin_list(16); - } - s.append(&self.parent_hash); - s.append(&self.ommers_hash); - s.append(&self.beneficiary); - s.append(&self.state_root); - s.append(&self.transactions_root); - s.append(&self.receipts_root); - s.append(&self.logs_bloom); - s.append(&self.difficulty); - s.append(&self.number); - s.append(&self.gas_limit); - s.append(&self.gas_used); - s.append(&self.timestamp); - s.append(&self.extra_data.as_ref()); - s.append(&self.mix_hash); - s.append(&self.nonce); - if let Some(ref base_fee) = self.base_fee_per_gas { - s.append(base_fee); - } - } -} - -impl rlp::Decodable for Header { - fn decode(rlp: &rlp::Rlp) -> Result { - let result = Header { - parent_hash: rlp.val_at(0)?, - ommers_hash: rlp.val_at(1)?, - beneficiary: rlp.val_at(2)?, - state_root: rlp.val_at(3)?, - transactions_root: rlp.val_at(4)?, - receipts_root: rlp.val_at(5)?, - logs_bloom: rlp.val_at(6)?, - difficulty: rlp.val_at(7)?, - number: rlp.val_at(8)?, - gas_limit: rlp.val_at(9)?, - gas_used: rlp.val_at(10)?, - timestamp: rlp.val_at(11)?, - extra_data: rlp.val_at::>(12)?.into(), - mix_hash: rlp.val_at(13)?, - nonce: rlp.val_at(14)?, - base_fee_per_gas: if let Ok(base_fee) = rlp.at(15) { - Some(::decode(&base_fee)?) - } else { - None - }, - }; - Ok(result) - } -} - -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Encodable for Header { - fn length(&self) -> usize { - // add each of the fields' rlp encoded lengths - let mut length = 0; - length += self.header_payload_length(); - length += open_fastrlp::length_of_length(length); - - length - } - - fn encode(&self, out: &mut dyn open_fastrlp::BufMut) { - let list_header = - open_fastrlp::Header { list: true, payload_length: self.header_payload_length() }; - list_header.encode(out); - self.parent_hash.encode(out); - self.ommers_hash.encode(out); - self.beneficiary.encode(out); - self.state_root.encode(out); - self.transactions_root.encode(out); - self.receipts_root.encode(out); - self.logs_bloom.encode(out); - self.difficulty.encode(out); - self.number.encode(out); - self.gas_limit.encode(out); - self.gas_used.encode(out); - self.timestamp.encode(out); - self.extra_data.encode(out); - self.mix_hash.encode(out); - self.nonce.encode(out); - if let Some(base_fee_per_gas) = self.base_fee_per_gas { - base_fee_per_gas.encode(out); - } - } -} - -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Decodable for Header { - fn decode(buf: &mut &[u8]) -> Result { - // slice out the rlp list header - let header = open_fastrlp::Header::decode(buf)?; - let start_len = buf.len(); - - Ok(Header { - parent_hash: ::decode(buf)?, - ommers_hash: ::decode(buf)?, - beneficiary:
::decode(buf)?, - state_root: ::decode(buf)?, - transactions_root: ::decode(buf)?, - receipts_root: ::decode(buf)?, - logs_bloom: ::decode(buf)?, - difficulty: ::decode(buf)?, - number: ::decode(buf)?, - gas_limit: ::decode(buf)?, - gas_used: ::decode(buf)?, - timestamp: ::decode(buf)?, - extra_data: ::decode(buf)?, - mix_hash: ::decode(buf)?, - nonce: ::decode(buf)?, - base_fee_per_gas: if start_len - header.payload_length < buf.len() { - // if there is leftover data in the payload, decode the base fee - Some(::decode(buf)?) - } else { - None - }, - }) - } -} - /// Partial header definition without ommers hash and transactions root -#[derive(Clone, Debug, PartialEq, Eq, Default)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct PartialHeader { - pub parent_hash: H256, + pub parent_hash: B256, pub beneficiary: Address, - pub state_root: H256, - pub receipts_root: H256, + pub state_root: B256, + pub receipts_root: B256, pub logs_bloom: Bloom, pub difficulty: U256, - pub number: U256, - pub gas_limit: U256, - pub gas_used: U256, + pub number: u64, + pub gas_limit: u128, + pub gas_used: u128, pub timestamp: u64, pub extra_data: Bytes, - pub mix_hash: H256, - pub nonce: H64, - pub base_fee: Option, + pub mix_hash: B256, + pub blob_gas_used: Option, + pub excess_blob_gas: Option, + pub parent_beacon_block_root: Option, + pub nonce: B64, + pub base_fee: Option, } impl From
for PartialHeader { - fn from(header: Header) -> PartialHeader { + fn from(value: Header) -> Self { Self { - parent_hash: header.parent_hash, - beneficiary: header.beneficiary, - state_root: header.state_root, - receipts_root: header.receipts_root, - logs_bloom: header.logs_bloom, - difficulty: header.difficulty, - number: header.number, - gas_limit: header.gas_limit, - gas_used: header.gas_used, - timestamp: header.timestamp, - extra_data: header.extra_data, - mix_hash: header.mix_hash, - nonce: header.nonce, - base_fee: header.base_fee_per_gas, + parent_hash: value.parent_hash, + beneficiary: value.beneficiary, + state_root: value.state_root, + receipts_root: value.receipts_root, + logs_bloom: value.logs_bloom, + difficulty: value.difficulty, + number: value.number, + gas_limit: value.gas_limit, + gas_used: value.gas_used, + timestamp: value.timestamp, + extra_data: value.extra_data, + mix_hash: value.mix_hash, + nonce: value.nonce, + base_fee: value.base_fee_per_gas, + blob_gas_used: value.blob_gas_used, + excess_blob_gas: value.excess_blob_gas, + parent_beacon_block_root: value.parent_beacon_block_root, } } } #[cfg(test)] mod tests { - use super::*; - use ethers_core::{ - types::H160, - utils::{hex, hex::FromHex}, + use alloy_primitives::{ + b256, + hex::{self, FromHex}, }; + use alloy_rlp::Decodable; + + use super::*; use std::str::FromStr; #[test] @@ -340,158 +147,133 @@ mod tests { receipts_root: Default::default(), logs_bloom: Default::default(), difficulty: Default::default(), - number: 124u64.into(), + number: 124u64, gas_limit: Default::default(), - gas_used: 1337u64.into(), + gas_used: 1337u128, timestamp: 0, extra_data: Default::default(), mix_hash: Default::default(), - nonce: 99u64.to_be_bytes().into(), + nonce: B64::with_last_byte(99), + withdrawals_root: Default::default(), + blob_gas_used: Default::default(), + excess_blob_gas: Default::default(), + parent_beacon_block_root: Default::default(), base_fee_per_gas: None, }; - let encoded = rlp::encode(&header); - let decoded: Header = rlp::decode(encoded.as_ref()).unwrap(); + let encoded = alloy_rlp::encode(&header); + let decoded: Header = Header::decode(&mut encoded.as_ref()).unwrap(); assert_eq!(header, decoded); - header.base_fee_per_gas = Some(12345u64.into()); + header.base_fee_per_gas = Some(12345u128); - let encoded = rlp::encode(&header); - let decoded: Header = rlp::decode(encoded.as_ref()).unwrap(); + let encoded = alloy_rlp::encode(&header); + let decoded: Header = Header::decode(&mut encoded.as_ref()).unwrap(); assert_eq!(header, decoded); } #[test] - #[cfg(feature = "fastrlp")] - fn header_fastrlp_roundtrip() { - let mut header = Header { - parent_hash: Default::default(), - ommers_hash: Default::default(), - beneficiary: Default::default(), - state_root: Default::default(), - transactions_root: Default::default(), - receipts_root: Default::default(), - logs_bloom: Default::default(), - difficulty: Default::default(), - number: 124u64.into(), - gas_limit: Default::default(), - gas_used: 1337u64.into(), - timestamp: 0, - extra_data: Default::default(), - mix_hash: Default::default(), - nonce: H64::from_low_u64_be(99u64), - base_fee_per_gas: None, - }; - - let mut encoded = vec![]; -
::encode(&header, &mut encoded); - let decoded: Header = -
::decode(&mut encoded.as_slice()).unwrap(); - assert_eq!(header, decoded); - - header.base_fee_per_gas = Some(12345u64.into()); - - encoded.clear(); -
::encode(&header, &mut encoded); - let decoded: Header = -
::decode(&mut encoded.as_slice()).unwrap(); - assert_eq!(header, decoded); - } - - #[test] - #[cfg(feature = "fastrlp")] - // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 fn test_encode_block_header() { - use open_fastrlp::Encodable; + use alloy_rlp::Encodable; let expected = hex::decode("f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000").unwrap(); let mut data = vec![]; let header = Header { - parent_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - ommers_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - beneficiary: H160::from_str("0000000000000000000000000000000000000000").unwrap(), - state_root: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - transactions_root: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - receipts_root: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - logs_bloom: <[u8; 256]>::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().into(), - difficulty: 0x8aeu64.into(), - number: 0xd05u64.into(), - gas_limit: 0x115cu64.into(), - gas_used: 0x15b3u64.into(), + parent_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + ommers_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + beneficiary: Address::from_str("0000000000000000000000000000000000000000").unwrap(), + state_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + transactions_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + receipts_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(), + difficulty: U256::from(2222), + number: 0xd05u64, + gas_limit: 0x115cu128, + gas_used: 0x15b3u128, timestamp: 0x1a0au64, extra_data: hex::decode("7788").unwrap().into(), - mix_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - nonce: H64::from_low_u64_be(0x0), + mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + withdrawals_root: None, + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + nonce: B64::ZERO, base_fee_per_gas: None, }; + header.encode(&mut data); assert_eq!(hex::encode(&data), hex::encode(expected)); assert_eq!(header.length(), data.len()); } #[test] - // Test vector from: https://github.com/ethereum/tests/blob/f47bbef4da376a49c8fc3166f09ab8a6d182f765/BlockchainTests/ValidBlocks/bcEIP1559/baseFee.json#L15-L36 - fn test_eip1559_block_header_hash() { - let expected_hash = - H256::from_str("6a251c7c3c5dca7b42407a3752ff48f3bbca1fab7f9868371d9918daf1988d1f") - .unwrap(); - let header = Header { - parent_hash: H256::from_str("e0a94a7a3c9617401586b1a27025d2d9671332d22d540e0af72b069170380f2a").unwrap(), - ommers_hash: H256::from_str("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").unwrap(), - beneficiary: H160::from_str("ba5e000000000000000000000000000000000000").unwrap(), - state_root: H256::from_str("ec3c94b18b8a1cff7d60f8d258ec723312932928626b4c9355eb4ab3568ec7f7").unwrap(), - transactions_root: H256::from_str("50f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accf").unwrap(), - receipts_root: H256::from_str("29b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9").unwrap(), - logs_bloom: <[u8; 256]>::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().into(), - difficulty: 0x020000.into(), - number: 0x01.into(), - gas_limit: U256::from_str("016345785d8a0000").unwrap(), - gas_used: 0x015534.into(), - timestamp: 0x079e, - extra_data: hex::decode("42").unwrap().into(), - mix_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - nonce: H64::from_low_u64_be(0x0), - base_fee_per_gas: Some(0x036b.into()), - }; - assert_eq!(header.hash(), expected_hash); - } - - #[test] - #[cfg(feature = "fastrlp")] // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 fn test_decode_block_header() { let data = hex::decode("f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000").unwrap(); let expected = Header { - parent_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - ommers_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - beneficiary: H160::from_str("0000000000000000000000000000000000000000").unwrap(), - state_root: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - transactions_root: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - receipts_root: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + parent_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + ommers_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + beneficiary: Address::from_str("0000000000000000000000000000000000000000").unwrap(), + state_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + transactions_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + receipts_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), logs_bloom: <[u8; 256]>::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().into(), - difficulty: 0x8aeu64.into(), - number: 0xd05u64.into(), - gas_limit: 0x115cu64.into(), - gas_used: 0x15b3u64.into(), + difficulty: U256::from(2222), + number: 0xd05u64, + gas_limit: 0x115cu128, + gas_used: 0x15b3u128, timestamp: 0x1a0au64, extra_data: hex::decode("7788").unwrap().into(), - mix_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - nonce: H64::from_low_u64_be(0x0), + mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + nonce: B64::ZERO, + withdrawals_root: None, + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, base_fee_per_gas: None, }; - let header =
::decode(&mut data.as_slice()).unwrap(); + let header = Header::decode(&mut data.as_slice()).unwrap(); assert_eq!(header, expected); } #[test] - #[cfg(feature = "fastrlp")] + // Test vector from: https://github.com/ethereum/tests/blob/f47bbef4da376a49c8fc3166f09ab8a6d182f765/BlockchainTests/ValidBlocks/bcEIP1559/baseFee.json#L15-L36 + fn test_eip1559_block_header_hash() { + let expected_hash = + b256!("6a251c7c3c5dca7b42407a3752ff48f3bbca1fab7f9868371d9918daf1988d1f"); + let header = Header { + parent_hash: B256::from_str("e0a94a7a3c9617401586b1a27025d2d9671332d22d540e0af72b069170380f2a").unwrap(), + ommers_hash: B256::from_str("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").unwrap(), + beneficiary: Address::from_str("ba5e000000000000000000000000000000000000").unwrap(), + state_root: B256::from_str("ec3c94b18b8a1cff7d60f8d258ec723312932928626b4c9355eb4ab3568ec7f7").unwrap(), + transactions_root: B256::from_str("50f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accf").unwrap(), + receipts_root: B256::from_str("29b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9").unwrap(), + logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(), + difficulty: U256::from(0x020000), + number: 1u64, + gas_limit: U256::from(0x016345785d8a0000u128).to::(), + gas_used: U256::from(0x015534).to::(), + timestamp: 0x079e, + extra_data: hex::decode("42").unwrap().into(), + mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + nonce: B64::ZERO, + base_fee_per_gas: Some(875), + withdrawals_root: None, + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + }; + assert_eq!(header.hash_slow(), expected_hash); + } + + #[test] // Test vector from network - fn block_network_fastrlp_roundtrip() { - use open_fastrlp::Encodable; + fn block_network_roundtrip() { + use alloy_rlp::Encodable; let data = hex::decode("f9034df90348a0fbdbd8d2d0ac5f14bd5fa90e547fe6f1d15019c724f8e7b60972d381cd5d9cf8a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794c9577e7945db22e38fc060909f2278c7746b0f9ba05017cfa3b0247e35197215ae8d610265ffebc8edca8ea66d6567eb0adecda867a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018355bb7b871fffffffffffff808462bd0e1ab9014bf90148a00000000000000000000000000000000000000000000000000000000000000000f85494319fa8f1bc4e53410e92d10d918659b16540e60a945a573efb304d04c1224cd012313e827eca5dce5d94a9c831c5a268031176ebf5f3de5051e8cba0dbfe94c9577e7945db22e38fc060909f2278c7746b0f9b808400000000f8c9b841a6946f2d16f68338cbcbd8b117374ab421128ce422467088456bceba9d70c34106128e6d4564659cf6776c08a4186063c0a05f7cffd695c10cf26a6f301b67f800b8412b782100c18c35102dc0a37ece1a152544f04ad7dc1868d18a9570f744ace60870f822f53d35e89a2ea9709ccbf1f4a25ee5003944faa845d02dde0a41d5704601b841d53caebd6c8a82456e85c2806a9e08381f959a31fb94a77e58f00e38ad97b2e0355b8519ab2122662cbe022f2a4ef7ff16adc0b2d5dcd123181ec79705116db300a063746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365880000000000000000c0c0").unwrap(); - let block = ::decode(&mut data.as_slice()).unwrap(); + let block = Block::decode(&mut data.as_slice()).unwrap(); // encode and check that it matches the original data let mut encoded = Vec::new(); diff --git a/crates/anvil/core/src/eth/mod.rs b/crates/anvil/core/src/eth/mod.rs index 3303bcd0bb80f..8378506532a2c 100644 --- a/crates/anvil/core/src/eth/mod.rs +++ b/crates/anvil/core/src/eth/mod.rs @@ -1,23 +1,18 @@ -use self::state::StateOverride; use crate::{ - eth::{ - subscription::{SubscriptionId, SubscriptionKind, SubscriptionParams}, - transaction::EthTransactionRequest, - }, + eth::subscription::SubscriptionId, types::{EvmMineOptions, Forking, Index}, }; -use ethers_core::{ - abi::ethereum_types::H64, - types::{ - transaction::eip712::TypedData, Address, BlockId, BlockNumber, Bytes, Filter, - GethDebugTracingOptions, TxHash, H256, U256, - }, +use alloy_primitives::{Address, Bytes, TxHash, B256, B64, U256}; +use alloy_rpc_types::{ + pubsub::{Params as SubscriptionParams, SubscriptionKind}, + request::TransactionRequest, + state::StateOverride, + BlockId, BlockNumberOrTag as BlockNumber, Filter, WithOtherFields, }; +use alloy_rpc_types_trace::geth::{GethDebugTracingOptions, GethDefaultTracingOptions}; pub mod block; pub mod proof; -pub mod receipt; -pub mod state; pub mod subscription; pub mod transaction; pub mod trie; @@ -27,13 +22,15 @@ pub mod utils; pub mod serde_helpers; #[cfg(feature = "serde")] -use ethers_core::types::serde_helpers::*; +use self::serde_helpers::*; #[cfg(feature = "serde")] -use self::serde_helpers::*; +use foundry_common::serde_helpers::{ + deserialize_number, deserialize_number_opt, deserialize_number_seq, +}; /// Wrapper type that ensures the type is named `params` -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] pub struct Params { #[cfg_attr(feature = "serde", serde(default))] @@ -41,7 +38,7 @@ pub struct Params { } /// Represents ethereum JSON-RPC API -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] #[cfg_attr(feature = "serde", serde(tag = "method", content = "params"))] pub enum EthRequest { @@ -88,11 +85,14 @@ pub enum EthRequest { EthGetStorageAt(Address, U256, Option), #[cfg_attr(feature = "serde", serde(rename = "eth_getBlockByHash"))] - EthGetBlockByHash(H256, bool), + EthGetBlockByHash(B256, bool), #[cfg_attr(feature = "serde", serde(rename = "eth_getBlockByNumber"))] EthGetBlockByNumber( - #[cfg_attr(feature = "serde", serde(deserialize_with = "lenient_block_number"))] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "lenient_block_number::lenient_block_number") + )] BlockNumber, bool, ), @@ -104,13 +104,13 @@ pub enum EthRequest { feature = "serde", serde(rename = "eth_getBlockTransactionCountByHash", with = "sequence") )] - EthGetTransactionCountByHash(H256), + EthGetTransactionCountByHash(B256), #[cfg_attr( feature = "serde", serde( rename = "eth_getBlockTransactionCountByNumber", - deserialize_with = "lenient_block_number_seq" + deserialize_with = "lenient_block_number::lenient_block_number_seq" ) )] EthGetTransactionCountByNumber(BlockNumber), @@ -119,13 +119,13 @@ pub enum EthRequest { feature = "serde", serde(rename = "eth_getUncleCountByBlockHash", with = "sequence") )] - EthGetUnclesCountByHash(H256), + EthGetUnclesCountByHash(B256), #[cfg_attr( feature = "serde", serde( rename = "eth_getUncleCountByBlockNumber", - deserialize_with = "lenient_block_number_seq" + deserialize_with = "lenient_block_number::lenient_block_number_seq" ) )] EthGetUnclesCountByNumber(BlockNumber), @@ -136,14 +136,14 @@ pub enum EthRequest { /// Returns the account and storage values of the specified account including the Merkle-proof. /// This call can be used to verify that the data you are pulling from is not tampered with. #[cfg_attr(feature = "serde", serde(rename = "eth_getProof"))] - EthGetProof(Address, Vec, Option), + EthGetProof(Address, Vec, Option), /// The sign method calculates an Ethereum specific signature with: - #[cfg_attr(feature = "serde", serde(rename = "eth_sign"))] + #[cfg_attr(feature = "serde", serde(rename = "eth_sign", alias = "personal_sign"))] EthSign(Address, Bytes), - #[cfg_attr(feature = "serde", serde(rename = "eth_signTransaction"))] - EthSignTransaction(Box), + #[cfg_attr(feature = "serde", serde(rename = "eth_signTransaction", with = "sequence"))] + EthSignTransaction(Box>), /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). #[cfg_attr(feature = "serde", serde(rename = "eth_signTypedData"))] @@ -155,31 +155,32 @@ pub enum EthRequest { /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md), and includes full support of arrays and recursive data structures. #[cfg_attr(feature = "serde", serde(rename = "eth_signTypedData_v4"))] - EthSignTypedDataV4(Address, TypedData), + EthSignTypedDataV4(Address, alloy_dyn_abi::TypedData), #[cfg_attr(feature = "serde", serde(rename = "eth_sendTransaction", with = "sequence"))] - EthSendTransaction(Box), + EthSendTransaction(Box>), #[cfg_attr(feature = "serde", serde(rename = "eth_sendRawTransaction", with = "sequence"))] EthSendRawTransaction(Bytes), #[cfg_attr(feature = "serde", serde(rename = "eth_call"))] EthCall( - EthTransactionRequest, + WithOtherFields, #[cfg_attr(feature = "serde", serde(default))] Option, #[cfg_attr(feature = "serde", serde(default))] Option, ), #[cfg_attr(feature = "serde", serde(rename = "eth_createAccessList"))] EthCreateAccessList( - EthTransactionRequest, + WithOtherFields, #[cfg_attr(feature = "serde", serde(default))] Option, ), #[cfg_attr(feature = "serde", serde(rename = "eth_estimateGas"))] EthEstimateGas( - EthTransactionRequest, + WithOtherFields, #[cfg_attr(feature = "serde", serde(default))] Option, + #[cfg_attr(feature = "serde", serde(default))] Option, ), #[cfg_attr(feature = "serde", serde(rename = "eth_getTransactionByHash", with = "sequence"))] @@ -189,21 +190,23 @@ pub enum EthRequest { EthGetTransactionByBlockHashAndIndex(TxHash, Index), #[cfg_attr(feature = "serde", serde(rename = "eth_getTransactionByBlockNumberAndIndex"))] - EthGetTransactionByBlockNumberAndIndex( - #[cfg_attr(feature = "serde", serde(deserialize_with = "lenient_block_number"))] - BlockNumber, - Index, - ), + EthGetTransactionByBlockNumberAndIndex(BlockNumber, Index), #[cfg_attr(feature = "serde", serde(rename = "eth_getTransactionReceipt", with = "sequence"))] - EthGetTransactionReceipt(H256), + EthGetTransactionReceipt(B256), + + #[cfg_attr(feature = "serde", serde(rename = "eth_getBlockReceipts", with = "sequence"))] + EthGetBlockReceipts(BlockNumber), #[cfg_attr(feature = "serde", serde(rename = "eth_getUncleByBlockHashAndIndex"))] - EthGetUncleByBlockHashAndIndex(H256, Index), + EthGetUncleByBlockHashAndIndex(B256, Index), #[cfg_attr(feature = "serde", serde(rename = "eth_getUncleByBlockNumberAndIndex"))] EthGetUncleByBlockNumberAndIndex( - #[cfg_attr(feature = "serde", serde(deserialize_with = "lenient_block_number"))] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "lenient_block_number::lenient_block_number") + )] BlockNumber, Index, ), @@ -244,10 +247,10 @@ pub enum EthRequest { EthGetWork(()), #[cfg_attr(feature = "serde", serde(rename = "eth_submitWork"))] - EthSubmitWork(H64, H256, H256), + EthSubmitWork(B64, B256, B256), #[cfg_attr(feature = "serde", serde(rename = "eth_submitHashrate"))] - EthSubmitHashRate(U256, H256), + EthSubmitHashRate(U256, B256), #[cfg_attr(feature = "serde", serde(rename = "eth_feeHistory"))] EthFeeHistory( @@ -262,26 +265,29 @@ pub enum EthRequest { /// geth's `debug_traceTransaction` endpoint #[cfg_attr(feature = "serde", serde(rename = "debug_traceTransaction"))] DebugTraceTransaction( - H256, + B256, #[cfg_attr(feature = "serde", serde(default))] GethDebugTracingOptions, ), /// geth's `debug_traceCall` endpoint #[cfg_attr(feature = "serde", serde(rename = "debug_traceCall"))] DebugTraceCall( - EthTransactionRequest, + WithOtherFields, #[cfg_attr(feature = "serde", serde(default))] Option, - #[cfg_attr(feature = "serde", serde(default))] GethDebugTracingOptions, + #[cfg_attr(feature = "serde", serde(default))] GethDefaultTracingOptions, ), /// Trace transaction endpoint for parity's `trace_transaction` #[cfg_attr(feature = "serde", serde(rename = "trace_transaction", with = "sequence"))] - TraceTransaction(H256), + TraceTransaction(B256), /// Trace transaction endpoint for parity's `trace_block` #[cfg_attr( feature = "serde", - serde(rename = "trace_block", deserialize_with = "lenient_block_number_seq") + serde( + rename = "trace_block", + deserialize_with = "lenient_block_number::lenient_block_number_seq" + ) )] TraceBlock(BlockNumber), @@ -363,7 +369,18 @@ pub enum EthRequest { with = "sequence" ) )] - DropTransaction(H256), + DropTransaction(B256), + + /// Removes transactions from the pool + #[cfg_attr( + feature = "serde", + serde( + rename = "anvil_dropAllTransactions", + alias = "hardhat_dropAllTransactions", + with = "empty_params" + ) + )] + DropAllTransactions(), /// Reset the fork to a fresh forked state, and optionally update the fork config #[cfg_attr(feature = "serde", serde(rename = "anvil_reset", alias = "hardhat_reset"))] @@ -411,7 +428,7 @@ pub enum EthRequest { /// slot U256, /// value - H256, + B256, ), /// Sets the coinbase address @@ -591,7 +608,7 @@ pub enum EthRequest { feature = "serde", serde(rename = "eth_sendUnsignedTransaction", with = "sequence") )] - EthSendUnsignedTransaction(Box), + EthSendUnsignedTransaction(Box>), /// Turn on call traces for transactions that are returned to the user when they execute a /// transaction (instead of just txhash/receipt) @@ -622,7 +639,10 @@ pub enum EthRequest { /// Related upstream issue: https://github.com/otterscan/otterscan/issues/1081 #[cfg_attr(feature = "serde", serde(rename = "erigon_getHeaderByNumber"))] ErigonGetHeaderByNumber( - #[cfg_attr(feature = "serde", serde(deserialize_with = "lenient_block_number_seq"))] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "lenient_block_number::lenient_block_number_seq") + )] BlockNumber, ), @@ -635,26 +655,29 @@ pub enum EthRequest { /// Traces internal ETH transfers, contracts creation (CREATE/CREATE2) and self-destructs for a /// certain transaction. #[cfg_attr(feature = "serde", serde(rename = "ots_getInternalOperations", with = "sequence"))] - OtsGetInternalOperations(H256), + OtsGetInternalOperations(B256), /// Otterscan's `ots_hasCode` endpoint /// Check if an ETH address contains code at a certain block number. #[cfg_attr(feature = "serde", serde(rename = "ots_hasCode"))] OtsHasCode( Address, - #[cfg_attr(feature = "serde", serde(deserialize_with = "lenient_block_number", default))] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "lenient_block_number::lenient_block_number", default) + )] BlockNumber, ), /// Otterscan's `ots_traceTransaction` endpoint /// Trace a transaction and generate a trace call tree. #[cfg_attr(feature = "serde", serde(rename = "ots_traceTransaction", with = "sequence"))] - OtsTraceTransaction(H256), + OtsTraceTransaction(B256), /// Otterscan's `ots_getTransactionError` endpoint /// Given a transaction hash, returns its raw revert reason. #[cfg_attr(feature = "serde", serde(rename = "ots_getTransactionError", with = "sequence"))] - OtsGetTransactionError(H256), + OtsGetTransactionError(B256), /// Otterscan's `ots_getBlockDetails` endpoint /// Given a block number, return its data. Similar to the standard eth_getBlockByNumber/Hash @@ -662,14 +685,17 @@ pub enum EthRequest { /// logBloom #[cfg_attr(feature = "serde", serde(rename = "ots_getBlockDetails"))] OtsGetBlockDetails( - #[cfg_attr(feature = "serde", serde(deserialize_with = "lenient_block_number_seq"))] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "lenient_block_number::lenient_block_number_seq", default) + )] BlockNumber, ), /// Otterscan's `ots_getBlockDetails` endpoint /// Same as `ots_getBlockDetails`, but receiving a block hash instead of number #[cfg_attr(feature = "serde", serde(rename = "ots_getBlockDetailsByHash", with = "sequence"))] - OtsGetBlockDetailsByHash(H256), + OtsGetBlockDetailsByHash(B256), /// Otterscan's `ots_getBlockTransactions` endpoint /// Gets paginated transaction data for a certain block. Return data is similar to @@ -702,6 +728,13 @@ pub enum EthRequest { /// contract. #[cfg_attr(feature = "serde", serde(rename = "ots_getContractCreator", with = "sequence"))] OtsGetContractCreator(Address), + + /// Removes transactions from the pool by sender origin. + #[cfg_attr( + feature = "serde", + serde(rename = "anvil_removePoolTransactions", with = "sequence") + )] + RemovePoolTransactions(Address), } /// Represents ethereum JSON-RPC API @@ -722,7 +755,7 @@ pub enum EthPubSub { } /// Container type for either a request or a pub sub -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] #[cfg_attr(feature = "serde", serde(untagged))] pub enum EthRpcCall { @@ -806,14 +839,16 @@ mod tests { #[test] fn test_custom_impersonate_account() { - let s = r#"{"method": "anvil_impersonateAccount", "params": ["0xd84de507f3fada7df80908082d3239466db55a71"]}"#; + let s = r#"{"method": "anvil_impersonateAccount", "params": +["0xd84de507f3fada7df80908082d3239466db55a71"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_stop_impersonate_account() { - let s = r#"{"method": "anvil_stopImpersonatingAccount", "params": ["0x364d6D0333432C3Ac016Ca832fb8594A8cE43Ca6"]}"#; + let s = r#"{"method": "anvil_stopImpersonatingAccount", "params": +["0x364d6D0333432C3Ac016Ca832fb8594A8cE43Ca6"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } @@ -844,8 +879,8 @@ mod tests { } _ => unreachable!(), } - let s = - r#"{"method": "anvil_mine", "params": ["0xd84de507f3fada7df80908082d3239466db55a71"]}"#; + let s = r#"{"method": "anvil_mine", "params": +["0xd84de507f3fada7df80908082d3239466db55a71"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { @@ -889,7 +924,8 @@ mod tests { #[test] fn test_custom_drop_tx() { - let s = r#"{"method": "anvil_dropTransaction", "params": ["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff"]}"#; + let s = r#"{"method": "anvil_dropTransaction", "params": +["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } @@ -1034,57 +1070,71 @@ mod tests { #[test] fn test_custom_set_balance() { - let s = r#"{"method": "anvil_setBalance", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; + let s = r#"{"method": "anvil_setBalance", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"method": "anvil_setBalance", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", 1337]}"#; + let s = r#"{"method": "anvil_setBalance", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", 1337]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_set_code() { - let s = r#"{"method": "anvil_setCode", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0123456789abcdef"]}"#; + let s = r#"{"method": "anvil_setCode", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", "0x0123456789abcdef"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"method": "anvil_setCode", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x"]}"#; + let s = r#"{"method": "anvil_setCode", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", "0x"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"method": "anvil_setCode", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", ""]}"#; + let s = r#"{"method": "anvil_setCode", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", ""]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_set_nonce() { - let s = r#"{"method": "anvil_setNonce", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; + let s = r#"{"method": "anvil_setNonce", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"method": "hardhat_setNonce", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; + let s = r#"{"method": +"hardhat_setNonce", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"method": "evm_setAccountNonce", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; + let s = r#"{"method": "evm_setAccountNonce", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_set_storage_at() { - let s = r#"{"method": "anvil_setStorageAt", "params": ["0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", "0x0000000000000000000000000000000000000000000000000000000000003039"]}"#; + let s = r#"{"method": "anvil_setStorageAt", "params": +["0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", +"0x0000000000000000000000000000000000000000000000000000000000003039"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"method": "hardhat_setStorageAt", "params": ["0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56", "0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49", "0x0000000000000000000000000000000000000000000000000000000000003039"]}"#; + let s = r#"{"method": "hardhat_setStorageAt", "params": +["0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56", +"0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49", +"0x0000000000000000000000000000000000000000000000000000000000003039"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_coinbase() { - let s = r#"{"method": "anvil_setCoinbase", "params": ["0x295a70b2de5e3953354a6a8344e616ed314d7251"]}"#; + let s = r#"{"method": "anvil_setCoinbase", "params": +["0x295a70b2de5e3953354a6a8344e616ed314d7251"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } @@ -1340,7 +1390,8 @@ mod tests { #[test] fn test_serde_eth_unsubscribe() { - let s = r#"{"id": 1, "method": "eth_unsubscribe", "params": ["0x9cef478923ff08bf67fde6c64013158d"]}"#; + let s = r#"{"id": 1, "method": "eth_unsubscribe", "params": +["0x9cef478923ff08bf67fde6c64013158d"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } @@ -1351,7 +1402,9 @@ mod tests { let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"id": 1, "method": "eth_subscribe", "params": ["logs", {"address": "0x8320fe7702b96808f7bbc0d4a888ed1468216cfd", "topics": ["0xd78a0cb8bb633d06981248b816e7bd33c2a35a6089241d099fa519e361cab902"]}]}"#; + let s = r#"{"id": 1, "method": "eth_subscribe", "params": ["logs", {"address": +"0x8320fe7702b96808f7bbc0d4a888ed1468216cfd", "topics": +["0xd78a0cb8bb633d06981248b816e7bd33c2a35a6089241d099fa519e361cab902"]}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); @@ -1366,15 +1419,19 @@ mod tests { #[test] fn test_serde_debug_trace_transaction() { - let s = r#"{"method": "debug_traceTransaction", "params": ["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff"]}"#; + let s = r#"{"method": "debug_traceTransaction", "params": +["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"method": "debug_traceTransaction", "params": ["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff", {}]}"#; + let s = r#"{"method": "debug_traceTransaction", "params": +["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff", {}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"method": "debug_traceTransaction", "params": ["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff", {"disableStorage": true}]}"#; + let s = r#"{"method": "debug_traceTransaction", "params": +["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff", {"disableStorage": +true}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } @@ -1404,7 +1461,8 @@ mod tests { #[test] fn test_serde_eth_storage() { - let s = r#"{"method": "eth_getStorageAt", "params": ["0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", "latest"]}"#; + let s = r#"{"method": "eth_getStorageAt", "params": +["0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", "latest"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } @@ -1412,27 +1470,28 @@ mod tests { #[test] fn test_eth_call() { let req = r#"{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}"#; - let _req = serde_json::from_str::(req).unwrap(); + let _req = serde_json::from_str::(req).unwrap(); - let s = r#"{"method": "eth_call", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"},"latest"]}"#; + let s = r#"{"method": "eth_call", "params":[{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"},"latest"]}"#; let _req = serde_json::from_str::(s).unwrap(); - let s = r#"{"method": "eth_call", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}]}"#; + let s = r#"{"method": "eth_call", "params":[{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}]}"#; let _req = serde_json::from_str::(s).unwrap(); - let s = r#"{"method": "eth_call", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockNumber": "latest" }]}"#; + let s = r#"{"method": "eth_call", "params":[{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockNumber": "latest" }]}"#; let _req = serde_json::from_str::(s).unwrap(); - let s = r#"{"method": "eth_call", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockNumber": "0x0" }]}"#; + let s = r#"{"method": "eth_call", "params":[{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockNumber": "0x0" }]}"#; let _req = serde_json::from_str::(s).unwrap(); - let s = r#"{"method": "eth_call", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" }]}"#; + let s = r#"{"method": "eth_call", "params":[{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockHash":"0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" }]}"#; let _req = serde_json::from_str::(s).unwrap(); } #[test] fn test_serde_eth_balance() { - let s = r#"{"method": "eth_getBalance", "params": ["0x295a70b2de5e3953354a6a8344e616ed314d7251", "latest"]}"#; + let s = r#"{"method": "eth_getBalance", "params": +["0x295a70b2de5e3953354a6a8344e616ed314d7251", "latest"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); @@ -1459,10 +1518,29 @@ mod tests { let _req = serde_json::from_value::(value).unwrap(); } + #[test] + fn test_eth_sign() { + let s = r#"{"method": "eth_sign", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", "0x00"]}"#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let _req = serde_json::from_value::(value).unwrap(); + let s = r#"{"method": "personal_sign", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", "0x00"]}"#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let _req = serde_json::from_value::(value).unwrap(); + } + #[test] fn test_eth_sign_typed_data() { let s = r#"{"method":"eth_signTypedData_v4","params":["0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", {"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"wallet","type":"address"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person"},{"name":"contents","type":"string"}]},"primaryType":"Mail","domain":{"name":"Ether Mail","version":"1","chainId":1,"verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"},"contents":"Hello, Bob!"}}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } + + #[test] + fn test_remove_pool_transactions() { + let s = r#"{"method": "anvil_removePoolTransactions", "params":["0x364d6D0333432C3Ac016Ca832fb8594A8cE43Ca6"]}"#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let _req = serde_json::from_value::(value).unwrap(); + } } diff --git a/crates/anvil/core/src/eth/proof.rs b/crates/anvil/core/src/eth/proof.rs index 09c14bec26070..ba2357bffd578 100644 --- a/crates/anvil/core/src/eth/proof.rs +++ b/crates/anvil/core/src/eth/proof.rs @@ -1,59 +1,24 @@ //! Return types for `eth_getProof` use crate::eth::trie::KECCAK_NULL_RLP; -use ethers_core::{ - types::{H256, U256}, - utils::rlp, -}; -use foundry_common::types::ToEthers; +use alloy_primitives::{B256, U256}; use revm::primitives::KECCAK_EMPTY; -// reexport for convenience -pub use ethers_core::types::{EIP1186ProofResponse as AccountProof, StorageProof}; - -/// Basic account type. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] +#[derive(Clone, Debug, PartialEq, Eq, alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable)] pub struct BasicAccount { - /// Nonce of the account. pub nonce: U256, - /// Balance of the account. pub balance: U256, - /// Storage root of the account. - pub storage_root: H256, - /// Code hash of the account. - pub code_hash: H256, + pub storage_root: B256, + pub code_hash: B256, } impl Default for BasicAccount { fn default() -> Self { BasicAccount { - balance: 0.into(), - nonce: 0.into(), - code_hash: KECCAK_EMPTY.to_ethers(), + balance: U256::ZERO, + nonce: U256::ZERO, + code_hash: KECCAK_EMPTY, storage_root: KECCAK_NULL_RLP, } } } - -impl rlp::Encodable for BasicAccount { - fn rlp_append(&self, stream: &mut rlp::RlpStream) { - stream.begin_list(4); - stream.append(&self.nonce); - stream.append(&self.balance); - stream.append(&self.storage_root); - stream.append(&self.code_hash); - } -} - -impl rlp::Decodable for BasicAccount { - fn decode(rlp: &rlp::Rlp) -> Result { - let result = BasicAccount { - nonce: rlp.val_at(0)?, - balance: rlp.val_at(1)?, - storage_root: rlp.val_at(2)?, - code_hash: rlp.val_at(3)?, - }; - Ok(result) - } -} diff --git a/crates/anvil/core/src/eth/receipt.rs b/crates/anvil/core/src/eth/receipt.rs deleted file mode 100644 index c651124878a73..0000000000000 --- a/crates/anvil/core/src/eth/receipt.rs +++ /dev/null @@ -1,376 +0,0 @@ -use crate::eth::utils::enveloped; -use ethers_core::{ - types::{Address, Bloom, Bytes, H256, U256}, - utils::{ - rlp, - rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}, - }, -}; -use foundry_common::types::{ToAlloy, ToEthers}; - -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Log { - pub address: Address, - pub topics: Vec, - pub data: Bytes, -} - -impl From for Log { - fn from(log: revm::primitives::Log) -> Self { - let revm::primitives::Log { address, topics, data } = log; - Log { - address: address.to_ethers(), - topics: topics.into_iter().map(|h| h.to_ethers()).collect(), - data: ethers_core::types::Bytes(data.0), - } - } -} - -impl From for revm::primitives::Log { - fn from(log: Log) -> Self { - let Log { address, topics, data } = log; - revm::primitives::Log { - address: address.to_alloy(), - topics: topics.into_iter().map(|t| t.to_alloy()).collect(), - data: alloy_primitives::Bytes(data.0), - } - } -} - -impl Encodable for Log { - fn rlp_append(&self, stream: &mut rlp::RlpStream) { - stream.begin_list(3); - stream.append(&self.address); - stream.append_list(&self.topics); - stream.append(&self.data.as_ref()); - } -} - -impl Decodable for Log { - fn decode(rlp: &Rlp) -> Result { - let result = Log { - address: rlp.val_at(0)?, - topics: rlp.list_at(1)?, - data: rlp.val_at::>(2)?.into(), - }; - Ok(result) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct EIP658Receipt { - pub status_code: u8, - pub gas_used: U256, - pub logs_bloom: Bloom, - pub logs: Vec, -} - -impl Encodable for EIP658Receipt { - fn rlp_append(&self, stream: &mut RlpStream) { - stream.begin_list(4); - stream.append(&self.status_code); - stream.append(&self.gas_used); - stream.append(&self.logs_bloom); - stream.append_list(&self.logs); - } -} - -impl Decodable for EIP658Receipt { - fn decode(rlp: &Rlp) -> Result { - let result = EIP658Receipt { - status_code: rlp.val_at(0)?, - gas_used: rlp.val_at(1)?, - logs_bloom: rlp.val_at(2)?, - logs: rlp.list_at(3)?, - }; - Ok(result) - } -} - -// same underlying data structure -pub type EIP2930Receipt = EIP658Receipt; -pub type EIP1559Receipt = EIP658Receipt; -pub type DepositReceipt = EIP658Receipt; - -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum TypedReceipt { - /// Legacy receipt - Legacy(EIP658Receipt), - /// EIP-2930 receipt - EIP2930(EIP2930Receipt), - /// EIP-1559 receipt - EIP1559(EIP1559Receipt), - /// op-stack deposit receipt - Deposit(DepositReceipt), -} - -// == impl TypedReceipt == - -impl TypedReceipt { - /// Returns the gas used by the transactions - pub fn gas_used(&self) -> U256 { - match self { - TypedReceipt::Legacy(r) | - TypedReceipt::EIP2930(r) | - TypedReceipt::EIP1559(r) | - TypedReceipt::Deposit(r) => r.gas_used, - } - } - - /// Returns the gas used by the transactions - pub fn logs_bloom(&self) -> &Bloom { - match self { - TypedReceipt::Legacy(r) | - TypedReceipt::EIP2930(r) | - TypedReceipt::EIP1559(r) | - TypedReceipt::Deposit(r) => &r.logs_bloom, - } - } -} - -impl Encodable for TypedReceipt { - fn rlp_append(&self, s: &mut RlpStream) { - match self { - TypedReceipt::Legacy(r) => r.rlp_append(s), - TypedReceipt::EIP2930(r) => enveloped(1, r, s), - TypedReceipt::EIP1559(r) => enveloped(2, r, s), - TypedReceipt::Deposit(r) => enveloped(0x7E, r, s), - } - } -} - -impl Decodable for TypedReceipt { - fn decode(rlp: &Rlp) -> Result { - let slice = rlp.data()?; - - let first = *slice.first().ok_or(DecoderError::Custom("empty receipt"))?; - - if rlp.is_list() { - return Ok(TypedReceipt::Legacy(Decodable::decode(rlp)?)) - } - - let s = slice.get(1..).ok_or(DecoderError::Custom("no receipt content"))?; - - if first == 0x01 { - return rlp::decode(s).map(TypedReceipt::EIP2930) - } - - if first == 0x02 { - return rlp::decode(s).map(TypedReceipt::EIP1559) - } - - if first == 0x7E { - return rlp::decode(s).map(TypedReceipt::Deposit) - } - - Err(DecoderError::Custom("unknown receipt type")) - } -} - -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Encodable for TypedReceipt { - fn length(&self) -> usize { - match self { - TypedReceipt::Legacy(r) => r.length(), - receipt => { - let payload_len = match receipt { - TypedReceipt::EIP2930(r) => r.length() + 1, - TypedReceipt::EIP1559(r) => r.length() + 1, - TypedReceipt::Deposit(r) => r.length() + 1, - _ => unreachable!("receipt already matched"), - }; - - // we include a string header for typed receipts, so include the length here - payload_len + open_fastrlp::length_of_length(payload_len) - } - } - } - fn encode(&self, out: &mut dyn open_fastrlp::BufMut) { - use open_fastrlp::Header; - - match self { - TypedReceipt::Legacy(r) => r.encode(out), - receipt => { - let payload_len = match receipt { - TypedReceipt::EIP2930(r) => r.length() + 1, - TypedReceipt::EIP1559(r) => r.length() + 1, - TypedReceipt::Deposit(r) => r.length() + 1, - _ => unreachable!("receipt already matched"), - }; - - match receipt { - TypedReceipt::EIP2930(r) => { - let receipt_string_header = - Header { list: false, payload_length: payload_len }; - - receipt_string_header.encode(out); - out.put_u8(0x01); - r.encode(out); - } - TypedReceipt::EIP1559(r) => { - let receipt_string_header = - Header { list: false, payload_length: payload_len }; - - receipt_string_header.encode(out); - out.put_u8(0x02); - r.encode(out); - } - TypedReceipt::Deposit(r) => { - let receipt_string_header = - Header { list: false, payload_length: payload_len }; - - receipt_string_header.encode(out); - out.put_u8(0x7E); - r.encode(out); - } - _ => unreachable!("receipt already matched"), - } - } - } - } -} - -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Decodable for TypedReceipt { - fn decode(buf: &mut &[u8]) -> Result { - use bytes::Buf; - use open_fastrlp::Header; - use std::cmp::Ordering; - - // a receipt is either encoded as a string (non legacy) or a list (legacy). - // We should not consume the buffer if we are decoding a legacy receipt, so let's - // check if the first byte is between 0x80 and 0xbf. - let rlp_type = *buf - .first() - .ok_or(open_fastrlp::DecodeError::Custom("cannot decode a receipt from empty bytes"))?; - - match rlp_type.cmp(&open_fastrlp::EMPTY_LIST_CODE) { - Ordering::Less => { - // strip out the string header - let _header = Header::decode(buf)?; - let receipt_type = *buf.first().ok_or(open_fastrlp::DecodeError::Custom( - "typed receipt cannot be decoded from an empty slice", - ))?; - if receipt_type == 0x01 { - buf.advance(1); - ::decode(buf) - .map(TypedReceipt::EIP2930) - } else if receipt_type == 0x02 { - buf.advance(1); - ::decode(buf) - .map(TypedReceipt::EIP1559) - } else if receipt_type == 0x7E { - buf.advance(1); - ::decode(buf) - .map(TypedReceipt::Deposit) - } else { - Err(open_fastrlp::DecodeError::Custom("invalid receipt type")) - } - } - Ordering::Equal => Err(open_fastrlp::DecodeError::Custom( - "an empty list is not a valid receipt encoding", - )), - Ordering::Greater => { - ::decode(buf).map(TypedReceipt::Legacy) - } - } - } -} - -impl From for EIP658Receipt { - fn from(v3: TypedReceipt) -> Self { - match v3 { - TypedReceipt::Legacy(receipt) => receipt, - TypedReceipt::EIP2930(receipt) => receipt, - TypedReceipt::EIP1559(receipt) => receipt, - TypedReceipt::Deposit(receipt) => receipt, - } - } -} - -#[cfg(test)] -mod tests { - #[test] - #[cfg(feature = "fastrlp")] - // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 - fn encode_legacy_receipt() { - use crate::eth::receipt::{EIP658Receipt, Log, TypedReceipt}; - use ethers_core::{ - types::{Bytes, H160, H256}, - utils::hex, - }; - use open_fastrlp::Encodable; - use std::str::FromStr; - - let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); - - let mut data = vec![]; - let receipt = TypedReceipt::Legacy(EIP658Receipt { - logs_bloom: [0; 256].into(), - gas_used: 0x1u64.into(), - logs: vec![Log { - address: H160::from_str("0000000000000000000000000000000000000011").unwrap(), - topics: vec![ - H256::from_str( - "000000000000000000000000000000000000000000000000000000000000dead", - ) - .unwrap(), - H256::from_str( - "000000000000000000000000000000000000000000000000000000000000beef", - ) - .unwrap(), - ], - data: Bytes::from_str("0100ff").unwrap(), - }], - status_code: 0, - }); - receipt.encode(&mut data); - - // check that the rlp length equals the length of the expected rlp - assert_eq!(receipt.length(), expected.len()); - assert_eq!(data, expected); - } - - #[test] - #[cfg(feature = "fastrlp")] - // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 - fn decode_legacy_receipt() { - use crate::eth::receipt::{EIP658Receipt, Log, TypedReceipt}; - use ethers_core::{ - types::{Bytes, H160, H256}, - utils::hex, - }; - use open_fastrlp::Decodable; - use std::str::FromStr; - - let data = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); - - let expected = TypedReceipt::Legacy(EIP658Receipt { - logs_bloom: [0; 256].into(), - gas_used: 0x1u64.into(), - logs: vec![Log { - address: H160::from_str("0000000000000000000000000000000000000011").unwrap(), - topics: vec![ - H256::from_str( - "000000000000000000000000000000000000000000000000000000000000dead", - ) - .unwrap(), - H256::from_str( - "000000000000000000000000000000000000000000000000000000000000beef", - ) - .unwrap(), - ], - data: Bytes::from_str("0100ff").unwrap(), - }], - status_code: 0, - }); - - let receipt = TypedReceipt::decode(&mut &data[..]).unwrap(); - assert_eq!(receipt, expected); - } -} diff --git a/crates/anvil/core/src/eth/serde_helpers.rs b/crates/anvil/core/src/eth/serde_helpers.rs index 0deeb69d6c8c1..311b3a9f27e80 100644 --- a/crates/anvil/core/src/eth/serde_helpers.rs +++ b/crates/anvil/core/src/eth/serde_helpers.rs @@ -33,6 +33,35 @@ pub mod sequence { } } +pub mod numeric { + use alloy_primitives::U256; + use serde::{Deserialize, Deserializer}; + /// Supports parsing u64 + /// + /// See + pub fn deserialize_stringified_u64_opt<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + if let Some(num) = Option::::deserialize(deserializer)? { + num.try_into().map(Some).map_err(serde::de::Error::custom) + } else { + Ok(None) + } + } + + /// Supports parsing u64 + /// + /// See + pub fn deserialize_stringified_u64<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let num = U256::deserialize(deserializer)?; + num.try_into().map_err(serde::de::Error::custom) + } +} + /// A module that deserializes `[]` optionally pub mod empty_params { use serde::{Deserialize, Deserializer}; @@ -51,3 +80,60 @@ pub mod empty_params { Ok(()) } } + +/// A module that deserializes either a BlockNumberOrTag, or a simple number. +pub mod lenient_block_number { + use alloy_rpc_types::BlockNumberOrTag; + use serde::{Deserialize, Deserializer}; + /// Following the spec the block parameter is either: + /// + /// > HEX String - an integer block number + /// > String "earliest" for the earliest/genesis block + /// > String "latest" - for the latest mined block + /// > String "pending" - for the pending state/transactions + /// + /// and with EIP-1898: + /// > blockNumber: QUANTITY - a block number + /// > blockHash: DATA - a block hash + /// + /// + /// + /// EIP-1898 does not all calls that use `BlockNumber` like `eth_getBlockByNumber` and doesn't + /// list raw integers as supported. + /// + /// However, there are dev node implementations that support integers, such as ganache: + /// + /// N.B.: geth does not support ints in `eth_getBlockByNumber` + pub fn lenient_block_number<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + LenientBlockNumber::deserialize(deserializer).map(Into::into) + } + + /// Same as `lenient_block_number` but requires to be `[num; 1]` + pub fn lenient_block_number_seq<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let num = <[LenientBlockNumber; 1]>::deserialize(deserializer)?[0].into(); + Ok(num) + } + + /// Various block number representations, See [`lenient_block_number()`] + #[derive(Clone, Copy, Deserialize)] + #[serde(untagged)] + pub enum LenientBlockNumber { + BlockNumber(BlockNumberOrTag), + Num(u64), + } + + impl From for BlockNumberOrTag { + fn from(b: LenientBlockNumber) -> Self { + match b { + LenientBlockNumber::BlockNumber(b) => b, + LenientBlockNumber::Num(b) => b.into(), + } + } + } +} diff --git a/crates/anvil/core/src/eth/state.rs b/crates/anvil/core/src/eth/state.rs deleted file mode 100644 index 1a1b9da3308a8..0000000000000 --- a/crates/anvil/core/src/eth/state.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ethers_core::types::{Address, Bytes, H256, U256}; -use std::collections::HashMap; - -#[derive(Clone, Debug, PartialEq, Eq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct AccountOverride { - pub nonce: Option, - pub code: Option, - pub balance: Option, - pub state: Option>, - pub state_diff: Option>, -} - -pub type StateOverride = HashMap; diff --git a/crates/anvil/core/src/eth/subscription.rs b/crates/anvil/core/src/eth/subscription.rs index 791bac2526951..cc162cc4dc9f7 100644 --- a/crates/anvil/core/src/eth/subscription.rs +++ b/crates/anvil/core/src/eth/subscription.rs @@ -1,78 +1,8 @@ //! Subscription types - -use crate::eth::block::Header; -use ethers_core::{ - rand::{distributions::Alphanumeric, thread_rng, Rng}, - types::{Filter, Log, TxHash}, - utils::hex, -}; +use alloy_primitives::hex; +use rand::{distributions::Alphanumeric, thread_rng, Rng}; use std::fmt; -/// Result of a subscription -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[cfg_attr(feature = "serde", serde(untagged))] -pub enum SubscriptionResult { - /// New block header - Header(Box
), - /// Log - Log(Box), - /// Transaction hash - TransactionHash(TxHash), - /// SyncStatus - Sync(SyncStatus), -} - -/// Sync status -#[derive(Debug, Eq, PartialEq, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct SyncStatus { - pub syncing: bool, -} - -/// Params for a subscription request -#[derive(Debug, PartialEq, Eq, Hash, Clone, Default)] -pub struct SubscriptionParams { - /// holds the filter params field if present in the request - pub filter: Option, -} - -#[cfg(feature = "serde")] -impl<'a> serde::Deserialize<'a> for SubscriptionParams { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'a>, - { - use serde::de::Error; - - let val = serde_json::Value::deserialize(deserializer)?; - if val.is_null() { - return Ok(SubscriptionParams::default()) - } - - let filter: Filter = serde_json::from_value(val) - .map_err(|e| D::Error::custom(format!("Invalid Subscription parameters: {e}")))?; - Ok(SubscriptionParams { filter: Some(filter) }) - } -} - -/// Subscription kind -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub enum SubscriptionKind { - /// subscribe to new heads - NewHeads, - /// subscribe to new logs - Logs, - /// subscribe to pending transactions - NewPendingTransactions, - /// syncing subscription - Syncing, -} - /// Unique subscription id #[derive(Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -112,7 +42,7 @@ impl fmt::Debug for SubscriptionId { } /// Provides random hex identifier with a certain length -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct HexIdProvider { len: usize, } diff --git a/crates/anvil/core/src/eth/transaction/ethers_compat.rs b/crates/anvil/core/src/eth/transaction/ethers_compat.rs deleted file mode 100644 index 54e2c16c535a8..0000000000000 --- a/crates/anvil/core/src/eth/transaction/ethers_compat.rs +++ /dev/null @@ -1,287 +0,0 @@ -//! ethers compatibility, this is mainly necessary so we can use all of `ethers` signers - -use super::EthTransactionRequest; -use crate::eth::transaction::{ - DepositTransactionRequest, EIP1559TransactionRequest, EIP2930TransactionRequest, - LegacyTransactionRequest, MaybeImpersonatedTransaction, TypedTransaction, - TypedTransactionRequest, -}; -use ethers_core::types::{ - transaction::{ - eip2718::TypedTransaction as EthersTypedTransactionRequest, optimism::DepositTransaction, - }, - Address, Eip1559TransactionRequest as EthersEip1559TransactionRequest, - Eip2930TransactionRequest as EthersEip2930TransactionRequest, NameOrAddress, - Transaction as EthersTransaction, TransactionRequest as EthersLegacyTransactionRequest, - TransactionRequest, H256, U256, U64, -}; - -impl From for EthersTypedTransactionRequest { - fn from(tx: TypedTransactionRequest) -> Self { - match tx { - TypedTransactionRequest::Legacy(tx) => { - let LegacyTransactionRequest { - nonce, - gas_price, - gas_limit, - kind, - value, - input, - chain_id, - } = tx; - EthersTypedTransactionRequest::Legacy(EthersLegacyTransactionRequest { - from: None, - to: kind.as_call().cloned().map(Into::into), - gas: Some(gas_limit), - gas_price: Some(gas_price), - value: Some(value), - data: Some(input), - nonce: Some(nonce), - chain_id: chain_id.map(Into::into), - }) - } - TypedTransactionRequest::EIP2930(tx) => { - let EIP2930TransactionRequest { - chain_id, - nonce, - gas_price, - gas_limit, - kind, - value, - input, - access_list, - } = tx; - EthersTypedTransactionRequest::Eip2930(EthersEip2930TransactionRequest { - tx: EthersLegacyTransactionRequest { - from: None, - to: kind.as_call().cloned().map(Into::into), - gas: Some(gas_limit), - gas_price: Some(gas_price), - value: Some(value), - data: Some(input), - nonce: Some(nonce), - chain_id: Some(chain_id.into()), - }, - access_list: access_list.into(), - }) - } - TypedTransactionRequest::EIP1559(tx) => { - let EIP1559TransactionRequest { - chain_id, - nonce, - max_priority_fee_per_gas, - max_fee_per_gas, - gas_limit, - kind, - value, - input, - access_list, - } = tx; - EthersTypedTransactionRequest::Eip1559(EthersEip1559TransactionRequest { - from: None, - to: kind.as_call().cloned().map(Into::into), - gas: Some(gas_limit), - value: Some(value), - data: Some(input), - nonce: Some(nonce), - access_list: access_list.into(), - max_priority_fee_per_gas: Some(max_priority_fee_per_gas), - max_fee_per_gas: Some(max_fee_per_gas), - chain_id: Some(chain_id.into()), - }) - } - TypedTransactionRequest::Deposit(tx) => { - let DepositTransactionRequest { - source_hash, - from, - kind, - mint, - value, - gas_limit, - is_system_tx, - input, - } = tx; - EthersTypedTransactionRequest::DepositTransaction(DepositTransaction { - tx: TransactionRequest { - from: Some(from), - to: kind.as_call().cloned().map(Into::into), - gas: Some(gas_limit), - value: Some(value), - data: Some(input), - gas_price: Some(0.into()), - nonce: Some(0.into()), - chain_id: None, - }, - source_hash, - mint: Some(mint), - is_system_tx, - }) - } - } - } -} - -fn to_ethers_transaction_with_hash_and_sender( - transaction: TypedTransaction, - hash: H256, - from: Address, -) -> EthersTransaction { - match transaction { - TypedTransaction::Legacy(t) => EthersTransaction { - hash, - nonce: t.nonce, - block_hash: None, - block_number: None, - transaction_index: None, - from, - to: None, - value: t.value, - gas_price: Some(t.gas_price), - max_fee_per_gas: Some(t.gas_price), - max_priority_fee_per_gas: Some(t.gas_price), - gas: t.gas_limit, - input: t.input.clone(), - chain_id: t.chain_id().map(Into::into), - v: t.signature.v.into(), - r: t.signature.r, - s: t.signature.s, - access_list: None, - transaction_type: None, - source_hash: H256::zero(), - mint: None, - is_system_tx: false, - other: Default::default(), - }, - TypedTransaction::EIP2930(t) => EthersTransaction { - hash, - nonce: t.nonce, - block_hash: None, - block_number: None, - transaction_index: None, - from, - to: None, - value: t.value, - gas_price: Some(t.gas_price), - max_fee_per_gas: Some(t.gas_price), - max_priority_fee_per_gas: Some(t.gas_price), - gas: t.gas_limit, - input: t.input.clone(), - chain_id: Some(t.chain_id.into()), - v: U64::from(t.odd_y_parity as u8), - r: U256::from(t.r.as_bytes()), - s: U256::from(t.s.as_bytes()), - access_list: Some(t.access_list), - transaction_type: Some(1u64.into()), - source_hash: H256::zero(), - mint: None, - is_system_tx: false, - other: Default::default(), - }, - TypedTransaction::EIP1559(t) => EthersTransaction { - hash, - nonce: t.nonce, - block_hash: None, - block_number: None, - transaction_index: None, - from, - to: None, - value: t.value, - gas_price: None, - max_fee_per_gas: Some(t.max_fee_per_gas), - max_priority_fee_per_gas: Some(t.max_priority_fee_per_gas), - gas: t.gas_limit, - input: t.input.clone(), - chain_id: Some(t.chain_id.into()), - v: U64::from(t.odd_y_parity as u8), - r: U256::from(t.r.as_bytes()), - s: U256::from(t.s.as_bytes()), - access_list: Some(t.access_list), - transaction_type: Some(2u64.into()), - source_hash: H256::zero(), - mint: None, - is_system_tx: false, - other: Default::default(), - }, - TypedTransaction::Deposit(t) => EthersTransaction { - hash, - nonce: t.nonce, - block_hash: None, - block_number: None, - transaction_index: None, - from, - to: None, - value: t.value, - gas_price: Some(0.into()), - max_fee_per_gas: Some(0.into()), - max_priority_fee_per_gas: Some(0.into()), - gas: t.gas_limit, - input: t.input.clone(), - chain_id: t.chain_id().map(Into::into), - v: 0.into(), - r: 0.into(), - s: 0.into(), - access_list: None, - transaction_type: Some(126u64.into()), - source_hash: t.source_hash, - mint: Some(t.mint), - is_system_tx: t.is_system_tx, - other: Default::default(), - }, - } -} - -impl From for EthersTransaction { - fn from(transaction: TypedTransaction) -> Self { - let hash = transaction.hash(); - let sender = transaction.recover().unwrap_or_default(); - to_ethers_transaction_with_hash_and_sender(transaction, hash, sender) - } -} - -impl From for EthersTransaction { - fn from(transaction: MaybeImpersonatedTransaction) -> Self { - let hash = transaction.hash(); - let sender = transaction.recover().unwrap_or_default(); - to_ethers_transaction_with_hash_and_sender(transaction.into(), hash, sender) - } -} - -impl From for EthTransactionRequest { - fn from(req: TransactionRequest) -> Self { - let TransactionRequest { from, to, gas, gas_price, value, data, nonce, chain_id, .. } = req; - EthTransactionRequest { - from, - to: to.and_then(|to| match to { - NameOrAddress::Name(_) => None, - NameOrAddress::Address(to) => Some(to), - }), - gas_price, - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - gas, - value, - data, - nonce, - chain_id, - access_list: None, - transaction_type: None, - optimism_fields: None, - } - } -} - -impl From for TransactionRequest { - fn from(req: EthTransactionRequest) -> Self { - let EthTransactionRequest { from, to, gas_price, gas, value, data, nonce, .. } = req; - TransactionRequest { - from, - to: to.map(NameOrAddress::Address), - gas, - gas_price, - value, - data, - nonce, - chain_id: None, - } - } -} diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index b07914480f8a3..c52beb16e1045 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -1,519 +1,195 @@ -//! transaction related data +//! Transaction related types -use crate::eth::{ - receipt::Log, - utils::{enveloped, to_revm_access_list}, +use crate::eth::transaction::optimism::{DepositTransaction, DepositTransactionRequest}; +use alloy_consensus::{ + AnyReceiptEnvelope, BlobTransactionSidecar, Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, + TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip4844WithSidecar, TxEnvelope, TxLegacy, + TxReceipt, }; -use ethers_core::{ - types::{ - transaction::eip2930::{AccessList, AccessListItem}, - Address, Bloom, Bytes, Signature, SignatureError, TxHash, H256, U256, U64, - }, - utils::{ - keccak256, rlp, - rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}, - }, +use alloy_eips::eip2718::{Decodable2718, Encodable2718}; +use alloy_primitives::{Address, Bloom, Bytes, Log, Signature, TxHash, TxKind, B256, U256}; +use alloy_rlp::{length_of_length, Decodable, Encodable, Header}; +use alloy_rpc_types::{ + request::TransactionRequest, AccessList, AnyTransactionReceipt, Signature as RpcSignature, + Transaction as RpcTransaction, TransactionReceipt, WithOtherFields, }; -use foundry_common::types::ToAlloy; -use foundry_evm::traces::CallTraceArena; +use bytes::BufMut; +use foundry_evm::traces::CallTraceNode; use revm::{ interpreter::InstructionResult, primitives::{CreateScheme, OptimismFields, TransactTo, TxEnv}, }; -use std::ops::Deref; +use serde::{Deserialize, Serialize}; +use std::ops::{Deref, Mul}; -/// compatibility with `ethers-rs` types -mod ethers_compat; +pub mod optimism; /// The signature used to bypass signing via the `eth_sendUnsignedTransaction` cheat RPC #[cfg(feature = "impersonated-tx")] -pub const IMPERSONATED_SIGNATURE: Signature = - Signature { r: U256([0, 0, 0, 0]), s: U256([0, 0, 0, 0]), v: 0 }; - -/// Container type for various Ethereum transaction requests -/// -/// Its variants correspond to specific allowed transactions: -/// 1. Legacy (pre-EIP2718) [`LegacyTransactionRequest`] -/// 2. EIP2930 (state access lists) [`EIP2930TransactionRequest`] -/// 3. EIP1559 [`EIP1559TransactionRequest`] -/// 4. Deposit [`DepositTransactionRequest`] -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum TypedTransactionRequest { - Legacy(LegacyTransactionRequest), - EIP2930(EIP2930TransactionRequest), - EIP1559(EIP1559TransactionRequest), - Deposit(DepositTransactionRequest), -} - -/// Represents _all_ transaction requests received from RPC -#[derive(Clone, Debug, PartialEq, Eq, Default, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct EthTransactionRequest { - /// from address - pub from: Option
, - /// to address - pub to: Option
, - /// legacy, gas Price - #[cfg_attr(feature = "serde", serde(default))] - pub gas_price: Option, - /// max base fee per gas sender is willing to pay - #[cfg_attr(feature = "serde", serde(default))] - pub max_fee_per_gas: Option, - /// miner tip - #[cfg_attr(feature = "serde", serde(default))] - pub max_priority_fee_per_gas: Option, - /// gas - pub gas: Option, - /// value of th tx in wei - pub value: Option, - /// Any additional data sent - #[cfg_attr(feature = "serde", serde(alias = "input"))] - pub data: Option, - /// Transaction nonce - pub nonce: Option, - /// chain id - #[cfg_attr(feature = "serde", serde(default))] - pub chain_id: Option, - /// warm storage access pre-payment - #[cfg_attr(feature = "serde", serde(default))] - pub access_list: Option>, - /// EIP-2718 type - #[cfg_attr(feature = "serde", serde(rename = "type"))] - pub transaction_type: Option, - /// Optimism Deposit Request Fields - #[serde(flatten)] - pub optimism_fields: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq, Default, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct OptimismDepositRequestFields { - /// op-stack deposit source hash - pub source_hash: H256, - /// op-stack deposit mint - pub mint: U256, - /// op-stack deposit system tx - pub is_system_tx: bool, -} - -// == impl EthTransactionRequest == - -impl EthTransactionRequest { - /// Converts the request into a [TypedTransactionRequest] - pub fn into_typed_request(self) -> Option { - let EthTransactionRequest { - from, - to, - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - gas, - value, - data, - nonce, - mut access_list, - chain_id, - transaction_type, - optimism_fields, - .. - } = self; - let chain_id = chain_id.map(|id| id.as_u64()); - let transaction_type = transaction_type.map(|id| id.as_u64()); - // op-stack deposit tx - if optimism_fields.is_some() && transaction_type == Some(126) { - return Some(TypedTransactionRequest::Deposit(DepositTransactionRequest { - source_hash: optimism_fields.clone()?.source_hash, - from: from.unwrap_or_default(), - kind: match to { - Some(to) => TransactionKind::Call(to), - None => TransactionKind::Create, - }, - mint: optimism_fields.clone()?.mint, - value: value.unwrap_or_default(), +pub fn impersonated_signature() -> Signature { + Signature::from_scalars_and_parity(B256::with_last_byte(1), B256::with_last_byte(1), false) + .unwrap() +} + +/// Converts a [TransactionRequest] into a [TypedTransactionRequest]. +/// Should be removed once the call builder abstraction for providers is in place. +pub fn transaction_request_to_typed( + tx: WithOtherFields, +) -> Option { + let WithOtherFields:: { + inner: + TransactionRequest { + from, + to, + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + max_fee_per_blob_gas, + blob_versioned_hashes, + gas, + value, + input, + nonce, + access_list, + sidecar, + transaction_type, + .. + }, + other, + } = tx; + + // Special case: OP-stack deposit tx + if transaction_type == Some(126) { + return Some(TypedTransactionRequest::Deposit(DepositTransactionRequest { + from: from.unwrap_or_default(), + source_hash: other.get_deserialized::("sourceHash")?.ok()?, + kind: to.unwrap_or_default(), + mint: other.get_deserialized::("mint")?.ok()?, + value: value.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), + is_system_tx: other.get_deserialized::("isSystemTx")?.ok()?, + input: input.into_input().unwrap_or_default(), + })) + } + + match ( + transaction_type, + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + access_list.as_ref(), + max_fee_per_blob_gas, + blob_versioned_hashes.as_ref(), + sidecar, + to, + ) { + // legacy transaction + (Some(0), _, None, None, None, None, None, None, _) | + (None, Some(_), None, None, None, None, None, None, _) => { + Some(TypedTransactionRequest::Legacy(TxLegacy { + nonce: nonce.unwrap_or_default(), + gas_price: gas_price.unwrap_or_default(), gas_limit: gas.unwrap_or_default(), - is_system_tx: optimism_fields.clone()?.is_system_tx, - input: data.clone().unwrap_or_default(), + value: value.unwrap_or(U256::ZERO), + input: input.into_input().unwrap_or_default(), + to: to.unwrap_or_default(), + chain_id: None, })) } - match ( - transaction_type, - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - access_list.take(), - ) { - // legacy transaction - (Some(0), _, None, None, None) | (None, Some(_), None, None, None) => { - Some(TypedTransactionRequest::Legacy(LegacyTransactionRequest { - nonce: nonce.unwrap_or(U256::zero()), - gas_price: gas_price.unwrap_or_default(), - gas_limit: gas.unwrap_or_default(), - value: value.unwrap_or(U256::zero()), - input: data.unwrap_or_default(), - kind: match to { - Some(to) => TransactionKind::Call(to), - None => TransactionKind::Create, - }, - chain_id, - })) - } - // EIP2930 - (Some(1), _, None, None, _) | (None, _, None, None, Some(_)) => { - Some(TypedTransactionRequest::EIP2930(EIP2930TransactionRequest { - nonce: nonce.unwrap_or(U256::zero()), - gas_price: gas_price.unwrap_or_default(), - gas_limit: gas.unwrap_or_default(), - value: value.unwrap_or(U256::zero()), - input: data.unwrap_or_default(), - kind: match to { - Some(to) => TransactionKind::Call(to), - None => TransactionKind::Create, - }, - chain_id: chain_id.unwrap_or_default(), - access_list: access_list.unwrap_or_default(), - })) - } - // EIP1559 - (Some(2), None, _, _, _) | - (None, None, Some(_), _, _) | - (None, None, _, Some(_), _) | - (None, None, None, None, None) => { - // Empty fields fall back to the canonical transaction schema. - Some(TypedTransactionRequest::EIP1559(EIP1559TransactionRequest { - nonce: nonce.unwrap_or(U256::zero()), - max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), - max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or(U256::zero()), - gas_limit: gas.unwrap_or_default(), - value: value.unwrap_or(U256::zero()), - input: data.unwrap_or_default(), - kind: match to { - Some(to) => TransactionKind::Call(to), - None => TransactionKind::Create, - }, - chain_id: chain_id.unwrap_or_default(), - access_list: access_list.unwrap_or_default(), - })) - } - _ => None, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum TransactionKind { - Call(Address), - Create, -} - -// == impl TransactionKind == - -impl TransactionKind { - /// If this transaction is a call this returns the address of the callee - pub fn as_call(&self) -> Option<&Address> { - match self { - TransactionKind::Call(to) => Some(to), - TransactionKind::Create => None, - } - } -} - -impl Encodable for TransactionKind { - fn rlp_append(&self, s: &mut RlpStream) { - match self { - TransactionKind::Call(address) => { - s.encoder().encode_value(&address[..]); - } - TransactionKind::Create => s.encoder().encode_value(&[]), - } - } -} - -impl Decodable for TransactionKind { - fn decode(rlp: &Rlp) -> Result { - if rlp.is_empty() { - if rlp.is_data() { - Ok(TransactionKind::Create) - } else { - Err(DecoderError::RlpExpectedToBeData) - } - } else { - Ok(TransactionKind::Call(rlp.as_val()?)) - } - } -} - -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Encodable for TransactionKind { - fn length(&self) -> usize { - match self { - TransactionKind::Call(to) => to.length(), - TransactionKind::Create => ([]).length(), - } - } - fn encode(&self, out: &mut dyn open_fastrlp::BufMut) { - match self { - TransactionKind::Call(to) => to.encode(out), - TransactionKind::Create => ([]).encode(out), - } - } -} - -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Decodable for TransactionKind { - fn decode(buf: &mut &[u8]) -> Result { - use bytes::Buf; - - if let Some(&first) = buf.first() { - if first == 0x80 { - buf.advance(1); - Ok(TransactionKind::Create) - } else { - let addr =
::decode(buf)?; - Ok(TransactionKind::Call(addr)) - } - } else { - Err(open_fastrlp::DecodeError::InputTooShort) - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -pub struct EIP2930TransactionRequest { - pub chain_id: u64, - pub nonce: U256, - pub gas_price: U256, - pub gas_limit: U256, - pub kind: TransactionKind, - pub value: U256, - pub input: Bytes, - pub access_list: Vec, -} - -impl EIP2930TransactionRequest { - pub fn hash(&self) -> H256 { - let encoded = rlp::encode(self); - let mut out = vec![0; 1 + encoded.len()]; - out[0] = 1; - out[1..].copy_from_slice(&encoded); - H256::from_slice(keccak256(&out).as_slice()) - } -} - -impl From for EIP2930TransactionRequest { - fn from(tx: EIP2930Transaction) -> Self { - Self { - chain_id: tx.chain_id, - nonce: tx.nonce, - gas_price: tx.gas_price, - gas_limit: tx.gas_limit, - kind: tx.kind, - value: tx.value, - input: tx.input, - access_list: tx.access_list.0, - } - } -} - -impl Encodable for EIP2930TransactionRequest { - fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(8); - s.append(&self.chain_id); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas_limit); - s.append(&self.kind); - s.append(&self.value); - s.append(&self.input.as_ref()); - s.append_list(&self.access_list); - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct LegacyTransactionRequest { - pub nonce: U256, - pub gas_price: U256, - pub gas_limit: U256, - pub kind: TransactionKind, - pub value: U256, - pub input: Bytes, - pub chain_id: Option, -} - -// == impl LegacyTransactionRequest == - -impl LegacyTransactionRequest { - pub fn hash(&self) -> H256 { - H256::from_slice(keccak256(&rlp::encode(self)).as_slice()) - } -} - -impl From for LegacyTransactionRequest { - fn from(tx: LegacyTransaction) -> Self { - let chain_id = tx.chain_id(); - Self { - nonce: tx.nonce, - gas_price: tx.gas_price, - gas_limit: tx.gas_limit, - kind: tx.kind, - value: tx.value, - input: tx.input, - chain_id, - } - } -} - -impl Encodable for LegacyTransactionRequest { - fn rlp_append(&self, s: &mut RlpStream) { - if let Some(chain_id) = self.chain_id { - s.begin_list(9); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas_limit); - s.append(&self.kind); - s.append(&self.value); - s.append(&self.input.as_ref()); - s.append(&chain_id); - s.append(&0u8); - s.append(&0u8); - } else { - s.begin_list(6); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas_limit); - s.append(&self.kind); - s.append(&self.value); - s.append(&self.input.as_ref()); + // EIP2930 + (Some(1), _, None, None, _, None, None, None, _) | + (None, _, None, None, Some(_), None, None, None, _) => { + Some(TypedTransactionRequest::EIP2930(TxEip2930 { + nonce: nonce.unwrap_or_default(), + gas_price: gas_price.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), + value: value.unwrap_or(U256::ZERO), + input: input.into_input().unwrap_or_default(), + to: to.unwrap_or_default(), + chain_id: 0, + access_list: access_list.unwrap_or_default(), + })) } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -pub struct EIP1559TransactionRequest { - pub chain_id: u64, - pub nonce: U256, - pub max_priority_fee_per_gas: U256, - pub max_fee_per_gas: U256, - pub gas_limit: U256, - pub kind: TransactionKind, - pub value: U256, - pub input: Bytes, - pub access_list: Vec, -} - -// == impl EIP1559TransactionRequest == - -impl EIP1559TransactionRequest { - pub fn hash(&self) -> H256 { - let encoded = rlp::encode(self); - let mut out = vec![0; 1 + encoded.len()]; - out[0] = 2; - out[1..].copy_from_slice(&encoded); - H256::from_slice(keccak256(&out).as_slice()) - } -} - -impl From for EIP1559TransactionRequest { - fn from(t: EIP1559Transaction) -> Self { - Self { - chain_id: t.chain_id, - nonce: t.nonce, - max_priority_fee_per_gas: t.max_priority_fee_per_gas, - max_fee_per_gas: t.max_fee_per_gas, - gas_limit: t.gas_limit, - kind: t.kind, - value: t.value, - input: t.input, - access_list: t.access_list.0, + // EIP1559 + (Some(2), None, _, _, _, _, None, None, _) | + (None, None, Some(_), _, _, _, None, None, _) | + (None, None, _, Some(_), _, _, None, None, _) | + (None, None, None, None, None, _, None, None, _) => { + // Empty fields fall back to the canonical transaction schema. + Some(TypedTransactionRequest::EIP1559(TxEip1559 { + nonce: nonce.unwrap_or_default(), + max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), + max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), + value: value.unwrap_or(U256::ZERO), + input: input.into_input().unwrap_or_default(), + to: to.unwrap_or_default(), + chain_id: 0, + access_list: access_list.unwrap_or_default(), + })) } - } -} - -impl Encodable for EIP1559TransactionRequest { - fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(9); - s.append(&self.chain_id); - s.append(&self.nonce); - s.append(&self.max_priority_fee_per_gas); - s.append(&self.max_fee_per_gas); - s.append(&self.gas_limit); - s.append(&self.kind); - s.append(&self.value); - s.append(&self.input.as_ref()); - s.append_list(&self.access_list); - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct DepositTransactionRequest { - pub from: Address, - pub source_hash: H256, - pub kind: TransactionKind, - pub mint: U256, - pub value: U256, - pub gas_limit: U256, - pub is_system_tx: bool, - pub input: Bytes, -} - -// == impl DepositTransactionRequest == - -impl DepositTransactionRequest { - pub fn hash(&self) -> H256 { - H256::from_slice(keccak256(&rlp::encode(self)).as_slice()) - } -} - -impl From for DepositTransactionRequest { - fn from(tx: DepositTransaction) -> Self { - Self { - from: tx.from, - source_hash: tx.source_hash, - kind: tx.kind, - mint: tx.mint, - value: tx.value, - gas_limit: tx.gas_limit, - is_system_tx: tx.is_system_tx, - input: tx.input, + // EIP4844 + (Some(3), None, _, _, _, Some(_), Some(_), Some(sidecar), to) => { + let tx = TxEip4844 { + nonce: nonce.unwrap_or_default(), + max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), + max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(), + max_fee_per_blob_gas: max_fee_per_blob_gas.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), + value: value.unwrap_or(U256::ZERO), + input: input.into_input().unwrap_or_default(), + to: match to.unwrap_or(TxKind::Create) { + TxKind::Call(to) => to, + TxKind::Create => Address::ZERO, + }, + chain_id: 0, + access_list: access_list.unwrap_or_default(), + blob_versioned_hashes: blob_versioned_hashes.unwrap_or_default(), + }; + let blob_sidecar = BlobTransactionSidecar { + blobs: sidecar + .blobs + .into_iter() + .map(|b| c_kzg::Blob::from_bytes(b.as_slice()).unwrap()) + .collect(), + commitments: sidecar + .commitments + .into_iter() + .map(|c| c_kzg::Bytes48::from_bytes(c.as_slice()).unwrap()) + .collect(), + proofs: sidecar + .proofs + .into_iter() + .map(|p| c_kzg::Bytes48::from_bytes(p.as_slice()).unwrap()) + .collect(), + }; + Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844WithSidecar( + TxEip4844WithSidecar::from_tx_and_sidecar(tx, blob_sidecar), + ))) } + _ => None, } } -impl Encodable for DepositTransactionRequest { - fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(8); - s.append(&self.from); - s.append(&self.source_hash); - s.append(&self.kind); - s.append(&self.mint); - s.append(&self.value); - s.append(&self.gas_limit); - s.append(&self.is_system_tx); - s.append(&self.input.as_ref()); - } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum TypedTransactionRequest { + Legacy(TxLegacy), + EIP2930(TxEip2930), + EIP1559(TxEip1559), + EIP4844(TxEip4844Variant), + Deposit(DepositTransactionRequest), } -/// A wrapper for `TypedTransaction` that allows impersonating accounts. +/// A wrapper for [TypedTransaction] that allows impersonating accounts. /// /// This is a helper that carries the `impersonated` sender so that the right hash /// [TypedTransaction::impersonated_hash] can be created. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct MaybeImpersonatedTransaction { - #[cfg_attr(feature = "serde", serde(flatten))] pub transaction: TypedTransaction, - #[cfg_attr(feature = "serde", serde(skip))] pub impersonated_sender: Option
, } -// === impl MaybeImpersonatedTransaction === - impl MaybeImpersonatedTransaction { /// Creates a new wrapper for the given transaction pub fn new(transaction: TypedTransaction) -> Self { @@ -530,7 +206,7 @@ impl MaybeImpersonatedTransaction { /// Note: this is feature gated so it does not conflict with the `Deref`ed /// [TypedTransaction::recover] function by default. #[cfg(feature = "impersonated-tx")] - pub fn recover(&self) -> Result { + pub fn recover(&self) -> Result { if let Some(sender) = self.impersonated_sender { return Ok(sender) } @@ -542,7 +218,7 @@ impl MaybeImpersonatedTransaction { /// Note: this is feature gated so it does not conflict with the `Deref`ed /// [TypedTransaction::hash] function by default. #[cfg(feature = "impersonated-tx")] - pub fn hash(&self) -> H256 { + pub fn hash(&self) -> B256 { if self.transaction.is_impersonated() { if let Some(sender) = self.impersonated_sender { return self.transaction.impersonated_hash(sender) @@ -553,8 +229,8 @@ impl MaybeImpersonatedTransaction { } impl Encodable for MaybeImpersonatedTransaction { - fn rlp_append(&self, s: &mut RlpStream) { - self.transaction.rlp_append(s) + fn encode(&self, out: &mut dyn bytes::BufMut) { + self.transaction.encode(out) } } @@ -571,26 +247,8 @@ impl From for MaybeImpersonatedTransaction { } impl Decodable for MaybeImpersonatedTransaction { - fn decode(rlp: &Rlp) -> Result { - let transaction = TypedTransaction::decode(rlp)?; - Ok(Self { transaction, impersonated_sender: None }) - } -} - -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Encodable for MaybeImpersonatedTransaction { - fn encode(&self, out: &mut dyn open_fastrlp::BufMut) { - self.transaction.encode(out) - } - fn length(&self) -> usize { - self.transaction.length() - } -} - -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Decodable for MaybeImpersonatedTransaction { - fn decode(buf: &mut &[u8]) -> Result { - Ok(Self { transaction: open_fastrlp::Decodable::decode(buf)?, impersonated_sender: None }) + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + TypedTransaction::decode(buf).map(MaybeImpersonatedTransaction::new) } } @@ -608,125 +266,514 @@ impl Deref for MaybeImpersonatedTransaction { } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum TypedTransaction { - /// Legacy transaction type - Legacy(LegacyTransaction), - /// EIP-2930 transaction - EIP2930(EIP2930Transaction), - /// EIP-1559 transaction - EIP1559(EIP1559Transaction), - /// op-stack deposit transaction - Deposit(DepositTransaction), +impl From for RpcTransaction { + fn from(value: MaybeImpersonatedTransaction) -> Self { + let hash = value.hash(); + let sender = value.recover().unwrap_or_default(); + to_alloy_transaction_with_hash_and_sender(value.transaction, hash, sender) + } +} + +pub fn to_alloy_transaction_with_hash_and_sender( + transaction: TypedTransaction, + hash: B256, + from: Address, +) -> RpcTransaction { + match transaction { + TypedTransaction::Legacy(t) => RpcTransaction { + hash, + nonce: t.tx().nonce, + block_hash: None, + block_number: None, + transaction_index: None, + from, + to: None, + value: t.tx().value, + gas_price: Some(t.tx().gas_price), + max_fee_per_gas: Some(t.tx().gas_price), + max_priority_fee_per_gas: Some(t.tx().gas_price), + gas: t.tx().gas_limit, + input: t.tx().input.clone(), + chain_id: t.tx().chain_id, + signature: Some(RpcSignature { + r: t.signature().r(), + s: t.signature().s(), + v: U256::from(t.signature().v().y_parity_byte()), + y_parity: None, + }), + access_list: None, + transaction_type: None, + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + other: Default::default(), + }, + TypedTransaction::EIP2930(t) => RpcTransaction { + hash, + nonce: t.tx().nonce, + block_hash: None, + block_number: None, + transaction_index: None, + from, + to: None, + value: t.tx().value, + gas_price: Some(t.tx().gas_price), + max_fee_per_gas: Some(t.tx().gas_price), + max_priority_fee_per_gas: Some(t.tx().gas_price), + gas: t.tx().gas_limit, + input: t.tx().input.clone(), + chain_id: Some(t.tx().chain_id), + signature: Some(RpcSignature { + r: t.signature().r(), + s: t.signature().s(), + v: U256::from(t.signature().v().y_parity_byte()), + y_parity: Some(alloy_rpc_types::Parity::from(t.signature().v().y_parity())), + }), + access_list: Some(t.tx().access_list.clone()), + transaction_type: Some(1), + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + other: Default::default(), + }, + TypedTransaction::EIP1559(t) => RpcTransaction { + hash, + nonce: t.tx().nonce, + block_hash: None, + block_number: None, + transaction_index: None, + from, + to: None, + value: t.tx().value, + gas_price: None, + max_fee_per_gas: Some(t.tx().max_fee_per_gas), + max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas), + gas: t.tx().gas_limit, + input: t.tx().input.clone(), + chain_id: Some(t.tx().chain_id), + signature: Some(RpcSignature { + r: t.signature().r(), + s: t.signature().s(), + v: U256::from(t.signature().v().y_parity_byte()), + y_parity: Some(alloy_rpc_types::Parity::from(t.signature().v().y_parity())), + }), + access_list: Some(t.tx().access_list.clone()), + transaction_type: Some(2), + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + other: Default::default(), + }, + TypedTransaction::EIP4844(t) => RpcTransaction { + hash, + nonce: t.tx().tx().nonce, + block_hash: None, + block_number: None, + transaction_index: None, + from, + to: None, + value: t.tx().tx().value, + gas_price: Some(t.tx().tx().max_fee_per_gas), + max_fee_per_gas: Some(t.tx().tx().max_fee_per_gas), + max_priority_fee_per_gas: Some(t.tx().tx().max_priority_fee_per_gas), + gas: t.tx().tx().gas_limit, + input: t.tx().tx().input.clone(), + chain_id: Some(t.tx().tx().chain_id), + signature: Some(RpcSignature { + r: t.signature().r(), + s: t.signature().s(), + v: U256::from(t.signature().v().y_parity_byte()), + y_parity: Some(alloy_rpc_types::Parity::from(t.signature().v().y_parity())), + }), + access_list: Some(t.tx().tx().access_list.clone()), + transaction_type: Some(3), + max_fee_per_blob_gas: Some(t.tx().tx().max_fee_per_blob_gas), + blob_versioned_hashes: Some(t.tx().tx().blob_versioned_hashes.clone()), + other: Default::default(), + }, + TypedTransaction::Deposit(t) => RpcTransaction { + hash, + nonce: t.nonce, + block_hash: None, + block_number: None, + transaction_index: None, + from, + to: None, + value: t.value, + gas_price: None, + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + gas: t.gas_limit, + input: t.input.clone().0.into(), + chain_id: t.chain_id().map(u64::from), + signature: None, + access_list: None, + transaction_type: None, + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + other: Default::default(), + }, + } } -// == impl TypedTransaction == +/// Queued transaction +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PendingTransaction { + /// The actual transaction + pub transaction: MaybeImpersonatedTransaction, + /// the recovered sender of this transaction + sender: Address, + /// hash of `transaction`, so it can easily be reused with encoding and hashing agan + hash: TxHash, +} -impl TypedTransaction { - /// Returns true if the transaction uses dynamic fees: EIP1559 - pub fn is_dynamic_fee(&self) -> bool { - matches!(self, TypedTransaction::EIP1559(_)) +impl PendingTransaction { + pub fn new(transaction: TypedTransaction) -> Result { + let sender = transaction.recover()?; + let hash = transaction.hash(); + Ok(Self { transaction: MaybeImpersonatedTransaction::new(transaction), sender, hash }) } - pub fn gas_price(&self) -> U256 { - match self { - TypedTransaction::Legacy(tx) => tx.gas_price, - TypedTransaction::EIP2930(tx) => tx.gas_price, - TypedTransaction::EIP1559(tx) => tx.max_fee_per_gas, - TypedTransaction::Deposit(_) => U256::from(0), + #[cfg(feature = "impersonated-tx")] + pub fn with_impersonated(transaction: TypedTransaction, sender: Address) -> Self { + let hash = transaction.impersonated_hash(sender); + Self { + transaction: MaybeImpersonatedTransaction::impersonated(transaction, sender), + sender, + hash, } } - pub fn gas_limit(&self) -> U256 { - match self { - TypedTransaction::Legacy(tx) => tx.gas_limit, - TypedTransaction::EIP2930(tx) => tx.gas_limit, - TypedTransaction::EIP1559(tx) => tx.gas_limit, - TypedTransaction::Deposit(tx) => tx.gas_limit, - } + pub fn nonce(&self) -> u64 { + self.transaction.nonce() } - pub fn value(&self) -> U256 { - match self { - TypedTransaction::Legacy(tx) => tx.value, - TypedTransaction::EIP2930(tx) => tx.value, - TypedTransaction::EIP1559(tx) => tx.value, - TypedTransaction::Deposit(tx) => tx.value, - } + pub fn hash(&self) -> &TxHash { + &self.hash } - pub fn data(&self) -> &Bytes { - match self { - TypedTransaction::Legacy(tx) => &tx.input, - TypedTransaction::EIP2930(tx) => &tx.input, - TypedTransaction::EIP1559(tx) => &tx.input, - TypedTransaction::Deposit(tx) => &tx.input, - } + pub fn sender(&self) -> &Address { + &self.sender } - /// Returns the transaction type - pub fn r#type(&self) -> Option { - match self { - TypedTransaction::Legacy(_) => None, - TypedTransaction::EIP2930(_) => Some(1), - TypedTransaction::EIP1559(_) => Some(2), - TypedTransaction::Deposit(_) => Some(0x7E), + /// Converts the [PendingTransaction] into the [TxEnv] context that [`revm`](foundry_evm) + /// expects. + pub fn to_revm_tx_env(&self) -> TxEnv { + fn transact_to(kind: &TxKind) -> TransactTo { + match kind { + TxKind::Call(c) => TransactTo::Call(*c), + TxKind::Create => TransactTo::Create(CreateScheme::Create), + } } - } - - /// Max cost of the transaction - pub fn max_cost(&self) -> U256 { - self.gas_limit().saturating_mul(self.gas_price()) - } - /// Returns a helper type that contains commonly used values as fields - pub fn essentials(&self) -> TransactionEssentials { - match self { - TypedTransaction::Legacy(t) => TransactionEssentials { - kind: t.kind, - input: t.input.clone(), - nonce: t.nonce, - gas_limit: t.gas_limit, - gas_price: Some(t.gas_price), - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - value: t.value, - chain_id: t.chain_id(), - access_list: Default::default(), - }, - TypedTransaction::EIP2930(t) => TransactionEssentials { - kind: t.kind, - input: t.input.clone(), - nonce: t.nonce, - gas_limit: t.gas_limit, - gas_price: Some(t.gas_price), - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - value: t.value, - chain_id: Some(t.chain_id), - access_list: t.access_list.clone(), - }, - TypedTransaction::EIP1559(t) => TransactionEssentials { - kind: t.kind, - input: t.input.clone(), - nonce: t.nonce, - gas_limit: t.gas_limit, - gas_price: None, - max_fee_per_gas: Some(t.max_fee_per_gas), - max_priority_fee_per_gas: Some(t.max_priority_fee_per_gas), - value: t.value, - chain_id: Some(t.chain_id), - access_list: t.access_list.clone(), - }, - TypedTransaction::Deposit(t) => TransactionEssentials { - kind: t.kind, - input: t.input.clone(), - nonce: t.nonce, - gas_limit: t.gas_limit, - gas_price: Some(U256::from(0)), - max_fee_per_gas: None, - max_priority_fee_per_gas: None, + let caller = *self.sender(); + match &self.transaction.transaction { + TypedTransaction::Legacy(tx) => { + let chain_id = tx.tx().chain_id; + let TxLegacy { nonce, gas_price, gas_limit, value, to, input, .. } = tx.tx(); + TxEnv { + caller, + transact_to: transact_to(to), + data: input.clone(), + chain_id, + nonce: Some(*nonce), + value: (*value), + gas_price: U256::from(*gas_price), + gas_priority_fee: None, + gas_limit: *gas_limit as u64, + access_list: vec![], + ..Default::default() + } + } + TypedTransaction::EIP2930(tx) => { + let TxEip2930 { + chain_id, + nonce, + gas_price, + gas_limit, + to, + value, + input, + access_list, + .. + } = tx.tx(); + TxEnv { + caller, + transact_to: transact_to(to), + data: input.clone(), + chain_id: Some(*chain_id), + nonce: Some(*nonce), + value: *value, + gas_price: U256::from(*gas_price), + gas_priority_fee: None, + gas_limit: *gas_limit as u64, + access_list: access_list.flattened(), + ..Default::default() + } + } + TypedTransaction::EIP1559(tx) => { + let TxEip1559 { + chain_id, + nonce, + max_priority_fee_per_gas, + max_fee_per_gas, + gas_limit, + to, + value, + input, + access_list, + .. + } = tx.tx(); + TxEnv { + caller, + transact_to: transact_to(to), + data: input.clone(), + chain_id: Some(*chain_id), + nonce: Some(*nonce), + value: *value, + gas_price: U256::from(*max_fee_per_gas), + gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)), + gas_limit: *gas_limit as u64, + access_list: access_list.flattened(), + ..Default::default() + } + } + TypedTransaction::EIP4844(tx) => { + let TxEip4844 { + chain_id, + nonce, + max_fee_per_blob_gas, + max_fee_per_gas, + max_priority_fee_per_gas, + gas_limit, + to, + value, + input, + access_list, + blob_versioned_hashes, + .. + } = tx.tx().tx(); + TxEnv { + caller, + transact_to: TransactTo::call(*to), + data: input.clone(), + chain_id: Some(*chain_id), + nonce: Some(*nonce), + value: *value, + gas_price: U256::from(*max_fee_per_gas), + gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)), + max_fee_per_blob_gas: Some(U256::from(*max_fee_per_blob_gas)), + blob_hashes: blob_versioned_hashes.clone(), + gas_limit: *gas_limit as u64, + access_list: access_list.flattened(), + ..Default::default() + } + } + TypedTransaction::Deposit(tx) => { + let chain_id = tx.chain_id(); + let DepositTransaction { + nonce, + source_hash, + gas_limit, + value, + kind, + mint, + input, + is_system_tx, + .. + } = tx; + TxEnv { + caller, + transact_to: transact_to(kind), + data: input.clone(), + chain_id, + nonce: Some(*nonce), + value: *value, + gas_price: U256::ZERO, + gas_priority_fee: None, + gas_limit: *gas_limit as u64, + access_list: vec![], + optimism: OptimismFields { + source_hash: Some(*source_hash), + mint: Some(mint.to::()), + is_system_transaction: Some(*is_system_tx), + enveloped_tx: None, + }, + ..Default::default() + } + } + } + } +} + +/// Container type for signed, typed transactions. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum TypedTransaction { + /// Legacy transaction type + Legacy(Signed), + /// EIP-2930 transaction + EIP2930(Signed), + /// EIP-1559 transaction + EIP1559(Signed), + /// EIP-4844 transaction + EIP4844(Signed), + /// op-stack deposit transaction + Deposit(DepositTransaction), +} + +impl TypedTransaction { + /// Returns true if the transaction uses dynamic fees: EIP1559 or EIP4844 + pub fn is_dynamic_fee(&self) -> bool { + matches!(self, TypedTransaction::EIP1559(_)) || matches!(self, TypedTransaction::EIP4844(_)) + } + + pub fn gas_price(&self) -> u128 { + match self { + TypedTransaction::Legacy(tx) => tx.tx().gas_price, + TypedTransaction::EIP2930(tx) => tx.tx().gas_price, + TypedTransaction::EIP1559(tx) => tx.tx().max_fee_per_gas, + TypedTransaction::EIP4844(tx) => tx.tx().tx().max_fee_per_blob_gas, + TypedTransaction::Deposit(_) => 0, + } + } + + pub fn gas_limit(&self) -> u128 { + match self { + TypedTransaction::Legacy(tx) => tx.tx().gas_limit, + TypedTransaction::EIP2930(tx) => tx.tx().gas_limit, + TypedTransaction::EIP1559(tx) => tx.tx().gas_limit, + TypedTransaction::EIP4844(tx) => tx.tx().tx().gas_limit, + TypedTransaction::Deposit(tx) => tx.gas_limit, + } + } + + pub fn value(&self) -> U256 { + U256::from(match self { + TypedTransaction::Legacy(tx) => tx.tx().value, + TypedTransaction::EIP2930(tx) => tx.tx().value, + TypedTransaction::EIP1559(tx) => tx.tx().value, + TypedTransaction::EIP4844(tx) => tx.tx().tx().value, + TypedTransaction::Deposit(tx) => tx.value, + }) + } + + pub fn data(&self) -> &Bytes { + match self { + TypedTransaction::Legacy(tx) => &tx.tx().input, + TypedTransaction::EIP2930(tx) => &tx.tx().input, + TypedTransaction::EIP1559(tx) => &tx.tx().input, + TypedTransaction::EIP4844(tx) => &tx.tx().tx().input, + TypedTransaction::Deposit(tx) => &tx.input, + } + } + + /// Returns the transaction type + pub fn r#type(&self) -> Option { + match self { + TypedTransaction::Legacy(_) => None, + TypedTransaction::EIP2930(_) => Some(1), + TypedTransaction::EIP1559(_) => Some(2), + TypedTransaction::EIP4844(_) => Some(3), + TypedTransaction::Deposit(_) => Some(0x7E), + } + } + + /// Max cost of the transaction + /// It is the gas limit multiplied by the gas price, + /// and if the transaction is EIP-4844, the result of (total blob gas cost * max fee per blob + /// gas) is also added + pub fn max_cost(&self) -> u128 { + let mut max_cost = self.gas_limit().saturating_mul(self.gas_price()); + + if self.is_eip4844() { + max_cost = max_cost.saturating_add( + self.blob_gas().unwrap_or(0).mul(self.max_fee_per_blob_gas().unwrap_or(0)), + ) + } + + max_cost + } + + pub fn blob_gas(&self) -> Option { + match self { + TypedTransaction::EIP4844(tx) => Some(tx.tx().tx().blob_gas() as u128), + _ => None, + } + } + + pub fn max_fee_per_blob_gas(&self) -> Option { + match self { + TypedTransaction::EIP4844(tx) => Some(tx.tx().tx().max_fee_per_blob_gas), + _ => None, + } + } + + /// Returns a helper type that contains commonly used values as fields + pub fn essentials(&self) -> TransactionEssentials { + match self { + TypedTransaction::Legacy(t) => TransactionEssentials { + kind: t.tx().to, + input: t.tx().input.clone(), + nonce: t.tx().nonce, + gas_limit: t.tx().gas_limit, + gas_price: Some(U256::from(t.tx().gas_price)), + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + value: t.tx().value, + chain_id: t.tx().chain_id, + access_list: Default::default(), + }, + TypedTransaction::EIP2930(t) => TransactionEssentials { + kind: t.tx().to, + input: t.tx().input.clone(), + nonce: t.tx().nonce, + gas_limit: t.tx().gas_limit, + gas_price: Some(U256::from(t.tx().gas_price)), + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + value: t.tx().value, + chain_id: Some(t.tx().chain_id), + access_list: t.tx().access_list.clone(), + }, + TypedTransaction::EIP1559(t) => TransactionEssentials { + kind: t.tx().to, + input: t.tx().input.clone(), + nonce: t.tx().nonce, + gas_limit: t.tx().gas_limit, + gas_price: None, + max_fee_per_gas: Some(U256::from(t.tx().max_fee_per_gas)), + max_priority_fee_per_gas: Some(U256::from(t.tx().max_priority_fee_per_gas)), + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + value: t.tx().value, + chain_id: Some(t.tx().chain_id), + access_list: t.tx().access_list.clone(), + }, + TypedTransaction::EIP4844(t) => TransactionEssentials { + kind: TxKind::Call(t.tx().tx().to), + input: t.tx().tx().input.clone(), + nonce: t.tx().tx().nonce, + gas_limit: t.tx().tx().gas_limit, + gas_price: Some(U256::from(t.tx().tx().max_fee_per_blob_gas)), + max_fee_per_gas: Some(U256::from(t.tx().tx().max_fee_per_gas)), + max_priority_fee_per_gas: Some(U256::from(t.tx().tx().max_priority_fee_per_gas)), + max_fee_per_blob_gas: Some(U256::from(t.tx().tx().max_fee_per_blob_gas)), + blob_versioned_hashes: Some(t.tx().tx().blob_versioned_hashes.clone()), + value: t.tx().tx().value, + chain_id: Some(t.tx().tx().chain_id), + access_list: t.tx().tx().access_list.clone(), + }, + TypedTransaction::Deposit(t) => TransactionEssentials { + kind: t.kind, + input: t.input.clone(), + nonce: t.nonce, + gas_limit: t.gas_limit, + gas_price: Some(U256::from(0)), + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, value: t.value, chain_id: t.chain_id(), access_list: Default::default(), @@ -734,25 +781,27 @@ impl TypedTransaction { } } - pub fn nonce(&self) -> &U256 { + pub fn nonce(&self) -> u64 { match self { - TypedTransaction::Legacy(t) => t.nonce(), - TypedTransaction::EIP2930(t) => t.nonce(), - TypedTransaction::EIP1559(t) => t.nonce(), - TypedTransaction::Deposit(t) => t.nonce(), + TypedTransaction::Legacy(t) => t.tx().nonce, + TypedTransaction::EIP2930(t) => t.tx().nonce, + TypedTransaction::EIP1559(t) => t.tx().nonce, + TypedTransaction::EIP4844(t) => t.tx().tx().nonce, + TypedTransaction::Deposit(t) => t.nonce, } } pub fn chain_id(&self) -> Option { match self { - TypedTransaction::Legacy(t) => t.chain_id(), - TypedTransaction::EIP2930(t) => Some(t.chain_id), - TypedTransaction::EIP1559(t) => Some(t.chain_id), + TypedTransaction::Legacy(t) => t.tx().chain_id, + TypedTransaction::EIP2930(t) => Some(t.tx().chain_id), + TypedTransaction::EIP1559(t) => Some(t.tx().chain_id), + TypedTransaction::EIP4844(t) => Some(t.tx().tx().chain_id), TypedTransaction::Deposit(t) => t.chain_id(), } } - pub fn as_legacy(&self) -> Option<&LegacyTransaction> { + pub fn as_legacy(&self) -> Option<&Signed> { match self { TypedTransaction::Legacy(tx) => Some(tx), _ => None, @@ -769,15 +818,26 @@ impl TypedTransaction { matches!(self, TypedTransaction::EIP1559(_)) } + /// Returns true whether this tx is a EIP2930 transaction + pub fn is_eip2930(&self) -> bool { + matches!(self, TypedTransaction::EIP2930(_)) + } + + /// Returns true whether this tx is a EIP4844 transaction + pub fn is_eip4844(&self) -> bool { + matches!(self, TypedTransaction::EIP4844(_)) + } + /// Returns the hash of the transaction. /// /// Note: If this transaction has the Impersonated signature then this returns a modified unique /// hash. This allows us to treat impersonated transactions as unique. - pub fn hash(&self) -> H256 { + pub fn hash(&self) -> B256 { match self { - TypedTransaction::Legacy(t) => t.hash(), - TypedTransaction::EIP2930(t) => t.hash(), - TypedTransaction::EIP1559(t) => t.hash(), + TypedTransaction::Legacy(t) => *t.hash(), + TypedTransaction::EIP2930(t) => *t.hash(), + TypedTransaction::EIP1559(t) => *t.hash(), + TypedTransaction::EIP4844(t) => *t.hash(), TypedTransaction::Deposit(t) => t.hash(), } } @@ -785,1067 +845,674 @@ impl TypedTransaction { /// Returns true if the transaction was impersonated (using the impersonate Signature) #[cfg(feature = "impersonated-tx")] pub fn is_impersonated(&self) -> bool { - self.signature() == IMPERSONATED_SIGNATURE + self.signature() == impersonated_signature() } /// Returns the hash if the transaction is impersonated (using a fake signature) /// /// This appends the `address` before hashing it #[cfg(feature = "impersonated-tx")] - pub fn impersonated_hash(&self, sender: Address) -> H256 { - let mut bytes = rlp::encode(self); - bytes.extend_from_slice(sender.as_ref()); - H256::from_slice(keccak256(&bytes).as_slice()) + pub fn impersonated_hash(&self, sender: Address) -> B256 { + let mut buffer = Vec::::new(); + Encodable::encode(self, &mut buffer); + buffer.extend_from_slice(sender.as_ref()); + B256::from_slice(alloy_primitives::utils::keccak256(&buffer).as_slice()) } /// Recovers the Ethereum address which was used to sign the transaction. - pub fn recover(&self) -> Result { + pub fn recover(&self) -> Result { match self { - TypedTransaction::Legacy(tx) => tx.recover(), - TypedTransaction::EIP2930(tx) => tx.recover(), - TypedTransaction::EIP1559(tx) => tx.recover(), + TypedTransaction::Legacy(tx) => tx.recover_signer(), + TypedTransaction::EIP2930(tx) => tx.recover_signer(), + TypedTransaction::EIP1559(tx) => tx.recover_signer(), + TypedTransaction::EIP4844(tx) => tx.recover_signer(), TypedTransaction::Deposit(tx) => tx.recover(), } } /// Returns what kind of transaction this is - pub fn kind(&self) -> &TransactionKind { + pub fn kind(&self) -> TxKind { match self { - TypedTransaction::Legacy(tx) => &tx.kind, - TypedTransaction::EIP2930(tx) => &tx.kind, - TypedTransaction::EIP1559(tx) => &tx.kind, - TypedTransaction::Deposit(tx) => &tx.kind, + TypedTransaction::Legacy(tx) => tx.tx().to, + TypedTransaction::EIP2930(tx) => tx.tx().to, + TypedTransaction::EIP1559(tx) => tx.tx().to, + TypedTransaction::EIP4844(tx) => TxKind::Call(tx.tx().tx().to), + TypedTransaction::Deposit(tx) => tx.kind, } } /// Returns the callee if this transaction is a call - pub fn to(&self) -> Option<&Address> { - self.kind().as_call() + pub fn to(&self) -> Option
{ + self.kind().to().copied() } /// Returns the Signature of the transaction pub fn signature(&self) -> Signature { match self { - TypedTransaction::Legacy(tx) => tx.signature, - TypedTransaction::EIP2930(tx) => { - let v = tx.odd_y_parity as u8; - let r = U256::from_big_endian(&tx.r[..]); - let s = U256::from_big_endian(&tx.s[..]); - Signature { r, s, v: v.into() } - } - TypedTransaction::EIP1559(tx) => { - let v = tx.odd_y_parity as u8; - let r = U256::from_big_endian(&tx.r[..]); - let s = U256::from_big_endian(&tx.s[..]); - Signature { r, s, v: v.into() } - } - TypedTransaction::Deposit(_) => Signature { r: U256::zero(), s: U256::zero(), v: 0 }, + TypedTransaction::Legacy(tx) => *tx.signature(), + TypedTransaction::EIP2930(tx) => *tx.signature(), + TypedTransaction::EIP1559(tx) => *tx.signature(), + TypedTransaction::EIP4844(tx) => *tx.signature(), + TypedTransaction::Deposit(_) => Signature::from_scalars_and_parity( + B256::with_last_byte(1), + B256::with_last_byte(1), + false, + ) + .unwrap(), } } } impl Encodable for TypedTransaction { - fn rlp_append(&self, s: &mut RlpStream) { + fn encode(&self, out: &mut dyn bytes::BufMut) { match self { - TypedTransaction::Legacy(tx) => tx.rlp_append(s), - TypedTransaction::EIP2930(tx) => enveloped(1, tx, s), - TypedTransaction::EIP1559(tx) => enveloped(2, tx, s), - TypedTransaction::Deposit(tx) => enveloped(0x7E, tx, s), + TypedTransaction::Legacy(tx) => TxEnvelope::from(tx.clone()).encode(out), + TypedTransaction::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode(out), + TypedTransaction::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode(out), + TypedTransaction::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode(out), + TypedTransaction::Deposit(tx) => { + let tx_payload_len = tx.fields_len(); + let tx_header_len = Header { list: false, payload_length: tx_payload_len }.length(); + Header { list: false, payload_length: 1 + tx_payload_len + tx_header_len } + .encode(out); + out.put_u8(0x7E); + tx.encode(out); + } } } } impl Decodable for TypedTransaction { - fn decode(rlp: &Rlp) -> Result { - if rlp.is_list() { - return Ok(TypedTransaction::Legacy(rlp.as_val()?)) + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let mut h_decode_copy = *buf; + let header = alloy_rlp::Header::decode(&mut h_decode_copy)?; + + // Legacy TX + if header.list { + return Ok(TxEnvelope::decode(buf)?.into()) } - let [first, s @ ..] = rlp.data()? else { return Err(DecoderError::Custom("empty slice")) }; - // "advance" the header, see comments in fastrlp impl below - let s = if s.is_empty() { &rlp.as_raw()[1..] } else { s }; - - match *first { - 0x01 => rlp::decode(s).map(TypedTransaction::EIP2930), - 0x02 => rlp::decode(s).map(TypedTransaction::EIP1559), - 0x7E => rlp::decode(s).map(TypedTransaction::Deposit), - _ => Err(DecoderError::Custom("invalid tx type")), + + // Check byte after header + let ty = *h_decode_copy.first().ok_or(alloy_rlp::Error::Custom("empty slice"))?; + + if ty != 0x7E { + Ok(TxEnvelope::decode(buf)?.into()) + } else { + Ok(Self::Deposit(DepositTransaction::decode(&mut h_decode_copy)?)) } } } -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Encodable for TypedTransaction { - fn encode(&self, out: &mut dyn open_fastrlp::BufMut) { - match self { - TypedTransaction::Legacy(tx) => tx.encode(out), - tx => { - let payload_len = match tx { - TypedTransaction::EIP2930(tx) => tx.length() + 1, - TypedTransaction::EIP1559(tx) => tx.length() + 1, - TypedTransaction::Deposit(tx) => tx.length() + 1, - _ => unreachable!("legacy tx length already matched"), - }; - - match tx { - TypedTransaction::EIP2930(tx) => { - let tx_string_header = - open_fastrlp::Header { list: false, payload_length: payload_len }; - - tx_string_header.encode(out); - out.put_u8(0x01); - tx.encode(out); - } - TypedTransaction::EIP1559(tx) => { - let tx_string_header = - open_fastrlp::Header { list: false, payload_length: payload_len }; - - tx_string_header.encode(out); - out.put_u8(0x02); - tx.encode(out); - } - TypedTransaction::Deposit(tx) => { - let tx_string_header = - open_fastrlp::Header { list: false, payload_length: payload_len }; +impl Encodable2718 for TypedTransaction { + fn type_flag(&self) -> Option { + self.r#type() + } - tx_string_header.encode(out); - out.put_u8(0x7E); - tx.encode(out); - } - _ => unreachable!("legacy tx encode already matched"), - } - } + fn encode_2718_len(&self) -> usize { + match self { + TypedTransaction::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), + TypedTransaction::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), + TypedTransaction::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), + TypedTransaction::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), + TypedTransaction::Deposit(tx) => 1 + tx.length(), } } - fn length(&self) -> usize { + + fn encode_2718(&self, out: &mut dyn BufMut) { match self { - TypedTransaction::Legacy(tx) => tx.length(), - tx => { - let payload_len = match tx { - TypedTransaction::EIP2930(tx) => tx.length() + 1, - TypedTransaction::EIP1559(tx) => tx.length() + 1, - TypedTransaction::Deposit(tx) => tx.length() + 1, - _ => unreachable!("legacy tx length already matched"), - }; - // we include a string header for signed types txs, so include the length here - payload_len + open_fastrlp::length_of_length(payload_len) + TypedTransaction::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), + TypedTransaction::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), + TypedTransaction::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), + TypedTransaction::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), + TypedTransaction::Deposit(tx) => { + out.put_u8(0x7E); + tx.encode(out); } } } } -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Decodable for TypedTransaction { - fn decode(buf: &mut &[u8]) -> Result { - use bytes::Buf; - use std::cmp::Ordering; +impl Decodable2718 for TypedTransaction { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> alloy_rlp::Result { + if ty == 0x7E { + return Ok(Self::Deposit(DepositTransaction::decode(buf)?)) + } + match TxEnvelope::typed_decode(ty, buf)? { + TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)), + TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)), + TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)), + _ => unreachable!(), + } + } - let first = *buf.first().ok_or(open_fastrlp::DecodeError::Custom("empty slice"))?; + fn fallback_decode(buf: &mut &[u8]) -> alloy_rlp::Result { + match TxEnvelope::fallback_decode(buf)? { + TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)), + _ => unreachable!(), + } + } +} - // a signed transaction is either encoded as a string (non legacy) or a list (legacy). - // We should not consume the buffer if we are decoding a legacy transaction, so let's - // check if the first byte is between 0x80 and 0xbf. - match first.cmp(&open_fastrlp::EMPTY_LIST_CODE) { - Ordering::Less => { - // strip out the string header - // NOTE: typed transaction encodings either contain a "rlp header" which contains - // the type of the payload and its length, or they do not contain a header and - // start with the tx type byte. - // - // This line works for both types of encodings because byte slices starting with - // 0x01 and 0x02 return a Header { list: false, payload_length: 1 } when input to - // Header::decode. - // If the encoding includes a header, the header will be properly decoded and - // consumed. - // Otherwise, header decoding will succeed but nothing is consumed. - let _header = open_fastrlp::Header::decode(buf)?; - let tx_type = *buf.first().ok_or(open_fastrlp::DecodeError::Custom( - "typed tx cannot be decoded from an empty slice", - ))?; - if tx_type == 0x01 { - buf.advance(1); - ::decode(buf) - .map(TypedTransaction::EIP2930) - } else if tx_type == 0x02 { - buf.advance(1); - ::decode(buf) - .map(TypedTransaction::EIP1559) - } else if tx_type == 0x7E { - buf.advance(1); - ::decode(buf) - .map(TypedTransaction::Deposit) - } else { - Err(open_fastrlp::DecodeError::Custom("invalid tx type")) - } - } - Ordering::Equal => Err(open_fastrlp::DecodeError::Custom( - "an empty list is not a valid transaction encoding", - )), - Ordering::Greater => ::decode(buf) - .map(TypedTransaction::Legacy), +impl From for TypedTransaction { + fn from(value: TxEnvelope) -> Self { + match value { + TxEnvelope::Legacy(tx) => TypedTransaction::Legacy(tx), + TxEnvelope::Eip2930(tx) => TypedTransaction::EIP2930(tx), + TxEnvelope::Eip1559(tx) => TypedTransaction::EIP1559(tx), + TxEnvelope::Eip4844(tx) => TypedTransaction::EIP4844(tx), + _ => unreachable!(), } } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct LegacyTransaction { - pub nonce: U256, - pub gas_price: U256, - pub gas_limit: U256, - pub kind: TransactionKind, - pub value: U256, +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TransactionEssentials { + pub kind: TxKind, pub input: Bytes, - pub signature: Signature, + pub nonce: u64, + pub gas_limit: u128, + pub gas_price: Option, + pub max_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, + pub max_fee_per_blob_gas: Option, + pub blob_versioned_hashes: Option>, + pub value: U256, + pub chain_id: Option, + pub access_list: AccessList, } -impl LegacyTransaction { - pub fn nonce(&self) -> &U256 { - &self.nonce - } +/// Represents all relevant information of an executed transaction +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TransactionInfo { + pub transaction_hash: B256, + pub transaction_index: u64, + pub from: Address, + pub to: Option
, + pub contract_address: Option
, + pub traces: Vec, + pub exit: InstructionResult, + pub out: Option, + pub nonce: u64, + pub gas_used: u128, +} - pub fn hash(&self) -> H256 { - H256::from_slice(keccak256(&rlp::encode(self)).as_slice()) +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct DepositReceipt { + #[serde(flatten)] + pub inner: ReceiptWithBloom, + pub deposit_nonce: Option, + pub deposit_nonce_version: Option, +} + +impl DepositReceipt { + fn payload_len(&self) -> usize { + self.inner.receipt.status.length() + + self.inner.receipt.cumulative_gas_used.length() + + self.inner.logs_bloom.length() + + self.inner.receipt.logs.length() + + self.deposit_nonce.map_or(0, |n| n.length()) + + self.deposit_nonce_version.map_or(0, |n| n.length()) + } + + /// Returns the rlp header for the receipt payload. + fn receipt_rlp_header(&self) -> alloy_rlp::Header { + alloy_rlp::Header { list: true, payload_length: self.payload_len() } + } + + /// Encodes the receipt data. + fn encode_fields(&self, out: &mut dyn BufMut) { + self.receipt_rlp_header().encode(out); + self.inner.receipt.status.encode(out); + self.inner.receipt.cumulative_gas_used.encode(out); + self.inner.logs_bloom.encode(out); + self.inner.receipt.logs.encode(out); + if let Some(n) = self.deposit_nonce { + n.encode(out); + } + if let Some(n) = self.deposit_nonce_version { + n.encode(out); + } } - /// Recovers the Ethereum address which was used to sign the transaction. - pub fn recover(&self) -> Result { - self.signature.recover(LegacyTransactionRequest::from(self.clone()).hash()) - } + /// Decodes the receipt payload + fn decode_receipt(buf: &mut &[u8]) -> alloy_rlp::Result { + let b: &mut &[u8] = &mut &**buf; + let rlp_head = alloy_rlp::Header::decode(b)?; + if !rlp_head.list { + return Err(alloy_rlp::Error::UnexpectedString); + } + let started_len = b.len(); + let remaining = |b: &[u8]| rlp_head.payload_length - (started_len - b.len()) > 0; + + let status = Decodable::decode(b)?; + let cumulative_gas_used = Decodable::decode(b)?; + let logs_bloom = Decodable::decode(b)?; + let logs = Decodable::decode(b)?; + let deposit_nonce = remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?; + let deposit_nonce_version = + remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?; + + let this = Self { + inner: ReceiptWithBloom { + receipt: Receipt { status, cumulative_gas_used, logs }, + logs_bloom, + }, + deposit_nonce, + deposit_nonce_version, + }; - pub fn chain_id(&self) -> Option { - if self.signature.v > 36 { - Some((self.signature.v - 35) / 2) - } else { - None + let consumed = started_len - b.len(); + if consumed != rlp_head.payload_length { + return Err(alloy_rlp::Error::ListLengthMismatch { + expected: rlp_head.payload_length, + got: consumed, + }); } - } - /// See - /// > If you do, then the v of the signature MUST be set to {0,1} + CHAIN_ID * 2 + 35 where - /// > {0,1} is the parity of the y value of the curve point for which r is the x-value in the - /// > secp256k1 signing process. - pub fn meets_eip155(&self, chain_id: u64) -> bool { - let double_chain_id = chain_id.saturating_mul(2); - let v = self.signature.v; - v == double_chain_id + 35 || v == double_chain_id + 36 + *buf = *b; + Ok(this) } } -impl Encodable for LegacyTransaction { - fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(9); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas_limit); - s.append(&self.kind); - s.append(&self.value); - s.append(&self.input.as_ref()); - s.append(&self.signature.v); - s.append(&self.signature.r); - s.append(&self.signature.s); +impl alloy_rlp::Encodable for DepositReceipt { + fn encode(&self, out: &mut dyn BufMut) { + self.encode_fields(out); } -} -impl Decodable for LegacyTransaction { - fn decode(rlp: &Rlp) -> Result { - if rlp.item_count()? != 9 { - return Err(DecoderError::RlpIncorrectListLen) - } + fn length(&self) -> usize { + let payload_length = self.payload_len(); + payload_length + length_of_length(payload_length) + } +} - let v = rlp.val_at(6)?; - let r = rlp.val_at::(7)?; - let s = rlp.val_at::(8)?; - - Ok(Self { - nonce: rlp.val_at(0)?, - gas_price: rlp.val_at(1)?, - gas_limit: rlp.val_at(2)?, - kind: rlp.val_at(3)?, - value: rlp.val_at(4)?, - input: rlp.val_at::>(5)?.into(), - signature: Signature { v, r, s }, - }) +impl alloy_rlp::Decodable for DepositReceipt { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + Self::decode_receipt(buf) } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct EIP2930Transaction { - pub chain_id: u64, - pub nonce: U256, - pub gas_price: U256, - pub gas_limit: U256, - pub kind: TransactionKind, - pub value: U256, - pub input: Bytes, - pub access_list: AccessList, - pub odd_y_parity: bool, - pub r: H256, - pub s: H256, -} - -impl EIP2930Transaction { - pub fn nonce(&self) -> &U256 { - &self.nonce - } - - pub fn hash(&self) -> H256 { - let encoded = rlp::encode(self); - let mut out = vec![0; 1 + encoded.len()]; - out[0] = 1; - out[1..].copy_from_slice(&encoded); - H256::from_slice(keccak256(&out).as_slice()) - } - - /// Recovers the Ethereum address which was used to sign the transaction. - pub fn recover(&self) -> Result { - let mut sig = [0u8; 65]; - sig[0..32].copy_from_slice(&self.r[..]); - sig[32..64].copy_from_slice(&self.s[..]); - sig[64] = self.odd_y_parity as u8; - let signature = Signature::try_from(&sig[..])?; - signature.recover(EIP2930TransactionRequest::from(self.clone()).hash()) - } +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum TypedReceipt { + #[serde(rename = "0x0", alias = "0x00")] + Legacy(ReceiptWithBloom), + #[serde(rename = "0x1", alias = "0x01")] + EIP2930(ReceiptWithBloom), + #[serde(rename = "0x2", alias = "0x02")] + EIP1559(ReceiptWithBloom), + #[serde(rename = "0x3", alias = "0x03")] + EIP4844(ReceiptWithBloom), + #[serde(rename = "0x7E", alias = "0x7e")] + Deposit(DepositReceipt), } -impl Encodable for EIP2930Transaction { - fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(11); - s.append(&self.chain_id); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas_limit); - s.append(&self.kind); - s.append(&self.value); - s.append(&self.input.as_ref()); - s.append(&self.access_list); - s.append(&self.odd_y_parity); - s.append(&U256::from_big_endian(&self.r[..])); - s.append(&U256::from_big_endian(&self.s[..])); - } -} - -impl Decodable for EIP2930Transaction { - fn decode(rlp: &Rlp) -> Result { - if rlp.item_count()? != 11 { - return Err(DecoderError::RlpIncorrectListLen) +impl TypedReceipt { + pub fn as_receipt_with_bloom(&self) -> &ReceiptWithBloom { + match self { + TypedReceipt::Legacy(r) | + TypedReceipt::EIP1559(r) | + TypedReceipt::EIP2930(r) | + TypedReceipt::EIP4844(r) => r, + TypedReceipt::Deposit(r) => &r.inner, } - - Ok(Self { - chain_id: rlp.val_at(0)?, - nonce: rlp.val_at(1)?, - gas_price: rlp.val_at(2)?, - gas_limit: rlp.val_at(3)?, - kind: rlp.val_at(4)?, - value: rlp.val_at(5)?, - input: rlp.val_at::>(6)?.into(), - access_list: rlp.val_at(7)?, - odd_y_parity: rlp.val_at(8)?, - r: { - let mut rarr = [0u8; 32]; - rlp.val_at::(9)?.to_big_endian(&mut rarr); - H256::from(rarr) - }, - s: { - let mut sarr = [0u8; 32]; - rlp.val_at::(10)?.to_big_endian(&mut sarr); - H256::from(sarr) - }, - }) } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct EIP1559Transaction { - pub chain_id: u64, - pub nonce: U256, - pub max_priority_fee_per_gas: U256, - pub max_fee_per_gas: U256, - pub gas_limit: U256, - pub kind: TransactionKind, - pub value: U256, - pub input: Bytes, - pub access_list: AccessList, - pub odd_y_parity: bool, - pub r: H256, - pub s: H256, -} - -impl EIP1559Transaction { - pub fn nonce(&self) -> &U256 { - &self.nonce - } - - pub fn hash(&self) -> H256 { - let encoded = rlp::encode(self); - let mut out = vec![0; 1 + encoded.len()]; - out[0] = 2; - out[1..].copy_from_slice(&encoded); - H256::from_slice(keccak256(&out).as_slice()) +impl TypedReceipt { + pub fn cumulative_gas_used(&self) -> u128 { + self.as_receipt_with_bloom().cumulative_gas_used() } - /// Recovers the Ethereum address which was used to sign the transaction. - pub fn recover(&self) -> Result { - let mut sig = [0u8; 65]; - sig[0..32].copy_from_slice(&self.r[..]); - sig[32..64].copy_from_slice(&self.s[..]); - sig[64] = self.odd_y_parity as u8; - let signature = Signature::try_from(&sig[..])?; - signature.recover(EIP1559TransactionRequest::from(self.clone()).hash()) + pub fn logs_bloom(&self) -> &Bloom { + &self.as_receipt_with_bloom().logs_bloom } -} -impl Encodable for EIP1559Transaction { - fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(12); - s.append(&self.chain_id); - s.append(&self.nonce); - s.append(&self.max_priority_fee_per_gas); - s.append(&self.max_fee_per_gas); - s.append(&self.gas_limit); - s.append(&self.kind); - s.append(&self.value); - s.append(&self.input.as_ref()); - s.append(&self.access_list); - s.append(&self.odd_y_parity); - s.append(&U256::from_big_endian(&self.r[..])); - s.append(&U256::from_big_endian(&self.s[..])); + pub fn logs(&self) -> &[Log] { + self.as_receipt_with_bloom().logs() } } -impl Decodable for EIP1559Transaction { - fn decode(rlp: &Rlp) -> Result { - if rlp.item_count()? != 12 { - return Err(DecoderError::RlpIncorrectListLen) +impl From> for TypedReceipt { + fn from(value: ReceiptEnvelope) -> Self { + match value { + ReceiptEnvelope::Legacy(r) => TypedReceipt::Legacy(r), + ReceiptEnvelope::Eip2930(r) => TypedReceipt::EIP2930(r), + ReceiptEnvelope::Eip1559(r) => TypedReceipt::EIP1559(r), + ReceiptEnvelope::Eip4844(r) => TypedReceipt::EIP4844(r), + _ => unreachable!(), } - - Ok(Self { - chain_id: rlp.val_at(0)?, - nonce: rlp.val_at(1)?, - max_priority_fee_per_gas: rlp.val_at(2)?, - max_fee_per_gas: rlp.val_at(3)?, - gas_limit: rlp.val_at(4)?, - kind: rlp.val_at(5)?, - value: rlp.val_at(6)?, - input: rlp.val_at::>(7)?.into(), - access_list: rlp.val_at(8)?, - odd_y_parity: rlp.val_at(9)?, - r: { - let mut rarr = [0u8; 32]; - rlp.val_at::(10)?.to_big_endian(&mut rarr); - H256::from(rarr) - }, - s: { - let mut sarr = [0u8; 32]; - rlp.val_at::(11)?.to_big_endian(&mut sarr); - H256::from(sarr) - }, - }) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - -pub struct DepositTransaction { - pub nonce: U256, - pub source_hash: H256, - pub from: Address, - pub kind: TransactionKind, - pub mint: U256, - pub value: U256, - pub gas_limit: U256, - pub is_system_tx: bool, - pub input: Bytes, -} - -impl DepositTransaction { - pub fn nonce(&self) -> &U256 { - &self.nonce - } - - pub fn hash(&self) -> H256 { - H256::from_slice(keccak256(&rlp::encode(self)).as_slice()) - } - - /// Recovers the Ethereum address which was used to sign the transaction. - pub fn recover(&self) -> Result { - Ok(self.from) - } - - pub fn chain_id(&self) -> Option { - None } } -impl Encodable for DepositTransaction { - fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(9); - s.append(&self.nonce); - s.append(&self.source_hash); - s.append(&self.from); - s.append(&self.kind); - s.append(&self.mint); - s.append(&self.value); - s.append(&self.gas_limit); - s.append(&self.is_system_tx); - s.append(&self.input.as_ref()); - } -} +impl Encodable for TypedReceipt { + fn encode(&self, out: &mut dyn bytes::BufMut) { + match self { + TypedReceipt::Legacy(r) => r.encode(out), + receipt => { + let payload_len = match receipt { + TypedReceipt::EIP2930(r) => r.length() + 1, + TypedReceipt::EIP1559(r) => r.length() + 1, + TypedReceipt::EIP4844(r) => r.length() + 1, + TypedReceipt::Deposit(r) => r.length() + 1, + _ => unreachable!("receipt already matched"), + }; -impl Decodable for DepositTransaction { - fn decode(rlp: &Rlp) -> Result { - if rlp.item_count()? != 8 { - return Err(DecoderError::RlpIncorrectListLen) + match receipt { + TypedReceipt::EIP2930(r) => { + Header { list: true, payload_length: payload_len }.encode(out); + 1u8.encode(out); + r.encode(out); + } + TypedReceipt::EIP1559(r) => { + Header { list: true, payload_length: payload_len }.encode(out); + 2u8.encode(out); + r.encode(out); + } + TypedReceipt::EIP4844(r) => { + Header { list: true, payload_length: payload_len }.encode(out); + 3u8.encode(out); + r.encode(out); + } + TypedReceipt::Deposit(r) => { + Header { list: true, payload_length: payload_len }.encode(out); + 0x7Eu8.encode(out); + r.encode(out); + } + _ => unreachable!("receipt already matched"), + } + } } - - Ok(Self { - source_hash: rlp.val_at(0)?, - from: rlp.val_at(1)?, - kind: rlp.val_at(2)?, - mint: rlp.val_at(3)?, - value: rlp.val_at(4)?, - gas_limit: rlp.val_at(5)?, - is_system_tx: rlp.val_at(6)?, - input: rlp.val_at::>(7)?.into(), - nonce: U256::from(0), - }) } } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct TransactionEssentials { - pub kind: TransactionKind, - pub input: Bytes, - pub nonce: U256, - pub gas_limit: U256, - pub gas_price: Option, - pub max_fee_per_gas: Option, - pub max_priority_fee_per_gas: Option, - pub value: U256, - pub chain_id: Option, - pub access_list: AccessList, -} - -/// Queued transaction -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct PendingTransaction { - /// The actual transaction - pub transaction: MaybeImpersonatedTransaction, - /// the recovered sender of this transaction - sender: Address, - /// hash of `transaction`, so it can easily be reused with encoding and hashing agan - hash: TxHash, -} - -// == impl PendingTransaction == - -impl PendingTransaction { - /// Creates a new pending transaction and tries to verify transaction and recover sender. - pub fn new(transaction: TypedTransaction) -> Result { - let sender = transaction.recover()?; - Ok(Self { hash: transaction.hash(), transaction: transaction.into(), sender }) - } - - /// Creates a new transaction with the given sender. - /// - /// In order to prevent collisions from multiple different impersonated accounts, we update the - /// transaction's hash with the address to make it unique. - /// - /// See: - #[cfg(feature = "impersonated-tx")] - pub fn with_impersonated(transaction: TypedTransaction, sender: Address) -> Self { - let hash = transaction.impersonated_hash(sender); - let transaction = MaybeImpersonatedTransaction::impersonated(transaction, sender); - Self { hash, transaction, sender } - } - - pub fn nonce(&self) -> &U256 { - self.transaction.nonce() - } - - pub fn hash(&self) -> &TxHash { - &self.hash - } - - pub fn sender(&self) -> &Address { - &self.sender - } +impl Decodable for TypedReceipt { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + use bytes::Buf; + use std::cmp::Ordering; - /// Converts the [PendingTransaction] into the [TxEnv] context that [`revm`](foundry_evm) - /// expects. - pub fn to_revm_tx_env(&self) -> TxEnv { - fn transact_to(kind: &TransactionKind) -> TransactTo { - match kind { - TransactionKind::Call(c) => TransactTo::Call((*c).to_alloy()), - TransactionKind::Create => TransactTo::Create(CreateScheme::Create), - } - } + // a receipt is either encoded as a string (non legacy) or a list (legacy). + // We should not consume the buffer if we are decoding a legacy receipt, so let's + // check if the first byte is between 0x80 and 0xbf. + let rlp_type = *buf + .first() + .ok_or(alloy_rlp::Error::Custom("cannot decode a receipt from empty bytes"))?; - let caller = *self.sender(); - match &self.transaction.transaction { - TypedTransaction::Legacy(tx) => { - let chain_id = tx.chain_id(); - let LegacyTransaction { nonce, gas_price, gas_limit, value, kind, input, .. } = tx; - TxEnv { - caller: caller.to_alloy(), - transact_to: transact_to(kind), - data: alloy_primitives::Bytes(input.0.clone()), - chain_id, - nonce: Some(nonce.as_u64()), - value: (*value).to_alloy(), - gas_price: (*gas_price).to_alloy(), - gas_priority_fee: None, - gas_limit: gas_limit.as_u64(), - access_list: vec![], - ..Default::default() + match rlp_type.cmp(&alloy_rlp::EMPTY_LIST_CODE) { + Ordering::Less => { + // strip out the string header + let _header = Header::decode(buf)?; + let receipt_type = *buf.first().ok_or(alloy_rlp::Error::Custom( + "typed receipt cannot be decoded from an empty slice", + ))?; + if receipt_type == 0x01 { + buf.advance(1); + ::decode(buf).map(TypedReceipt::EIP2930) + } else if receipt_type == 0x02 { + buf.advance(1); + ::decode(buf).map(TypedReceipt::EIP1559) + } else if receipt_type == 0x03 { + buf.advance(1); + ::decode(buf).map(TypedReceipt::EIP4844) + } else if receipt_type == 0x7E { + buf.advance(1); + ::decode(buf).map(TypedReceipt::Deposit) + } else { + Err(alloy_rlp::Error::Custom("invalid receipt type")) } } - TypedTransaction::EIP2930(tx) => { - let EIP2930Transaction { - chain_id, - nonce, - gas_price, - gas_limit, - kind, - value, - input, - access_list, - .. - } = tx; - TxEnv { - caller: (caller).to_alloy(), - transact_to: transact_to(kind), - data: alloy_primitives::Bytes(input.0.clone()), - chain_id: Some(*chain_id), - nonce: Some(nonce.as_u64()), - value: (*value).to_alloy(), - gas_price: (*gas_price).to_alloy(), - gas_priority_fee: None, - gas_limit: gas_limit.as_u64(), - access_list: to_revm_access_list(access_list.0.clone()), - ..Default::default() - } + Ordering::Equal => { + Err(alloy_rlp::Error::Custom("an empty list is not a valid receipt encoding")) } - TypedTransaction::EIP1559(tx) => { - let EIP1559Transaction { - chain_id, - nonce, - max_priority_fee_per_gas, - max_fee_per_gas, - gas_limit, - kind, - value, - input, - access_list, - .. - } = tx; - TxEnv { - caller: (caller).to_alloy(), - transact_to: transact_to(kind), - data: alloy_primitives::Bytes(input.0.clone()), - chain_id: Some(*chain_id), - nonce: Some(nonce.as_u64()), - value: (*value).to_alloy(), - gas_price: (*max_fee_per_gas).to_alloy(), - gas_priority_fee: Some((*max_priority_fee_per_gas).to_alloy()), - gas_limit: gas_limit.as_u64(), - access_list: to_revm_access_list(access_list.0.clone()), - ..Default::default() - } - } - TypedTransaction::Deposit(tx) => { - let chain_id = tx.chain_id(); - let DepositTransaction { - nonce, - source_hash, - gas_limit, - value, - kind, - mint, - input, - is_system_tx, - .. - } = tx; - TxEnv { - caller: caller.to_alloy(), - transact_to: transact_to(kind), - data: alloy_primitives::Bytes(input.0.clone()), - chain_id, - nonce: Some(nonce.as_u64()), - value: (*value).to_alloy(), - gas_price: 0.to_alloy(), - gas_priority_fee: None, - gas_limit: gas_limit.as_u64(), - access_list: vec![], - optimism: OptimismFields { - source_hash: Some(source_hash.to_alloy()), - mint: Some(mint.as_u128()), - is_system_transaction: Some(*is_system_tx), - enveloped_tx: None, - }, - ..Default::default() - } + Ordering::Greater => { + ::decode(buf).map(TypedReceipt::Legacy) } } } } -/// Represents all relevant information of an executed transaction -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct TransactionInfo { - pub transaction_hash: H256, - pub transaction_index: u32, - pub from: Address, - pub to: Option
, - pub contract_address: Option
, - pub logs: Vec, - pub logs_bloom: Bloom, - pub traces: CallTraceArena, - pub exit: InstructionResult, - pub out: Option, - pub nonce: u64, -} - -// === impl TransactionInfo === - -impl TransactionInfo { - /// Returns the `traceAddress` of the node in the arena - /// - /// The `traceAddress` field of all returned traces, gives the exact location in the call trace - /// [index in root, index in first CALL, index in second CALL, …]. - /// - /// # Panics - /// - /// if the `idx` does not belong to a node - pub fn trace_address(&self, idx: usize) -> Vec { - if idx == 0 { - // root call has empty traceAddress - return vec![] - } - let mut graph = vec![]; - let mut node = &self.traces.arena[idx]; - while let Some(parent) = node.parent { - // the index of the child call in the arena - let child_idx = node.idx; - node = &self.traces.arena[parent]; - // find the index of the child call in the parent node - let call_idx = node - .children - .iter() - .position(|child| *child == child_idx) - .expect("child exists in parent"); - graph.push(call_idx); - } - graph.reverse(); - graph - } +pub type ReceiptResponse = TransactionReceipt>; + +pub fn convert_to_anvil_receipt(receipt: AnyTransactionReceipt) -> Option { + let WithOtherFields { + inner: + TransactionReceipt { + transaction_hash, + transaction_index, + block_hash, + block_number, + gas_used, + contract_address, + effective_gas_price, + from, + to, + blob_gas_price, + blob_gas_used, + state_root, + inner: AnyReceiptEnvelope { inner: receipt_with_bloom, r#type }, + }, + other, + } = receipt; + + Some(TransactionReceipt { + transaction_hash, + transaction_index, + block_hash, + block_number, + gas_used, + contract_address, + effective_gas_price, + from, + to, + blob_gas_price, + blob_gas_used, + state_root, + inner: match r#type { + 0x00 => TypedReceipt::Legacy(receipt_with_bloom), + 0x01 => TypedReceipt::EIP2930(receipt_with_bloom), + 0x02 => TypedReceipt::EIP1559(receipt_with_bloom), + 0x03 => TypedReceipt::EIP4844(receipt_with_bloom), + 0x7E => TypedReceipt::Deposit(DepositReceipt { + inner: receipt_with_bloom, + deposit_nonce: other.get("depositNonce").and_then(|v| v.as_u64()), + deposit_nonce_version: other.get("depositNonceVersion").and_then(|v| v.as_u64()), + }), + _ => return None, + }, + }) } #[cfg(test)] mod tests { - use super::*; - use ethers_core::utils::hex; + use alloy_primitives::{b256, hex, LogData}; + use std::str::FromStr; - #[test] - fn can_recover_sender() { - // random mainnet tx: https://etherscan.io/tx/0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f - let bytes = hex::decode("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9").unwrap(); - - let Ok(TypedTransaction::EIP1559(tx)) = rlp::decode(&bytes) else { - panic!("decoding TypedTransaction failed"); - }; - assert_eq!( - tx.hash(), - "0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f".parse().unwrap() - ); - assert_eq!( - tx.recover().unwrap(), - "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5".parse().unwrap() - ); - } + use super::*; #[test] - fn can_recover_sender_not_normalized() { - let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap(); + fn test_decode_call() { + let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..]; + let decoded = TypedTransaction::decode(&mut &bytes_first[..]).unwrap(); - let tx: TypedTransaction = rlp::decode(&bytes).expect("decoding TypedTransaction failed"); - let tx = match tx { - TypedTransaction::Legacy(tx) => tx, - _ => panic!("Invalid typed transaction"), + let tx = TxLegacy { + nonce: 2u64, + gas_price: 1000000000u128, + gas_limit: 100000u128, + to: TxKind::Call(Address::from_slice( + &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..], + )), + value: U256::from(1000000000000000u64), + input: Bytes::default(), + chain_id: Some(4), }; - assert_eq!(tx.input, Bytes::from(b"")); - assert_eq!(tx.gas_price, U256::from(0x01u64)); - assert_eq!(tx.gas_limit, U256::from(0x5208u64)); - assert_eq!(tx.nonce, U256::from(0x00u64)); - if let TransactionKind::Call(ref to) = tx.kind { - assert_eq!(*to, "095e7baea6a6c7c4c2dfeb977efac326af552d87".parse().unwrap()); - } else { - panic!(); - } - assert_eq!(tx.value, U256::from(0x0au64)); - assert_eq!( - tx.recover().unwrap(), - "0f65fe9276bc9a24ae7083ae28e2660ef72df99e".parse().unwrap() - ); - } - #[test] - #[cfg(feature = "fastrlp")] - fn test_decode_fastrlp_create() { - use bytes::BytesMut; - use open_fastrlp::Encodable; - - // tests that a contract creation tx encodes and decodes properly - - let tx = TypedTransaction::EIP2930(EIP2930Transaction { - chain_id: 1u64, - nonce: U256::from(0), - gas_price: U256::from(1), - gas_limit: U256::from(2), - kind: TransactionKind::Create, - value: U256::from(3), - input: Bytes::from(vec![1, 2]), - odd_y_parity: true, - r: H256::default(), - s: H256::default(), - access_list: vec![].into(), - }); + let signature = Signature::from_str("0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b").unwrap(); - let mut encoded = BytesMut::new(); - tx.encode(&mut encoded); + let tx = TypedTransaction::Legacy(Signed::new_unchecked( + tx.clone(), + signature, + b256!("a517b206d2223278f860ea017d3626cacad4f52ff51030dc9a96b432f17f8d34"), + )); - let decoded = - ::decode(&mut &*encoded).unwrap(); - assert_eq!(decoded, tx); + assert_eq!(tx, decoded); } #[test] - #[cfg(feature = "fastrlp")] - fn test_decode_fastrlp_create_goerli() { + fn test_decode_create_goerli() { // test that an example create tx from goerli decodes properly let tx_bytes = hex::decode("02f901ee05228459682f008459682f11830209bf8080b90195608060405234801561001057600080fd5b50610175806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630c49c36c14610030575b600080fd5b61003861004e565b604051610045919061011d565b60405180910390f35b60606020600052600f6020527f68656c6c6f2073746174656d696e64000000000000000000000000000000000060405260406000f35b600081519050919050565b600082825260208201905092915050565b60005b838110156100be5780820151818401526020810190506100a3565b838111156100cd576000848401525b50505050565b6000601f19601f8301169050919050565b60006100ef82610084565b6100f9818561008f565b93506101098185602086016100a0565b610112816100d3565b840191505092915050565b6000602082019050818103600083015261013781846100e4565b90509291505056fea264697066735822122051449585839a4ea5ac23cae4552ef8a96b64ff59d0668f76bfac3796b2bdbb3664736f6c63430008090033c080a0136ebffaa8fc8b9fda9124de9ccb0b1f64e90fbd44251b4c4ac2501e60b104f9a07eb2999eec6d185ef57e91ed099afb0a926c5b536f0155dd67e537c7476e1471") .unwrap(); - let _decoded = - ::decode(&mut &tx_bytes[..]).unwrap(); + let _decoded = TypedTransaction::decode(&mut &tx_bytes[..]).unwrap(); } #[test] - #[cfg(feature = "fastrlp")] - fn test_decode_fastrlp_call() { - use bytes::BytesMut; - use open_fastrlp::Encodable; - - let tx = TypedTransaction::EIP2930(EIP2930Transaction { - chain_id: 1u64, - nonce: U256::from(0), - gas_price: U256::from(1), - gas_limit: U256::from(2), - kind: TransactionKind::Call(Address::default()), - value: U256::from(3), - input: Bytes::from(vec![1, 2]), - odd_y_parity: true, - r: H256::default(), - s: H256::default(), - access_list: vec![].into(), - }); - - let mut encoded = BytesMut::new(); - tx.encode(&mut encoded); + fn can_recover_sender() { + // random mainnet tx: https://etherscan.io/tx/0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f + let bytes = hex::decode("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9").unwrap(); - let decoded = - ::decode(&mut &*encoded).unwrap(); - assert_eq!(decoded, tx); - } + let Ok(TypedTransaction::EIP1559(tx)) = TypedTransaction::decode(&mut &bytes[..]) else { + panic!("decoding TypedTransaction failed"); + }; - #[test] - #[cfg(feature = "fastrlp")] - fn decode_transaction_consumes_buffer() { - let bytes = &mut &hex::decode("b87502f872041a8459682f008459682f0d8252089461815774383099e24810ab832a5b2a5425c154d58829a2241af62c000080c001a059e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafda0016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469").unwrap()[..]; - let _transaction_res = - ::decode(bytes).unwrap(); assert_eq!( - bytes.len(), - 0, - "did not consume all bytes in the buffer, {:?} remaining", - bytes.len() + tx.hash(), + &"0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f" + .parse::() + .unwrap() + ); + assert_eq!( + tx.recover_signer().unwrap(), + "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5".parse::
().unwrap() ); } + // Test vector from https://sepolia.etherscan.io/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 + // Blobscan: https://sepolia.blobscan.com/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 #[test] - #[cfg(feature = "fastrlp")] - fn decode_multiple_network_txs() { - use std::str::FromStr; + fn test_decode_live_4844_tx() { + use alloy_primitives::{address, b256}; - let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..]; - let expected = TypedTransaction::Legacy(LegacyTransaction { - nonce: 2u64.into(), - gas_price: 1000000000u64.into(), - gas_limit: 100000u64.into(), - kind: TransactionKind::Call(Address::from_slice( - &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..], - )), - value: 1000000000000000u64.into(), - input: Bytes::default(), - signature: Signature { - v: 43, - r: U256::from_str( - "eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae", - ) - .unwrap(), - s: U256::from_str( - "3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18", - ) - .unwrap(), - }, - }); - assert_eq!( - expected, - ::decode(bytes_first).unwrap() - ); + // https://sepolia.etherscan.io/getRawTx?tx=0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 + let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap(); + let res = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap(); + assert_eq!(res.r#type(), Some(3)); - let bytes_second = &mut &hex::decode("f86b01843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac3960468702769bb01b2a00802ba0e24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0aa05406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631da").unwrap()[..]; - let expected = TypedTransaction::Legacy(LegacyTransaction { - nonce: 1u64.into(), - gas_price: 1000000000u64.into(), - gas_limit: 100000u64.into(), - kind: TransactionKind::Call(Address::from_slice( - &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..], - )), - value: 693361000000000u64.into(), - input: Bytes::default(), - signature: Signature { - v: 43, - r: U256::from_str( - "e24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0a", - ) - .unwrap(), - s: U256::from_str( - "5406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631da", - ) - .unwrap(), - }, - }); - assert_eq!( - expected, - ::decode(bytes_second).unwrap() - ); + let tx = match res { + TypedTransaction::EIP4844(tx) => tx, + _ => unreachable!(), + }; + + assert_eq!(tx.tx().tx().to, address!("11E9CA82A3a762b4B5bd264d4173a242e7a77064")); - let bytes_third = &mut &hex::decode("f86b0384773594008398968094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba0ce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071a03ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88").unwrap()[..]; - let expected = TypedTransaction::Legacy(LegacyTransaction { - nonce: 3u64.into(), - gas_price: 2000000000u64.into(), - gas_limit: 10000000u64.into(), - kind: TransactionKind::Call(Address::from_slice( - &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..], - )), - value: 1000000000000000u64.into(), - input: Bytes::default(), - signature: Signature { - v: 43, - r: U256::from_str( - "ce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071", - ) - .unwrap(), - s: U256::from_str( - "3ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88", - ) - .unwrap(), - }, - }); assert_eq!( - expected, - ::decode(bytes_third).unwrap() + tx.tx().tx().blob_versioned_hashes, + vec![ + b256!("012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"), + b256!("0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"), + b256!("013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"), + b256!("01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"), + b256!("011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549") + ] ); - let bytes_fourth = &mut &hex::decode("b87502f872041a8459682f008459682f0d8252089461815774383099e24810ab832a5b2a5425c154d58829a2241af62c000080c001a059e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafda0016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469").unwrap()[..]; - let expected = TypedTransaction::EIP1559(EIP1559Transaction { - chain_id: 4, - nonce: 26u64.into(), - max_priority_fee_per_gas: 1500000000u64.into(), - max_fee_per_gas: 1500000013u64.into(), - gas_limit: 21000u64.into(), - kind: TransactionKind::Call(Address::from_slice( - &hex::decode("61815774383099e24810ab832a5b2a5425c154d5").unwrap()[..], - )), - value: 3000000000000000000u64.into(), - input: Bytes::default(), - access_list: AccessList::default(), - odd_y_parity: true, - r: H256::from_str("59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd") - .unwrap(), - s: H256::from_str("016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469") - .unwrap(), - }); + let from = tx.recover_signer().unwrap(); + assert_eq!(from, address!("A83C816D4f9b2783761a22BA6FADB0eB0606D7B2")); + } + + #[test] + fn can_recover_sender_not_normalized() { + let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap(); + + let Ok(TypedTransaction::Legacy(tx)) = TypedTransaction::decode(&mut &bytes[..]) else { + panic!("decoding TypedTransaction failed"); + }; + + assert_eq!(tx.tx().input, Bytes::from(b"")); + assert_eq!(tx.tx().gas_price, 1); + assert_eq!(tx.tx().gas_limit, 21000); + assert_eq!(tx.tx().nonce, 0); + if let TxKind::Call(to) = tx.tx().to { + assert_eq!( + to, + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87".parse::
().unwrap() + ); + } else { + panic!("expected a call transaction"); + } + assert_eq!(tx.tx().value, U256::from(0x0au64)); assert_eq!( - expected, - ::decode(bytes_fourth).unwrap() + tx.recover_signer().unwrap(), + "0f65fe9276bc9a24ae7083ae28e2660ef72df99e".parse::
().unwrap() ); + } - let bytes_fifth = &mut &hex::decode("f8650f84832156008287fb94cf7f9e66af820a19257a2108375b180b0ec491678204d2802ca035b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981a0612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860").unwrap()[..]; - let expected = TypedTransaction::Legacy(LegacyTransaction { - nonce: 15u64.into(), - gas_price: 2200000000u64.into(), - gas_limit: 34811u64.into(), - kind: TransactionKind::Call(Address::from_slice( - &hex::decode("cf7f9e66af820a19257a2108375b180b0ec49167").unwrap()[..], - )), - value: 1234u64.into(), - input: Bytes::default(), - signature: Signature { - v: 44, - r: U256::from_str( - "35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981", - ) - .unwrap(), - s: U256::from_str( - "612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860", - ) - .unwrap(), + #[test] + fn encode_legacy_receipt() { + let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); + + let mut data = vec![]; + let receipt = TypedReceipt::Legacy(ReceiptWithBloom { + receipt: Receipt { + status: false, + cumulative_gas_used: 0x1u128, + logs: vec![Log { + address: Address::from_str("0000000000000000000000000000000000000011").unwrap(), + data: LogData::new_unchecked( + vec![ + B256::from_str( + "000000000000000000000000000000000000000000000000000000000000dead", + ) + .unwrap(), + B256::from_str( + "000000000000000000000000000000000000000000000000000000000000beef", + ) + .unwrap(), + ], + Bytes::from_str("0100ff").unwrap(), + ), + }], }, + logs_bloom: [0; 256].into(), }); - assert_eq!( - expected, - ::decode(bytes_fifth).unwrap() - ); - let bytes_sixth = &mut &hex::decode("b8587ef85507a0000000000000000000000000000000000000000000000000000000000000000094cf7f9e66af820a19257a2108375b180b0ec491679461815774383099e24810ab832a5b2a5425c154d5808230398287fb0180").unwrap()[..]; - let expected: TypedTransaction = TypedTransaction::Deposit(DepositTransaction { - nonce: 7u64.into(), - source_hash: H256::from_str( - "0000000000000000000000000000000000000000000000000000000000000000", - ) - .unwrap(), - from: "cf7f9e66af820a19257a2108375b180b0ec49167".parse().unwrap(), - kind: TransactionKind::Call(Address::from_slice( - &hex::decode("61815774383099e24810ab832a5b2a5425c154d5").unwrap()[..], - )), - mint: U256::zero(), - value: 12345u64.into(), - gas_limit: 34811u64.into(), - input: Bytes::default(), - is_system_tx: true, - }); - assert_eq!( - expected, - ::decode(bytes_sixth).unwrap() - ); + receipt.encode(&mut data); + + // check that the rlp length equals the length of the expected rlp + assert_eq!(receipt.length(), expected.len()); + assert_eq!(data, expected); } - // #[test] - fn test_recover_legacy_tx() { - let raw_tx = "f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8"; + fn decode_legacy_receipt() { + let data = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); + + let expected = TypedReceipt::Legacy(ReceiptWithBloom { + receipt: Receipt { + status: false, + cumulative_gas_used: 0x1u128, + logs: vec![Log { + address: Address::from_str("0000000000000000000000000000000000000011").unwrap(), + data: LogData::new_unchecked( + vec![ + B256::from_str( + "000000000000000000000000000000000000000000000000000000000000dead", + ) + .unwrap(), + B256::from_str( + "000000000000000000000000000000000000000000000000000000000000beef", + ) + .unwrap(), + ], + Bytes::from_str("0100ff").unwrap(), + ), + }], + }, + logs_bloom: [0; 256].into(), + }); + + let receipt = TypedReceipt::decode(&mut &data[..]).unwrap(); - let tx: TypedTransaction = rlp::decode(&hex::decode(raw_tx).unwrap()).unwrap(); - let recovered = tx.recover().unwrap(); - let expected: Address = "0xa12e1462d0ced572f396f58b6e2d03894cd7c8a4".parse().unwrap(); - assert_eq!(expected, recovered); + assert_eq!(receipt, expected); } } diff --git a/crates/anvil/core/src/eth/transaction/optimism.rs b/crates/anvil/core/src/eth/transaction/optimism.rs new file mode 100644 index 0000000000000..dedaffaf34589 --- /dev/null +++ b/crates/anvil/core/src/eth/transaction/optimism.rs @@ -0,0 +1,335 @@ +use alloy_consensus::{SignableTransaction, Signed, Transaction, TxType}; +use alloy_primitives::{keccak256, Address, Bytes, ChainId, Signature, TxKind, B256, U256}; +use alloy_rlp::{ + length_of_length, Decodable, Encodable, Error as DecodeError, Header as RlpHeader, +}; +use std::mem; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DepositTransactionRequest { + pub source_hash: B256, + pub from: Address, + pub kind: TxKind, + pub mint: U256, + pub value: U256, + pub gas_limit: u128, + pub is_system_tx: bool, + pub input: Bytes, +} + +impl DepositTransactionRequest { + pub fn hash(&self) -> B256 { + B256::from_slice(alloy_primitives::keccak256(alloy_rlp::encode(self)).as_slice()) + } + + /// Encodes only the transaction's fields into the desired buffer, without a RLP header. + pub(crate) fn encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) { + self.from.encode(out); + self.source_hash.encode(out); + self.kind.encode(out); + self.mint.encode(out); + self.value.encode(out); + self.gas_limit.encode(out); + self.is_system_tx.encode(out); + self.input.encode(out); + } + + /// Calculates the length of the RLP-encoded transaction's fields. + pub(crate) fn fields_len(&self) -> usize { + let mut len = 0; + len += self.source_hash.length(); + len += self.from.length(); + len += self.kind.length(); + len += self.mint.length(); + len += self.value.length(); + len += self.gas_limit.length(); + len += self.is_system_tx.length(); + len += self.input.length(); + len + } + + /// Decodes the inner [DepositTransactionRequest] fields from RLP bytes. + /// + /// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following + /// RLP fields in the following order: + /// + /// - `source_hash` + /// - `from` + /// - `kind` + /// - `mint` + /// - `value` + /// - `gas_limit` + /// - `is_system_tx` + /// - `input` + pub fn decode_inner(buf: &mut &[u8]) -> Result { + Ok(Self { + source_hash: Decodable::decode(buf)?, + from: Decodable::decode(buf)?, + kind: Decodable::decode(buf)?, + mint: Decodable::decode(buf)?, + value: Decodable::decode(buf)?, + gas_limit: Decodable::decode(buf)?, + is_system_tx: Decodable::decode(buf)?, + input: Decodable::decode(buf)?, + }) + } + + /// Inner encoding function that is used for both rlp [`Encodable`] trait and for calculating + /// hash that for eip2718 does not require rlp header + pub(crate) fn encode_with_signature( + &self, + signature: &Signature, + out: &mut dyn alloy_rlp::BufMut, + ) { + let payload_length = self.fields_len() + signature.rlp_vrs_len(); + let header = alloy_rlp::Header { list: true, payload_length }; + header.encode(out); + self.encode_fields(out); + signature.write_rlp_vrs(out); + } + + /// Output the length of the RLP signed transaction encoding, _without_ a RLP string header. + pub fn payload_len_with_signature_without_header(&self, signature: &Signature) -> usize { + let payload_length = self.fields_len() + signature.rlp_vrs_len(); + // 'transaction type byte length' + 'header length' + 'payload length' + 1 + length_of_length(payload_length) + payload_length + } + + /// Output the length of the RLP signed transaction encoding. This encodes with a RLP header. + pub fn payload_len_with_signature(&self, signature: &Signature) -> usize { + let len = self.payload_len_with_signature_without_header(signature); + length_of_length(len) + len + } + + /// Get transaction type + pub(crate) const fn tx_type(&self) -> TxType { + TxType::Eip1559 + } + + /// Calculates a heuristic for the in-memory size of the [DepositTransaction] transaction. + #[inline] + pub fn size(&self) -> usize { + mem::size_of::() + // source_hash + mem::size_of::
() + // from + self.kind.size() + // to + mem::size_of::() + // mint + mem::size_of::() + // value + mem::size_of::() + // gas_limit + mem::size_of::() + // is_system_transaction + self.input.len() // input + } + + /// Encodes the legacy transaction in RLP for signing. + pub(crate) fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) { + out.put_u8(self.tx_type() as u8); + alloy_rlp::Header { list: true, payload_length: self.fields_len() }.encode(out); + self.encode_fields(out); + } + + /// Outputs the length of the signature RLP encoding for the transaction. + pub(crate) fn payload_len_for_signature(&self) -> usize { + let payload_length = self.fields_len(); + // 'transaction type byte length' + 'header length' + 'payload length' + 1 + length_of_length(payload_length) + payload_length + } + + fn encoded_len_with_signature(&self, signature: &Signature) -> usize { + // this counts the tx fields and signature fields + let payload_length = self.fields_len() + signature.rlp_vrs_len(); + + // this counts: + // * tx type byte + // * inner header length + // * inner payload length + 1 + alloy_rlp::Header { list: true, payload_length }.length() + payload_length + } +} + +impl Transaction for DepositTransactionRequest { + fn input(&self) -> &[u8] { + &self.input + } + + /// Get `to`. + fn to(&self) -> TxKind { + self.kind + } + + /// Get `value`. + fn value(&self) -> U256 { + self.value + } + + /// Get `chain_id`. + fn chain_id(&self) -> Option { + None + } + + /// Get `nonce`. + fn nonce(&self) -> u64 { + u64::MAX + } + + /// Get `gas_limit`. + fn gas_limit(&self) -> u128 { + self.gas_limit + } + + /// Get `gas_price`. + fn gas_price(&self) -> Option { + None + } +} + +impl SignableTransaction for DepositTransactionRequest { + fn set_chain_id(&mut self, _chain_id: ChainId) {} + + fn payload_len_for_signature(&self) -> usize { + self.payload_len_for_signature() + } + + fn into_signed(self, signature: Signature) -> Signed { + let mut buf = Vec::with_capacity(self.encoded_len_with_signature(&signature)); + self.encode_with_signature(&signature, &mut buf); + let hash = keccak256(&buf); + + // Drop any v chain id value to ensure the signature format is correct at the time of + // combination for an EIP-4844 transaction. V should indicate the y-parity of the + // signature. + Signed::new_unchecked(self, signature.with_parity_bool(), hash) + } + + fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) { + self.encode_for_signing(out); + } +} + +impl From for DepositTransactionRequest { + fn from(tx: DepositTransaction) -> Self { + Self { + from: tx.from, + source_hash: tx.source_hash, + kind: tx.kind, + mint: tx.mint, + value: tx.value, + gas_limit: tx.gas_limit, + is_system_tx: tx.is_system_tx, + input: tx.input, + } + } +} + +impl Encodable for DepositTransactionRequest { + fn encode(&self, out: &mut dyn bytes::BufMut) { + RlpHeader { list: true, payload_length: self.fields_len() }.encode(out); + self.encode_fields(out); + } +} + +/// An op-stack deposit transaction. +/// See +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct DepositTransaction { + pub nonce: u64, + pub source_hash: B256, + pub from: Address, + pub kind: TxKind, + pub mint: U256, + pub value: U256, + pub gas_limit: u128, + pub is_system_tx: bool, + pub input: Bytes, +} + +impl DepositTransaction { + pub fn nonce(&self) -> &u64 { + &self.nonce + } + + pub fn hash(&self) -> B256 { + B256::from_slice(alloy_primitives::keccak256(alloy_rlp::encode(self)).as_slice()) + } + + // /// Recovers the Ethereum address which was used to sign the transaction. + pub fn recover(&self) -> Result { + Ok(self.from) + } + + pub fn chain_id(&self) -> Option { + None + } + + /// Encodes only the transaction's fields into the desired buffer, without a RLP header. + pub(crate) fn encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) { + self.nonce.encode(out); + self.source_hash.encode(out); + self.from.encode(out); + self.kind.encode(out); + self.mint.encode(out); + self.value.encode(out); + self.gas_limit.encode(out); + self.is_system_tx.encode(out); + self.input.encode(out); + } + + /// Calculates the length of the RLP-encoded transaction's fields. + pub(crate) fn fields_len(&self) -> usize { + let mut len = 0; + len += self.source_hash.length(); + len += self.from.length(); + len += self.kind.length(); + len += self.mint.length(); + len += self.value.length(); + len += self.gas_limit.length(); + len += self.is_system_tx.length(); + len += self.input.length(); + len + } + + /// Decodes the inner [TxDeposit] fields from RLP bytes. + /// + /// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following + /// RLP fields in the following order: + /// + /// - `source_hash` + /// - `from` + /// - `kind` + /// - `mint` + /// - `value` + /// - `gas_limit` + /// - `is_system_tx` + /// - `input` + pub fn decode_inner(buf: &mut &[u8]) -> Result { + Ok(Self { + nonce: 0, + source_hash: Decodable::decode(buf)?, + from: Decodable::decode(buf)?, + kind: Decodable::decode(buf)?, + mint: Decodable::decode(buf)?, + value: Decodable::decode(buf)?, + gas_limit: Decodable::decode(buf)?, + is_system_tx: Decodable::decode(buf)?, + input: Decodable::decode(buf)?, + }) + } +} + +impl Encodable for DepositTransaction { + fn encode(&self, out: &mut dyn bytes::BufMut) { + RlpHeader { list: true, payload_length: self.fields_len() }.encode(out); + self.encode_fields(out); + } +} + +impl Decodable for DepositTransaction { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let header = RlpHeader::decode(buf)?; + let remaining_len = buf.len(); + + if header.payload_length > remaining_len { + return Err(alloy_rlp::Error::InputTooShort); + } + + Self::decode_inner(buf) + } +} diff --git a/crates/anvil/core/src/eth/trie.rs b/crates/anvil/core/src/eth/trie.rs index 6187c5087d950..bcc86ac8ba36b 100644 --- a/crates/anvil/core/src/eth/trie.rs +++ b/crates/anvil/core/src/eth/trie.rs @@ -1,42 +1,30 @@ //! Utility functions for Ethereum adapted from https://github.dev/rust-blockchain/ethereum/blob/755dffaa4903fbec1269f50cde9863cf86269a14/src/util.rs -use ethers_core::types::H256; +use std::collections::BTreeMap; -pub use keccak_hasher::KeccakHasher; - -// reexport some trie types -pub use reference_trie::*; +use alloy_primitives::{fixed_bytes, B256}; +use alloy_trie::{HashBuilder, Nibbles}; /// The KECCAK of the RLP encoding of empty data. -pub const KECCAK_NULL_RLP: H256 = H256([ - 0x56, 0xe8, 0x1f, 0x17, 0x1b, 0xcc, 0x55, 0xa6, 0xff, 0x83, 0x45, 0xe6, 0x92, 0xc0, 0xf8, 0x6e, - 0x5b, 0x48, 0xe0, 0x1b, 0x99, 0x6c, 0xad, 0xc0, 0x01, 0x62, 0x2f, 0xb5, 0xe3, 0x63, 0xb4, 0x21, -]); - -/// Generates a trie root hash for a vector of key-value tuples -pub fn trie_root(input: I) -> H256 -where - I: IntoIterator, - K: AsRef<[u8]> + Ord, - V: AsRef<[u8]>, -{ - H256::from(triehash::trie_root::(input)) -} - -/// Generates a key-hashed (secure) trie root hash for a vector of key-value tuples. -pub fn sec_trie_root(input: I) -> H256 -where - I: IntoIterator, - K: AsRef<[u8]>, - V: AsRef<[u8]>, -{ - H256::from(triehash::sec_trie_root::(input)) -} +pub const KECCAK_NULL_RLP: B256 = + fixed_bytes!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); /// Generates a trie root hash for a vector of values -pub fn ordered_trie_root(input: I) -> H256 +pub fn ordered_trie_root(input: I) -> B256 where I: IntoIterator, V: AsRef<[u8]>, { - H256::from(triehash::ordered_trie_root::(input)) + let mut builder = HashBuilder::default(); + + let input = input + .into_iter() + .enumerate() + .map(|(i, v)| (alloy_rlp::encode(i), v)) + .collect::>(); + + for (key, value) in input { + builder.add_leaf(Nibbles::unpack(key), value.as_ref()); + } + + builder.root() } diff --git a/crates/anvil/core/src/eth/utils.rs b/crates/anvil/core/src/eth/utils.rs index 21711cb3eb9cb..a604392801238 100644 --- a/crates/anvil/core/src/eth/utils.rs +++ b/crates/anvil/core/src/eth/utils.rs @@ -1,28 +1,13 @@ -use alloy_primitives::{Address, U256}; -use ethers_core::{ - types::transaction::eip2930::AccessListItem, - utils::{ - rlp, - rlp::{Encodable, RlpStream}, - }, -}; -use foundry_common::types::ToAlloy; +use alloy_primitives::Parity; -pub fn enveloped(id: u8, v: &T, s: &mut RlpStream) { - let encoded = rlp::encode(v); - let mut out = vec![0; 1 + encoded.len()]; - out[0] = id; - out[1..].copy_from_slice(&encoded); - out.rlp_append(s) -} - -pub fn to_revm_access_list(list: Vec) -> Vec<(Address, Vec)> { - list.into_iter() - .map(|item| { - ( - item.address.to_alloy(), - item.storage_keys.into_iter().map(|k| k.to_alloy().into()).collect(), - ) - }) - .collect() +/// See +/// > If you do, then the v of the signature MUST be set to {0,1} + CHAIN_ID * 2 + 35 where +/// > {0,1} is the parity of the y value of the curve point for which r is the x-value in the +/// > secp256k1 signing process. +pub fn meets_eip155(chain_id: u64, v: Parity) -> bool { + let double_chain_id = chain_id.saturating_mul(2); + match v { + Parity::Eip155(v) => v == double_chain_id + 35 || v == double_chain_id + 36, + _ => false, + } } diff --git a/crates/anvil/core/src/types.rs b/crates/anvil/core/src/types.rs index 34d6627498b8d..2e0c63b13485f 100644 --- a/crates/anvil/core/src/types.rs +++ b/crates/anvil/core/src/types.rs @@ -1,7 +1,6 @@ -use std::collections::BTreeMap; - -use ethers_core::types::{TxHash, H256, U256, U64}; +use alloy_primitives::{TxHash, B256, U256, U64}; use revm::primitives::SpecId; +use std::collections::BTreeMap; #[cfg(feature = "serde")] use serde::{de::Error, Deserializer, Serializer}; @@ -9,7 +8,7 @@ use serde::{de::Error, Deserializer, Serializer}; /// Represents the params to set forking which can take various forms /// - untagged /// - tagged `forking` -#[derive(Clone, Debug, PartialEq, Eq, Default)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct Forking { pub json_rpc_url: Option, pub block_number: Option, @@ -27,7 +26,7 @@ impl<'de> serde::Deserialize<'de> for Forking { pub json_rpc_url: Option, #[serde( default, - deserialize_with = "ethers_core::types::serde_helpers::deserialize_stringified_u64_opt" + deserialize_with = "crate::eth::serde_helpers::numeric::deserialize_stringified_u64_opt" )] pub block_number: Option, } @@ -64,7 +63,7 @@ pub enum EvmMineOptions { #[cfg_attr( feature = "serde", serde( - deserialize_with = "ethers_core::types::serde_helpers::deserialize_stringified_u64_opt" + deserialize_with = "crate::eth::serde_helpers::numeric::deserialize_stringified_u64_opt" ) )] timestamp: Option, @@ -76,7 +75,7 @@ pub enum EvmMineOptions { #[cfg_attr( feature = "serde", serde( - deserialize_with = "ethers_core::types::serde_helpers::deserialize_stringified_u64_opt" + deserialize_with = "crate::eth::serde_helpers::numeric::deserialize_stringified_u64_opt" ) )] Timestamp(Option), @@ -90,11 +89,11 @@ impl Default for EvmMineOptions { /// Represents the result of `eth_getWork` /// This may or may not include the block number -#[derive(Debug, PartialEq, Eq, Default)] +#[derive(Debug, Default, PartialEq, Eq)] pub struct Work { - pub pow_hash: H256, - pub seed_hash: H256, - pub target: H256, + pub pow_hash: B256, + pub seed_hash: B256, + pub target: B256, pub number: Option, } @@ -113,7 +112,7 @@ impl serde::Serialize for Work { } /// A hex encoded or decimal index -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Index(usize); impl From for usize { @@ -174,30 +173,30 @@ impl<'a> serde::Deserialize<'a> for Index { } } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct NodeInfo { pub current_block_number: U64, pub current_block_timestamp: u64, - pub current_block_hash: H256, + pub current_block_hash: B256, pub hard_fork: SpecId, pub transaction_order: String, pub environment: NodeEnvironment, pub fork_config: NodeForkConfig, } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct NodeEnvironment { - pub base_fee: U256, + pub base_fee: u128, pub chain_id: u64, - pub gas_limit: U256, - pub gas_price: U256, + pub gas_limit: u128, + pub gas_price: u128, } -#[derive(Debug, Clone, Default, Eq, PartialEq)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct NodeForkConfig { @@ -209,22 +208,22 @@ pub struct NodeForkConfig { /// Anvil equivalent of `hardhat_metadata`. /// Metadata about the current Anvil instance. /// See -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct AnvilMetadata { pub client_version: &'static str, pub chain_id: u64, - pub instance_id: H256, + pub instance_id: B256, pub latest_block_number: u64, - pub latest_block_hash: H256, + pub latest_block_hash: B256, pub forked_network: Option, - pub snapshots: BTreeMap, + pub snapshots: BTreeMap, } /// Information about the forked network. /// See -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ForkedNetwork { diff --git a/crates/anvil/rpc/src/error.rs b/crates/anvil/rpc/src/error.rs index 427acd384ced4..b1929818fa8c6 100644 --- a/crates/anvil/rpc/src/error.rs +++ b/crates/anvil/rpc/src/error.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{borrow::Cow, fmt}; /// Represents a JSON-RPC error -#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct RpcError { pub code: ErrorCode, @@ -75,7 +75,7 @@ impl fmt::Display for RpcError { } /// List of JSON-RPC error codes -#[derive(Debug, Copy, PartialEq, Eq, Clone)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ErrorCode { /// Server received Invalid JSON. /// server side error while parsing JSON diff --git a/crates/anvil/rpc/src/request.rs b/crates/anvil/rpc/src/request.rs index 8745cf2a3b09e..8630f4a4c1f5a 100644 --- a/crates/anvil/rpc/src/request.rs +++ b/crates/anvil/rpc/src/request.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use std::fmt; /// A JSON-RPC request object, a method call -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct RpcMethodCall { /// The version of the protocol @@ -26,7 +26,7 @@ impl RpcMethodCall { /// Represents a JSON-RPC request which is considered a notification (missing [Id] optional /// [Version]) -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct RpcNotification { pub jsonrpc: Option, @@ -36,7 +36,7 @@ pub struct RpcNotification { } /// Representation of a single JSON-RPC call -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum RpcCall { /// the RPC method to invoke @@ -52,7 +52,7 @@ pub enum RpcCall { } /// Represents a JSON-RPC request. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] #[serde(untagged)] pub enum Request { @@ -63,7 +63,7 @@ pub enum Request { } /// Request parameters -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged, deny_unknown_fields)] pub enum RequestParams { /// no parameters provided @@ -89,13 +89,13 @@ fn no_params() -> RequestParams { } /// Represents the version of the RPC protocol -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Version { #[serde(rename = "2.0")] V2, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum Id { String(String), diff --git a/crates/anvil/rpc/src/response.rs b/crates/anvil/rpc/src/response.rs index 8600996f46b6c..c7649dbe351a8 100644 --- a/crates/anvil/rpc/src/response.rs +++ b/crates/anvil/rpc/src/response.rs @@ -5,7 +5,7 @@ use crate::{ use serde::{Deserialize, Serialize}; /// Response of a _single_ rpc call -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct RpcResponse { // JSON RPC version @@ -33,7 +33,7 @@ impl RpcResponse { } /// Represents the result of a call either success or error -#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum ResponseResult { #[serde(rename = "result")] @@ -61,7 +61,7 @@ impl From for ResponseResult { } } /// Synchronous response -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] #[serde(untagged)] pub enum Response { diff --git a/crates/anvil/server/Cargo.toml b/crates/anvil/server/Cargo.toml index fb880a422de95..a9d87051050da 100644 --- a/crates/anvil/server/Cargo.toml +++ b/crates/anvil/server/Cargo.toml @@ -15,7 +15,6 @@ anvil-rpc = { path = "../rpc" } # axum related axum = { workspace = true, features = ["ws"] } -hyper.workspace = true tower-http = { workspace = true, features = ["trace", "cors"] } # tracing diff --git a/crates/anvil/server/src/config.rs b/crates/anvil/server/src/config.rs index 3e7170718809a..f968a6da39640 100644 --- a/crates/anvil/server/src/config.rs +++ b/crates/anvil/server/src/config.rs @@ -3,17 +3,16 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::str::FromStr; /// Additional server options. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "clap", derive(clap::Parser), clap(next_help_heading = "Server options"))] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "clap", derive(clap::Parser), command(next_help_heading = "Server options"))] pub struct ServerConfig { /// The cors `allow_origin` header #[cfg_attr( feature = "clap", - clap( + arg( long, help = "Set the CORS allow_origin", default_value = "*", - name = "allow-origin", value_name = "ALLOW_ORIGIN" ) )] @@ -21,7 +20,7 @@ pub struct ServerConfig { /// Whether to enable CORS #[cfg_attr( feature = "clap", - clap(long, help = "Disable CORS", conflicts_with = "allow-origin") + arg(long, help = "Disable CORS", conflicts_with = "allow_origin") )] pub no_cors: bool, } @@ -48,7 +47,7 @@ impl Default for ServerConfig { } } -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct HeaderValueWrapper(pub HeaderValue); impl FromStr for HeaderValueWrapper { diff --git a/crates/anvil/server/src/ipc.rs b/crates/anvil/server/src/ipc.rs index 97ec0709887dc..152b41873f95a 100644 --- a/crates/anvil/server/src/ipc.rs +++ b/crates/anvil/server/src/ipc.rs @@ -24,8 +24,8 @@ pub struct IpcEndpoint { impl IpcEndpoint { /// Creates a new endpoint with the given handler - pub fn new(handler: Handler, endpoint: impl Into) -> Self { - Self { handler, endpoint: Endpoint::new(endpoint.into()) } + pub fn new(handler: Handler, endpoint: String) -> Self { + Self { handler, endpoint: Endpoint::new(endpoint) } } /// Returns a stream of incoming connection handlers diff --git a/crates/anvil/server/src/lib.rs b/crates/anvil/server/src/lib.rs index 898d3a7659d5d..5bd81b043e8ea 100644 --- a/crates/anvil/server/src/lib.rs +++ b/crates/anvil/server/src/lib.rs @@ -1,4 +1,4 @@ -//! Bootstrap [axum] RPC servers +//! Bootstrap [axum] RPC servers. #![warn(missing_docs, unused_crate_dependencies)] @@ -12,91 +12,66 @@ use anvil_rpc::{ }; use axum::{ http::{header, HeaderValue, Method}, - routing::{post, IntoMakeService}, - Router, Server, + routing::{post, MethodRouter}, + Router, }; -use hyper::server::conn::AddrIncoming; use serde::de::DeserializeOwned; -use std::{fmt, net::SocketAddr}; +use std::fmt; use tower_http::{cors::CorsLayer, trace::TraceLayer}; mod config; +pub use config::ServerConfig; mod error; -/// handlers for axum server mod handler; -#[cfg(feature = "ipc")] -pub mod ipc; + mod pubsub; -mod ws; +pub use pubsub::{PubSubContext, PubSubRpcHandler}; -pub use crate::pubsub::{PubSubContext, PubSubRpcHandler}; -pub use config::ServerConfig; +mod ws; -/// Type alias for the configured axum server -pub type AnvilServer = Server>; +#[cfg(feature = "ipc")] +pub mod ipc; -/// Configures an [axum::Server] that handles RPC-Calls, both HTTP requests and requests via -/// websocket -pub fn serve_http_ws( - addr: SocketAddr, - config: ServerConfig, - http: Http, - ws: Ws, -) -> AnvilServer +/// Configures an [`axum::Router`] that handles JSON-RPC calls via both HTTP and WS. +pub fn http_ws_router(config: ServerConfig, http: Http, ws: Ws) -> Router where Http: RpcHandler, Ws: PubSubRpcHandler, { - let ServerConfig { allow_origin, no_cors } = config; - - let svc = Router::new() - .route("/", post(handler::handle).get(ws::handle_ws)) - .with_state((http, ws)) - .layer(TraceLayer::new_for_http()); - - let svc = if no_cors { - svc - } else { - svc.layer( - // see https://docs.rs/tower-http/latest/tower_http/cors/index.html - // for more details - CorsLayer::new() - .allow_origin(allow_origin.0) - .allow_headers(vec![header::CONTENT_TYPE]) - .allow_methods(vec![Method::GET, Method::POST]), - ) - } - .into_make_service(); - Server::bind(&addr).serve(svc) + router_inner(config, post(handler::handle).get(ws::handle_ws), (http, ws)) } -/// Configures an [axum::Server] that handles RPC-Calls listing for POST on `/` -pub fn serve_http(addr: SocketAddr, config: ServerConfig, http: Http) -> AnvilServer +/// Configures an [`axum::Router`] that handles JSON-RPC calls via HTTP. +pub fn http_router(config: ServerConfig, http: Http) -> Router where Http: RpcHandler, { + router_inner(config, post(handler::handle), (http, ())) +} + +fn router_inner( + config: ServerConfig, + root_method_router: MethodRouter, + state: S, +) -> Router { let ServerConfig { allow_origin, no_cors } = config; - let svc = Router::new() - .route("/", post(handler::handle)) - .with_state((http, ())) + let mut router = Router::new() + .route("/", root_method_router) + .with_state(state) .layer(TraceLayer::new_for_http()); - let svc = if no_cors { - svc - } else { - svc.layer( - // see https://docs.rs/tower-http/latest/tower_http/cors/index.html - // for more details + if !no_cors { + // See [`tower_http::cors`](https://docs.rs/tower-http/latest/tower_http/cors/index.html) + // for more details. + router = router.layer( CorsLayer::new() .allow_origin(allow_origin.0) - .allow_headers(vec![header::CONTENT_TYPE]) - .allow_methods(vec![Method::GET, Method::POST]), - ) + .allow_headers([header::CONTENT_TYPE]) + .allow_methods([Method::GET, Method::POST]), + ); } - .into_make_service(); - - Server::bind(&addr).serve(svc) + router } /// Helper trait that is used to execute ethereum rpc calls @@ -117,7 +92,7 @@ pub trait RpcHandler: Clone + Send + Sync + 'static { /// **Note**: override this function if the expected `Request` deviates from `{ "method" : /// "", "params": "" }` async fn on_call(&self, call: RpcMethodCall) -> RpcResponse { - trace!(target: "rpc", id = ?call.id , method = ?call.method, "received method call"); + trace!(target: "rpc", id = ?call.id , method = ?call.method, params = ?call.params, "received method call"); let RpcMethodCall { method, params, id, .. } = call; let params: serde_json::Value = params.into(); diff --git a/crates/anvil/src/anvil.rs b/crates/anvil/src/anvil.rs index ebdb1fadb7975..1aa204587b200 100644 --- a/crates/anvil/src/anvil.rs +++ b/crates/anvil/src/anvil.rs @@ -1,50 +1,58 @@ //! The `anvil` cli + use anvil::cmd::NodeArgs; use clap::{CommandFactory, Parser, Subcommand}; +use foundry_cli::utils; + +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; /// A fast local Ethereum development node. -#[derive(Debug, Parser)] -#[clap(name = "anvil", version = anvil::VERSION_MESSAGE, next_display_order = None)] -pub struct App { - #[clap(flatten)] +#[derive(Parser)] +#[command(name = "anvil", version = anvil::VERSION_MESSAGE, next_display_order = None)] +pub struct Anvil { + #[command(flatten)] pub node: NodeArgs, - #[clap(subcommand)] - pub cmd: Option, + #[command(subcommand)] + pub cmd: Option, } -#[derive(Clone, Debug, Subcommand, Eq, PartialEq)] -pub enum Commands { +#[derive(Subcommand)] +pub enum AnvilSubcommand { /// Generate shell completions script. - #[clap(visible_alias = "com")] + #[command(visible_alias = "com")] Completions { - #[clap(value_enum)] + #[arg(value_enum)] shell: clap_complete::Shell, }, /// Generate Fig autocompletion spec. - #[clap(visible_alias = "fig")] + #[command(visible_alias = "fig")] GenerateFigSpec, } #[tokio::main] -async fn main() -> Result<(), Box> { - let mut app = App::parse(); +async fn main() -> eyre::Result<()> { + utils::load_dotenv(); + + let mut app = Anvil::parse(); app.node.evm_opts.resolve_rpc_alias(); if let Some(ref cmd) = app.cmd { match cmd { - Commands::Completions { shell } => { + AnvilSubcommand::Completions { shell } => { clap_complete::generate( *shell, - &mut App::command(), + &mut Anvil::command(), "anvil", &mut std::io::stdout(), ); } - Commands::GenerateFigSpec => clap_complete::generate( + AnvilSubcommand::GenerateFigSpec => clap_complete::generate( clap_complete_fig::Fig, - &mut App::command(), + &mut Anvil::command(), "anvil", &mut std::io::stdout(), ), @@ -62,14 +70,22 @@ async fn main() -> Result<(), Box> { mod tests { use super::*; + #[test] + fn verify_cli() { + Anvil::command().debug_assert(); + } + #[test] fn can_parse_help() { - let _: App = App::parse_from(["anvil", "--help"]); + let _: Anvil = Anvil::parse_from(["anvil", "--help"]); } #[test] fn can_parse_completions() { - let args: App = App::parse_from(["anvil", "completions", "bash"]); - assert_eq!(args.cmd, Some(Commands::Completions { shell: clap_complete::Shell::Bash })); + let args: Anvil = Anvil::parse_from(["anvil", "completions", "bash"]); + assert!(matches!( + args.cmd, + Some(AnvilSubcommand::Completions { shell: clap_complete::Shell::Bash }) + )); } } diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 5f184cb80dd74..4a4b329da9704 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -1,23 +1,21 @@ use crate::{ config::DEFAULT_MNEMONIC, eth::{backend::db::SerializableState, pool::transactions::TransactionOrder, EthApi}, - genesis::Genesis, AccountGenerator, Hardfork, NodeConfig, CHAIN_ID, }; +use alloy_genesis::Genesis; +use alloy_primitives::{utils::Unit, U256}; +use alloy_signer_wallet::coins_bip39::{English, Mnemonic}; use anvil_server::ServerConfig; use clap::Parser; use core::fmt; -use ethers::{ - signers::coins_bip39::{English, Mnemonic}, - utils::WEI_IN_ETHER, -}; -use foundry_config::{Chain, Config}; +use foundry_config::{Chain, Config, FigmentProviders}; use futures::FutureExt; use rand::{rngs::StdRng, SeedableRng}; use std::{ future::Future, net::IpAddr, - path::PathBuf, + path::{Path, PathBuf}, pin::Pin, str::FromStr, sync::{ @@ -32,31 +30,31 @@ use tokio::time::{Instant, Interval}; #[derive(Clone, Debug, Parser)] pub struct NodeArgs { /// Port number to listen on. - #[clap(long, short, default_value = "8545", value_name = "NUM")] + #[arg(long, short, default_value = "8545", value_name = "NUM")] pub port: u16, /// Number of dev accounts to generate and configure. - #[clap(long, short, default_value = "10", value_name = "NUM")] + #[arg(long, short, default_value = "10", value_name = "NUM")] pub accounts: u64, /// The balance of every dev account in Ether. - #[clap(long, default_value = "10000", value_name = "NUM")] + #[arg(long, default_value = "10000", value_name = "NUM")] pub balance: u64, /// The timestamp of the genesis block. - #[clap(long, value_name = "NUM")] + #[arg(long, value_name = "NUM")] pub timestamp: Option, /// BIP39 mnemonic phrase used for generating accounts. /// Cannot be used if `mnemonic_random` or `mnemonic_seed` are used - #[clap(long, short, conflicts_with_all = &["mnemonic_seed", "mnemonic_random"])] + #[arg(long, short, conflicts_with_all = &["mnemonic_seed", "mnemonic_random"])] pub mnemonic: Option, /// Automatically generates a BIP39 mnemonic phrase, and derives accounts from it. /// Cannot be used with other `mnemonic` options /// You can specify the number of words you want in the mnemonic. /// [default: 12] - #[clap(long, conflicts_with_all = &["mnemonic", "mnemonic_seed"], default_missing_value = "12", num_args(0..=1))] + #[arg(long, conflicts_with_all = &["mnemonic", "mnemonic_seed"], default_missing_value = "12", num_args(0..=1))] pub mnemonic_random: Option, /// Generates a BIP39 mnemonic phrase from a given seed @@ -64,40 +62,44 @@ pub struct NodeArgs { /// /// CAREFUL: this is NOT SAFE and should only be used for testing. /// Never use the private keys generated in production. - #[clap(long = "mnemonic-seed-unsafe", conflicts_with_all = &["mnemonic", "mnemonic_random"])] + #[arg(long = "mnemonic-seed-unsafe", conflicts_with_all = &["mnemonic", "mnemonic_random"])] pub mnemonic_seed: Option, /// Sets the derivation path of the child key to be derived. /// /// [default: m/44'/60'/0'/0/] - #[clap(long)] + #[arg(long)] pub derivation_path: Option, /// Don't print anything on startup and don't print logs - #[clap(long)] + #[arg(long)] pub silent: bool, /// The EVM hardfork to use. /// /// Choose the hardfork by name, e.g. `shanghai`, `paris`, `london`, etc... /// [default: latest] - #[clap(long, value_parser = Hardfork::from_str)] + #[arg(long, value_parser = Hardfork::from_str)] pub hardfork: Option, /// Block time in seconds for interval mining. - #[clap(short, long, visible_alias = "blockTime", name = "block-time", value_name = "SECONDS")] - pub block_time: Option, + #[arg(short, long, visible_alias = "blockTime", value_name = "SECONDS", value_parser = duration_from_secs_f64)] + pub block_time: Option, + + /// Slots in an epoch + #[arg(long, value_name = "SLOTS_IN_AN_EPOCH", default_value_t = 32)] + pub slots_in_an_epoch: u64, /// Writes output of `anvil` as json to user-specified file. - #[clap(long, value_name = "OUT_FILE")] + #[arg(long, value_name = "OUT_FILE")] pub config_out: Option, /// Disable auto and interval mining, and mine on demand instead. - #[clap(long, visible_alias = "no-mine", conflicts_with = "block-time")] + #[arg(long, visible_alias = "no-mine", conflicts_with = "block_time")] pub no_mining: bool, /// The hosts the server will listen on. - #[clap( + #[arg( long, value_name = "IP_ADDR", env = "ANVIL_IP_ADDR", @@ -108,18 +110,18 @@ pub struct NodeArgs { pub host: Vec, /// How transactions are sorted in the mempool. - #[clap(long, default_value = "fees")] + #[arg(long, default_value = "fees")] pub order: TransactionOrder, /// Initialize the genesis block with the given `genesis.json` file. - #[clap(long, value_name = "PATH", value_parser = Genesis::parse)] + #[arg(long, value_name = "PATH", value_parser= read_genesis_file)] pub init: Option, /// This is an alias for both --load-state and --dump-state. /// - /// It initializes the chain with the state stored at the file, if it exists, and dumps the - /// chain's state on exit. - #[clap( + /// It initializes the chain with the state and block environment stored at the file, if it + /// exists, and dumps the chain's state on exit. + #[arg( long, value_name = "PATH", value_parser = StateFile::parse, @@ -131,20 +133,20 @@ pub struct NodeArgs { )] pub state: Option, - /// Interval in seconds at which the status is to be dumped to disk. + /// Interval in seconds at which the state and block environment is to be dumped to disk. /// /// See --state and --dump-state - #[clap(short, long, value_name = "SECONDS")] + #[arg(short, long, value_name = "SECONDS")] pub state_interval: Option, - /// Dump the state of chain on exit to the given file. + /// Dump the state and block environment of chain on exit to the given file. /// /// If the value is a directory, the state will be written to `/state.json`. - #[clap(long, value_name = "PATH", conflicts_with = "init")] + #[arg(long, value_name = "PATH", conflicts_with = "init")] pub dump_state: Option, /// Initialize the chain from a previously saved state snapshot. - #[clap( + #[arg( long, value_name = "PATH", value_parser = SerializableState::parse, @@ -152,22 +154,22 @@ pub struct NodeArgs { )] pub load_state: Option, - #[clap(long, help = IPC_HELP, value_name = "PATH", visible_alias = "ipcpath")] + #[arg(long, help = IPC_HELP, value_name = "PATH", visible_alias = "ipcpath")] pub ipc: Option>, /// Don't keep full chain history. /// If a number argument is specified, at most this number of states is kept in memory. - #[clap(long)] + #[arg(long)] pub prune_history: Option>, /// Number of blocks with transactions to keep in memory. - #[clap(long)] + #[arg(long)] pub transaction_block_keeper: Option, - #[clap(flatten)] + #[command(flatten)] pub evm_opts: AnvilEvmArgs, - #[clap(flatten)] + #[command(flatten)] pub server_config: ServerConfig, } @@ -184,7 +186,7 @@ const DEFAULT_DUMP_INTERVAL: Duration = Duration::from_secs(60); impl NodeArgs { pub fn into_node_config(self) -> NodeConfig { - let genesis_balance = WEI_IN_ETHER.saturating_mul(self.balance.into()); + let genesis_balance = Unit::ETHER.wei().saturating_mul(U256::from(self.balance)); let compute_units_per_second = if self.evm_opts.no_rate_limit { Some(u64::MAX) } else { @@ -196,7 +198,7 @@ impl NodeArgs { .disable_block_gas_limit(self.evm_opts.disable_block_gas_limit) .with_gas_price(self.evm_opts.gas_price) .with_hardfork(self.hardfork) - .with_blocktime(self.block_time.map(Duration::from_secs)) + .with_blocktime(self.block_time) .with_no_mining(self.no_mining) .with_account_generator(self.account_generator()) .with_genesis_balance(genesis_balance) @@ -208,7 +210,7 @@ impl NodeArgs { .or_else(|| self.evm_opts.fork_url.as_ref().and_then(|f| f.block)), ) .with_fork_headers(self.evm_opts.fork_headers) - .with_fork_chain_id(self.evm_opts.fork_chain_id.map(u64::from)) + .with_fork_chain_id(self.evm_opts.fork_chain_id.map(u64::from).map(U256::from)) .fork_request_timeout(self.evm_opts.fork_request_timeout.map(Duration::from_millis)) .fork_request_retries(self.evm_opts.fork_request_retries) .fork_retry_backoff(self.evm_opts.fork_retry_backoff.map(Duration::from_millis)) @@ -231,6 +233,9 @@ impl NodeArgs { .with_init_state(self.load_state.or_else(|| self.state.and_then(|s| s.state))) .with_transaction_block_keeper(self.transaction_block_keeper) .with_optimism(self.evm_opts.optimism) + .with_disable_default_create2_deployer(self.evm_opts.disable_default_create2_deployer) + .with_slots_in_an_epoch(self.slots_in_an_epoch) + .with_memory_limit(self.evm_opts.memory_limit) } fn account_generator(&self) -> AccountGenerator { @@ -265,12 +270,12 @@ impl NodeArgs { /// Starts the node /// /// See also [crate::spawn()] - pub async fn run(self) -> Result<(), Box> { + pub async fn run(self) -> eyre::Result<()> { let dump_state = self.dump_state_path(); let dump_interval = self.state_interval.map(Duration::from_secs).unwrap_or(DEFAULT_DUMP_INTERVAL); - let (api, mut handle) = crate::spawn(self.into_node_config()).await; + let (api, mut handle) = crate::try_spawn(self.into_node_config()).await?; // sets the signal handler to gracefully shutdown. let mut fork = api.get_fork(); @@ -345,13 +350,13 @@ impl NodeArgs { } /// Anvil's EVM related arguments. -#[derive(Debug, Clone, Parser)] -#[clap(next_help_heading = "EVM options")] +#[derive(Clone, Debug, Parser)] +#[command(next_help_heading = "EVM options")] pub struct AnvilEvmArgs { /// Fetch state over a remote endpoint instead of starting from an empty state. /// /// If you want to fetch state from a specific block number, add a block number like `http://localhost:8545@1400000` or use the `--fork-block-number` argument. - #[clap( + #[arg( long, short, visible_alias = "rpc-url", @@ -363,7 +368,7 @@ pub struct AnvilEvmArgs { /// Headers to use for the rpc client, e.g. "User-Agent: test-agent" /// /// See --fork-url. - #[clap( + #[arg( long = "fork-header", value_name = "HEADERS", help_heading = "Fork config", @@ -374,35 +379,25 @@ pub struct AnvilEvmArgs { /// Timeout in ms for requests sent to remote JSON-RPC server in forking mode. /// /// Default value 45000 - #[clap( - long = "timeout", - name = "timeout", - help_heading = "Fork config", - requires = "fork_url" - )] + #[arg(id = "timeout", long = "timeout", help_heading = "Fork config", requires = "fork_url")] pub fork_request_timeout: Option, /// Number of retry requests for spurious networks (timed out requests) /// /// Default value 5 - #[clap( - long = "retries", - name = "retries", - help_heading = "Fork config", - requires = "fork_url" - )] + #[arg(id = "retries", long = "retries", help_heading = "Fork config", requires = "fork_url")] pub fork_request_retries: Option, /// Fetch state from a specific block number over a remote endpoint. /// /// See --fork-url. - #[clap(long, requires = "fork_url", value_name = "BLOCK", help_heading = "Fork config")] + #[arg(long, requires = "fork_url", value_name = "BLOCK", help_heading = "Fork config")] pub fork_block_number: Option, /// Initial retry backoff on encountering errors. /// /// See --fork-url. - #[clap(long, requires = "fork_url", value_name = "BACKOFF", help_heading = "Fork config")] + #[arg(long, requires = "fork_url", value_name = "BACKOFF", help_heading = "Fork config")] pub fork_retry_backoff: Option, /// Specify chain id to skip fetching it from remote endpoint. This enables offline-start mode. @@ -410,7 +405,7 @@ pub struct AnvilEvmArgs { /// You still must pass both `--fork-url` and `--fork-block-number`, and already have your /// required state cached on disk, anything missing locally would be fetched from the /// remote. - #[clap( + #[arg( long, help_heading = "Fork config", value_name = "CHAIN", @@ -424,7 +419,7 @@ pub struct AnvilEvmArgs { /// /// See --fork-url. /// See also, https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second - #[clap( + #[arg( long, requires = "fork_url", alias = "cups", @@ -439,7 +434,7 @@ pub struct AnvilEvmArgs { /// /// See --fork-url. /// See also, https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second - #[clap( + #[arg( long, requires = "fork_url", value_name = "NO_RATE_LIMITS", @@ -455,15 +450,15 @@ pub struct AnvilEvmArgs { /// This flag overrides the project's configuration file. /// /// See --fork-url. - #[clap(long, requires = "fork_url", help_heading = "Fork config")] + #[arg(long, requires = "fork_url", help_heading = "Fork config")] pub no_storage_caching: bool, /// The block gas limit. - #[clap(long, alias = "block-gas-limit", help_heading = "Environment config")] - pub gas_limit: Option, + #[arg(long, alias = "block-gas-limit", help_heading = "Environment config")] + pub gas_limit: Option, /// Disable the `call.gas_limit <= block.gas_limit` constraint. - #[clap( + #[arg( long, value_name = "DISABLE_GAS_LIMIT", help_heading = "Environment config", @@ -474,37 +469,45 @@ pub struct AnvilEvmArgs { /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. By /// default, it is 0x6000 (~25kb). - #[clap(long, value_name = "CODE_SIZE", help_heading = "Environment config")] + #[arg(long, value_name = "CODE_SIZE", help_heading = "Environment config")] pub code_size_limit: Option, /// The gas price. - #[clap(long, help_heading = "Environment config")] - pub gas_price: Option, + #[arg(long, help_heading = "Environment config")] + pub gas_price: Option, /// The base fee in a block. - #[clap( + #[arg( long, visible_alias = "base-fee", value_name = "FEE", help_heading = "Environment config" )] - pub block_base_fee_per_gas: Option, + pub block_base_fee_per_gas: Option, /// The chain ID. - #[clap(long, alias = "chain", help_heading = "Environment config")] + #[arg(long, alias = "chain", help_heading = "Environment config")] pub chain_id: Option, /// Enable steps tracing used for debug calls returning geth-style traces - #[clap(long, visible_alias = "tracing")] + #[arg(long, visible_alias = "tracing")] pub steps_tracing: bool, /// Enable autoImpersonate on startup - #[clap(long, visible_alias = "auto-impersonate")] + #[arg(long, visible_alias = "auto-impersonate")] pub auto_impersonate: bool, /// Run an Optimism chain - #[clap(long, visible_alias = "optimism")] + #[arg(long, visible_alias = "optimism")] pub optimism: bool, + + /// Disable the default create2 deployer + #[arg(long, visible_alias = "no-create2")] + pub disable_default_create2_deployer: bool, + + /// The memory limit per EVM execution in bytes. + #[arg(long)] + pub memory_limit: Option, } /// Resolves an alias passed as fork-url to the matching url defined in the rpc_endpoints section @@ -513,7 +516,7 @@ pub struct AnvilEvmArgs { impl AnvilEvmArgs { pub fn resolve_rpc_alias(&mut self) { if let Some(fork_url) = &self.fork_url { - let config = Config::load(); + let config = Config::load_with_providers(FigmentProviders::Anvil); if let Some(Ok(url)) = config.get_rpc_url_with_alias(&fork_url.url) { self.fork_url = Some(ForkUrl { url: url.to_string(), block: fork_url.block }); } @@ -604,7 +607,7 @@ impl Future for PeriodicStateDumper { } /// Represents the --state flag and where to load from, or dump the state to -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct StateFile { pub path: PathBuf, pub state: Option, @@ -614,7 +617,12 @@ impl StateFile { /// This is used as the clap `value_parser` implementation to parse from file but only if it /// exists fn parse(path: &str) -> Result { - let mut path = PathBuf::from(path); + Self::parse_path(path) + } + + /// Parse from file but only if it exists + pub fn parse_path(path: impl AsRef) -> Result { + let mut path = path.as_ref().to_path_buf(); if path.is_dir() { path = path.join("state.json"); } @@ -631,7 +639,7 @@ impl StateFile { /// Represents the input URL for a fork with an optional trailing block number: /// `http://localhost:8545@1000000` -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ForkUrl { /// The endpoint url pub url: String, @@ -669,6 +677,19 @@ impl FromStr for ForkUrl { } } +/// Clap's value parser for genesis. Loads a genesis.json file. +fn read_genesis_file(path: &str) -> Result { + foundry_common::fs::read_json_file(path.as_ref()).map_err(|err| err.to_string()) +} + +fn duration_from_secs_f64(s: &str) -> Result { + let s = s.parse::().map_err(|e| e.to_string())?; + if s == 0.0 { + return Err("Duration must be greater than 0".to_string()); + } + Duration::try_from_secs_f64(s).map_err(|e| e.to_string()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 27b39aac9575b..6905b69dcfe44 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -1,4 +1,5 @@ use crate::{ + cmd::StateFile, eth::{ backend::{ db::{Db, SerializableState}, @@ -10,43 +11,42 @@ use crate::{ fees::{INITIAL_BASE_FEE, INITIAL_GAS_PRICE}, pool::transactions::TransactionOrder, }, - genesis::Genesis, - mem, - mem::in_memory_db::MemDb, - FeeManager, Hardfork, + mem::{self, in_memory_db::MemDb}, + FeeManager, Hardfork, PrecompileFactory, }; -use anvil_server::ServerConfig; -use ethers::{ - core::k256::ecdsa::SigningKey, - prelude::{rand::thread_rng, Wallet, U256}, - providers::Middleware, - signers::{ - coins_bip39::{English, Mnemonic}, - MnemonicBuilder, Signer, - }, - types::BlockNumber, - utils::{format_ether, hex, to_checksum, WEI_IN_ETHER}, +use alloy_genesis::Genesis; +use alloy_network::AnyNetwork; +use alloy_primitives::{hex, utils::Unit, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::BlockNumberOrTag; +use alloy_signer::Signer; +use alloy_signer_wallet::{ + coins_bip39::{English, Mnemonic}, + LocalWallet, MnemonicBuilder, }; +use alloy_transport::{Transport, TransportError}; +use anvil_server::ServerConfig; use foundry_common::{ - types::{ToAlloy, ToEthers}, - ProviderBuilder, ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, REQUEST_TIMEOUT, + provider::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, REQUEST_TIMEOUT, }; use foundry_config::Config; use foundry_evm::{ constants::DEFAULT_CREATE2_DEPLOYER, fork::{BlockchainDb, BlockchainDbMeta, SharedBackend}, revm, - revm::primitives::{BlockEnv, CfgEnv, SpecId, TxEnv, U256 as rU256}, + revm::primitives::{BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv}, utils::apply_chain_and_block_specific_env_changes, }; use parking_lot::RwLock; +use rand::thread_rng; +use revm::primitives::BlobExcessGasAndPrice; use serde_json::{json, to_writer, Value}; use std::{ collections::HashMap, fmt::Write as FmtWrite, fs::File, net::{IpAddr, Ipv4Addr}, - path::PathBuf, + path::{Path, PathBuf}, sync::Arc, time::Duration, }; @@ -87,28 +87,30 @@ const BANNER: &str = r" "; /// Configurations of the EVM node -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct NodeConfig { /// Chain ID of the EVM chain pub chain_id: Option, /// Default gas limit for all txs - pub gas_limit: U256, + pub gas_limit: u128, /// If set to `true`, disables the block gas limit pub disable_block_gas_limit: bool, /// Default gas price for all txs - pub gas_price: Option, + pub gas_price: Option, /// Default base fee - pub base_fee: Option, + pub base_fee: Option, + /// Default blob excess gas and price + pub blob_excess_gas_and_price: Option, /// The hardfork to use pub hardfork: Option, /// Signer accounts that will be initialised with `genesis_balance` in the genesis block - pub genesis_accounts: Vec>, + pub genesis_accounts: Vec, /// Native token balance of every genesis account in the genesis block pub genesis_balance: U256, /// Genesis block timestamp pub genesis_timestamp: Option, /// Signer accounts that can sign messages/transactions from the EVM node - pub signer_accounts: Vec>, + pub signer_accounts: Vec, /// Configured block time for the EVM chain. Use `None` to mine a new block for every tx pub block_time: Option, /// Disable auto, interval mining mode uns use `MiningMode::None` instead @@ -171,18 +173,20 @@ pub struct NodeConfig { pub disable_default_create2_deployer: bool, /// Enable Optimism deposit transaction pub enable_optimism: bool, + /// Slots in an epoch + pub slots_in_an_epoch: u64, + /// The memory limit per EVM execution in bytes. + pub memory_limit: Option, + /// Factory used by `anvil` to extend the EVM's precompiles. + pub precompile_factory: Option>, } impl NodeConfig { fn as_string(&self, fork: Option<&ClientFork>) -> String { let mut config_string: String = String::new(); - let _ = write!(config_string, "\n{}", Paint::green(BANNER)); + let _ = write!(config_string, "\n{}", BANNER.green()); let _ = write!(config_string, "\n {VERSION_MESSAGE}"); - let _ = write!( - config_string, - "\n {}", - Paint::green("https://github.com/foundry-rs/foundry") - ); + let _ = write!(config_string, "\n {}", "https://github.com/foundry-rs/foundry".green()); let _ = write!( config_string, @@ -192,13 +196,9 @@ Available Accounts ================== "# ); - let balance = format_ether(self.genesis_balance); + let balance = alloy_primitives::utils::format_ether(self.genesis_balance); for (idx, wallet) in self.genesis_accounts.iter().enumerate() { - let _ = write!( - config_string, - "\n({idx}) {:?} ({balance} ETH)", - to_checksum(&wallet.address(), None) - ); + write!(config_string, "\n({idx}) {} ({balance} ETH)", wallet.address()).unwrap(); } let _ = write!( @@ -254,9 +254,10 @@ Chain ID: {} Chain ID ================== + {} "#, - Paint::green(format!("\n{}", self.get_chain_id())) + self.get_chain_id().green() ); } @@ -266,9 +267,10 @@ Chain ID r#" Gas Price ================== + {} "#, - Paint::green(format!("\n{}", self.get_gas_price())) + self.get_gas_price().green() ); } else { let _ = write!( @@ -276,9 +278,10 @@ Gas Price r#" Base Fee ================== + {} "#, - Paint::green(format!("\n{}", self.get_base_fee())) + self.get_base_fee().green() ); } @@ -287,9 +290,10 @@ Base Fee r#" Gas Limit ================== + {} "#, - Paint::green(format!("\n{}", self.gas_limit)) + self.gas_limit.green() ); let _ = write!( @@ -297,9 +301,10 @@ Gas Limit r#" Genesis Timestamp ================== + {} "#, - Paint::green(format!("\n{}", self.get_genesis_timestamp())) + self.get_genesis_timestamp().green() ); config_string @@ -359,6 +364,16 @@ impl NodeConfig { pub fn test() -> Self { Self { enable_tracing: true, silent: true, port: 0, ..Default::default() } } + + /// Returns a new config which does not initialize any accounts on node startup. + pub fn empty_state() -> Self { + Self { + genesis_accounts: vec![], + signer_accounts: vec![], + disable_default_create2_deployer: true, + ..Default::default() + } + } } impl Default for NodeConfig { @@ -367,7 +382,7 @@ impl Default for NodeConfig { let genesis_accounts = AccountGenerator::new(10).phrase(DEFAULT_MNEMONIC).gen(); Self { chain_id: None, - gas_limit: U256::from(30_000_000), + gas_limit: 30_000_000, disable_block_gas_limit: false, gas_price: None, hardfork: None, @@ -375,7 +390,7 @@ impl Default for NodeConfig { genesis_timestamp: None, genesis_accounts, // 100ETH default balance - genesis_balance: WEI_IN_ETHER.saturating_mul(100u64.into()), + genesis_balance: Unit::ETHER.wei().saturating_mul(U256::from(100u64)), block_time: None, no_mining: false, port: NODE_PORT, @@ -386,6 +401,7 @@ impl Default for NodeConfig { fork_block_number: None, account_generator: None, base_fee: None, + blob_excess_gas_and_price: None, enable_tracing: true, enable_steps_tracing: false, enable_auto_impersonate: false, @@ -409,21 +425,41 @@ impl Default for NodeConfig { transaction_block_keeper: None, disable_default_create2_deployer: false, enable_optimism: false, + slots_in_an_epoch: 32, + memory_limit: None, + precompile_factory: None, } } } impl NodeConfig { + /// Returns the memory limit of the node + #[must_use] + pub fn with_memory_limit(mut self, mems_value: Option) -> Self { + self.memory_limit = mems_value; + self + } /// Returns the base fee to use - pub fn get_base_fee(&self) -> U256 { + pub fn get_base_fee(&self) -> u128 { self.base_fee .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas)) - .unwrap_or_else(|| INITIAL_BASE_FEE.into()) + .unwrap_or(INITIAL_BASE_FEE) } /// Returns the base fee to use - pub fn get_gas_price(&self) -> U256 { - self.gas_price.unwrap_or_else(|| INITIAL_GAS_PRICE.into()) + pub fn get_gas_price(&self) -> u128 { + self.gas_price.unwrap_or(INITIAL_GAS_PRICE) + } + + pub fn get_blob_excess_gas_and_price(&self) -> BlobExcessGasAndPrice { + if let Some(blob_excess_gas_and_price) = &self.blob_excess_gas_and_price { + blob_excess_gas_and_price.clone() + } else if let Some(excess_blob_gas) = self.genesis.as_ref().and_then(|g| g.excess_blob_gas) + { + BlobExcessGasAndPrice::new(excess_blob_gas as u64) + } else { + BlobExcessGasAndPrice { blob_gasprice: 0, excess_blob_gas: 0 } + } } /// Returns the base fee to use @@ -438,13 +474,20 @@ impl NodeConfig { self } - /// Sets a custom code size limit + /// Sets the init state if any #[must_use] pub fn with_init_state(mut self, init_state: Option) -> Self { self.init_state = init_state; self } + /// Loads the init state from a file if it exists + #[must_use] + pub fn with_init_state_path(mut self, path: impl AsRef) -> Self { + self.init_state = StateFile::parse_path(path).ok().and_then(|file| file.state); + self + } + /// Sets the chain ID #[must_use] pub fn with_chain_id>(mut self, chain_id: Option) -> Self { @@ -455,7 +498,7 @@ impl NodeConfig { /// Returns the chain ID to use pub fn get_chain_id(&self) -> u64 { self.chain_id - .or_else(|| self.genesis.as_ref().and_then(|g| g.chain_id())) + .or_else(|| self.genesis.as_ref().map(|g| g.config.chain_id)) .unwrap_or(CHAIN_ID) } @@ -464,18 +507,18 @@ impl NodeConfig { self.chain_id = chain_id.map(Into::into); let chain_id = self.get_chain_id(); self.genesis_accounts.iter_mut().for_each(|wallet| { - *wallet = wallet.clone().with_chain_id(chain_id); + *wallet = wallet.clone().with_chain_id(Some(chain_id)); }); self.signer_accounts.iter_mut().for_each(|wallet| { - *wallet = wallet.clone().with_chain_id(chain_id); + *wallet = wallet.clone().with_chain_id(Some(chain_id)); }) } /// Sets the gas limit #[must_use] - pub fn with_gas_limit>(mut self, gas_limit: Option) -> Self { + pub fn with_gas_limit(mut self, gas_limit: Option) -> Self { if let Some(gas_limit) = gas_limit { - self.gas_limit = gas_limit.into(); + self.gas_limit = gas_limit; } self } @@ -491,8 +534,8 @@ impl NodeConfig { /// Sets the gas price #[must_use] - pub fn with_gas_price>(mut self, gas_price: Option) -> Self { - self.gas_price = gas_price.map(Into::into); + pub fn with_gas_price(mut self, gas_price: Option) -> Self { + self.gas_price = gas_price; self } @@ -515,8 +558,8 @@ impl NodeConfig { /// Sets the base fee #[must_use] - pub fn with_base_fee>(mut self, base_fee: Option) -> Self { - self.base_fee = base_fee.map(Into::into); + pub fn with_base_fee(mut self, base_fee: Option) -> Self { + self.base_fee = base_fee; self } @@ -530,7 +573,7 @@ impl NodeConfig { /// Returns the genesis timestamp to use pub fn get_genesis_timestamp(&self) -> u64 { self.genesis_timestamp - .or_else(|| self.genesis.as_ref().and_then(|g| g.timestamp)) + .or_else(|| self.genesis.as_ref().map(|g| g.timestamp)) .unwrap_or_else(|| duration_since_unix_epoch().as_secs()) } @@ -552,14 +595,14 @@ impl NodeConfig { /// Sets the genesis accounts #[must_use] - pub fn with_genesis_accounts(mut self, accounts: Vec>) -> Self { + pub fn with_genesis_accounts(mut self, accounts: Vec) -> Self { self.genesis_accounts = accounts; self } /// Sets the signer accounts #[must_use] - pub fn with_signer_accounts(mut self, accounts: Vec>) -> Self { + pub fn with_signer_accounts(mut self, accounts: Vec) -> Self { self.signer_accounts = accounts; self } @@ -594,6 +637,13 @@ impl NodeConfig { self } + /// Sets the slots in an epoch + #[must_use] + pub fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self { + self.slots_in_an_epoch = slots_in_an_epoch; + self + } + /// Sets the port to use #[must_use] pub fn with_port(mut self, port: u16) -> Self { @@ -660,7 +710,7 @@ impl NodeConfig { /// Sets the `fork_chain_id` to use to fork off local cache from #[must_use] - pub fn with_fork_chain_id>(mut self, fork_chain_id: Option) -> Self { + pub fn with_fork_chain_id(mut self, fork_chain_id: Option) -> Self { self.fork_chain_id = fork_chain_id.map(Into::into); self } @@ -769,7 +819,7 @@ impl NodeConfig { .expect("Failed writing json"); } if self.silent { - return + return; } println!("{}", self.as_string(fork)) @@ -780,7 +830,7 @@ impl NodeConfig { /// See also [ Config::foundry_block_cache_file()] pub fn block_cache_path(&self, block: u64) -> Option { if self.no_storage_caching || self.eth_rpc_url.is_none() { - return None + return None; } let chain_id = self.get_chain_id(); @@ -794,6 +844,20 @@ impl NodeConfig { self } + /// Sets whether to disable the default create2 deployer + #[must_use] + pub fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self { + self.disable_default_create2_deployer = yes; + self + } + + /// Injects precompiles to `anvil`'s EVM. + #[must_use] + pub fn with_precompile_factory(mut self, factory: impl PrecompileFactory + 'static) -> Self { + self.precompile_factory = Some(Arc::new(factory)); + self + } + /// Configures everything related to env, backend and database and returns the /// [Backend](mem::Backend) /// @@ -801,8 +865,8 @@ impl NodeConfig { pub(crate) async fn setup(&mut self) -> mem::Backend { // configure the revm environment - let mut cfg = CfgEnv::default(); - cfg.spec_id = self.get_hardfork().into(); + let mut cfg = + CfgEnvWithHandlerCfg::new_with_spec_id(CfgEnv::default(), self.get_hardfork().into()); cfg.chain_id = self.get_chain_id(); cfg.limit_contract_code_size = self.code_size_limit; // EIP-3607 rejects transactions from senders with deployed code. @@ -810,18 +874,29 @@ impl NodeConfig { // caller is a contract. So we disable the check by default. cfg.disable_eip3607 = true; cfg.disable_block_gas_limit = self.disable_block_gas_limit; - cfg.optimism = self.enable_optimism; + cfg.handler_cfg.is_optimism = self.enable_optimism; - let mut env = revm::primitives::Env { - cfg, + if let Some(value) = self.memory_limit { + cfg.memory_limit = value; + } + + let env = revm::primitives::Env { + cfg: cfg.cfg_env, block: BlockEnv { - gas_limit: self.gas_limit.to_alloy(), - basefee: self.get_base_fee().to_alloy(), + gas_limit: U256::from(self.gas_limit), + basefee: U256::from(self.get_base_fee()), ..Default::default() }, tx: TxEnv { chain_id: self.get_chain_id().into(), ..Default::default() }, }; - let fees = FeeManager::new(env.cfg.spec_id, self.get_base_fee(), self.get_gas_price()); + let mut env = EnvWithHandlerCfg::new(Box::new(env), cfg.handler_cfg); + + let fees = FeeManager::new( + cfg.handler_cfg.spec_id, + self.get_base_fee(), + self.get_gas_price(), + self.get_blob_excess_gas_and_price(), + ); let (db, fork): (Arc>>, Option) = if let Some(eth_rpc_url) = self.eth_rpc_url.clone() { @@ -832,12 +907,20 @@ impl NodeConfig { // if provided use all settings of `genesis.json` if let Some(ref genesis) = self.genesis { - genesis.apply(&mut env); + env.cfg.chain_id = genesis.config.chain_id; + env.block.timestamp = U256::from(genesis.timestamp); + if let Some(base_fee) = genesis.base_fee_per_gas { + env.block.basefee = U256::from(base_fee); + } + if let Some(number) = genesis.number { + env.block.number = U256::from(number); + } + env.block.coinbase = genesis.coinbase; } let genesis = GenesisConfig { timestamp: self.get_genesis_timestamp(), - balance: self.genesis_balance.to_alloy(), + balance: self.genesis_balance, accounts: self.genesis_accounts.iter().map(|acc| acc.address()).collect(), fork_genesis_account_infos: Arc::new(Default::default()), genesis_init: self.genesis.clone(), @@ -862,18 +945,13 @@ impl NodeConfig { // if the option is not disabled and we are not forking. if !self.disable_default_create2_deployer && self.eth_rpc_url.is_none() { backend - .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER.to_ethers()) + .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER) .await .expect("Failed to create default create2 deployer"); } - if let Some(ref state) = self.init_state { - backend - .get_db() - .write() - .await - .load_state(state.clone()) - .expect("Failed to load init state"); + if let Some(state) = self.init_state.clone() { + backend.load_state(state).await.expect("Failed to load init state"); } backend @@ -888,7 +966,7 @@ impl NodeConfig { pub async fn setup_fork_db( &mut self, eth_rpc_url: String, - env: &mut revm::primitives::Env, + env: &mut EnvWithHandlerCfg, fees: &FeeManager, ) -> (Arc>>, Option) { let (db, config) = self.setup_fork_db_config(eth_rpc_url, env, fees).await; @@ -910,7 +988,7 @@ impl NodeConfig { pub async fn setup_fork_db_config( &mut self, eth_rpc_url: String, - env: &mut revm::primitives::Env, + env: &mut EnvWithHandlerCfg, fees: &FeeManager, ) -> (ForkedDatabase, ClientForkConfig) { // TODO make provider agnostic @@ -936,13 +1014,13 @@ impl NodeConfig { // auto adjust hardfork if not specified // but only if we're forking mainnet let chain_id = - provider.get_chainid().await.expect("Failed to fetch network chain id"); - if chain_id == ethers::types::Chain::Mainnet.into() { + provider.get_chain_id().await.expect("Failed to fetch network chain ID"); + if alloy_chains::NamedChain::Mainnet == chain_id { let hardfork: Hardfork = fork_block_number.into(); - env.cfg.spec_id = hardfork.into(); + env.handler_cfg.spec_id = hardfork.into(); self.hardfork = Some(hardfork); } - Some(chain_id) + Some(U256::from(chain_id)) } else { None }; @@ -950,14 +1028,13 @@ impl NodeConfig { (fork_block_number, chain_id) } else { // pick the last block number but also ensure it's not pending anymore - ( - find_latest_fork_block(&provider).await.expect("Failed to get fork block number"), - None, - ) + let bn = + find_latest_fork_block(&provider).await.expect("Failed to get fork block number"); + (bn, None) }; let block = provider - .get_block(BlockNumber::Number(fork_block_number.into())) + .get_block(BlockNumberOrTag::Number(fork_block_number).into(), false) .await .expect("Failed to get fork block"); @@ -972,7 +1049,7 @@ latest block number: {latest_block}" // If the `eth_getBlockByNumber` call succeeds, but returns null instead of // the block, and the block number is less than equal the latest block, then // the user is forking from a non-archive node with an older block number. - if fork_block_number <= latest_block.as_u64() { + if fork_block_number <= latest_block { message.push_str(&format!("\n{}", NON_ARCHIVE_NODE_WARNING)); } panic!("{}", message); @@ -983,42 +1060,50 @@ latest block number: {latest_block}" // we only use the gas limit value of the block if it is non-zero and the block gas // limit is enabled, since there are networks where this is not used and is always // `0x0` which would inevitably result in `OutOfGas` errors as soon as the evm is about to record gas, See also - let gas_limit = if self.disable_block_gas_limit || block.gas_limit.is_zero() { - rU256::from(u64::MAX) + let gas_limit = if self.disable_block_gas_limit || block.header.gas_limit == 0 { + u64::MAX as u128 } else { - block.gas_limit.to_alloy() + block.header.gas_limit }; env.block = BlockEnv { - number: rU256::from(fork_block_number), - timestamp: block.timestamp.to_alloy(), - difficulty: block.difficulty.to_alloy(), + number: U256::from(fork_block_number), + timestamp: U256::from(block.header.timestamp), + difficulty: block.header.difficulty, // ensures prevrandao is set - prevrandao: Some(block.mix_hash.unwrap_or_default()).map(|h| h.to_alloy()), - gas_limit, + prevrandao: Some(block.header.mix_hash.unwrap_or_default()), + gas_limit: U256::from(gas_limit), // Keep previous `coinbase` and `basefee` value coinbase: env.block.coinbase, basefee: env.block.basefee, ..Default::default() }; - // apply changes such as difficulty -> prevrandao - apply_chain_and_block_specific_env_changes(env, &block); - // if not set explicitly we use the base fee of the latest block if self.base_fee.is_none() { - if let Some(base_fee) = block.base_fee_per_gas { + if let Some(base_fee) = block.header.base_fee_per_gas { self.base_fee = Some(base_fee); - env.block.basefee = base_fee.to_alloy(); + env.block.basefee = U256::from(base_fee); // this is the base fee of the current block, but we need the base fee of // the next block let next_block_base_fee = fees.get_next_block_base_fee_per_gas( - block.gas_used, - block.gas_limit, - block.base_fee_per_gas.unwrap_or_default(), + block.header.gas_used, + block.header.gas_limit, + block.header.base_fee_per_gas.unwrap_or_default(), ); // update next base fee - fees.set_base_fee(next_block_base_fee.into()); + fees.set_base_fee(next_block_base_fee); + } + if let (Some(blob_excess_gas), Some(blob_gas_used)) = + (block.header.excess_blob_gas, block.header.blob_gas_used) + { + env.block.blob_excess_gas_and_price = + Some(BlobExcessGasAndPrice::new(blob_excess_gas as u64)); + let next_block_blob_excess_gas = + fees.get_next_block_blob_excess_gas(blob_excess_gas, blob_gas_used); + fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new( + next_block_blob_excess_gas, + )); } } @@ -1030,17 +1115,16 @@ latest block number: {latest_block}" } } - let block_hash = block.hash.unwrap_or_default(); + let block_hash = block.header.hash.unwrap_or_default(); let chain_id = if let Some(chain_id) = self.chain_id { chain_id } else { let chain_id = if let Some(fork_chain_id) = fork_chain_id { - fork_chain_id + fork_chain_id.to() } else { - provider.get_chainid().await.unwrap() - } - .as_u64(); + provider.get_chain_id().await.unwrap() + }; // need to update the dev signers and env with the chain id self.set_chain_id(Some(chain_id)); @@ -1049,8 +1133,10 @@ latest block number: {latest_block}" chain_id }; let override_chain_id = self.chain_id; + // apply changes such as difficulty -> prevrandao and chain specifics for current chain id + apply_chain_and_block_specific_env_changes(env, &block); - let meta = BlockchainDbMeta::new(env.clone(), eth_rpc_url.clone()); + let meta = BlockchainDbMeta::new(*env.env.clone(), eth_rpc_url.clone()); let block_chain_db = if self.fork_chain_id.is_some() { BlockchainDb::new_skip_check(meta, self.block_cache_path(fork_block_number)) } else { @@ -1072,20 +1158,25 @@ latest block number: {latest_block}" provider, chain_id, override_chain_id, - timestamp: block.timestamp.as_u64(), - base_fee: block.base_fee_per_gas, + timestamp: block.header.timestamp, + base_fee: block.header.base_fee_per_gas, timeout: self.fork_request_timeout, retries: self.fork_request_retries, backoff: self.fork_retry_backoff, compute_units_per_second: self.compute_units_per_second, - total_difficulty: block.total_difficulty.unwrap_or_default(), + total_difficulty: block.header.total_difficulty.unwrap_or_default(), }; - (ForkedDatabase::new(backend, block_chain_db), config) + let mut db = ForkedDatabase::new(backend, block_chain_db); + + // need to insert the forked block's hash + db.insert_block_hash(U256::from(config.block_number), config.block_hash); + + (db, config) } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct PruneStateHistoryConfig { pub enabled: bool, pub max_memory_history: Option, @@ -1110,7 +1201,7 @@ impl PruneStateHistoryConfig { } /// Can create dev accounts -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct AccountGenerator { chain_id: u64, amount: usize, @@ -1160,18 +1251,17 @@ impl AccountGenerator { } impl AccountGenerator { - pub fn gen(&self) -> Vec> { + pub fn gen(&self) -> Vec { let builder = MnemonicBuilder::::default().phrase(self.phrase.as_str()); - // use the + // use the derivation path let derivation_path = self.get_derivation_path(); let mut wallets = Vec::with_capacity(self.amount); - for idx in 0..self.amount { let builder = builder.clone().derivation_path(&format!("{derivation_path}{idx}")).unwrap(); - let wallet = builder.build().unwrap().with_chain_id(self.chain_id); + let wallet = builder.build().unwrap().with_chain_id(Some(self.chain_id)); wallets.push(wallet) } wallets @@ -1192,15 +1282,17 @@ pub fn anvil_tmp_dir() -> Option { /// /// This fetches the "latest" block and checks whether the `Block` is fully populated (`hash` field /// is present). This prevents edge cases where anvil forks the "latest" block but `eth_getBlockByNumber` still returns a pending block, -async fn find_latest_fork_block(provider: M) -> Result { - let mut num = provider.get_block_number().await?.as_u64(); +async fn find_latest_fork_block, T: Transport + Clone>( + provider: P, +) -> Result { + let mut num = provider.get_block_number().await?; // walk back from the head of the chain, but at most 2 blocks, which should be more than enough // leeway for _ in 0..2 { - if let Some(block) = provider.get_block(num).await? { - if block.hash.is_some() { - break + if let Some(block) = provider.get_block(num.into(), false).await? { + if block.header.hash.is_some() { + break; } } // block not actually finalized, so we try the block before diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 232045e7552d9..5d8dfc1dc7a34 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -1,4 +1,7 @@ -use super::{backend::mem::BlockRequest, sign::build_typed_transaction}; +use super::{ + backend::mem::{state, BlockRequest, State}, + sign::build_typed_transaction, +}; use crate::{ eth::{ backend, @@ -9,8 +12,7 @@ use crate::{ validate::TransactionValidator, }, error::{ - decode_revert_reason, BlockchainError, FeeHistoryError, InvalidTransactionError, - Result, ToRpcResponseResult, + BlockchainError, FeeHistoryError, InvalidTransactionError, Result, ToRpcResponseResult, }, fees::{FeeDetails, FeeHistoryCache}, macros::node_info, @@ -26,17 +28,33 @@ use crate::{ }, filter::{EthFilter, Filters, LogsFilter}, mem::transaction_build, - revm::primitives::Output, + revm::primitives::{BlobExcessGasAndPrice, Output}, ClientFork, LoggingManager, Miner, MiningMode, StorageInfo, }; +use alloy_consensus::TxEip4844Variant; +use alloy_dyn_abi::TypedData; +use alloy_eips::eip2718::Encodable2718; +use alloy_network::eip2718::Decodable2718; +use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, B64, U256, U64}; +use alloy_rpc_types::{ + request::TransactionRequest, + state::StateOverride, + txpool::{TxpoolContent, TxpoolInspect, TxpoolInspectSummary, TxpoolStatus}, + AccessList, AccessListWithGasUsed, Block, BlockId, BlockNumberOrTag as BlockNumber, + BlockTransactions, EIP1186AccountProofResponse, FeeHistory, Filter, FilteredParams, Log, + Transaction, WithOtherFields, +}; +use alloy_rpc_types_trace::{ + geth::{DefaultFrame, GethDebugTracingOptions, GethDefaultTracingOptions, GethTrace}, + parity::LocalizedTransactionTrace, +}; +use alloy_transport::TransportErrorKind; use anvil_core::{ eth::{ block::BlockInfo, - proof::AccountProof, - state::StateOverride, transaction::{ - EthTransactionRequest, LegacyTransaction, PendingTransaction, TransactionKind, - TypedTransaction, TypedTransactionRequest, + transaction_request_to_typed, PendingTransaction, ReceiptResponse, TypedTransaction, + TypedTransactionRequest, }, EthRequest, }, @@ -46,24 +64,10 @@ use anvil_core::{ }, }; use anvil_rpc::{error::RpcError, response::ResponseResult}; -use ethers::{ - abi::ethereum_types::H64, - prelude::{DefaultFrame, TxpoolInspect}, - providers::ProviderError, - types::{ - transaction::{ - eip2930::{AccessList, AccessListWithGasUsed}, - eip712::TypedData, - }, - Address, Block, BlockId, BlockNumber, Bytes, FeeHistory, Filter, FilteredParams, - GethDebugTracingOptions, GethTrace, Log, Signature, Trace, Transaction, TransactionReceipt, - TxHash, TxpoolContent, TxpoolInspectSummary, TxpoolStatus, H256, U256, U64, - }, - utils::rlp, -}; -use foundry_common::{types::ToEthers, ProviderBuilder}; +use foundry_common::provider::ProviderBuilder; use foundry_evm::{ backend::DatabaseError, + decode::RevertDecoder, revm::{ db::DatabaseRef, interpreter::{return_ok, return_revert, InstructionResult}, @@ -109,7 +113,7 @@ pub struct EthApi { /// Whether we're listening for RPC calls net_listening: bool, /// The instance ID. Changes on every reset. - instance_id: Arc>, + instance_id: Arc>, } // === impl Eth RPC API === @@ -140,7 +144,7 @@ impl EthApi { filters, net_listening: true, transaction_order: Arc::new(RwLock::new(transactions_order)), - instance_id: Arc::new(RwLock::new(H256::random())), + instance_id: Arc::new(RwLock::new(B256::random())), } } @@ -228,8 +232,8 @@ impl EthApi { EthRequest::EthCreateAccessList(call, block) => { self.create_access_list(call, block).await.to_rpc_result() } - EthRequest::EthEstimateGas(call, block) => { - self.estimate_gas(call, block).await.to_rpc_result() + EthRequest::EthEstimateGas(call, block, overrides) => { + self.estimate_gas(call, block, overrides).await.to_rpc_result() } EthRequest::EthGetTransactionByBlockHashAndIndex(hash, index) => { self.transaction_by_block_hash_and_index(hash, index).await.to_rpc_result() @@ -240,6 +244,9 @@ impl EthApi { EthRequest::EthGetTransactionReceipt(tx) => { self.transaction_receipt(tx).await.to_rpc_result() } + EthRequest::EthGetBlockReceipts(number) => { + self.block_receipts(number).await.to_rpc_result() + } EthRequest::EthGetUncleByBlockHashAndIndex(hash, index) => { self.uncle_by_block_hash_and_index(hash, index).await.to_rpc_result() } @@ -291,6 +298,9 @@ impl EthApi { EthRequest::DropTransaction(tx) => { self.anvil_drop_transaction(tx).await.to_rpc_result() } + EthRequest::DropAllTransactions() => { + self.anvil_drop_all_transactions().await.to_rpc_result() + } EthRequest::Reset(fork) => { self.anvil_reset(fork.and_then(|p| p.params)).await.to_rpc_result() } @@ -323,16 +333,22 @@ impl EthApi { EthRequest::EvmRevert(id) => self.evm_revert(id).await.to_rpc_result(), EthRequest::EvmIncreaseTime(time) => self.evm_increase_time(time).await.to_rpc_result(), EthRequest::EvmSetNextBlockTimeStamp(time) => { - match u64::try_from(time).map_err(BlockchainError::UintConversion) { - Ok(time) => self.evm_set_next_block_timestamp(time).to_rpc_result(), - err @ Err(_) => err.to_rpc_result(), + if time >= U256::from(u64::MAX) { + return ResponseResult::Error(RpcError::invalid_params( + "The timestamp is too big", + )) } + let time = time.to::(); + self.evm_set_next_block_timestamp(time).to_rpc_result() } EthRequest::EvmSetTime(timestamp) => { - match u64::try_from(timestamp).map_err(BlockchainError::UintConversion) { - Ok(timestamp) => self.evm_set_time(timestamp).to_rpc_result(), - err @ Err(_) => err.to_rpc_result(), + if timestamp >= U256::from(u64::MAX) { + return ResponseResult::Error(RpcError::invalid_params( + "The timestamp is too big", + )) } + let time = timestamp.to::(); + self.evm_set_time(time).to_rpc_result() } EthRequest::EvmSetBlockGasLimit(gas_limit) => { self.evm_set_block_gas_limit(gas_limit).to_rpc_result() @@ -400,6 +416,9 @@ impl EthApi { EthRequest::OtsGetContractCreator(address) => { self.ots_get_contract_creator(address).await.to_rpc_result() } + EthRequest::RemovePoolTransactions(address) => { + self.anvil_remove_pool_transactions(address).await.to_rpc_result() + } } } @@ -410,9 +429,14 @@ impl EthApi { ) -> Result { match request { TypedTransactionRequest::Deposit(_) => { - const NIL_SIGNATURE: ethers::types::Signature = - Signature { r: U256::zero(), s: U256::zero(), v: 0 }; - return build_typed_transaction(request, NIL_SIGNATURE) + let nil_signature: alloy_primitives::Signature = + alloy_primitives::Signature::from_scalars_and_parity( + B256::with_last_byte(1), + B256::with_last_byte(1), + false, + ) + .unwrap(); + return build_typed_transaction(request, nil_signature) } _ => { for signer in self.signers.iter() { @@ -426,11 +450,6 @@ impl EthApi { Err(BlockchainError::NoSignerAvailable) } - /// Queries the current gas limit - fn current_gas_limit(&self) -> Result { - Ok(self.backend.gas_limit()) - } - async fn block_request(&self, block_number: Option) -> Result { let block_request = match block_number { Some(BlockId::Number(BlockNumber::Pending)) => { @@ -439,7 +458,7 @@ impl EthApi { } _ => { let number = self.backend.ensure_block_number(block_number).await?; - BlockRequest::Number(number.into()) + BlockRequest::Number(number) } }; Ok(block_request) @@ -458,8 +477,8 @@ impl EthApi { /// Handler for ETH RPC call: `web3_sha3` pub fn sha3(&self, bytes: Bytes) -> Result { node_info!("web3_sha3"); - let hash = ethers::utils::keccak256(bytes.as_ref()); - Ok(ethers::utils::hex::encode(&hash[..])) + let hash = alloy_primitives::keccak256(bytes.as_ref()); + Ok(alloy_primitives::hex::encode_prefixed(&hash[..])) } /// Returns protocol version encoded as a string (quotes are necessary). @@ -475,7 +494,7 @@ impl EthApi { /// Handler for ETH RPC call: `eth_hashrate` pub fn hashrate(&self) -> Result { node_info!("eth_hashrate"); - Ok(U256::zero()) + Ok(U256::ZERO) } /// Returns the client coinbase address. @@ -501,7 +520,7 @@ impl EthApi { /// Handler for ETH RPC call: `eth_chainId` pub fn eth_chain_id(&self) -> Result> { node_info!("eth_chainId"); - Ok(Some(self.backend.chain_id().into())) + Ok(Some(self.backend.chain_id().to::())) } /// Returns the same as `chain_id` @@ -509,7 +528,7 @@ impl EthApi { /// Handler for ETH RPC call: `eth_networkId` pub fn network_id(&self) -> Result> { node_info!("eth_networkId"); - let chain_id = self.backend.chain_id(); + let chain_id = self.backend.chain_id().to::(); Ok(Some(format!("{chain_id}"))) } @@ -529,7 +548,12 @@ impl EthApi { /// Returns the current gas price pub fn gas_price(&self) -> Result { - Ok(self.backend.gas_price()) + Ok(U256::from(self.backend.gas_price())) + } + + /// Returns the excess blob gas and current blob gas price + pub fn excess_blob_gas_and_price(&self) -> Result> { + Ok(self.backend.excess_blob_gas_and_price()) } /// Returns a fee per gas that is an estimate of how much you can pay as a priority fee, or @@ -537,12 +561,12 @@ impl EthApi { /// /// Handler for ETH RPC call: `eth_maxPriorityFeePerGas` pub fn gas_max_priority_fee_per_gas(&self) -> Result { - Ok(self.backend.max_priority_fee_per_gas()) + Ok(U256::from(self.backend.max_priority_fee_per_gas())) } /// Returns the block gas limit pub fn gas_limit(&self) -> U256 { - self.backend.gas_limit() + U256::from(self.backend.gas_limit()) } /// Returns the accounts list @@ -551,7 +575,7 @@ impl EthApi { pub fn accounts(&self) -> Result> { node_info!("eth_accounts"); let mut unique = HashSet::new(); - let mut accounts = Vec::new(); + let mut accounts: Vec
= Vec::new(); for signer in self.signers.iter() { accounts.extend(signer.accounts().into_iter().filter(|acc| unique.insert(*acc))); } @@ -562,7 +586,7 @@ impl EthApi { .into_iter() .filter(|acc| unique.insert(*acc)), ); - Ok(accounts) + Ok(accounts.into_iter().collect()) } /// Returns the number of most recent block. @@ -570,7 +594,7 @@ impl EthApi { /// Handler for ETH RPC call: `eth_blockNumber` pub fn block_number(&self) -> Result { node_info!("eth_blockNumber"); - Ok(self.backend.best_number().as_u64().into()) + Ok(U256::from(self.backend.best_number())) } /// Returns balance of the given account. @@ -581,10 +605,10 @@ impl EthApi { let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = &block_request { + if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { - if fork.predates_fork(number.as_u64()) { - return Ok(fork.get_balance(address, number.as_u64()).await?) + if fork.predates_fork(number) { + return Ok(fork.get_balance(address, number).await?) } } } @@ -600,17 +624,17 @@ impl EthApi { address: Address, index: U256, block_number: Option, - ) -> Result { + ) -> Result { node_info!("eth_getStorageAt"); let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = &block_request { + if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { - if fork.predates_fork(number.as_u64()) { - return Ok(fork - .storage_at(address, index, Some(BlockNumber::Number(*number))) - .await?) + if fork.predates_fork(number) { + return Ok(B256::from( + fork.storage_at(address, index, Some(BlockNumber::Number(number))).await?, + )); } } } @@ -621,7 +645,7 @@ impl EthApi { /// Returns block with given hash. /// /// Handler for ETH RPC call: `eth_getBlockByHash` - pub async fn block_by_hash(&self, hash: H256) -> Result>> { + pub async fn block_by_hash(&self, hash: B256) -> Result> { node_info!("eth_getBlockByHash"); self.backend.block_by_hash(hash).await } @@ -629,7 +653,7 @@ impl EthApi { /// Returns a _full_ block with given hash. /// /// Handler for ETH RPC call: `eth_getBlockByHash` - pub async fn block_by_hash_full(&self, hash: H256) -> Result>> { + pub async fn block_by_hash_full(&self, hash: B256) -> Result> { node_info!("eth_getBlockByHash"); self.backend.block_by_hash_full(hash).await } @@ -637,10 +661,10 @@ impl EthApi { /// Returns block with given number. /// /// Handler for ETH RPC call: `eth_getBlockByNumber` - pub async fn block_by_number(&self, number: BlockNumber) -> Result>> { + pub async fn block_by_number(&self, number: BlockNumber) -> Result> { node_info!("eth_getBlockByNumber"); if number == BlockNumber::Pending { - return Ok(Some(self.pending_block().await)) + return Ok(Some(self.pending_block().await)); } self.backend.block_by_number(number).await @@ -649,13 +673,10 @@ impl EthApi { /// Returns a _full_ block with given number /// /// Handler for ETH RPC call: `eth_getBlockByNumber` - pub async fn block_by_number_full( - &self, - number: BlockNumber, - ) -> Result>> { + pub async fn block_by_number_full(&self, number: BlockNumber) -> Result> { node_info!("eth_getBlockByNumber"); if number == BlockNumber::Pending { - return Ok(self.pending_block_full().await) + return Ok(self.pending_block_full().await); } self.backend.block_by_number_full(number).await } @@ -672,16 +693,21 @@ impl EthApi { block_number: Option, ) -> Result { node_info!("eth_getTransactionCount"); - self.get_transaction_count(address, block_number).await + self.get_transaction_count(address, block_number).await.map(U256::from) } /// Returns the number of transactions in a block with given hash. /// /// Handler for ETH RPC call: `eth_getBlockTransactionCountByHash` - pub async fn block_transaction_count_by_hash(&self, hash: H256) -> Result> { + pub async fn block_transaction_count_by_hash(&self, hash: B256) -> Result> { node_info!("eth_getBlockTransactionCountByHash"); let block = self.backend.block_by_hash(hash).await?; - Ok(block.map(|b| b.transactions.len().into())) + let txs = block.map(|b| match b.transactions { + BlockTransactions::Full(txs) => U256::from(txs.len()), + BlockTransactions::Hashes(txs) => U256::from(txs.len()), + BlockTransactions::Uncle => U256::from(0), + }); + Ok(txs) } /// Returns the number of transactions in a block with given block number. @@ -695,20 +721,25 @@ impl EthApi { let block_request = self.block_request(Some(block_number.into())).await?; if let BlockRequest::Pending(txs) = block_request { let block = self.backend.pending_block(txs).await; - return Ok(Some(block.transactions.len().into())) + return Ok(Some(U256::from(block.transactions.len()))); } let block = self.backend.block_by_number(block_number).await?; - Ok(block.map(|b| b.transactions.len().into())) + let txs = block.map(|b| match b.transactions { + BlockTransactions::Full(txs) => U256::from(txs.len()), + BlockTransactions::Hashes(txs) => U256::from(txs.len()), + BlockTransactions::Uncle => U256::from(0), + }); + Ok(txs) } /// Returns the number of uncles in a block with given hash. /// /// Handler for ETH RPC call: `eth_getUncleCountByBlockHash` - pub async fn block_uncles_count_by_hash(&self, hash: H256) -> Result { + pub async fn block_uncles_count_by_hash(&self, hash: B256) -> Result { node_info!("eth_getUncleCountByBlockHash"); let block = self.backend.block_by_hash(hash).await?.ok_or(BlockchainError::BlockNotFound)?; - Ok(block.uncles.len().into()) + Ok(U256::from(block.uncles.len())) } /// Returns the number of uncles in a block with given block number. @@ -721,7 +752,7 @@ impl EthApi { .block_by_number(block_number) .await? .ok_or(BlockchainError::BlockNotFound)?; - Ok(block.uncles.len().into()) + Ok(U256::from(block.uncles.len())) } /// Returns the code at given address at given time (block number). @@ -731,10 +762,10 @@ impl EthApi { node_info!("eth_getCode"); let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = &block_request { + if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { - if fork.predates_fork(number.as_u64()) { - return Ok(fork.get_code(address, number.as_u64()).await?) + if fork.predates_fork(number) { + return Ok(fork.get_code(address, number).await?) } } } @@ -748,18 +779,18 @@ impl EthApi { pub async fn get_proof( &self, address: Address, - keys: Vec, + keys: Vec, block_number: Option, - ) -> Result { + ) -> Result { node_info!("eth_getProof"); let block_request = self.block_request(block_number).await?; - if let BlockRequest::Number(number) = &block_request { + // If we're in forking mode, or still on the forked block (no blocks mined yet) then we can + // delegate the call. + if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { - // if we're in forking mode, or still on the forked block (no blocks mined yet) then - // we can delegate the call - if fork.predates_fork_inclusive(number.as_u64()) { - return Ok(fork.get_proof(address, keys, Some((*number).into())).await?) + if fork.predates_fork_inclusive(number) { + return Ok(fork.get_proof(address, keys, Some(number.into())).await?) } } } @@ -799,6 +830,7 @@ impl EthApi { node_info!("eth_signTypedData_v4"); let signer = self.get_signer(address).ok_or(BlockchainError::NoSignerAvailable)?; let signature = signer.sign_typed_data(address, data).await?; + let signature = alloy_primitives::hex::encode(signature.as_bytes()); Ok(format!("0x{signature}")) } @@ -808,14 +840,18 @@ impl EthApi { pub async fn sign(&self, address: Address, content: impl AsRef<[u8]>) -> Result { node_info!("eth_sign"); let signer = self.get_signer(address).ok_or(BlockchainError::NoSignerAvailable)?; - let signature = signer.sign(address, content.as_ref()).await?; + let signature = + alloy_primitives::hex::encode(signer.sign(address, content.as_ref()).await?.as_bytes()); Ok(format!("0x{signature}")) } /// Signs a transaction /// /// Handler for ETH RPC call: `eth_signTransaction` - pub async fn sign_transaction(&self, request: EthTransactionRequest) -> Result { + pub async fn sign_transaction( + &self, + mut request: WithOtherFields, + ) -> Result { node_info!("eth_signTransaction"); let from = request.from.map(Ok).unwrap_or_else(|| { @@ -824,25 +860,40 @@ impl EthApi { let (nonce, _) = self.request_nonce(&request, from).await?; + if request.gas.is_none() { + // estimate if not provided + if let Ok(gas) = self.estimate_gas(request.clone(), None, None).await { + request.gas = Some(gas.to()); + } + } + let request = self.build_typed_tx_request(request, nonce)?; - let signer = self.get_signer(from).ok_or(BlockchainError::NoSignerAvailable)?; - let signature = signer.sign_transaction(request, &from)?; - Ok(format!("0x{signature}")) + let signed_transaction = self.sign_request(&from, request)?.encoded_2718(); + Ok(alloy_primitives::hex::encode_prefixed(signed_transaction)) } /// Sends a transaction /// /// Handler for ETH RPC call: `eth_sendTransaction` - pub async fn send_transaction(&self, request: EthTransactionRequest) -> Result { + pub async fn send_transaction( + &self, + mut request: WithOtherFields, + ) -> Result { node_info!("eth_sendTransaction"); let from = request.from.map(Ok).unwrap_or_else(|| { self.accounts()?.first().cloned().ok_or(BlockchainError::NoSignerAvailable) })?; - let (nonce, on_chain_nonce) = self.request_nonce(&request, from).await?; + if request.gas.is_none() { + // estimate if not provided + if let Ok(gas) = self.estimate_gas(request.clone(), None, None).await { + request.gas = Some(gas.to()); + } + } + let request = self.build_typed_tx_request(request, nonce)?; // if the sender is currently impersonated we need to "bypass" signing let pending_transaction = if self.is_impersonated(from) { @@ -856,12 +907,11 @@ impl EthApi { self.ensure_typed_transaction_supported(&transaction)?; PendingTransaction::new(transaction)? }; - // pre-validate self.backend.validate_pool_transaction(&pending_transaction).await?; let requires = required_marker(nonce, on_chain_nonce, from); - let provides = vec![to_marker(nonce.as_u64(), from)]; + let provides = vec![to_marker(nonce, from)]; debug_assert!(requires != provides); self.add_pending_transaction(pending_transaction, requires, provides) @@ -872,31 +922,14 @@ impl EthApi { /// Handler for ETH RPC call: `eth_sendRawTransaction` pub async fn send_raw_transaction(&self, tx: Bytes) -> Result { node_info!("eth_sendRawTransaction"); - let data = tx.as_ref(); + let mut data = tx.as_ref(); if data.is_empty() { - return Err(BlockchainError::EmptyRawTransactionData) + return Err(BlockchainError::EmptyRawTransactionData); } - let transaction = if data[0] > 0x7f { - // legacy transaction - match rlp::decode::(data) { - Ok(transaction) => TypedTransaction::Legacy(transaction), - Err(_) => return Err(BlockchainError::FailedToDecodeSignedTransaction), - } - } else { - // the [TypedTransaction] requires a valid rlp input, - // but EIP-1559 prepends a version byte, so we need to encode the data first to get a - // valid rlp and then rlp decode impl of `TypedTransaction` will remove and check the - // version byte - let extend = rlp::encode(&data); - let tx = match rlp::decode::(&extend[..]) { - Ok(transaction) => transaction, - Err(_) => return Err(BlockchainError::FailedToDecodeSignedTransaction), - }; + let transaction = TypedTransaction::decode_2718(&mut data) + .map_err(|_| BlockchainError::FailedToDecodeSignedTransaction)?; - self.ensure_typed_transaction_supported(&tx)?; - - tx - }; + self.ensure_typed_transaction_supported(&transaction)?; let pending_transaction = PendingTransaction::new(transaction)?; @@ -905,13 +938,13 @@ impl EthApi { let on_chain_nonce = self.backend.current_nonce(*pending_transaction.sender()).await?; let from = *pending_transaction.sender(); - let nonce = *pending_transaction.transaction.nonce(); + let nonce = pending_transaction.transaction.nonce(); let requires = required_marker(nonce, on_chain_nonce, from); let priority = self.transaction_priority(&pending_transaction.transaction); let pool_transaction = PoolTransaction { requires, - provides: vec![to_marker(nonce.as_u64(), *pending_transaction.sender())], + provides: vec![to_marker(nonce, *pending_transaction.sender())], pending_transaction, priority, }; @@ -926,20 +959,20 @@ impl EthApi { /// Handler for ETH RPC call: `eth_call` pub async fn call( &self, - request: EthTransactionRequest, + request: WithOtherFields, block_number: Option, overrides: Option, ) -> Result { node_info!("eth_call"); let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = &block_request { + if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { - if fork.predates_fork(number.as_u64()) { + if fork.predates_fork(number) { if overrides.is_some() { return Err(BlockchainError::StateOverrideError( "not available on past forked blocks".to_string(), - )) + )); } return Ok(fork.call(&request, Some(number.into())).await?) } @@ -950,9 +983,9 @@ impl EthApi { request.gas_price, request.max_fee_per_gas, request.max_priority_fee_per_gas, + request.max_fee_per_blob_gas, )? .or_zero_fees(); - // this can be blocking for a bit, especially in forking mode // self.on_blocking_task(|this| async move { @@ -980,15 +1013,15 @@ impl EthApi { /// Handler for ETH RPC call: `eth_createAccessList` pub async fn create_access_list( &self, - mut request: EthTransactionRequest, + mut request: WithOtherFields, block_number: Option, ) -> Result { node_info!("eth_createAccessList"); let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = &block_request { + if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { - if fork.predates_fork(number.as_u64()) { + if fork.predates_fork(number) { return Ok(fork.create_access_list(&request, Some(number.into())).await?) } } @@ -1005,7 +1038,7 @@ impl EthApi { ensure_return_ok(exit, &out)?; // execute again but with access list set - request.access_list = Some(access_list.0.clone()); + request.access_list = Some(access_list.clone()); let (exit, out, gas_used, _) = self.backend.call_with_state( &state, @@ -1017,7 +1050,7 @@ impl EthApi { Ok(AccessListWithGasUsed { access_list: AccessList(access_list.0), - gas_used: gas_used.into(), + gas_used: U256::from(gas_used), }) }) .await? @@ -1029,12 +1062,18 @@ impl EthApi { /// Handler for ETH RPC call: `eth_estimateGas` pub async fn estimate_gas( &self, - request: EthTransactionRequest, + request: WithOtherFields, block_number: Option, + overrides: Option, ) -> Result { node_info!("eth_estimateGas"); - self.do_estimate_gas(request, block_number.or_else(|| Some(BlockNumber::Pending.into()))) - .await + self.do_estimate_gas( + request, + block_number.or_else(|| Some(BlockNumber::Pending.into())), + overrides, + ) + .await + .map(U256::from) } /// Get transaction by its hash. @@ -1043,7 +1082,10 @@ impl EthApi { /// this will also scan the mempool for a matching pending transaction /// /// Handler for ETH RPC call: `eth_getTransactionByHash` - pub async fn transaction_by_hash(&self, hash: H256) -> Result> { + pub async fn transaction_by_hash( + &self, + hash: B256, + ) -> Result>> { node_info!("eth_getTransactionByHash"); let mut tx = self.pool.get_transaction(hash).map(|pending| { let from = *pending.sender(); @@ -1071,9 +1113,9 @@ impl EthApi { /// Handler for ETH RPC call: `eth_getTransactionByBlockHashAndIndex` pub async fn transaction_by_block_hash_and_index( &self, - hash: H256, + hash: B256, index: Index, - ) -> Result> { + ) -> Result>> { node_info!("eth_getTransactionByBlockHashAndIndex"); self.backend.transaction_by_block_hash_and_index(hash, index).await } @@ -1085,7 +1127,7 @@ impl EthApi { &self, block: BlockNumber, idx: Index, - ) -> Result> { + ) -> Result>> { node_info!("eth_getTransactionByBlockNumberAndIndex"); self.backend.transaction_by_block_number_and_index(block, idx).await } @@ -1093,25 +1135,37 @@ impl EthApi { /// Returns transaction receipt by transaction hash. /// /// Handler for ETH RPC call: `eth_getTransactionReceipt` - pub async fn transaction_receipt(&self, hash: H256) -> Result> { + pub async fn transaction_receipt(&self, hash: B256) -> Result> { node_info!("eth_getTransactionReceipt"); let tx = self.pool.get_transaction(hash); if tx.is_some() { - return Ok(None) + return Ok(None); } self.backend.transaction_receipt(hash).await } + /// Returns block receipts by block number. + /// + /// Handler for ETH RPC call: `eth_getBlockReceipts` + pub async fn block_receipts( + &self, + number: BlockNumber, + ) -> Result>> { + node_info!("eth_getBlockReceipts"); + self.backend.block_receipts(number).await + } + /// Returns an uncles at given block and index. /// /// Handler for ETH RPC call: `eth_getUncleByBlockHashAndIndex` pub async fn uncle_by_block_hash_and_index( &self, - block_hash: H256, + block_hash: B256, idx: Index, - ) -> Result>> { + ) -> Result> { node_info!("eth_getUncleByBlockHashAndIndex"); - let number = self.backend.ensure_block_number(Some(BlockId::Hash(block_hash))).await?; + let number = + self.backend.ensure_block_number(Some(BlockId::Hash(block_hash.into()))).await?; if let Some(fork) = self.get_fork() { if fork.predates_fork_inclusive(number) { return Ok(fork.uncle_by_block_hash_and_index(block_hash, idx.into()).await?) @@ -1128,12 +1182,12 @@ impl EthApi { &self, block_number: BlockNumber, idx: Index, - ) -> Result>> { + ) -> Result> { node_info!("eth_getUncleByBlockNumberAndIndex"); let number = self.backend.ensure_block_number(Some(BlockId::Number(block_number))).await?; if let Some(fork) = self.get_fork() { if fork.predates_fork_inclusive(number) { - return Ok(fork.uncle_by_block_number_and_index(number, idx.into()).await?) + return Ok(fork.uncle_by_block_number_and_index(number, idx.into()).await?); } } // It's impossible to have uncles outside of fork mode @@ -1167,7 +1221,7 @@ impl EthApi { /// Used for submitting a proof-of-work solution. /// /// Handler for ETH RPC call: `eth_submitWork` - pub fn submit_work(&self, _: H64, _: H256, _: H256) -> Result { + pub fn submit_work(&self, _: B64, _: B256, _: B256) -> Result { node_info!("eth_submitWork"); Err(BlockchainError::RpcUnimplemented) } @@ -1175,7 +1229,7 @@ impl EthApi { /// Used for submitting mining hashrate. /// /// Handler for ETH RPC call: `eth_submitHashrate` - pub fn submit_hashrate(&self, _: U256, _: H256) -> Result { + pub fn submit_hashrate(&self, _: U256, _: B256) -> Result { node_info!("eth_submitHashrate"); Err(BlockchainError::RpcUnimplemented) } @@ -1192,13 +1246,13 @@ impl EthApi { node_info!("eth_feeHistory"); // max number of blocks in the requested range - let current = self.backend.best_number().as_u64(); + let current = self.backend.best_number(); let slots_in_an_epoch = 32u64; let number = match newest_block { BlockNumber::Latest | BlockNumber::Pending => current, BlockNumber::Earliest => 0, - BlockNumber::Number(n) => n.as_u64(), + BlockNumber::Number(n) => n, BlockNumber::Safe => current.saturating_sub(slots_in_an_epoch), BlockNumber::Finalized => current.saturating_sub(slots_in_an_epoch * 2), }; @@ -1208,89 +1262,75 @@ impl EthApi { // if we're still at the forked block we don't have any history and can't compute it // efficiently, instead we fetch it from the fork if fork.predates_fork_inclusive(number) { - return Ok(fork - .fee_history( - block_count, - BlockNumber::Number(number.into()), - &reward_percentiles, - ) - .await?) + return fork + .fee_history(block_count.to(), BlockNumber::Number(number), &reward_percentiles) + .await + .map_err(BlockchainError::AlloyForkProvider); } } const MAX_BLOCK_COUNT: u64 = 1024u64; - let range_limit = U256::from(MAX_BLOCK_COUNT); - let block_count = - if block_count > range_limit { range_limit.as_u64() } else { block_count.as_u64() }; + let block_count = block_count.to::().min(MAX_BLOCK_COUNT); // highest and lowest block num in the requested range let highest = number; let lowest = highest.saturating_sub(block_count.saturating_sub(1)); // only support ranges that are in cache range - if lowest < self.backend.best_number().as_u64().saturating_sub(self.fee_history_limit) { - return Err(FeeHistoryError::InvalidBlockRange.into()) + if lowest < self.backend.best_number().saturating_sub(self.fee_history_limit) { + return Err(FeeHistoryError::InvalidBlockRange.into()); } - let fee_history = self.fee_history_cache.lock(); - let mut response = FeeHistory { - oldest_block: U256::from(lowest), + oldest_block: lowest, base_fee_per_gas: Vec::new(), gas_used_ratio: Vec::new(), - reward: Default::default(), + reward: Some(Default::default()), + base_fee_per_blob_gas: Default::default(), + blob_gas_used_ratio: Default::default(), }; - let mut rewards = Vec::new(); - // iter over the requested block range - for n in lowest..=highest { - // - if let Some(block) = fee_history.get(&n) { - response.base_fee_per_gas.push(U256::from(block.base_fee)); - response.gas_used_ratio.push(block.gas_used_ratio); - - // requested percentiles - if !reward_percentiles.is_empty() { - let mut block_rewards = Vec::new(); - let resolution_per_percentile: f64 = 2.0; - for p in &reward_percentiles { - let p = p.clamp(0.0, 100.0); - let index = ((p.round() / 2f64) * 2f64) * resolution_per_percentile; - let reward = if let Some(r) = block.rewards.get(index as usize) { - U256::from(*r) - } else { - U256::zero() - }; - block_rewards.push(reward); + + { + let fee_history = self.fee_history_cache.lock(); + + // iter over the requested block range + for n in lowest..=highest { + // + if let Some(block) = fee_history.get(&n) { + response.base_fee_per_gas.push(block.base_fee); + response.base_fee_per_blob_gas.push(block.base_fee_per_blob_gas.unwrap_or(0)); + response.blob_gas_used_ratio.push(block.blob_gas_used_ratio); + response.gas_used_ratio.push(block.gas_used_ratio); + + // requested percentiles + if !reward_percentiles.is_empty() { + let mut block_rewards = Vec::new(); + let resolution_per_percentile: f64 = 2.0; + for p in &reward_percentiles { + let p = p.clamp(0.0, 100.0); + let index = ((p.round() / 2f64) * 2f64) * resolution_per_percentile; + let reward = block.rewards.get(index as usize).map_or(0, |r| *r); + block_rewards.push(reward); + } + rewards.push(block_rewards); } - rewards.push(block_rewards); } } } - response.reward = rewards; + response.reward = Some(rewards); - // calculate next base fee - if let (Some(last_gas_used), Some(last_fee_per_gas)) = - (response.gas_used_ratio.last(), response.base_fee_per_gas.last()) - { - let elasticity = self.backend.elasticity(); - let last_fee_per_gas = last_fee_per_gas.as_u64() as f64; - if last_gas_used > &0.5 { - // increase base gas - let increase = ((last_gas_used - 0.5) * 2f64) * elasticity; - let new_base_fee = (last_fee_per_gas + (last_fee_per_gas * increase)) as u64; - response.base_fee_per_gas.push(U256::from(new_base_fee)); - } else if last_gas_used < &0.5 { - // decrease gas - let increase = ((0.5 - last_gas_used) * 2f64) * elasticity; - let new_base_fee = (last_fee_per_gas - (last_fee_per_gas * increase)) as u64; - response.base_fee_per_gas.push(U256::from(new_base_fee)); - } else { - // same base gas - response.base_fee_per_gas.push(U256::from(last_fee_per_gas as u64)); - } - } + // add the next block's base fee to the response + // The spec states that `base_fee_per_gas` "[..] includes the next block after the + // newest of the returned range, because this value can be derived from the + // newest block" + response.base_fee_per_gas.push(self.backend.fees().base_fee()); + + // Same goes for the `base_fee_per_blob_gas`: + // > [..] includes the next block after the newest of the returned range, because this + // > value can be derived from the newest block. + response.base_fee_per_blob_gas.push(self.backend.fees().base_fee_per_blob_gas()); Ok(response) } @@ -1303,7 +1343,7 @@ impl EthApi { /// Handler for ETH RPC call: `eth_maxPriorityFeePerGas` pub fn max_priority_fee_per_gas(&self) -> Result { node_info!("eth_maxPriorityFeePerGas"); - Ok(self.backend.max_priority_fee_per_gas()) + Ok(U256::from(self.backend.max_priority_fee_per_gas())) } /// Creates a filter object, based on filter options, to notify when the state changes (logs). @@ -1376,14 +1416,10 @@ impl EthApi { /// Handler for RPC call: `debug_traceTransaction` pub async fn debug_trace_transaction( &self, - tx_hash: H256, + tx_hash: B256, opts: GethDebugTracingOptions, ) -> Result { node_info!("debug_traceTransaction"); - if opts.tracer.is_some() { - return Err(RpcError::invalid_params("non-default tracer not supported yet").into()) - } - self.backend.debug_trace_transaction(tx_hash, opts).await } @@ -1392,19 +1428,17 @@ impl EthApi { /// Handler for RPC call: `debug_traceCall` pub async fn debug_trace_call( &self, - request: EthTransactionRequest, + request: WithOtherFields, block_number: Option, - opts: GethDebugTracingOptions, + opts: GethDefaultTracingOptions, ) -> Result { node_info!("debug_traceCall"); - if opts.tracer.is_some() { - return Err(RpcError::invalid_params("non-default tracer not supported yet").into()) - } let block_request = self.block_request(block_number).await?; let fees = FeeDetails::new( request.gas_price, request.max_fee_per_gas, request.max_priority_fee_per_gas, + request.max_fee_per_blob_gas, )? .or_zero_fees(); @@ -1414,7 +1448,7 @@ impl EthApi { /// Returns traces for the transaction hash via parity's tracing endpoint /// /// Handler for RPC call: `trace_transaction` - pub async fn trace_transaction(&self, tx_hash: H256) -> Result> { + pub async fn trace_transaction(&self, tx_hash: B256) -> Result> { node_info!("trace_transaction"); self.backend.trace_transaction(tx_hash).await } @@ -1422,7 +1456,7 @@ impl EthApi { /// Returns traces for the transaction hash via parity's tracing endpoint /// /// Handler for RPC call: `trace_block` - pub async fn trace_block(&self, block: BlockNumber) -> Result> { + pub async fn trace_block(&self, block: BlockNumber) -> Result> { node_info!("trace_block"); self.backend.trace_block(block).await } @@ -1474,7 +1508,7 @@ impl EthApi { node_info!("evm_setAutomine"); if self.miner.is_auto_mine() { if enable_automine { - return Ok(()) + return Ok(()); } self.miner.set_mining_mode(MiningMode::None); } else if enable_automine { @@ -1490,14 +1524,14 @@ impl EthApi { /// Handler for ETH RPC call: `anvil_mine` pub async fn anvil_mine(&self, num_blocks: Option, interval: Option) -> Result<()> { node_info!("anvil_mine"); - let interval = interval.map(|i| i.as_u64()); - let blocks = num_blocks.unwrap_or_else(U256::one); - if blocks == U256::zero() { - return Ok(()) + let interval = interval.map(|i| i.to::()); + let blocks = num_blocks.unwrap_or(U256::from(1)); + if blocks.is_zero() { + return Ok(()); } // mine all the blocks - for _ in 0..blocks.as_u64() { + for _ in 0..blocks.to::() { self.mine_one().await; // If we have an interval, jump forwards in time to the "next" timestamp @@ -1531,9 +1565,18 @@ impl EthApi { /// Removes transactions from the pool /// /// Handler for RPC call: `anvil_dropTransaction` - pub async fn anvil_drop_transaction(&self, tx_hash: H256) -> Result> { + pub async fn anvil_drop_transaction(&self, tx_hash: B256) -> Result> { node_info!("anvil_dropTransaction"); - Ok(self.pool.drop_transaction(tx_hash).map(|tx| *tx.hash())) + Ok(self.pool.drop_transaction(tx_hash).map(|tx| tx.hash())) + } + + /// Removes all transactions from the pool + /// + /// Handler for RPC call: `anvil_dropAllTransactions` + pub async fn anvil_drop_all_transactions(&self) -> Result<()> { + node_info!("anvil_dropAllTransactions"); + self.pool.clear(); + Ok(()) } /// Reset the fork to a fresh forked state, and optionally update the fork config. @@ -1592,7 +1635,7 @@ impl EthApi { &self, address: Address, slot: U256, - val: H256, + val: B256, ) -> Result { node_info!("anvil_setStorageAt"); self.backend.set_storage_at(address, slot, val).await?; @@ -1617,9 +1660,9 @@ impl EthApi { return Err(RpcError::invalid_params( "anvil_setMinGasPrice is not supported when EIP-1559 is active", ) - .into()) + .into()); } - self.backend.set_gas_price(gas); + self.backend.set_gas_price(gas.to()); Ok(()) } @@ -1632,9 +1675,9 @@ impl EthApi { return Err(RpcError::invalid_params( "anvil_setNextBlockBaseFeePerGas is only supported when EIP-1559 is active", ) - .into()) + .into()); } - self.backend.set_base_fee(basefee); + self.backend.set_base_fee(basefee.to()); Ok(()) } @@ -1667,7 +1710,7 @@ impl EthApi { /// Handler for RPC call: `anvil_loadState` pub async fn anvil_load_state(&self, buf: Bytes) -> Result { node_info!("anvil_loadState"); - self.backend.load_state(buf).await + self.backend.load_state_bytes(buf).await } /// Retrieves the Anvil node configuration params. @@ -1681,17 +1724,17 @@ impl EthApi { let tx_order = self.transaction_order.read(); Ok(NodeInfo { - current_block_number: self.backend.best_number(), + current_block_number: U64::from(self.backend.best_number()), current_block_timestamp: env.block.timestamp.try_into().unwrap_or(u64::MAX), current_block_hash: self.backend.best_hash(), - hard_fork: env.cfg.spec_id, + hard_fork: env.handler_cfg.spec_id, transaction_order: match *tx_order { TransactionOrder::Fifo => "fifo".to_string(), TransactionOrder::Fees => "fees".to_string(), }, environment: NodeEnvironment { base_fee: self.backend.base_fee(), - chain_id: self.backend.chain_id(), + chain_id: self.backend.chain_id().to::(), gas_limit: self.backend.gas_limit(), gas_price: self.backend.gas_price(), }, @@ -1719,9 +1762,9 @@ impl EthApi { Ok(AnvilMetadata { client_version: CLIENT_VERSION, - chain_id: self.backend.chain_id(), + chain_id: self.backend.chain_id().to::(), latest_block_hash: self.backend.best_hash(), - latest_block_number: self.backend.best_number().as_u64(), + latest_block_number: self.backend.best_number(), instance_id: *self.instance_id.read(), forked_network: fork_config.map(|cfg| ForkedNetwork { chain_id: cfg.chain_id(), @@ -1732,6 +1775,12 @@ impl EthApi { }) } + pub async fn anvil_remove_pool_transactions(&self, address: Address) -> Result<()> { + node_info!("anvil_removePoolTransactions"); + self.pool.remove_transactions_by_address(address); + Ok(()) + } + /// Snapshot the state of the blockchain at the current block. /// /// Handler for RPC call: `evm_snapshot` @@ -1784,7 +1833,7 @@ impl EthApi { /// Handler for RPC call: `evm_setBlockGasLimit` pub fn evm_set_block_gas_limit(&self, gas_limit: U256) -> Result { node_info!("evm_setBlockGasLimit"); - self.backend.set_gas_limit(gas_limit); + self.backend.set_gas_limit(gas_limit.to()); Ok(true) } @@ -1828,28 +1877,31 @@ impl EthApi { /// **Note**: This behaves exactly as [Self::evm_mine] but returns different output, for /// compatibility reasons, this is a separate call since `evm_mine` is not an anvil original. /// and `ganache` may change the `0x0` placeholder. - pub async fn evm_mine_detailed( - &self, - opts: Option, - ) -> Result>> { + pub async fn evm_mine_detailed(&self, opts: Option) -> Result> { node_info!("evm_mine_detailed"); let mined_blocks = self.do_evm_mine(opts).await?; let mut blocks = Vec::with_capacity(mined_blocks as usize); - let latest = self.backend.best_number().as_u64(); + let latest = self.backend.best_number(); for offset in (0..mined_blocks).rev() { let block_num = latest - offset; if let Some(mut block) = - self.backend.block_by_number_full(BlockNumber::Number(block_num.into())).await? + self.backend.block_by_number_full(BlockNumber::Number(block_num)).await? { - for tx in block.transactions.iter_mut() { + let mut block_txs = match block.transactions { + BlockTransactions::Full(txs) => txs, + BlockTransactions::Hashes(_) | BlockTransactions::Uncle => unreachable!(), + }; + for tx in block_txs.iter_mut() { if let Some(receipt) = self.backend.mined_transaction_receipt(tx.hash) { if let Some(output) = receipt.out { // insert revert reason if failure - if receipt.inner.status.unwrap_or_default().as_u64() == 0 { - if let Some(reason) = decode_revert_reason(&output) { + if !receipt.inner.inner.as_receipt_with_bloom().receipt.status { + if let Some(reason) = + RevertDecoder::new().maybe_decode(&output, None) + { tx.other.insert( "revertReason".to_string(), serde_json::to_value(reason).expect("Infallible"), @@ -1863,6 +1915,7 @@ impl EthApi { } } } + block.transactions = BlockTransactions::Full(block_txs); blocks.push(block); } } @@ -1886,16 +1939,16 @@ impl EthApi { node_info!("anvil_setRpcUrl"); if let Some(fork) = self.backend.get_fork() { let mut config = fork.config.write(); - let interval = config.provider.get_interval(); + // let interval = config.provider.get_interval(); let new_provider = Arc::new( - ProviderBuilder::new(&url) - .max_retry(10) - .initial_backoff(1000) - .build() - .map_err(|_| { - ProviderError::CustomError(format!("Failed to parse invalid url {url}")) - })? - .interval(interval), + ProviderBuilder::new(&url).max_retry(10).initial_backoff(1000).build().map_err( + |_| { + TransportErrorKind::custom_str( + format!("Failed to parse invalid url {url}").as_str(), + ) + }, + // TODO: Add interval + )?, // .interval(interval), ); config.provider = new_provider; trace!(target: "backend", "Updated fork rpc from \"{}\" to \"{}\"", config.eth_rpc_url, url); @@ -1918,7 +1971,7 @@ impl EthApi { /// Handler for ETH RPC call: `eth_sendUnsignedTransaction` pub async fn eth_send_unsigned_transaction( &self, - request: EthTransactionRequest, + request: WithOtherFields, ) -> Result { node_info!("eth_sendUnsignedTransaction"); // either use the impersonated account of the request's `from` field @@ -1939,7 +1992,7 @@ impl EthApi { self.backend.validate_pool_transaction(&pending_transaction).await?; let requires = required_marker(nonce, on_chain_nonce, from); - let provides = vec![to_marker(nonce.as_u64(), from)]; + let provides = vec![to_marker(nonce, from)]; self.add_pending_transaction(pending_transaction, requires, provides) } @@ -1966,7 +2019,7 @@ impl EthApi { fn convert(tx: Arc) -> TxpoolInspectSummary { let tx = &tx.pending_transaction.transaction; - let to = tx.to().copied(); + let to = tx.to(); let gas_price = tx.gas_price(); let value = tx.value(); let gas = tx.gas_limit(); @@ -2004,7 +2057,7 @@ impl EthApi { fn convert(tx: Arc) -> Transaction { let from = *tx.pending_transaction.sender(); let mut tx = transaction_build( - Some(*tx.hash()), + Some(tx.hash()), tx.pending_transaction.transaction.clone(), None, None, @@ -2014,7 +2067,7 @@ impl EthApi { // we set the from field here explicitly to the set sender of the pending transaction, // in case the transaction is impersonated. tx.from = from; - tx + tx.inner } for pending in self.pool.ready_transactions() { @@ -2084,21 +2137,33 @@ impl EthApi { async fn do_estimate_gas( &self, - request: EthTransactionRequest, + request: WithOtherFields, block_number: Option, - ) -> Result { + overrides: Option, + ) -> Result { let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = &block_request { + if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { - if fork.predates_fork(number.as_u64()) { + if fork.predates_fork(number) { + if overrides.is_some() { + return Err(BlockchainError::StateOverrideError( + "not available on past forked blocks".to_string(), + )); + } return Ok(fork.estimate_gas(&request, Some(number.into())).await?) } } } self.backend - .with_database_at(Some(block_request), |state, block| { + .with_database_at(Some(block_request), |mut state, block| { + if let Some(overrides) = overrides { + state = Box::new(state::apply_state_override( + overrides.into_iter().collect(), + state, + )?); + } self.do_estimate_gas_with_state(request, state, block) }) .await? @@ -2106,24 +2171,25 @@ impl EthApi { /// Estimates the gas usage of the `request` with the state. /// - /// This will execute the [EthTransactionRequest] and find the best gas limit via binary search + /// This will execute the [CallRequest] and find the best gas limit via binary search fn do_estimate_gas_with_state( &self, - mut request: EthTransactionRequest, + mut request: WithOtherFields, state: D, block_env: BlockEnv, - ) -> Result + ) -> Result where D: DatabaseRef, { - // if the request is a simple transfer we can optimize - let likely_transfer = - request.data.as_ref().map(|data| data.as_ref().is_empty()).unwrap_or(true); + // If the request is a simple native token transfer we can optimize + // We assume it's a transfer if we have no input data. + let to = request.to.as_ref().and_then(TxKind::to); + let likely_transfer = request.input.clone().into_input().is_none(); if likely_transfer { - if let Some(to) = request.to { - if let Ok(target_code) = self.backend.get_code_with_state(&state, to) { + if let Some(to) = to { + if let Ok(target_code) = self.backend.get_code_with_state(&state, *to) { if target_code.as_ref().is_empty() { - return Ok(MIN_TRANSACTION_GAS) + return Ok(MIN_TRANSACTION_GAS); } } } @@ -2133,108 +2199,68 @@ impl EthApi { request.gas_price, request.max_fee_per_gas, request.max_priority_fee_per_gas, + request.max_fee_per_blob_gas, )? .or_zero_fees(); // get the highest possible gas limit, either the request's set value or the currently // configured gas limit - let mut highest_gas_limit = request.gas.unwrap_or(block_env.gas_limit.to_ethers()); + let mut highest_gas_limit = request.gas.unwrap_or(block_env.gas_limit.to()); - // check with the funds of the sender - if let Some(from) = request.from { - let gas_price = fees.gas_price.unwrap_or_default(); - if gas_price > U256::zero() { + let gas_price = fees.gas_price.unwrap_or_default(); + // If we have non-zero gas price, cap gas limit by sender balance + if gas_price > 0 { + if let Some(from) = request.from { let mut available_funds = self.backend.get_balance_with_state(&state, from)?; if let Some(value) = request.value { if value > available_funds { - return Err(InvalidTransactionError::InsufficientFunds.into()) + return Err(InvalidTransactionError::InsufficientFunds.into()); } // safe: value < available_funds available_funds -= value; } // amount of gas the sender can afford with the `gas_price` - let allowance = available_funds.checked_div(gas_price).unwrap_or_default(); - if highest_gas_limit > allowance { - trace!(target: "node", "eth_estimateGas capped by limited user funds"); - highest_gas_limit = allowance; - } + let allowance = + available_funds.checked_div(U256::from(gas_price)).unwrap_or_default(); + highest_gas_limit = std::cmp::min(highest_gas_limit, allowance.saturating_to()); } } - // if the provided gas limit is less than computed cap, use that - let gas_limit = std::cmp::min(request.gas.unwrap_or(highest_gas_limit), highest_gas_limit); let mut call_to_estimate = request.clone(); - call_to_estimate.gas = Some(gas_limit); + call_to_estimate.gas = Some(highest_gas_limit); // execute the call without writing to db let ethres = self.backend.call_with_state(&state, call_to_estimate, fees.clone(), block_env.clone()); - // Exceptional case: init used too much gas, we need to increase the gas limit and try - // again - if let Err(BlockchainError::InvalidTransaction(InvalidTransactionError::GasTooHigh(_))) = - ethres - { - // if price or limit was included in the request then we can execute the request - // again with the block's gas limit to check if revert is gas related or not - if request.gas.is_some() || request.gas_price.is_some() { - return Err(map_out_of_gas_err( - request, - state, - self.backend.clone(), - block_env, - fees, - gas_limit, - )) + let gas_used = match ethres.try_into()? { + GasEstimationCallResult::Success(gas) => Ok(gas), + GasEstimationCallResult::OutOfGas => { + Err(InvalidTransactionError::BasicOutOfGas(highest_gas_limit).into()) } - } - - let (exit, out, gas, _) = ethres?; - match exit { - return_ok!() => { - // succeeded - } - InstructionResult::OutOfGas | InstructionResult::OutOfFund => { - return Err(InvalidTransactionError::BasicOutOfGas(gas_limit).into()) - } - // need to check if the revert was due to lack of gas or unrelated reason - // we're also checking for InvalidFEOpcode here because this can be used to trigger an error common usage in openzeppelin - return_revert!() | InstructionResult::InvalidFEOpcode => { - // if price or limit was included in the request then we can execute the request - // again with the max gas limit to check if revert is gas related or not - return if request.gas.is_some() || request.gas_price.is_some() { - Err(map_out_of_gas_err( - request, - state, - self.backend.clone(), - block_env, - fees, - gas_limit, - )) - } else { - // the transaction did fail due to lack of gas from the user - Err(InvalidTransactionError::Revert(Some(convert_transact_out(&out))).into()) - } + GasEstimationCallResult::Revert(output) => { + Err(InvalidTransactionError::Revert(output).into()) } - reason => { - warn!(target: "node", "estimation failed due to {:?}", reason); - return Err(BlockchainError::EvmError(reason)) + GasEstimationCallResult::EvmError(err) => { + warn!(target: "node", "estimation failed due to {:?}", err); + Err(BlockchainError::EvmError(err)) } - } + }?; // at this point we know the call succeeded but want to find the _best_ (lowest) gas the // transaction succeeds with. we find this by doing a binary search over the // possible range NOTE: this is the gas the transaction used, which is less than the // transaction requires to succeed - let gas: U256 = gas.into(); + // Get the starting lowest gas needed depending on the transaction kind. - let mut lowest_gas_limit = determine_base_gas_by_kind(request.clone()); + let mut lowest_gas_limit = determine_base_gas_by_kind(&request); // pick a point that's close to the estimated gas - let mut mid_gas_limit = std::cmp::min(gas * 3, (highest_gas_limit + lowest_gas_limit) / 2); + let mut mid_gas_limit = + std::cmp::min(gas_used * 3, (highest_gas_limit + lowest_gas_limit) / 2); // Binary search for the ideal gas limit - while (highest_gas_limit - lowest_gas_limit) > U256::one() { + while (highest_gas_limit - lowest_gas_limit) > 1 { request.gas = Some(mid_gas_limit); let ethres = self.backend.call_with_state( &state, @@ -2243,52 +2269,25 @@ impl EthApi { block_env.clone(), ); - // Exceptional case: init used too much gas, we need to increase the gas limit and try - // again - if let Err(BlockchainError::InvalidTransaction(InvalidTransactionError::GasTooHigh( - _, - ))) = ethres - { - // increase the lowest gas limit - lowest_gas_limit = mid_gas_limit; - - // new midpoint - mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; - continue - } - - match ethres { - Ok((exit, _, _gas, _)) => match exit { + match ethres.try_into()? { + GasEstimationCallResult::Success(_) => { // If the transaction succeeded, we can set a ceiling for the highest gas limit // at the current midpoint, as spending any more gas would // make no sense (as the TX would still succeed). - return_ok!() => { - highest_gas_limit = mid_gas_limit; - } - // If the transaction failed due to lack of gas, we can set a floor for the - // lowest gas limit at the current midpoint, as spending any - // less gas would make no sense (as the TX would still revert due to lack of - // gas). - InstructionResult::Revert | - InstructionResult::OutOfGas | - InstructionResult::OutOfFund | - // we're also checking for InvalidFEOpcode here because this can be used to trigger an error common usage in openzeppelin - InstructionResult::InvalidFEOpcode => { - lowest_gas_limit = mid_gas_limit; - } - // The tx failed for some other reason. - reason => { - warn!(target: "node", "estimation failed due to {:?}", reason); - return Err(BlockchainError::EvmError(reason)) - } - }, - // We've already checked for the exceptional GasTooHigh case above, so this is a - // real error. - Err(reason) => { - warn!(target: "node", "estimation failed due to {:?}", reason); - return Err(reason) + highest_gas_limit = mid_gas_limit; } - } + GasEstimationCallResult::OutOfGas | + GasEstimationCallResult::Revert(_) | + GasEstimationCallResult::EvmError(_) => { + // If the transaction failed, we can set a floor for the lowest gas limit at the + // current midpoint, as spending any less gas would make no + // sense (as the TX would still revert due to lack of gas). + // + // We don't care about the reason here, as we known that trasaction is correct + // as it succeeded earlier + lowest_gas_limit = mid_gas_limit; + } + }; // new midpoint mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; } @@ -2310,21 +2309,22 @@ impl EthApi { /// Returns the chain ID used for transaction pub fn chain_id(&self) -> u64 { - self.backend.chain_id() + self.backend.chain_id().to::() } + /// Returns the configured fork, if any. pub fn get_fork(&self) -> Option { self.backend.get_fork() } /// Returns the current instance's ID. - pub fn instance_id(&self) -> H256 { + pub fn instance_id(&self) -> B256 { *self.instance_id.read() } /// Resets the instance ID. pub fn reset_instance_id(&self) { - *self.instance_id.write() = H256::random(); + *self.instance_id.write() = B256::random(); } /// Returns the first signer that can sign for the given address @@ -2363,19 +2363,19 @@ impl EthApi { } /// Returns the pending block with tx hashes - async fn pending_block(&self) -> Block { + async fn pending_block(&self) -> Block { let transactions = self.pool.ready_transactions().collect::>(); let info = self.backend.pending_block(transactions).await; self.backend.convert_block(info.block) } /// Returns the full pending block with `Transaction` objects - async fn pending_block_full(&self) -> Option> { + async fn pending_block_full(&self) -> Option { let transactions = self.pool.ready_transactions().collect::>(); let BlockInfo { block, transactions, receipts: _ } = self.backend.pending_block(transactions).await; - let ethers_block = self.backend.convert_block(block.clone()); + let partial_block = self.backend.convert_block(block.clone()); let mut block_transactions = Vec::with_capacity(block.transactions.len()); let base_fee = self.backend.base_fee(); @@ -2390,30 +2390,31 @@ impl EthApi { Some(info), Some(base_fee), ); - block_transactions.push(tx); + block_transactions.push(tx.inner); } - Some(ethers_block.into_full_block(block_transactions)) + Some(partial_block.into_full_block(block_transactions)) } fn build_typed_tx_request( &self, - request: EthTransactionRequest, - nonce: U256, + request: WithOtherFields, + nonce: u64, ) -> Result { - let chain_id = request.chain_id.map(|c| c.as_u64()).unwrap_or_else(|| self.chain_id()); + let chain_id = request.chain_id.unwrap_or_else(|| self.chain_id()); let max_fee_per_gas = request.max_fee_per_gas; + let max_fee_per_blob_gas = request.max_fee_per_blob_gas; let gas_price = request.gas_price; - let gas_limit = request.gas.map(Ok).unwrap_or_else(|| self.current_gas_limit())?; + let gas_limit = request.gas.unwrap_or(self.backend.gas_limit()); - let request = match request.into_typed_request() { + let request = match transaction_request_to_typed(request) { Some(TypedTransactionRequest::Legacy(mut m)) => { m.nonce = nonce; m.chain_id = Some(chain_id); m.gas_limit = gas_limit; if gas_price.is_none() { - m.gas_price = self.gas_price().unwrap_or_default(); + m.gas_price = self.backend.gas_price() } TypedTransactionRequest::Legacy(m) } @@ -2422,7 +2423,7 @@ impl EthApi { m.chain_id = chain_id; m.gas_limit = gas_limit; if gas_price.is_none() { - m.gas_price = self.gas_price().unwrap_or_default(); + m.gas_price = self.backend.gas_price(); } TypedTransactionRequest::EIP2930(m) } @@ -2431,15 +2432,41 @@ impl EthApi { m.chain_id = chain_id; m.gas_limit = gas_limit; if max_fee_per_gas.is_none() { - m.max_fee_per_gas = self.gas_price().unwrap_or_default(); + m.max_fee_per_gas = self.backend.gas_price(); } TypedTransactionRequest::EIP1559(m) } + Some(TypedTransactionRequest::EIP4844(m)) => { + TypedTransactionRequest::EIP4844(match m { + // We only accept the TxEip4844 variant which has the sidecar. + TxEip4844Variant::TxEip4844WithSidecar(mut m) => { + m.tx.nonce = nonce; + m.tx.chain_id = chain_id; + m.tx.gas_limit = gas_limit; + if max_fee_per_gas.is_none() { + m.tx.max_fee_per_gas = + self.gas_price().unwrap_or_default().to::(); + } + if max_fee_per_blob_gas.is_none() { + m.tx.max_fee_per_blob_gas = self + .excess_blob_gas_and_price() + .unwrap_or_default() + .map_or(0, |g| g.blob_gasprice) + } + TxEip4844Variant::TxEip4844WithSidecar(m) + } + // It is not valid to receive a TxEip4844 without a sidecar, therefore + // we must reject it. + TxEip4844Variant::TxEip4844(_) => { + return Err(BlockchainError::FailedToDecodeTransaction) + } + }) + } Some(TypedTransactionRequest::Deposit(mut m)) => { m.gas_limit = gas_limit; TypedTransactionRequest::Deposit(m) } - _ => return Err(BlockchainError::FailedToDecodeTransaction), + None => return Err(BlockchainError::FailedToDecodeTransaction), }; Ok(request) } @@ -2454,20 +2481,18 @@ impl EthApi { &self, address: Address, block_number: Option, - ) -> Result { + ) -> Result { let block_request = self.block_request(block_number).await?; - if let BlockRequest::Number(number) = &block_request { + if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { - if fork.predates_fork_inclusive(number.as_u64()) { - return Ok(fork.get_nonce(address, number.as_u64()).await?) + if fork.predates_fork_inclusive(number) { + return Ok(fork.get_nonce(address, number).await?); } } } - let nonce = self.backend.get_nonce(address, Some(block_request)).await?; - - Ok(nonce) + self.backend.get_nonce(address, block_request).await } /// Returns the nonce for this request @@ -2479,9 +2504,9 @@ impl EthApi { /// This will also check the tx pool for pending transactions from the sender. async fn request_nonce( &self, - request: &EthTransactionRequest, + request: &TransactionRequest, from: Address, - ) -> Result<(U256, U256)> { + ) -> Result<(u64, u64)> { let highest_nonce = self.get_transaction_count(from, Some(BlockId::Number(BlockNumber::Pending))).await?; let nonce = request.nonce.unwrap_or(highest_nonce); @@ -2506,7 +2531,7 @@ impl EthApi { } /// Returns the current state root - pub async fn state_root(&self) -> Option { + pub async fn state_root(&self) -> Option { self.backend.get_db().read().await.maybe_state_root() } @@ -2515,19 +2540,20 @@ impl EthApi { match &tx { TypedTransaction::EIP2930(_) => self.backend.ensure_eip2930_active(), TypedTransaction::EIP1559(_) => self.backend.ensure_eip1559_active(), + TypedTransaction::EIP4844(_) => self.backend.ensure_eip4844_active(), TypedTransaction::Deposit(_) => self.backend.ensure_op_deposits_active(), TypedTransaction::Legacy(_) => Ok(()), } } } -fn required_marker(provided_nonce: U256, on_chain_nonce: U256, from: Address) -> Vec { +fn required_marker(provided_nonce: u64, on_chain_nonce: u64, from: Address) -> Vec { if provided_nonce == on_chain_nonce { - return Vec::new() + return Vec::new(); } - let prev_nonce = provided_nonce.saturating_sub(U256::one()); + let prev_nonce = provided_nonce.saturating_sub(1); if on_chain_nonce <= prev_nonce { - vec![to_marker(prev_nonce.as_u64(), from)] + vec![to_marker(prev_nonce, from)] } else { Vec::new() } @@ -2546,67 +2572,32 @@ fn ensure_return_ok(exit: InstructionResult, out: &Option) -> Result Ok(out), - return_revert!() => Err(InvalidTransactionError::Revert(Some(out)).into()), + return_revert!() => Err(InvalidTransactionError::Revert(Some(out.0.into())).into()), reason => Err(BlockchainError::EvmError(reason)), } } -/// Executes the requests again after an out of gas error to check if the error is gas related or -/// not -#[inline] -fn map_out_of_gas_err( - mut request: EthTransactionRequest, - state: D, - backend: Arc, - block_env: BlockEnv, - fees: FeeDetails, - gas_limit: U256, -) -> BlockchainError -where - D: DatabaseRef, -{ - request.gas = Some(backend.gas_limit()); - let (exit, out, _, _) = match backend.call_with_state(&state, request, fees, block_env) { - Ok(res) => res, - Err(err) => return err, - }; - match exit { - return_ok!() => { - // transaction succeeded by manually increasing the gas limit to - // highest, which means the caller lacks funds to pay for the tx - InvalidTransactionError::BasicOutOfGas(gas_limit).into() - } - return_revert!() => { - // reverted again after bumping the limit - InvalidTransactionError::Revert(Some(convert_transact_out(&out))).into() - } - reason => { - warn!(target: "node", "estimation failed due to {:?}", reason); - BlockchainError::EvmError(reason) - } - } -} - /// Determines the minimum gas needed for a transaction depending on the transaction kind. #[inline] -fn determine_base_gas_by_kind(request: EthTransactionRequest) -> U256 { - match request.into_typed_request() { +fn determine_base_gas_by_kind(request: &WithOtherFields) -> u128 { + match transaction_request_to_typed(request.clone()) { Some(request) => match request { - TypedTransactionRequest::Legacy(req) => match req.kind { - TransactionKind::Call(_) => MIN_TRANSACTION_GAS, - TransactionKind::Create => MIN_CREATE_GAS, + TypedTransactionRequest::Legacy(req) => match req.to { + TxKind::Call(_) => MIN_TRANSACTION_GAS, + TxKind::Create => MIN_CREATE_GAS, }, - TypedTransactionRequest::EIP1559(req) => match req.kind { - TransactionKind::Call(_) => MIN_TRANSACTION_GAS, - TransactionKind::Create => MIN_CREATE_GAS, + TypedTransactionRequest::EIP1559(req) => match req.to { + TxKind::Call(_) => MIN_TRANSACTION_GAS, + TxKind::Create => MIN_CREATE_GAS, }, - TypedTransactionRequest::EIP2930(req) => match req.kind { - TransactionKind::Call(_) => MIN_TRANSACTION_GAS, - TransactionKind::Create => MIN_CREATE_GAS, + TypedTransactionRequest::EIP2930(req) => match req.to { + TxKind::Call(_) => MIN_TRANSACTION_GAS, + TxKind::Create => MIN_CREATE_GAS, }, + TypedTransactionRequest::EIP4844(_) => MIN_TRANSACTION_GAS, TypedTransactionRequest::Deposit(req) => match req.kind { - TransactionKind::Call(_) => MIN_TRANSACTION_GAS, - TransactionKind::Create => MIN_CREATE_GAS, + TxKind::Call(_) => MIN_TRANSACTION_GAS, + TxKind::Create => MIN_CREATE_GAS, }, }, // Tighten the gas limit upwards if we don't know the transaction type to avoid deployments @@ -2614,3 +2605,58 @@ fn determine_base_gas_by_kind(request: EthTransactionRequest) -> U256 { _ => MIN_CREATE_GAS, } } + +/// Keeps result of a call to revm EVM used for gas estimation +enum GasEstimationCallResult { + Success(u128), + OutOfGas, + Revert(Option), + EvmError(InstructionResult), +} + +/// Converts the result of a call to revm EVM into a [GasEstimationCallRes]. +impl TryFrom, u128, State)>> for GasEstimationCallResult { + type Error = BlockchainError; + + fn try_from(res: Result<(InstructionResult, Option, u128, State)>) -> Result { + match res { + // Exceptional case: init used too much gas, treated as out of gas error + Err(BlockchainError::InvalidTransaction(InvalidTransactionError::GasTooHigh(_))) => { + Ok(Self::OutOfGas) + } + Err(err) => Err(err), + Ok((exit, output, gas, _)) => match exit { + return_ok!() | InstructionResult::CallOrCreate => Ok(Self::Success(gas)), + + InstructionResult::Revert => Ok(Self::Revert(output.map(|o| o.into_data()))), + + InstructionResult::OutOfGas | + InstructionResult::MemoryOOG | + InstructionResult::MemoryLimitOOG | + InstructionResult::PrecompileOOG | + InstructionResult::InvalidOperandOOG => Ok(Self::OutOfGas), + + InstructionResult::OpcodeNotFound | + InstructionResult::CallNotAllowedInsideStatic | + InstructionResult::StateChangeDuringStaticCall | + InstructionResult::InvalidFEOpcode | + InstructionResult::InvalidJump | + InstructionResult::NotActivated | + InstructionResult::StackUnderflow | + InstructionResult::StackOverflow | + InstructionResult::OutOfOffset | + InstructionResult::CreateCollision | + InstructionResult::OverflowPayment | + InstructionResult::PrecompileError | + InstructionResult::NonceOverflow | + InstructionResult::CreateContractSizeLimit | + InstructionResult::CreateContractStartingWithEF | + InstructionResult::CreateInitCodeSizeLimit | + InstructionResult::ActiveAccountUnsetAuthCall | + InstructionResult::FatalExternalError | + InstructionResult::OutOfFunds | + InstructionResult::CallTooDeep => Ok(Self::EvmError(exit)), + }, + } + } +} diff --git a/crates/anvil/src/eth/backend/cheats.rs b/crates/anvil/src/eth/backend/cheats.rs index 2a90d2156264a..0dc3189b07eb4 100644 --- a/crates/anvil/src/eth/backend/cheats.rs +++ b/crates/anvil/src/eth/backend/cheats.rs @@ -1,15 +1,14 @@ //! Support for "cheat codes" / bypass functions -use anvil_core::eth::transaction::IMPERSONATED_SIGNATURE; -use ethers::types::{Address, Signature}; -use foundry_evm::hashbrown::HashSet; +use alloy_primitives::{Address, Signature}; +use anvil_core::eth::transaction::impersonated_signature; use parking_lot::RwLock; -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; /// Manages user modifications that may affect the node's behavior /// /// Contains the state of executed, non-eth standard cheat code RPC -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct CheatsManager { /// shareable state state: Arc>, @@ -70,7 +69,7 @@ impl CheatsManager { } /// Container type for all the state variables -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct CheatsState { /// All accounts that are currently impersonated pub impersonated_accounts: HashSet
, @@ -84,7 +83,7 @@ impl Default for CheatsState { fn default() -> Self { Self { impersonated_accounts: Default::default(), - bypass_signature: IMPERSONATED_SIGNATURE, + bypass_signature: impersonated_signature(), auto_impersonate_accounts: false, } } diff --git a/crates/anvil/src/eth/backend/db.rs b/crates/anvil/src/eth/backend/db.rs index a36d7953abce8..6149c01080f6b 100644 --- a/crates/anvil/src/eth/backend/db.rs +++ b/crates/anvil/src/eth/backend/db.rs @@ -1,40 +1,25 @@ //! Helper types for working with [revm](foundry_evm::revm) -use crate::{mem::state::trie_hash_db, revm::primitives::AccountInfo, U256}; -use alloy_primitives::{Address as B160, B256, U256 as rU256}; -use anvil_core::eth::trie::KeccakHasher; -use ethers::{ - prelude::{Address, Bytes}, - types::{BlockId, H256}, - utils::keccak256, -}; -use foundry_common::{errors::FsPathError, types::ToAlloy}; +use crate::revm::primitives::AccountInfo; +use alloy_primitives::{keccak256, Address, Bytes, B256, U256, U64}; +use alloy_rpc_types::BlockId; +use foundry_common::errors::FsPathError; use foundry_evm::{ backend::{DatabaseError, DatabaseResult, MemDb, RevertSnapshotAction, StateSnapshot}, fork::BlockchainDb, - hashbrown::HashMap, revm::{ db::{CacheDB, DatabaseRef, DbAccount}, - primitives::{Bytecode, KECCAK_EMPTY}, + primitives::{BlockEnv, Bytecode, HashMap, KECCAK_EMPTY}, Database, DatabaseCommit, }, }; -use hash_db::HashDB; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt, path::Path}; -/// Type alias for the `HashDB` representation of the Database -pub type AsHashDB = Box>>; - -/// Helper trait get access to the data in `HashDb` form +/// Helper trait get access to the full state data of the database #[auto_impl::auto_impl(Box)] -pub trait MaybeHashDatabase: DatabaseRef { - /// Return the DB as read-only hashdb and the root key - fn maybe_as_hash_db(&self) -> Option<(AsHashDB, H256)> { - None - } - /// Return the storage DB as read-only hashdb and the storage root of the account - fn maybe_account_db(&self, _addr: Address) -> Option<(AsHashDB, H256)> { +pub trait MaybeFullDatabase: DatabaseRef { + fn maybe_as_full_db(&self) -> Option<&HashMap> { None } @@ -48,15 +33,12 @@ pub trait MaybeHashDatabase: DatabaseRef { fn init_from_snapshot(&mut self, snapshot: StateSnapshot); } -impl<'a, T: 'a + MaybeHashDatabase + ?Sized> MaybeHashDatabase for &'a T +impl<'a, T: 'a + MaybeFullDatabase + ?Sized> MaybeFullDatabase for &'a T where &'a T: DatabaseRef, { - fn maybe_as_hash_db(&self) -> Option<(AsHashDB, H256)> { - T::maybe_as_hash_db(self) - } - fn maybe_account_db(&self, addr: Address) -> Option<(AsHashDB, H256)> { - T::maybe_account_db(self, addr) + fn maybe_as_full_db(&self) -> Option<&HashMap> { + T::maybe_as_full_db(self) } fn clear_into_snapshot(&mut self) -> StateSnapshot { @@ -84,7 +66,7 @@ pub trait Db: DatabaseRef + Database + DatabaseCommit - + MaybeHashDatabase + + MaybeFullDatabase + MaybeForkedDatabase + fmt::Debug + Send @@ -95,7 +77,7 @@ pub trait Db: /// Sets the nonce of the given address fn set_nonce(&mut self, address: Address, nonce: u64) -> DatabaseResult<()> { - let mut info = self.basic(address.to_alloy())?.unwrap_or_default(); + let mut info = self.basic(address)?.unwrap_or_default(); info.nonce = nonce; self.insert_account(address, info); Ok(()) @@ -103,15 +85,15 @@ pub trait Db: /// Sets the balance of the given address fn set_balance(&mut self, address: Address, balance: U256) -> DatabaseResult<()> { - let mut info = self.basic(address.to_alloy())?.unwrap_or_default(); - info.balance = balance.to_alloy(); + let mut info = self.basic(address)?.unwrap_or_default(); + info.balance = balance; self.insert_account(address, info); Ok(()) } /// Sets the balance of the given address fn set_code(&mut self, address: Address, code: Bytes) -> DatabaseResult<()> { - let mut info = self.basic(address.to_alloy())?.unwrap_or_default(); + let mut info = self.basic(address)?.unwrap_or_default(); let code_hash = if code.as_ref().is_empty() { KECCAK_EMPTY } else { @@ -127,15 +109,19 @@ pub trait Db: fn set_storage_at(&mut self, address: Address, slot: U256, val: U256) -> DatabaseResult<()>; /// inserts a blockhash for the given number - fn insert_block_hash(&mut self, number: U256, hash: H256); + fn insert_block_hash(&mut self, number: U256, hash: B256); /// Write all chain data to serialized bytes buffer - fn dump_state(&self) -> DatabaseResult>; + fn dump_state( + &self, + at: BlockEnv, + best_number: U64, + ) -> DatabaseResult>; /// Deserialize and add all chain data to the backend storage fn load_state(&mut self, state: SerializableState) -> DatabaseResult { for (addr, account) in state.accounts.into_iter() { - let old_account_nonce = DatabaseRef::basic_ref(self, addr.to_alloy()) + let old_account_nonce = DatabaseRef::basic_ref(self, addr) .ok() .and_then(|acc| acc.map(|acc| acc.nonce)) .unwrap_or_default(); @@ -146,7 +132,7 @@ pub trait Db: self.insert_account( addr, AccountInfo { - balance: account.balance.to_alloy(), + balance: account.balance, code_hash: KECCAK_EMPTY, // will be set automatically code: if account.code.0.is_empty() { None @@ -175,7 +161,7 @@ pub trait Db: fn revert(&mut self, snapshot: U256, action: RevertSnapshotAction) -> bool; /// Returns the state root if possible to compute - fn maybe_state_root(&self) -> Option { + fn maybe_state_root(&self) -> Option { None } @@ -189,23 +175,27 @@ pub trait Db: /// [Backend::pending_block()](crate::eth::backend::mem::Backend::pending_block()) impl + Send + Sync + Clone + fmt::Debug> Db for CacheDB { fn insert_account(&mut self, address: Address, account: AccountInfo) { - self.insert_account_info(address.to_alloy(), account) + self.insert_account_info(address, account) } fn set_storage_at(&mut self, address: Address, slot: U256, val: U256) -> DatabaseResult<()> { - self.insert_account_storage(address.to_alloy(), slot.to_alloy(), val.to_alloy()) + self.insert_account_storage(address, slot, val) } - fn insert_block_hash(&mut self, number: U256, hash: H256) { - self.block_hashes.insert(number.to_alloy(), hash.to_alloy()); + fn insert_block_hash(&mut self, number: U256, hash: B256) { + self.block_hashes.insert(number, hash); } - fn dump_state(&self) -> DatabaseResult> { + fn dump_state( + &self, + _at: BlockEnv, + _best_number: U64, + ) -> DatabaseResult> { Ok(None) } fn snapshot(&mut self) -> U256 { - U256::zero() + U256::ZERO } fn revert(&mut self, _snapshot: U256, _action: RevertSnapshotAction) -> bool { @@ -217,10 +207,11 @@ impl + Send + Sync + Clone + fmt::Debug> D } } -impl> MaybeHashDatabase for CacheDB { - fn maybe_as_hash_db(&self) -> Option<(AsHashDB, H256)> { - Some(trie_hash_db(&self.accounts)) +impl> MaybeFullDatabase for CacheDB { + fn maybe_as_full_db(&self) -> Option<&HashMap> { + Some(&self.accounts) } + fn clear_into_snapshot(&mut self) -> StateSnapshot { let db_accounts = std::mem::take(&mut self.accounts); let mut accounts = HashMap::new(); @@ -275,19 +266,19 @@ impl> MaybeForkedDatabase for CacheDB { } /// Represents a state at certain point -pub struct StateDb(pub(crate) Box); +pub struct StateDb(pub(crate) Box); // === impl StateDB === impl StateDb { - pub fn new(db: impl MaybeHashDatabase + Send + Sync + 'static) -> Self { + pub fn new(db: impl MaybeFullDatabase + Send + Sync + 'static) -> Self { Self(Box::new(db)) } } impl DatabaseRef for StateDb { type Error = DatabaseError; - fn basic_ref(&self, address: B160) -> DatabaseResult> { + fn basic_ref(&self, address: Address) -> DatabaseResult> { self.0.basic_ref(address) } @@ -295,22 +286,18 @@ impl DatabaseRef for StateDb { self.0.code_by_hash_ref(code_hash) } - fn storage_ref(&self, address: B160, index: rU256) -> DatabaseResult { + fn storage_ref(&self, address: Address, index: U256) -> DatabaseResult { self.0.storage_ref(address, index) } - fn block_hash_ref(&self, number: rU256) -> DatabaseResult { + fn block_hash_ref(&self, number: U256) -> DatabaseResult { self.0.block_hash_ref(number) } } -impl MaybeHashDatabase for StateDb { - fn maybe_as_hash_db(&self) -> Option<(AsHashDB, H256)> { - self.0.maybe_as_hash_db() - } - - fn maybe_account_db(&self, addr: Address) -> Option<(AsHashDB, H256)> { - self.0.maybe_account_db(addr) +impl MaybeFullDatabase for StateDb { + fn maybe_as_full_db(&self) -> Option<&HashMap> { + self.0.maybe_as_full_db() } fn clear_into_snapshot(&mut self) -> StateSnapshot { @@ -326,9 +313,15 @@ impl MaybeHashDatabase for StateDb { } } -#[derive(Serialize, Deserialize, Clone, Debug, Default)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct SerializableState { + /// The block number of the state + /// + /// Note: This is an Option for backwards compatibility: + pub block: Option, pub accounts: BTreeMap, + /// The best block number of the state, can be different from block number (Arbitrum chain). + pub best_block_number: Option, } // === impl SerializableState === @@ -350,7 +343,7 @@ impl SerializableState { } } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct SerializableAccountRecord { pub nonce: u64, pub balance: U256, diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index df71bc1fd1518..999bfae4fc8b6 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -4,38 +4,41 @@ use crate::{ error::InvalidTransactionError, pool::transactions::PoolTransaction, }, + inject_precompiles, mem::inspector::Inspector, + PrecompileFactory, }; +use alloy_consensus::{Header, Receipt, ReceiptWithBloom}; +use alloy_primitives::{Bloom, BloomInput, Log, B256}; use anvil_core::eth::{ - block::{Block, BlockInfo, Header, PartialHeader}, - receipt::{DepositReceipt, EIP1559Receipt, EIP2930Receipt, EIP658Receipt, Log, TypedReceipt}, - transaction::{PendingTransaction, TransactionInfo, TypedTransaction}, + block::{Block, BlockInfo, PartialHeader}, + transaction::{ + DepositReceipt, PendingTransaction, TransactionInfo, TypedReceipt, TypedTransaction, + }, trie, }; -use ethers::{ - abi::ethereum_types::BloomInput, - types::{Bloom, H256, U256}, - utils::rlp, -}; -use foundry_common::types::{ToAlloy, ToEthers}; use foundry_evm::{ backend::DatabaseError, - revm, + inspectors::{TracingInspector, TracingInspectorConfig}, revm::{ interpreter::InstructionResult, - primitives::{BlockEnv, CfgEnv, EVMError, Env, ExecutionResult, Output, SpecId}, + primitives::{ + BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, ExecutionResult, Output, + SpecId, + }, }, - traces::{CallTraceArena, CallTraceNode}, - utils::{eval_to_instruction_result, halt_to_instruction_result}, + traces::CallTraceNode, }; +use revm::primitives::MAX_BLOB_GAS_PER_BLOCK; use std::sync::Arc; /// Represents an executed transaction (transacted on the DB) +#[derive(Debug)] pub struct ExecutedTransaction { transaction: Arc, exit_reason: InstructionResult, out: Option, - gas_used: u64, + gas_used: u128, logs: Vec, traces: Vec, nonce: u64, @@ -45,45 +48,32 @@ pub struct ExecutedTransaction { impl ExecutedTransaction { /// Creates the receipt for the transaction - fn create_receipt(&self) -> TypedReceipt { - let used_gas: U256 = self.gas_used.into(); - let mut bloom = Bloom::default(); - logs_bloom(self.logs.clone(), &mut bloom); + fn create_receipt(&self, cumulative_gas_used: &mut u128) -> TypedReceipt { let logs = self.logs.clone(); + *cumulative_gas_used = cumulative_gas_used.saturating_add(self.gas_used); // successful return see [Return] let status_code = u8::from(self.exit_reason as u8 <= InstructionResult::SelfDestruct as u8); + let receipt_with_bloom: ReceiptWithBloom = + Receipt { status: status_code == 1, cumulative_gas_used: *cumulative_gas_used, logs } + .into(); + match &self.transaction.pending_transaction.transaction.transaction { - TypedTransaction::Legacy(_) => TypedReceipt::Legacy(EIP658Receipt { - status_code, - gas_used: used_gas, - logs_bloom: bloom, - logs, - }), - TypedTransaction::EIP2930(_) => TypedReceipt::EIP2930(EIP2930Receipt { - status_code, - gas_used: used_gas, - logs_bloom: bloom, - logs, - }), - TypedTransaction::EIP1559(_) => TypedReceipt::EIP1559(EIP1559Receipt { - status_code, - gas_used: used_gas, - logs_bloom: bloom, - logs, - }), - TypedTransaction::Deposit(_) => TypedReceipt::Deposit(DepositReceipt { - status_code, - gas_used: used_gas, - logs_bloom: bloom, - logs, + TypedTransaction::Legacy(_) => TypedReceipt::Legacy(receipt_with_bloom), + TypedTransaction::EIP2930(_) => TypedReceipt::EIP2930(receipt_with_bloom), + TypedTransaction::EIP1559(_) => TypedReceipt::EIP1559(receipt_with_bloom), + TypedTransaction::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom), + TypedTransaction::Deposit(tx) => TypedReceipt::Deposit(DepositReceipt { + inner: receipt_with_bloom, + deposit_nonce: Some(tx.nonce), + deposit_nonce_version: Some(1), }), } } } /// Represents the outcome of mining a new block -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ExecutedTransactions { /// The block created after executing the `included` transactions pub block: BlockInfo, @@ -103,11 +93,16 @@ pub struct TransactionExecutor<'a, Db: ?Sized, Validator: TransactionValidator> /// all pending transactions pub pending: std::vec::IntoIter>, pub block_env: BlockEnv, - pub cfg_env: CfgEnv, - pub parent_hash: H256, + /// The configuration environment and spec id + pub cfg_env: CfgEnvWithHandlerCfg, + pub parent_hash: B256, /// Cumulative gas used by all executed transactions - pub gas_used: U256, + pub gas_used: u128, + /// Cumulative blob gas used by all executed transactions + pub blob_gas_used: u128, pub enable_steps_tracing: bool, + /// Precompiles to inject to the EVM. + pub precompile_factory: Option>, } impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<'a, DB, Validator> { @@ -117,29 +112,41 @@ impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<' let mut transaction_infos = Vec::new(); let mut receipts = Vec::new(); let mut bloom = Bloom::default(); - let mut cumulative_gas_used = U256::zero(); + let mut cumulative_gas_used: u128 = 0; let mut invalid = Vec::new(); let mut included = Vec::new(); - let gas_limit = self.block_env.gas_limit; + let gas_limit = self.block_env.gas_limit.to::(); let parent_hash = self.parent_hash; - let block_number = self.block_env.number; + let block_number = self.block_env.number.to::(); let difficulty = self.block_env.difficulty; let beneficiary = self.block_env.coinbase; - let timestamp = self.block_env.timestamp.to_ethers().as_u64(); - let base_fee = if (self.cfg_env.spec_id as u8) >= (SpecId::LONDON as u8) { - Some(self.block_env.basefee) + let timestamp = self.block_env.timestamp.to::(); + let base_fee = if self.cfg_env.handler_cfg.spec_id.is_enabled_in(SpecId::LONDON) { + Some(self.block_env.basefee.to::()) } else { None }; + let is_cancun = self.cfg_env.handler_cfg.spec_id >= SpecId::CANCUN; + let excess_blob_gas = if is_cancun { self.block_env.get_blob_excess_gas() } else { None }; + let mut cumulative_blob_gas_used = if is_cancun { Some(0u128) } else { None }; + for tx in self.into_iter() { let tx = match tx { TransactionExecutionOutcome::Executed(tx) => { included.push(tx.transaction.clone()); tx } - TransactionExecutionOutcome::Exhausted(_) => continue, + TransactionExecutionOutcome::Exhausted(tx) => { + trace!(target: "backend", tx_gas_limit = %tx.pending_transaction.transaction.gas_limit(), ?tx, "block gas limit exhausting, skipping transaction"); + continue + } + TransactionExecutionOutcome::BlobGasExhausted(tx) => { + trace!(target: "backend", blob_gas = %tx.pending_transaction.transaction.blob_gas().unwrap_or_default(), ?tx, "block blob gas limit exhausting, skipping transaction"); + continue + } TransactionExecutionOutcome::Invalid(tx, _) => { + trace!(target: "backend", ?tx, "skipping invalid transaction"); invalid.push(tx); continue } @@ -150,35 +157,47 @@ impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<' continue } }; - let receipt = tx.create_receipt(); - cumulative_gas_used = cumulative_gas_used.saturating_add(receipt.gas_used()); + if is_cancun { + let tx_blob_gas = tx + .transaction + .pending_transaction + .transaction + .transaction + .blob_gas() + .unwrap_or(0); + cumulative_blob_gas_used = + Some(cumulative_blob_gas_used.unwrap_or(0u128).saturating_add(tx_blob_gas)); + } + let receipt = tx.create_receipt(&mut cumulative_gas_used); + let ExecutedTransaction { transaction, logs, out, traces, exit_reason: exit, .. } = tx; - logs_bloom(logs.clone(), &mut bloom); + build_logs_bloom(logs.clone(), &mut bloom); - let contract_address = if let Some(Output::Create(_, contract_address)) = out { - trace!(target: "backend", "New contract deployed: at {:?}", contract_address); - contract_address - } else { - None - }; + let contract_address = out.as_ref().and_then(|out| { + if let Output::Create(_, contract_address) = out { + trace!(target: "backend", "New contract deployed: at {:?}", contract_address); + *contract_address + } else { + None + } + }); - let transaction_index = transaction_infos.len() as u32; + let transaction_index = transaction_infos.len() as u64; let info = TransactionInfo { - transaction_hash: *transaction.hash(), + transaction_hash: transaction.hash(), transaction_index, from: *transaction.pending_transaction.sender(), - to: transaction.pending_transaction.transaction.to().copied(), - contract_address: contract_address.map(|c| c.to_ethers()), - logs, - logs_bloom: *receipt.logs_bloom(), - traces: CallTraceArena { arena: traces }, + to: transaction.pending_transaction.transaction.to(), + contract_address, + traces, exit, out: match out { - Some(Output::Call(b)) => Some(ethers::types::Bytes(b.0)), - Some(Output::Create(b, _)) => Some(ethers::types::Bytes(b.0)), + Some(Output::Call(b)) => Some(alloy_primitives::Bytes(b.0)), + Some(Output::Create(b, _)) => Some(alloy_primitives::Bytes(b.0)), _ => None, }, nonce: tx.nonce, + gas_used: tx.gas_used, }; transaction_infos.push(info); @@ -187,23 +206,26 @@ impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<' } let ommers: Vec
= Vec::new(); - let receipts_root = trie::ordered_trie_root(receipts.iter().map(rlp::encode)); + let receipts_root = trie::ordered_trie_root(receipts.iter().map(alloy_rlp::encode)); let partial_header = PartialHeader { parent_hash, - beneficiary: beneficiary.to_ethers(), + beneficiary, state_root: self.db.maybe_state_root().unwrap_or_default(), receipts_root, logs_bloom: bloom, - difficulty: difficulty.to_ethers(), - number: block_number.to_ethers(), - gas_limit: gas_limit.to_ethers(), + difficulty, + number: block_number, + gas_limit, gas_used: cumulative_gas_used, timestamp, extra_data: Default::default(), mix_hash: Default::default(), nonce: Default::default(), - base_fee: base_fee.map(|b| b.to_ethers()), + base_fee, + parent_beacon_block_root: Default::default(), + blob_gas_used: cumulative_blob_gas_used, + excess_blob_gas: excess_blob_gas.map(|g| g as u128), }; let block = Block::new(partial_header, transactions.clone(), ommers); @@ -211,12 +233,19 @@ impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<' ExecutedTransactions { block, included, invalid } } - fn env_for(&self, tx: &PendingTransaction) -> Env { - Env { cfg: self.cfg_env.clone(), block: self.block_env.clone(), tx: tx.to_revm_tx_env() } + fn env_for(&self, tx: &PendingTransaction) -> EnvWithHandlerCfg { + let mut tx_env = tx.to_revm_tx_env(); + if self.cfg_env.handler_cfg.is_optimism { + tx_env.optimism.enveloped_tx = + Some(alloy_rlp::encode(&tx.transaction.transaction).into()); + } + + EnvWithHandlerCfg::new_with_cfg_env(self.cfg_env.clone(), self.block_env.clone(), tx_env) } } /// Represents the result of a single transaction execution attempt +#[derive(Debug)] pub enum TransactionExecutionOutcome { /// Transaction successfully executed Executed(ExecutedTransaction), @@ -224,6 +253,8 @@ pub enum TransactionExecutionOutcome { Invalid(Arc, InvalidTransactionError), /// Execution skipped because could exceed gas limit Exhausted(Arc), + /// Execution skipped because it exceeded the blob gas limit + BlobGasExhausted(Arc), /// When an error occurred during execution DatabaseError(Arc, DatabaseError), } @@ -236,17 +267,26 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator fn next(&mut self) -> Option { let transaction = self.pending.next()?; let sender = *transaction.pending_transaction.sender(); - let account = match self.db.basic(sender.to_alloy()).map(|acc| acc.unwrap_or_default()) { + let account = match self.db.basic(sender).map(|acc| acc.unwrap_or_default()) { Ok(account) => account, Err(err) => return Some(TransactionExecutionOutcome::DatabaseError(transaction, err)), }; let env = self.env_for(&transaction.pending_transaction); + // check that we comply with the block's gas limit - let max_gas = self.gas_used.saturating_add(U256::from(env.tx.gas_limit)); - if max_gas > env.block.gas_limit.to_ethers() { + let max_gas = self.gas_used.saturating_add(env.tx.gas_limit as u128); + if max_gas > env.block.gas_limit.to::() { return Some(TransactionExecutionOutcome::Exhausted(transaction)) } + // check that we comply with the block's blob gas limit + let max_blob_gas = self.blob_gas_used.saturating_add( + transaction.pending_transaction.transaction.transaction.blob_gas().unwrap_or(0u128), + ); + if max_blob_gas > MAX_BLOB_GAS_PER_BLOCK as u128 { + return Some(TransactionExecutionOutcome::BlobGasExhausted(transaction)) + } + // validate before executing if let Err(err) = self.validator.validate_pool_transaction_for( &transaction.pending_transaction, @@ -259,33 +299,43 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator let nonce = account.nonce; - let mut evm = revm::EVM::new(); - evm.env = env; - evm.database(&mut self.db); - // records all call and step traces let mut inspector = Inspector::default().with_tracing(); if self.enable_steps_tracing { inspector = inspector.with_steps_tracing(); } - trace!(target: "backend", "[{:?}] executing", transaction.hash()); - // transact and commit the transaction - let exec_result = match evm.inspect_commit(&mut inspector) { - Ok(exec_result) => exec_result, - Err(err) => { - warn!(target: "backend", "[{:?}] failed to execute: {:?}", transaction.hash(), err); - match err { - EVMError::Database(err) => { - return Some(TransactionExecutionOutcome::DatabaseError(transaction, err)) - } - EVMError::Transaction(err) => { - return Some(TransactionExecutionOutcome::Invalid(transaction, err.into())) - } - // This will correspond to prevrandao not set, and it should never happen. - // If it does, it's a bug. - e => { - panic!("Failed to execute transaction. This is a bug.\n {:?}", e) + let exec_result = { + let mut evm = + foundry_evm::utils::new_evm_with_inspector(&mut *self.db, env, &mut inspector); + if let Some(ref factory) = self.precompile_factory { + inject_precompiles(&mut evm, factory.precompiles()); + } + + trace!(target: "backend", "[{:?}] executing", transaction.hash()); + // transact and commit the transaction + match evm.transact_commit() { + Ok(exec_result) => exec_result, + Err(err) => { + warn!(target: "backend", "[{:?}] failed to execute: {:?}", transaction.hash(), err); + match err { + EVMError::Database(err) => { + return Some(TransactionExecutionOutcome::DatabaseError( + transaction, + err, + )) + } + EVMError::Transaction(err) => { + return Some(TransactionExecutionOutcome::Invalid( + transaction, + err.into(), + )) + } + // This will correspond to prevrandao not set, and it should never happen. + // If it does, it's a bug. + e => { + panic!("Failed to execute transaction. This is a bug.\n {:?}", e) + } } } } @@ -294,14 +344,12 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator let (exit_reason, gas_used, out, logs) = match exec_result { ExecutionResult::Success { reason, gas_used, logs, output, .. } => { - (eval_to_instruction_result(reason), gas_used, Some(output), Some(logs)) + (reason.into(), gas_used, Some(output), Some(logs)) } ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None) } - ExecutionResult::Halt { reason, gas_used } => { - (halt_to_instruction_result(reason), gas_used, None, None) - } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None, None), }; if exit_reason == InstructionResult::OutOfGas { @@ -311,7 +359,13 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", transaction.hash(), out); - self.gas_used.saturating_add(U256::from(gas_used)); + // Track the total gas used for total gas per block checks + self.gas_used = self.gas_used.saturating_add(gas_used as u128); + + // Track the total blob gas used for total blob gas per blob checks + if let Some(blob_gas) = transaction.pending_transaction.transaction.transaction.blob_gas() { + self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas); + } trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", transaction.hash(), exit_reason, gas_used); @@ -319,9 +373,14 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator transaction, exit_reason, out, - gas_used, - logs: logs.unwrap_or_default().into_iter().map(Into::into).collect(), - traces: inspector.tracer.unwrap_or_default().traces.arena, + gas_used: gas_used as u128, + logs: logs.unwrap_or_default(), + traces: inspector + .tracer + .unwrap_or(TracingInspector::new(TracingInspectorConfig::all())) + .get_traces() + .clone() + .into_nodes(), nonce, }; @@ -330,10 +389,10 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator } /// Inserts all logs into the bloom -fn logs_bloom(logs: Vec, bloom: &mut Bloom) { +fn build_logs_bloom(logs: Vec, bloom: &mut Bloom) { for log in logs { bloom.accrue(BloomInput::Raw(&log.address[..])); - for topic in log.topics { + for topic in log.topics() { bloom.accrue(BloomInput::Raw(&topic[..])); } } diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index 6ffd32d5d1b93..df8c23d742397 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -1,18 +1,20 @@ //! Support for forking off another client use crate::eth::{backend::db::Db, error::BlockchainError}; -use anvil_core::eth::{proof::AccountProof, transaction::EthTransactionRequest}; -use ethers::{ - prelude::BlockNumber, - providers::{Middleware, ProviderError}, - types::{ - transaction::eip2930::AccessListWithGasUsed, Address, Block, BlockId, Bytes, FeeHistory, - Filter, GethDebugTracingOptions, GethTrace, Log, Trace, Transaction, TransactionReceipt, - TxHash, H256, U256, - }, +use alloy_primitives::{Address, Bytes, StorageValue, B256, U256}; +use alloy_provider::{ext::DebugApi, Provider}; +use alloy_rpc_types::{ + request::TransactionRequest, AccessListWithGasUsed, Block, BlockId, + BlockNumberOrTag as BlockNumber, BlockTransactions, EIP1186AccountProofResponse, FeeHistory, + Filter, Log, Transaction, WithOtherFields, }; -use foundry_common::{ProviderBuilder, RetryProvider}; -use foundry_evm::utils::u256_to_h256_be; +use alloy_rpc_types_trace::{ + geth::{GethDebugTracingOptions, GethTrace}, + parity::LocalizedTransactionTrace as Trace, +}; +use alloy_transport::TransportError; +use anvil_core::eth::transaction::{convert_to_anvil_receipt, ReceiptResponse}; +use foundry_common::provider::{ProviderBuilder, RetryProvider}; use parking_lot::{ lock_api::{RwLockReadGuard, RwLockWriteGuard}, RawRwLock, RwLock, @@ -24,7 +26,7 @@ use tokio::sync::RwLock as AsyncRwLock; /// /// This type contains a subset of the [`EthApi`](crate::eth::EthApi) functions but will exclusively /// fetch the requested data from the remote client, if it wasn't already fetched. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ClientFork { /// Contains the cached data pub storage: Arc>, @@ -63,30 +65,28 @@ impl ClientFork { self.config.write().update_url(url)?; let override_chain_id = self.config.read().override_chain_id; let chain_id = if let Some(chain_id) = override_chain_id { - chain_id.into() + chain_id } else { - self.provider().get_chainid().await? + self.provider().get_chain_id().await? }; - self.config.write().chain_id = chain_id.as_u64(); + self.config.write().chain_id = chain_id; } let provider = self.provider(); let block = - provider.get_block(block_number).await?.ok_or(BlockchainError::BlockNotFound)?; - let block_hash = block.hash.ok_or(BlockchainError::BlockNotFound)?; - let timestamp = block.timestamp.as_u64(); - let base_fee = block.base_fee_per_gas; - let total_difficulty = block.total_difficulty.unwrap_or_default(); - - self.config.write().update_block( - block.number.ok_or(BlockchainError::BlockNotFound)?.as_u64(), - block_hash, - timestamp, - base_fee, - total_difficulty, - ); + provider.get_block(block_number, false).await?.ok_or(BlockchainError::BlockNotFound)?; + let block_hash = block.header.hash.ok_or(BlockchainError::BlockNotFound)?; + let timestamp = block.header.timestamp; + let base_fee = block.header.base_fee_per_gas; + let total_difficulty = block.header.total_difficulty.unwrap_or_default(); + + let number = block.header.number.ok_or(BlockchainError::BlockNotFound)?; + self.config.write().update_block(number, block_hash, timestamp, base_fee, total_difficulty); self.clear_cached_storage(); + + self.database.write().await.insert_block_hash(U256::from(number), block_hash); + Ok(()) } @@ -117,11 +117,11 @@ impl ClientFork { self.config.read().total_difficulty } - pub fn base_fee(&self) -> Option { + pub fn base_fee(&self) -> Option { self.config.read().base_fee } - pub fn block_hash(&self) -> H256 { + pub fn block_hash(&self) -> B256 { self.config.read().block_hash } @@ -148,77 +148,43 @@ impl ClientFork { /// Returns the fee history `eth_feeHistory` pub async fn fee_history( &self, - block_count: U256, + block_count: u64, newest_block: BlockNumber, reward_percentiles: &[f64], - ) -> Result { - self.provider().fee_history(block_count, newest_block, reward_percentiles).await + ) -> Result { + self.provider().get_fee_history(block_count, newest_block, reward_percentiles).await } /// Sends `eth_getProof` pub async fn get_proof( &self, address: Address, - keys: Vec, + keys: Vec, block_number: Option, - ) -> Result { - self.provider().get_proof(address, keys, block_number).await + ) -> Result { + self.provider().get_proof(address, keys, block_number.unwrap_or(BlockId::latest())).await } /// Sends `eth_call` pub async fn call( &self, - request: &EthTransactionRequest, + request: &WithOtherFields, block: Option, - ) -> Result { - let request = Arc::new(request.clone()); + ) -> Result { let block = block.unwrap_or(BlockNumber::Latest); + let res = self.provider().call(request, block.into()).await?; - if let BlockNumber::Number(num) = block { - // check if this request was already been sent - let key = (request.clone(), num.as_u64()); - if let Some(res) = self.storage_read().eth_call.get(&key).cloned() { - return Ok(res) - } - } - - let tx = ethers::utils::serialize(request.as_ref()); - let block_value = ethers::utils::serialize(&block); - let res: Bytes = self.provider().request("eth_call", [tx, block_value]).await?; - - if let BlockNumber::Number(num) = block { - // cache result - let mut storage = self.storage_write(); - storage.eth_call.insert((request, num.as_u64()), res.clone()); - } Ok(res) } /// Sends `eth_call` pub async fn estimate_gas( &self, - request: &EthTransactionRequest, + request: &WithOtherFields, block: Option, - ) -> Result { - let request = Arc::new(request.clone()); + ) -> Result { let block = block.unwrap_or(BlockNumber::Latest); - - if let BlockNumber::Number(num) = block { - // check if this request was already been sent - let key = (request.clone(), num.as_u64()); - if let Some(res) = self.storage_read().eth_gas_estimations.get(&key).cloned() { - return Ok(res) - } - } - let tx = ethers::utils::serialize(request.as_ref()); - let block_value = ethers::utils::serialize(&block); - let res = self.provider().request("eth_estimateGas", [tx, block_value]).await?; - - if let BlockNumber::Number(num) = block { - // cache result - let mut storage = self.storage_write(); - storage.eth_gas_estimations.insert((request, num.as_u64()), res); - } + let res = self.provider().estimate_gas(request, block.into()).await?; Ok(res) } @@ -226,12 +192,12 @@ impl ClientFork { /// Sends `eth_createAccessList` pub async fn create_access_list( &self, - request: &EthTransactionRequest, + request: &WithOtherFields, block: Option, - ) -> Result { - let tx = ethers::utils::serialize(request); - let block = ethers::utils::serialize(&block.unwrap_or(BlockNumber::Latest)); - self.provider().request("eth_createAccessList", [tx, block]).await + ) -> Result { + self.provider() + .create_access_list(request, block.unwrap_or(BlockNumber::Latest).into()) + .await } pub async fn storage_at( @@ -239,14 +205,15 @@ impl ClientFork { address: Address, index: U256, number: Option, - ) -> Result { - let index = u256_to_h256_be(index); - self.provider().get_storage_at(address, index, number.map(Into::into)).await + ) -> Result { + self.provider() + .get_storage_at(address, index, number.unwrap_or(BlockNumber::Latest).into()) + .await } - pub async fn logs(&self, filter: &Filter) -> Result, ProviderError> { + pub async fn logs(&self, filter: &Filter) -> Result, TransportError> { if let Some(logs) = self.storage_read().logs.get(filter).cloned() { - return Ok(logs) + return Ok(logs); } let logs = self.provider().get_logs(filter).await?; @@ -260,15 +227,18 @@ impl ClientFork { &self, address: Address, blocknumber: u64, - ) -> Result { + ) -> Result { trace!(target: "backend::fork", "get_code={:?}", address); if let Some(code) = self.storage_read().code_at.get(&(address, blocknumber)).cloned() { - return Ok(code) + return Ok(code); } - let code = self.provider().get_code(address, Some(blocknumber.into())).await?; + let block_id = BlockId::Number(blocknumber.into()); + + let code = self.provider().get_code_at(address, block_id).await?; + let mut storage = self.storage_write(); - storage.code_at.insert((address, blocknumber), code.clone()); + storage.code_at.insert((address, blocknumber), code.clone().0.into()); Ok(code) } @@ -277,28 +247,35 @@ impl ClientFork { &self, address: Address, blocknumber: u64, - ) -> Result { + ) -> Result { trace!(target: "backend::fork", "get_balance={:?}", address); - self.provider().get_balance(address, Some(blocknumber.into())).await + self.provider().get_balance(address, blocknumber.into()).await } - pub async fn get_nonce( - &self, - address: Address, - blocknumber: u64, - ) -> Result { + pub async fn get_nonce(&self, address: Address, block: u64) -> Result { trace!(target: "backend::fork", "get_nonce={:?}", address); - self.provider().get_transaction_count(address, Some(blocknumber.into())).await + self.provider().get_transaction_count(address, block.into()).await } pub async fn transaction_by_block_number_and_index( &self, number: u64, index: usize, - ) -> Result, ProviderError> { + ) -> Result>, TransportError> { if let Some(block) = self.block_by_number(number).await? { - if let Some(tx_hash) = block.transactions.get(index) { - return self.transaction_by_hash(*tx_hash).await + match block.transactions { + BlockTransactions::Full(txs) => { + if let Some(tx) = txs.get(index) { + return Ok(Some(WithOtherFields::new(tx.clone()))); + } + } + BlockTransactions::Hashes(hashes) => { + if let Some(tx_hash) = hashes.get(index) { + return self.transaction_by_hash(*tx_hash).await; + } + } + // TODO(evalir): Is it possible to reach this case? Should we support it + BlockTransactions::Uncle => panic!("Uncles not supported"), } } Ok(None) @@ -306,12 +283,23 @@ impl ClientFork { pub async fn transaction_by_block_hash_and_index( &self, - hash: H256, + hash: B256, index: usize, - ) -> Result, ProviderError> { + ) -> Result>, TransportError> { if let Some(block) = self.block_by_hash(hash).await? { - if let Some(tx_hash) = block.transactions.get(index) { - return self.transaction_by_hash(*tx_hash).await + match block.transactions { + BlockTransactions::Full(txs) => { + if let Some(tx) = txs.get(index) { + return Ok(Some(WithOtherFields::new(tx.clone()))); + } + } + BlockTransactions::Hashes(hashes) => { + if let Some(tx_hash) = hashes.get(index) { + return self.transaction_by_hash(*tx_hash).await; + } + } + // TODO(evalir): Is it possible to reach this case? Should we support it + BlockTransactions::Uncle => panic!("Uncles not supported"), } } Ok(None) @@ -319,27 +307,27 @@ impl ClientFork { pub async fn transaction_by_hash( &self, - hash: H256, - ) -> Result, ProviderError> { + hash: B256, + ) -> Result>, TransportError> { trace!(target: "backend::fork", "transaction_by_hash={:?}", hash); if let tx @ Some(_) = self.storage_read().transactions.get(&hash).cloned() { - return Ok(tx) + return Ok(tx); } - if let Some(tx) = self.provider().get_transaction(hash).await? { - let mut storage = self.storage_write(); - storage.transactions.insert(hash, tx.clone()); - return Ok(Some(tx)) - } - Ok(None) + let tx = self.provider().get_transaction_by_hash(hash).await?; + + let mut storage = self.storage_write(); + storage.transactions.insert(hash, tx.clone()); + Ok(Some(tx)) } - pub async fn trace_transaction(&self, hash: H256) -> Result, ProviderError> { + pub async fn trace_transaction(&self, hash: B256) -> Result, TransportError> { if let Some(traces) = self.storage_read().transaction_traces.get(&hash).cloned() { - return Ok(traces) + return Ok(traces); } - let traces = self.provider().trace_transaction(hash).await?; + let traces = self.provider().trace_transaction(hash).await?.into_iter().collect::>(); + let mut storage = self.storage_write(); storage.transaction_traces.insert(hash, traces.clone()); @@ -348,26 +336,29 @@ impl ClientFork { pub async fn debug_trace_transaction( &self, - hash: H256, + hash: B256, opts: GethDebugTracingOptions, - ) -> Result { + ) -> Result { if let Some(traces) = self.storage_read().geth_transaction_traces.get(&hash).cloned() { - return Ok(traces) + return Ok(traces); } let trace = self.provider().debug_trace_transaction(hash, opts).await?; + let mut storage = self.storage_write(); storage.geth_transaction_traces.insert(hash, trace.clone()); Ok(trace) } - pub async fn trace_block(&self, number: u64) -> Result, ProviderError> { + pub async fn trace_block(&self, number: u64) -> Result, TransportError> { if let Some(traces) = self.storage_read().block_traces.get(&number).cloned() { - return Ok(traces) + return Ok(traces); } - let traces = self.provider().trace_block(number.into()).await?; + let traces = + self.provider().trace_block(number.into()).await?.into_iter().collect::>(); + let mut storage = self.storage_write(); storage.block_traces.insert(number, traces.clone()); @@ -376,35 +367,73 @@ impl ClientFork { pub async fn transaction_receipt( &self, - hash: H256, - ) -> Result, ProviderError> { + hash: B256, + ) -> Result, BlockchainError> { if let Some(receipt) = self.storage_read().transaction_receipts.get(&hash).cloned() { - return Ok(Some(receipt)) + return Ok(Some(receipt)); } if let Some(receipt) = self.provider().get_transaction_receipt(hash).await? { + let receipt = + convert_to_anvil_receipt(receipt).ok_or(BlockchainError::FailedToDecodeReceipt)?; let mut storage = self.storage_write(); storage.transaction_receipts.insert(hash, receipt.clone()); - return Ok(Some(receipt)) + return Ok(Some(receipt)); } Ok(None) } - pub async fn block_by_hash(&self, hash: H256) -> Result>, ProviderError> { - if let Some(block) = self.storage_read().blocks.get(&hash).cloned() { - return Ok(Some(block)) + pub async fn block_receipts( + &self, + number: u64, + ) -> Result>, BlockchainError> { + if let receipts @ Some(_) = self.storage_read().block_receipts.get(&number).cloned() { + return Ok(receipts); } - let block = self.fetch_full_block(hash).await?.map(Into::into); - Ok(block) + + // TODO Needs to be removed. + // Since alloy doesn't indicate in the result whether the block exists, + // this is being temporarily implemented in anvil. + if self.predates_fork_inclusive(number) { + let receipts = self.provider().get_block_receipts(BlockNumber::Number(number)).await?; + let receipts = receipts + .map(|r| { + r.into_iter() + .map(|r| { + convert_to_anvil_receipt(r) + .ok_or(BlockchainError::FailedToDecodeReceipt) + }) + .collect::, _>>() + }) + .transpose()?; + + if let Some(receipts) = receipts.clone() { + let mut storage = self.storage_write(); + storage.block_receipts.insert(number, receipts); + } + + return Ok(receipts); + } + + Ok(None) } - pub async fn block_by_hash_full( - &self, - hash: H256, - ) -> Result>, ProviderError> { + pub async fn block_by_hash(&self, hash: B256) -> Result, TransportError> { + if let Some(mut block) = self.storage_read().blocks.get(&hash).cloned() { + block.transactions.convert_to_hashes(); + return Ok(Some(block)); + } + + Ok(self.fetch_full_block(hash).await?.map(|mut b| { + b.transactions.convert_to_hashes(); + b + })) + } + + pub async fn block_by_hash_full(&self, hash: B256) -> Result, TransportError> { if let Some(block) = self.storage_read().blocks.get(&hash).cloned() { - return Ok(Some(self.convert_to_full_block(block))) + return Ok(Some(self.convert_to_full_block(block))); } self.fetch_full_block(hash).await } @@ -412,25 +441,28 @@ impl ClientFork { pub async fn block_by_number( &self, block_number: u64, - ) -> Result>, ProviderError> { - if let Some(block) = self + ) -> Result, TransportError> { + if let Some(mut block) = self .storage_read() .hashes .get(&block_number) - .copied() - .and_then(|hash| self.storage_read().blocks.get(&hash).cloned()) + .and_then(|hash| self.storage_read().blocks.get(hash).cloned()) { - return Ok(Some(block)) + block.transactions.convert_to_hashes(); + return Ok(Some(block)); } - let block = self.fetch_full_block(block_number).await?.map(Into::into); + let mut block = self.fetch_full_block(block_number).await?; + if let Some(block) = &mut block { + block.transactions.convert_to_hashes(); + } Ok(block) } pub async fn block_by_number_full( &self, block_number: u64, - ) -> Result>, ProviderError> { + ) -> Result, TransportError> { if let Some(block) = self .storage_read() .hashes @@ -438,7 +470,7 @@ impl ClientFork { .copied() .and_then(|hash| self.storage_read().blocks.get(&hash).cloned()) { - return Ok(Some(self.convert_to_full_block(block))) + return Ok(Some(self.convert_to_full_block(block))); } self.fetch_full_block(block_number).await @@ -447,16 +479,22 @@ impl ClientFork { async fn fetch_full_block( &self, block_id: impl Into, - ) -> Result>, ProviderError> { - if let Some(block) = self.provider().get_block_with_txs(block_id.into()).await? { - let hash = block.hash.unwrap(); - let block_number = block.number.unwrap().as_u64(); + ) -> Result, TransportError> { + if let Some(block) = self.provider().get_block(block_id.into(), true).await? { + let hash = block.header.hash.unwrap(); + let block_number = block.header.number.unwrap(); let mut storage = self.storage_write(); // also insert all transactions - storage.transactions.extend(block.transactions.iter().map(|tx| (tx.hash, tx.clone()))); + let block_txs = match block.clone().transactions { + BlockTransactions::Full(txs) => txs, + _ => vec![], + }; + storage + .transactions + .extend(block_txs.iter().map(|tx| (tx.hash, WithOtherFields::new(tx.clone())))); storage.hashes.insert(block_number, hash); - storage.blocks.insert(hash, block.clone().into()); - return Ok(Some(block)) + storage.blocks.insert(hash, block.clone()); + return Ok(Some(block)); } Ok(None) @@ -464,11 +502,11 @@ impl ClientFork { pub async fn uncle_by_block_hash_and_index( &self, - hash: H256, + hash: B256, index: usize, - ) -> Result>, ProviderError> { + ) -> Result, TransportError> { if let Some(block) = self.block_by_hash(hash).await? { - return self.uncles_by_block_and_index(block, index).await + return self.uncles_by_block_and_index(block, index).await; } Ok(None) } @@ -477,31 +515,31 @@ impl ClientFork { &self, number: u64, index: usize, - ) -> Result>, ProviderError> { + ) -> Result, TransportError> { if let Some(block) = self.block_by_number(number).await? { - return self.uncles_by_block_and_index(block, index).await + return self.uncles_by_block_and_index(block, index).await; } Ok(None) } async fn uncles_by_block_and_index( &self, - block: Block, + block: Block, index: usize, - ) -> Result>, ProviderError> { - let block_hash = block - .hash - .ok_or_else(|| ProviderError::CustomError("missing block-hash".to_string()))?; + ) -> Result, TransportError> { + let block_hash = block.header.hash.expect("Missing block hash"); + let block_number = block.header.number.expect("Missing block number"); if let Some(uncles) = self.storage_read().uncles.get(&block_hash) { - return Ok(uncles.get(index).cloned()) + return Ok(uncles.get(index).cloned()); } let mut uncles = Vec::with_capacity(block.uncles.len()); for (uncle_idx, _) in block.uncles.iter().enumerate() { - let uncle = match self.provider().get_uncle(block_hash, uncle_idx.into()).await? { - Some(u) => u, - None => return Ok(None), - }; + let uncle = + match self.provider().get_uncle(block_number.into(), uncle_idx as u64).await? { + Some(u) => u, + None => return Ok(None), + }; uncles.push(uncle); } self.storage_write().uncles.insert(block_hash, uncles.clone()); @@ -509,24 +547,33 @@ impl ClientFork { } /// Converts a block of hashes into a full block - fn convert_to_full_block(&self, block: Block) -> Block { + fn convert_to_full_block(&self, block: Block) -> Block { let storage = self.storage.read(); - let mut transactions = Vec::with_capacity(block.transactions.len()); - for tx in block.transactions.iter() { + let block_txs_len = match block.transactions { + BlockTransactions::Full(ref txs) => txs.len(), + BlockTransactions::Hashes(ref hashes) => hashes.len(), + // TODO: Should this be supported at all? + BlockTransactions::Uncle => 0, + }; + let mut transactions = Vec::with_capacity(block_txs_len); + for tx in block.transactions.hashes() { if let Some(tx) = storage.transactions.get(tx).cloned() { - transactions.push(tx); + transactions.push(tx.inner); } } + // TODO: fix once blocks have generic transactions block.into_full_block(transactions) } } /// Contains all fork metadata -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ClientForkConfig { pub eth_rpc_url: String, + /// The block number of the forked block pub block_number: u64, - pub block_hash: H256, + /// The hash of the forked block + pub block_hash: B256, // TODO make provider agnostic pub provider: Arc, pub chain_id: u64, @@ -534,7 +581,7 @@ pub struct ClientForkConfig { /// The timestamp for the forked block pub timestamp: u64, /// The basefee of the forked block - pub base_fee: Option, + pub base_fee: Option, /// request timeout pub timeout: Duration, /// request retries for spurious networks @@ -556,7 +603,7 @@ impl ClientForkConfig { /// /// This will fail if no new provider could be established (erroneous URL) fn update_url(&mut self, url: String) -> Result<(), BlockchainError> { - let interval = self.provider.get_interval(); + // let interval = self.provider.get_interval(); self.provider = Arc::new( ProviderBuilder::new(url.as_str()) .timeout(self.timeout) @@ -565,8 +612,7 @@ impl ClientForkConfig { .initial_backoff(self.backoff.as_millis() as u64) .compute_units_per_second(self.compute_units_per_second) .build() - .map_err(|_| BlockchainError::InvalidUrl(url.clone()))? - .interval(interval), + .map_err(|_| BlockchainError::InvalidUrl(url.clone()))?, // .interval(interval), ); trace!(target: "fork", "Updated rpc url {}", url); self.eth_rpc_url = url; @@ -576,9 +622,9 @@ impl ClientForkConfig { pub fn update_block( &mut self, block_number: u64, - block_hash: H256, + block_hash: B256, timestamp: u64, - base_fee: Option, + base_fee: Option, total_difficulty: U256, ) { self.block_number = block_number; @@ -591,19 +637,20 @@ impl ClientForkConfig { } /// Contains cached state fetched to serve EthApi requests -#[derive(Debug, Clone, Default)] +/// +/// This is used as a cache so repeated requests to the same data are not sent to the remote client +#[derive(Clone, Debug, Default)] pub struct ForkedStorage { - pub uncles: HashMap>>, - pub blocks: HashMap>, - pub hashes: HashMap, - pub transactions: HashMap, - pub transaction_receipts: HashMap, - pub transaction_traces: HashMap>, + pub uncles: HashMap>, + pub blocks: HashMap, + pub hashes: HashMap, + pub transactions: HashMap>, + pub transaction_receipts: HashMap, + pub transaction_traces: HashMap>, pub logs: HashMap>, - pub geth_transaction_traces: HashMap, + pub geth_transaction_traces: HashMap, pub block_traces: HashMap>, - pub eth_gas_estimations: HashMap<(Arc, u64), U256>, - pub eth_call: HashMap<(Arc, u64), Bytes>, + pub block_receipts: HashMap>, pub code_at: HashMap<(Address, u64), Bytes>, } diff --git a/crates/anvil/src/eth/backend/genesis.rs b/crates/anvil/src/eth/backend/genesis.rs index 9c52a5b3c7ba0..bbfbda55edc8d 100644 --- a/crates/anvil/src/eth/backend/genesis.rs +++ b/crates/anvil/src/eth/backend/genesis.rs @@ -1,15 +1,8 @@ //! Genesis settings -use crate::{ - eth::backend::db::{Db, MaybeHashDatabase}, - genesis::Genesis, -}; -use alloy_primitives::{Address as aAddress, B256, U256}; -use ethers::{ - abi::ethereum_types::BigEndianHash, - types::{Address, H256}, -}; -use foundry_common::types::{ToAlloy, ToEthers}; +use crate::eth::backend::db::{Db, MaybeFullDatabase}; +use alloy_genesis::{Genesis, GenesisAccount}; +use alloy_primitives::{Address, B256, U256}; use foundry_evm::{ backend::{DatabaseError, DatabaseResult, StateSnapshot}, revm::{ @@ -22,7 +15,7 @@ use std::{collections::HashMap, sync::Arc}; use tokio::sync::RwLockWriteGuard; /// Genesis settings -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct GenesisConfig { /// The initial timestamp for the genesis block pub timestamp: u64, @@ -62,24 +55,36 @@ impl GenesisConfig { mut db: RwLockWriteGuard<'_, Box>, ) -> DatabaseResult<()> { if let Some(ref genesis) = self.genesis_init { - for (addr, mut acc) in genesis.alloc.accounts.clone() { + for (addr, mut acc) in genesis.alloc.clone() { let storage = std::mem::take(&mut acc.storage); // insert all accounts - db.insert_account(addr, acc.into()); + db.insert_account(addr, self.genesis_to_account_info(&acc)); // insert all storage values - for (k, v) in storage.iter() { - db.set_storage_at(addr, k.into_uint(), v.into_uint())?; + for (k, v) in storage.unwrap_or_default().iter() { + db.set_storage_at(addr, U256::from_be_bytes(k.0), U256::from_be_bytes(v.0))?; } } } Ok(()) } + /// Converts a [`GenesisAccount`] to an [`AccountInfo`] + fn genesis_to_account_info(&self, acc: &GenesisAccount) -> AccountInfo { + let GenesisAccount { code, balance, nonce, .. } = acc.clone(); + let code = code.map(Bytecode::new_raw); + AccountInfo { + balance, + nonce: nonce.unwrap_or_default(), + code_hash: code.as_ref().map(|code| code.hash_slow()).unwrap_or(KECCAK_EMPTY), + code, + } + } + /// Returns a database wrapper that points to the genesis and is aware of all provided /// [AccountInfo] pub(crate) fn state_db_at_genesis<'a>( &self, - db: Box, + db: Box, ) -> AtGenesisStateDb<'a> { AtGenesisStateDb { genesis: self.genesis_init.clone(), @@ -98,13 +103,13 @@ impl GenesisConfig { pub(crate) struct AtGenesisStateDb<'a> { genesis: Option, accounts: HashMap, - db: Box, + db: Box, } impl<'a> DatabaseRef for AtGenesisStateDb<'a> { type Error = DatabaseError; - fn basic_ref(&self, address: aAddress) -> DatabaseResult> { - if let Some(acc) = self.accounts.get(&(address.to_ethers())).cloned() { + fn basic_ref(&self, address: Address) -> DatabaseResult> { + if let Some(acc) = self.accounts.get(&(address)).cloned() { return Ok(Some(acc)) } self.db.basic_ref(address) @@ -117,18 +122,13 @@ impl<'a> DatabaseRef for AtGenesisStateDb<'a> { self.db.code_by_hash_ref(code_hash) } - fn storage_ref(&self, address: aAddress, index: U256) -> DatabaseResult { - if let Some(acc) = self - .genesis - .as_ref() - .and_then(|genesis| genesis.alloc.accounts.get(&(address.to_ethers()))) - { - let value = acc - .storage - .get(&H256::from_uint(&(index.to_ethers()))) - .copied() - .unwrap_or_default(); - return Ok(value.into_uint().to_alloy()) + fn storage_ref(&self, address: Address, index: U256) -> DatabaseResult { + if let Some(acc) = self.genesis.as_ref().and_then(|genesis| genesis.alloc.get(&(address))) { + if let Some(storage) = acc.storage.as_ref() { + return Ok(U256::from_be_bytes( + storage.get(&B256::from(index)).copied().unwrap_or_default().0, + )) + } } self.db.storage_ref(address, index) } @@ -138,7 +138,7 @@ impl<'a> DatabaseRef for AtGenesisStateDb<'a> { } } -impl<'a> MaybeHashDatabase for AtGenesisStateDb<'a> { +impl<'a> MaybeFullDatabase for AtGenesisStateDb<'a> { fn clear_into_snapshot(&mut self) -> StateSnapshot { self.db.clear_into_snapshot() } diff --git a/crates/anvil/src/eth/backend/info.rs b/crates/anvil/src/eth/backend/info.rs index 8d4e3601b30d4..7b4c5d9c7e7ab 100644 --- a/crates/anvil/src/eth/backend/info.rs +++ b/crates/anvil/src/eth/backend/info.rs @@ -1,8 +1,9 @@ //! Handler that can get current storage related data use crate::mem::Backend; -use anvil_core::eth::{block::Block, receipt::TypedReceipt}; -use ethers::types::{Block as EthersBlock, TxHash, H256}; +use alloy_primitives::B256; +use alloy_rpc_types::Block as AlloyBlock; +use anvil_core::eth::{block::Block, transaction::TypedReceipt}; use std::{fmt, sync::Arc}; /// A type that can fetch data related to the ethereum storage. @@ -33,17 +34,17 @@ impl StorageInfo { } /// Returns the receipts of the block with the given hash - pub fn receipts(&self, hash: H256) -> Option> { + pub fn receipts(&self, hash: B256) -> Option> { self.backend.mined_receipts(hash) } /// Returns the block with the given hash - pub fn block(&self, hash: H256) -> Option { + pub fn block(&self, hash: B256) -> Option { self.backend.get_block_by_hash(hash) } /// Returns the block with the given hash in the format of the ethereum API - pub fn eth_block(&self, hash: H256) -> Option> { + pub fn eth_block(&self, hash: B256) -> Option { let block = self.block(hash)?; Some(self.backend.convert_block(block)) } diff --git a/crates/anvil/src/eth/backend/mem/cache.rs b/crates/anvil/src/eth/backend/mem/cache.rs index a3990e82de2a7..d4aff594878d0 100644 --- a/crates/anvil/src/eth/backend/mem/cache.rs +++ b/crates/anvil/src/eth/backend/mem/cache.rs @@ -1,5 +1,5 @@ use crate::config::anvil_tmp_dir; -use ethers::prelude::H256; +use alloy_primitives::B256; use foundry_evm::backend::StateSnapshot; use std::{ io, @@ -19,7 +19,7 @@ pub struct DiskStateCache { impl DiskStateCache { /// Returns the cache file for the given hash - fn with_cache_file(&mut self, hash: H256, f: F) -> Option + fn with_cache_file(&mut self, hash: B256, f: F) -> Option where F: FnOnce(PathBuf) -> R, { @@ -56,7 +56,7 @@ impl DiskStateCache { /// Note: this writes the state on a new spawned task /// /// Caution: this requires a running tokio Runtime. - pub fn write(&mut self, hash: H256, state: StateSnapshot) { + pub fn write(&mut self, hash: B256, state: StateSnapshot) { self.with_cache_file(hash, |file| { tokio::task::spawn(async move { match foundry_common::fs::write_json_file(&file, &state) { @@ -74,7 +74,7 @@ impl DiskStateCache { /// Loads the snapshot file for the given hash /// /// Returns None if it doesn't exist or deserialization failed - pub fn read(&mut self, hash: H256) -> Option { + pub fn read(&mut self, hash: B256) -> Option { self.with_cache_file(hash, |file| { match foundry_common::fs::read_json_file::(&file) { Ok(state) => { @@ -91,7 +91,7 @@ impl DiskStateCache { } /// Removes the cache file for the given hash, if it exists - pub fn remove(&mut self, hash: H256) { + pub fn remove(&mut self, hash: B256) { self.with_cache_file(hash, |file| { foundry_common::fs::remove_file(file).map_err(|err| { error!(target: "backend", %err, %hash, "Failed to remove state snapshot"); diff --git a/crates/anvil/src/eth/backend/mem/fork_db.rs b/crates/anvil/src/eth/backend/mem/fork_db.rs index 41cf8cdaf7920..d99aeb5ed415b 100644 --- a/crates/anvil/src/eth/backend/mem/fork_db.rs +++ b/crates/anvil/src/eth/backend/mem/fork_db.rs @@ -1,13 +1,12 @@ use crate::{ eth::backend::db::{ - Db, MaybeForkedDatabase, MaybeHashDatabase, SerializableAccountRecord, SerializableState, + Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableState, StateDb, }, revm::primitives::AccountInfo, - Address, U256, }; -use ethers::{prelude::H256, types::BlockId}; -use foundry_common::types::{ToAlloy, ToEthers}; +use alloy_primitives::{Address, B256, U256, U64}; +use alloy_rpc_types::BlockId; use foundry_evm::{ backend::{DatabaseResult, RevertSnapshotAction, StateSnapshot}, fork::{database::ForkDbSnapshot, BlockchainDb}, @@ -15,6 +14,7 @@ use foundry_evm::{ }; pub use foundry_evm::fork::database::ForkedDatabase; +use foundry_evm::revm::primitives::BlockEnv; /// Implement the helper for the fork database impl Db for ForkedDatabase { @@ -24,15 +24,19 @@ impl Db for ForkedDatabase { fn set_storage_at(&mut self, address: Address, slot: U256, val: U256) -> DatabaseResult<()> { // this ensures the account is loaded first - let _ = Database::basic(self, address.to_alloy())?; + let _ = Database::basic(self, address)?; self.database_mut().set_storage_at(address, slot, val) } - fn insert_block_hash(&mut self, number: U256, hash: H256) { - self.inner().block_hashes().write().insert(number.to_alloy(), hash.to_alloy()); + fn insert_block_hash(&mut self, number: U256, hash: B256) { + self.inner().block_hashes().write().insert(number, hash); } - fn dump_state(&self) -> DatabaseResult> { + fn dump_state( + &self, + at: BlockEnv, + best_number: U64, + ) -> DatabaseResult> { let mut db = self.database().clone(); let accounts = self .database() @@ -47,29 +51,29 @@ impl Db for ForkedDatabase { } .to_checked(); Ok(( - k.to_ethers(), + k, SerializableAccountRecord { nonce: v.info.nonce, - balance: v.info.balance.to_ethers(), - code: code.bytes()[..code.len()].to_vec().into(), - storage: v - .storage - .into_iter() - .map(|kv| (kv.0.to_ethers(), kv.1.to_ethers())) - .collect(), + balance: v.info.balance, + code: code.original_bytes(), + storage: v.storage.into_iter().collect(), }, )) }) .collect::>()?; - Ok(Some(SerializableState { accounts })) + Ok(Some(SerializableState { + block: Some(at), + accounts, + best_block_number: Some(best_number), + })) } fn snapshot(&mut self) -> U256 { - self.insert_snapshot().to_ethers() + self.insert_snapshot() } fn revert(&mut self, id: U256, action: RevertSnapshotAction) -> bool { - self.revert_snapshot(id.to_alloy(), action) + self.revert_snapshot(id, action) } fn current_state(&self) -> StateDb { @@ -77,7 +81,7 @@ impl Db for ForkedDatabase { } } -impl MaybeHashDatabase for ForkedDatabase { +impl MaybeFullDatabase for ForkedDatabase { fn clear_into_snapshot(&mut self) -> StateSnapshot { let db = self.inner().db(); let accounts = std::mem::take(&mut *db.accounts.write()); @@ -100,7 +104,7 @@ impl MaybeHashDatabase for ForkedDatabase { } } -impl MaybeHashDatabase for ForkDbSnapshot { +impl MaybeFullDatabase for ForkDbSnapshot { fn clear_into_snapshot(&mut self) -> StateSnapshot { std::mem::take(&mut self.snapshot) } diff --git a/crates/anvil/src/eth/backend/mem/in_memory_db.rs b/crates/anvil/src/eth/backend/mem/in_memory_db.rs index acf929e0c6584..1c96a0eb585f6 100644 --- a/crates/anvil/src/eth/backend/mem/in_memory_db.rs +++ b/crates/anvil/src/eth/backend/mem/in_memory_db.rs @@ -2,38 +2,42 @@ use crate::{ eth::backend::db::{ - AsHashDB, Db, MaybeForkedDatabase, MaybeHashDatabase, SerializableAccountRecord, - SerializableState, StateDb, + Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableState, + StateDb, }, - mem::state::{state_merkle_trie_root, storage_trie_db, trie_hash_db}, - revm::primitives::AccountInfo, - Address, U256, + mem::state::state_root, + revm::{db::DbAccount, primitives::AccountInfo}, }; -use ethers::{prelude::H256, types::BlockId}; -use foundry_common::types::{ToAlloy, ToEthers}; +use alloy_primitives::{Address, B256, U256, U64}; +use alloy_rpc_types::BlockId; use foundry_evm::{ backend::{DatabaseResult, StateSnapshot}, fork::BlockchainDb, + hashbrown::HashMap, }; // reexport for convenience -use foundry_evm::backend::RevertSnapshotAction; pub use foundry_evm::{backend::MemDb, revm::db::DatabaseRef}; +use foundry_evm::{backend::RevertSnapshotAction, revm::primitives::BlockEnv}; impl Db for MemDb { fn insert_account(&mut self, address: Address, account: AccountInfo) { - self.inner.insert_account_info(address.to_alloy(), account) + self.inner.insert_account_info(address, account) } fn set_storage_at(&mut self, address: Address, slot: U256, val: U256) -> DatabaseResult<()> { - self.inner.insert_account_storage(address.to_alloy(), slot.to_alloy(), val.to_alloy()) + self.inner.insert_account_storage(address, slot, val) } - fn insert_block_hash(&mut self, number: U256, hash: H256) { - self.inner.block_hashes.insert(number.to_alloy(), hash.to_alloy()); + fn insert_block_hash(&mut self, number: U256, hash: B256) { + self.inner.block_hashes.insert(number, hash); } - fn dump_state(&self) -> DatabaseResult> { + fn dump_state( + &self, + at: BlockEnv, + best_number: U64, + ) -> DatabaseResult> { let accounts = self .inner .accounts @@ -47,35 +51,35 @@ impl Db for MemDb { } .to_checked(); Ok(( - k.to_ethers(), + k, SerializableAccountRecord { nonce: v.info.nonce, - balance: v.info.balance.to_ethers(), - code: code.bytes()[..code.len()].to_vec().into(), - storage: v - .storage - .into_iter() - .map(|k| (k.0.to_ethers(), k.1.to_ethers())) - .collect(), + balance: v.info.balance, + code: code.original_bytes(), + storage: v.storage.into_iter().collect(), }, )) }) .collect::>()?; - Ok(Some(SerializableState { accounts })) + Ok(Some(SerializableState { + block: Some(at), + accounts, + best_block_number: Some(best_number), + })) } /// Creates a new snapshot fn snapshot(&mut self) -> U256 { let id = self.snapshots.insert(self.inner.clone()); trace!(target: "backend::memdb", "Created new snapshot {}", id); - id.to_ethers() + id } fn revert(&mut self, id: U256, action: RevertSnapshotAction) -> bool { - if let Some(snapshot) = self.snapshots.remove(id.to_alloy()) { + if let Some(snapshot) = self.snapshots.remove(id) { if action.is_keep() { - self.snapshots.insert_at(snapshot.clone(), id.to_alloy()); + self.snapshots.insert_at(snapshot.clone(), id); } self.inner = snapshot; trace!(target: "backend::memdb", "Reverted snapshot {}", id); @@ -86,8 +90,8 @@ impl Db for MemDb { } } - fn maybe_state_root(&self) -> Option { - Some(state_merkle_trie_root(&self.inner.accounts)) + fn maybe_state_root(&self) -> Option { + Some(state_root(&self.inner.accounts)) } fn current_state(&self) -> StateDb { @@ -95,17 +99,9 @@ impl Db for MemDb { } } -impl MaybeHashDatabase for MemDb { - fn maybe_as_hash_db(&self) -> Option<(AsHashDB, H256)> { - Some(trie_hash_db(&self.inner.accounts)) - } - - fn maybe_account_db(&self, addr: Address) -> Option<(AsHashDB, H256)> { - if let Some(acc) = self.inner.accounts.get(&addr.to_alloy()) { - Some(storage_trie_db(&acc.storage)) - } else { - Some(storage_trie_db(&Default::default())) - } +impl MaybeFullDatabase for MemDb { + fn maybe_as_full_db(&self) -> Option<&HashMap> { + Some(&self.inner.accounts) } fn clear_into_snapshot(&mut self) -> StateSnapshot { @@ -138,18 +134,8 @@ impl MaybeForkedDatabase for MemDb { #[cfg(test)] mod tests { use super::*; - use crate::{ - eth::backend::db::{Db, SerializableAccountRecord, SerializableState}, - revm::primitives::AccountInfo, - Address, - }; - use alloy_primitives::{Bytes, U256 as rU256}; - use ethers::types::U256; - use foundry_common::types::ToAlloy; - use foundry_evm::{ - backend::MemDb, - revm::primitives::{Bytecode, KECCAK_EMPTY}, - }; + use alloy_primitives::Bytes; + use foundry_evm::revm::primitives::{Bytecode, KECCAK_EMPTY}; use std::{collections::BTreeMap, str::FromStr}; // verifies that all substantial aspects of a loaded account remain the state after an account @@ -166,7 +152,7 @@ mod tests { dump_db.insert_account( test_addr, AccountInfo { - balance: rU256::from(123456), + balance: U256::from(123456), code_hash: KECCAK_EMPTY, code: Some(contract_code.clone()), nonce: 1234, @@ -175,21 +161,18 @@ mod tests { dump_db.set_storage_at(test_addr, U256::from(1234567), U256::from(1)).unwrap(); - let state = dump_db.dump_state().unwrap().unwrap(); + let state = dump_db.dump_state(Default::default(), U64::ZERO).unwrap().unwrap(); let mut load_db = MemDb::default(); load_db.load_state(state).unwrap(); - let loaded_account = load_db.basic_ref(test_addr.to_alloy()).unwrap().unwrap(); + let loaded_account = load_db.basic_ref(test_addr).unwrap().unwrap(); - assert_eq!(loaded_account.balance, rU256::from(123456)); + assert_eq!(loaded_account.balance, U256::from(123456)); assert_eq!(load_db.code_by_hash_ref(loaded_account.code_hash).unwrap(), contract_code); assert_eq!(loaded_account.nonce, 1234); - assert_eq!( - load_db.storage_ref(test_addr.to_alloy(), rU256::from(1234567)).unwrap(), - rU256::from(1) - ); + assert_eq!(load_db.storage_ref(test_addr, U256::from(1234567)).unwrap(), U256::from(1)); } // verifies that multiple accounts can be loaded at a time, and storage is merged within those @@ -208,7 +191,7 @@ mod tests { db.insert_account( test_addr, AccountInfo { - balance: rU256::from(123456), + balance: U256::from(123456), code_hash: KECCAK_EMPTY, code: Some(contract_code.clone()), nonce: 1234, @@ -236,7 +219,7 @@ mod tests { new_state.accounts.insert( test_addr, SerializableAccountRecord { - balance: 100100.into(), + balance: U256::from(100100), code: contract_code.bytes()[..contract_code.len()].to_vec().into(), nonce: 100, storage: new_storage, @@ -245,21 +228,15 @@ mod tests { db.load_state(new_state).unwrap(); - let loaded_account = db.basic_ref(test_addr.to_alloy()).unwrap().unwrap(); - let loaded_account2 = db.basic_ref(test_addr2.to_alloy()).unwrap().unwrap(); + let loaded_account = db.basic_ref(test_addr).unwrap().unwrap(); + let loaded_account2 = db.basic_ref(test_addr2).unwrap().unwrap(); assert_eq!(loaded_account2.nonce, 1); - assert_eq!(loaded_account.balance, rU256::from(100100)); + assert_eq!(loaded_account.balance, U256::from(100100)); assert_eq!(db.code_by_hash_ref(loaded_account.code_hash).unwrap(), contract_code); assert_eq!(loaded_account.nonce, 1234); - assert_eq!( - db.storage_ref(test_addr.to_alloy(), rU256::from(1234567)).unwrap(), - rU256::from(1) - ); - assert_eq!( - db.storage_ref(test_addr.to_alloy(), rU256::from(1234568)).unwrap(), - rU256::from(5) - ); + assert_eq!(db.storage_ref(test_addr, U256::from(1234567)).unwrap(), U256::from(1)); + assert_eq!(db.storage_ref(test_addr, U256::from(1234568)).unwrap(), U256::from(5)); } } diff --git a/crates/anvil/src/eth/backend/mem/inspector.rs b/crates/anvil/src/eth/backend/mem/inspector.rs index 295558408ba3d..6ea16a34057e2 100644 --- a/crates/anvil/src/eth/backend/mem/inspector.rs +++ b/crates/anvil/src/eth/backend/mem/inspector.rs @@ -1,23 +1,25 @@ //! Anvil specific [`revm::Inspector`] implementation use crate::{eth::macros::node_info, revm::Database}; -use ethers::types::Log; +use alloy_primitives::{Address, Log}; use foundry_evm::{ call_inspectors, decode::decode_console_logs, - inspectors::{LogCollector, Tracer}, - revm, + inspectors::{LogCollector, TracingInspector}, revm::{ - interpreter::{CallInputs, CreateInputs, Gas, InstructionResult, Interpreter}, - primitives::{Address, Bytes, B256}, - EVMData, + self, + interpreter::{CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter}, + primitives::U256, + EvmContext, }, + traces::TracingInspectorConfig, + InspectorExt, }; /// The [`revm::Inspector`] used when transacting in the evm -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct Inspector { - pub tracer: Option, + pub tracer: Option, /// collects all `console.sol` logs pub log_collector: LogCollector, } @@ -34,111 +36,109 @@ impl Inspector { /// Configures the `Tracer` [`revm::Inspector`] pub fn with_tracing(mut self) -> Self { - self.tracer = Some(Default::default()); + self.tracer = Some(TracingInspector::new(TracingInspectorConfig::all().set_steps(false))); self } /// Enables steps recording for `Tracer`. pub fn with_steps_tracing(mut self) -> Self { - let tracer = self.tracer.get_or_insert_with(Default::default); - tracer.record_steps(); + self.tracer = Some(TracingInspector::new(TracingInspectorConfig::all())); self } } impl revm::Inspector for Inspector { #[inline] - fn initialize_interp(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { + fn initialize_interp(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { call_inspectors!([&mut self.tracer], |inspector| { - inspector.initialize_interp(interp, data); + inspector.initialize_interp(interp, ecx); }); } #[inline] - fn step(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { + fn step(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { call_inspectors!([&mut self.tracer], |inspector| { - inspector.step(interp, data); + inspector.step(interp, ecx); }); } #[inline] - fn log( - &mut self, - evm_data: &mut EVMData<'_, DB>, - address: &Address, - topics: &[B256], - data: &Bytes, - ) { - call_inspectors!([&mut self.tracer, Some(&mut self.log_collector)], |inspector| { - inspector.log(evm_data, address, topics, data); + fn step_end(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { + call_inspectors!([&mut self.tracer], |inspector| { + inspector.step_end(interp, ecx); }); } #[inline] - fn step_end(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - call_inspectors!([&mut self.tracer], |inspector| { - inspector.step_end(interp, data); + fn log(&mut self, ecx: &mut EvmContext, log: &Log) { + call_inspectors!([&mut self.tracer, Some(&mut self.log_collector)], |inspector| { + inspector.log(ecx, log); }); } #[inline] - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { + fn call(&mut self, ecx: &mut EvmContext, inputs: &mut CallInputs) -> Option { call_inspectors!([&mut self.tracer, Some(&mut self.log_collector)], |inspector| { - inspector.call(data, call); + if let Some(outcome) = inspector.call(ecx, inputs) { + return Some(outcome); + } }); - (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) + None } #[inline] fn call_end( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext, inputs: &CallInputs, - remaining_gas: Gas, - ret: InstructionResult, - out: Bytes, - ) -> (InstructionResult, Gas, Bytes) { - call_inspectors!([&mut self.tracer], |inspector| { - inspector.call_end(data, inputs, remaining_gas, ret, out.clone()); - }); - (ret, remaining_gas, out) + outcome: CallOutcome, + ) -> CallOutcome { + if let Some(tracer) = &mut self.tracer { + return tracer.call_end(ecx, inputs, outcome); + } + + outcome } #[inline] fn create( &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { - call_inspectors!([&mut self.tracer], |inspector| { - inspector.create(data, call); - }); - - (InstructionResult::Continue, None, Gas::new(call.gas_limit), Bytes::new()) + ecx: &mut EvmContext, + inputs: &mut CreateInputs, + ) -> Option { + if let Some(tracer) = &mut self.tracer { + if let Some(out) = tracer.create(ecx, inputs) { + return Some(out); + } + } + None } #[inline] fn create_end( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext, inputs: &CreateInputs, - status: InstructionResult, - address: Option
, - gas: Gas, - retdata: Bytes, - ) -> (InstructionResult, Option
, Gas, Bytes) { - call_inspectors!([&mut self.tracer], |inspector| { - inspector.create_end(data, inputs, status, address, gas, retdata.clone()); - }); - (status, address, gas, retdata) + outcome: CreateOutcome, + ) -> CreateOutcome { + if let Some(tracer) = &mut self.tracer { + return tracer.create_end(ecx, inputs, outcome); + } + + outcome + } + + #[inline] + fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { + if let Some(tracer) = &mut self.tracer { + revm::Inspector::::selfdestruct(tracer, contract, target, value); + } } } +impl InspectorExt for Inspector {} + /// Prints all the logs #[inline] pub fn print_logs(logs: &[Log]) { diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 6dcf120d1cd72..ed27557b311e1 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -1,14 +1,18 @@ //! In memory blockchain backend +use self::state::trie_storage; use crate::{ config::PruneStateHistoryConfig, eth::{ backend::{ cheats::CheatsManager, - db::{AsHashDB, Db, MaybeHashDatabase, SerializableState}, + db::{Db, MaybeFullDatabase, SerializableState}, executor::{ExecutedTransactions, TransactionExecutor}, fork::ClientFork, genesis::GenesisConfig, - mem::storage::MinedTransactionReceipt, + mem::{ + state::{storage_root, trie_accounts}, + storage::MinedTransactionReceipt, + }, notifications::{NewBlockNotification, NewBlockNotifications}, time::{utc_from_secs, TimeManager}, validate::TransactionValidator, @@ -19,73 +23,74 @@ use crate::{ pool::transactions::PoolTransaction, util::get_precompiles_for, }, + inject_precompiles, mem::{ inspector::Inspector, storage::{BlockchainStorage, InMemoryBlockStates, MinedBlockOutcome}, }, - revm::{ - db::DatabaseRef, - primitives::{AccountInfo, U256 as rU256}, - }, - NodeConfig, + revm::{db::DatabaseRef, primitives::AccountInfo}, + NodeConfig, PrecompileFactory, +}; +use alloy_consensus::{Header, Receipt, ReceiptWithBloom}; +use alloy_eips::eip4844::MAX_BLOBS_PER_BLOCK; +use alloy_primitives::{keccak256, Address, Bytes, TxHash, TxKind, B256, U256, U64}; +use alloy_rpc_types::{ + request::TransactionRequest, serde_helpers::JsonStorageKey, state::StateOverride, AccessList, + Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, + EIP1186AccountProofResponse as AccountProof, EIP1186StorageProof as StorageProof, Filter, + FilteredParams, Header as AlloyHeader, Log, Transaction, TransactionReceipt, WithOtherFields, }; +use alloy_rpc_types_trace::{ + geth::{DefaultFrame, GethDebugTracingOptions, GethDefaultTracingOptions, GethTrace}, + parity::LocalizedTransactionTrace, +}; +use alloy_trie::{HashBuilder, Nibbles}; use anvil_core::{ eth::{ - block::{Block, BlockInfo, Header}, - proof::{AccountProof, BasicAccount, StorageProof}, - receipt::{EIP658Receipt, TypedReceipt}, - state::StateOverride, + block::{Block, BlockInfo}, transaction::{ - EthTransactionRequest, MaybeImpersonatedTransaction, PendingTransaction, - TransactionInfo, TypedTransaction, + DepositReceipt, MaybeImpersonatedTransaction, PendingTransaction, ReceiptResponse, + TransactionInfo, TypedReceipt, TypedTransaction, }, - trie::RefTrieDB, - utils::to_revm_access_list, + utils::meets_eip155, }, types::{Forking, Index}, }; use anvil_rpc::error::RpcError; -use ethers::{ - abi::ethereum_types::BigEndianHash, - prelude::{BlockNumber, GethTraceFrame, TxHash, H256, U256, U64}, - types::{ - transaction::eip2930::AccessList, Address, Block as EthersBlock, BlockId, Bytes, - DefaultFrame, Filter, FilteredParams, GethDebugTracingOptions, GethTrace, Log, OtherFields, - Trace, Transaction, TransactionReceipt, H160, - }, - utils::{keccak256, rlp}, -}; use flate2::{read::GzDecoder, write::GzEncoder, Compression}; -use foundry_common::types::{ToAlloy, ToEthers}; use foundry_evm::{ backend::{DatabaseError, DatabaseResult, RevertSnapshotAction}, constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE, - decode::decode_revert, - inspectors::AccessListTracer, + decode::RevertDecoder, + inspectors::AccessListInspector, revm::{ self, db::CacheDB, interpreter::InstructionResult, primitives::{ - Account, BlockEnv, CreateScheme, EVMError, Env, ExecutionResult, InvalidHeader, Output, - SpecId, TransactTo, TxEnv, KECCAK_EMPTY, + BlockEnv, CfgEnvWithHandlerCfg, CreateScheme, EnvWithHandlerCfg, ExecutionResult, + Output, SpecId, TransactTo, TxEnv, KECCAK_EMPTY, }, }, - utils::{eval_to_instruction_result, halt_to_instruction_result, u256_to_h256_be}, + utils::new_evm_with_inspector_ref, + InspectorExt, }; use futures::channel::mpsc::{unbounded, UnboundedSender}; -use hash_db::HashDB; use parking_lot::{Mutex, RwLock}; +use revm::{ + db::WrapDatabaseRef, + primitives::{ + calc_blob_gasprice, BlobExcessGasAndPrice, HashMap, OptimismFields, ResultAndState, + }, +}; use std::{ - collections::{BTreeMap, HashMap}, + collections::BTreeMap, io::{Read, Write}, - ops::Deref, sync::Arc, time::Duration, }; use storage::{Blockchain, MinedTransaction}; use tokio::sync::RwLock as AsyncRwLock; -use trie_db::{Recorder, Trie}; pub mod cache; pub mod fork_db; @@ -95,25 +100,24 @@ pub mod state; pub mod storage; // Gas per transaction not creating a contract. -pub const MIN_TRANSACTION_GAS: U256 = U256([21_000, 0, 0, 0]); +pub const MIN_TRANSACTION_GAS: u128 = 21000; // Gas per transaction creating a contract. -pub const MIN_CREATE_GAS: U256 = U256([53_000, 0, 0, 0]); +pub const MIN_CREATE_GAS: u128 = 53000; -// TODO: This is the same as foundry_evm::utils::StateChangeset but with ethers H160 -pub type State = foundry_evm::hashbrown::HashMap; +pub type State = foundry_evm::utils::StateChangeset; /// A block request, which includes the Pool Transactions if it's Pending #[derive(Debug)] pub enum BlockRequest { Pending(Vec>), - Number(U64), + Number(u64), } impl BlockRequest { pub fn block_number(&self) -> BlockNumber { - match self { + match *self { BlockRequest::Pending(_) => BlockNumber::Pending, - BlockRequest::Number(n) => BlockNumber::Number(*n), + BlockRequest::Number(n) => BlockNumber::Number(n), } } } @@ -145,7 +149,7 @@ pub struct Backend { /// Historic states of previous blocks states: Arc>, /// env data of the chain - env: Arc>, + env: Arc>, /// this is set if this is currently forked off another client fork: Arc>>, /// provides time related info, like timestamp @@ -159,13 +163,17 @@ pub struct Backend { /// listeners for new blocks that get notified when a new block was imported new_block_listeners: Arc>>>, /// keeps track of active snapshots at a specific block - active_snapshots: Arc>>, + active_snapshots: Arc>>, enable_steps_tracing: bool, /// How to keep history state prune_state_history_config: PruneStateHistoryConfig, /// max number of blocks with transactions in memory transaction_block_keeper: Option, node_config: Arc>, + /// Slots in an epoch + slots_in_an_epoch: u64, + /// Precompiles to inject to the EVM. + precompile_factory: Option>, } impl Backend { @@ -173,7 +181,7 @@ impl Backend { #[allow(clippy::too_many_arguments)] pub async fn with_genesis( db: Arc>>, - env: Arc>, + env: Arc>, genesis: GenesisConfig, fees: FeeManager, fork: Arc>>, @@ -212,6 +220,11 @@ impl Backend { Default::default() }; + let (slots_in_an_epoch, precompile_factory) = { + let cfg = node_config.read().await; + (cfg.slots_in_an_epoch, cfg.precompile_factory.clone()) + }; + let backend = Self { db, blockchain, @@ -228,6 +241,8 @@ impl Backend { prune_state_history_config, transaction_block_keeper, node_config, + slots_in_an_epoch, + precompile_factory, }; if let Some(interval_block_time) = automine_block_time { @@ -267,7 +282,7 @@ impl Backend { // accounts concurrently by spawning the job to a new task genesis_accounts_futures.push(tokio::task::spawn(async move { let db = db.read().await; - let info = db.basic_ref(address.to_alloy())?.unwrap_or_default(); + let info = db.basic_ref(address)?.unwrap_or_default(); Ok::<_, DatabaseError>((address, info)) })); } @@ -295,6 +310,10 @@ impl Backend { for (account, info) in self.genesis.account_infos() { db.insert_account(account, info); } + + // insert the new genesis hash to the database so it's available for the next block in + // the evm + db.insert_block_hash(U256::from(self.best_number()), self.best_hash()); } let db = self.db.write().await; @@ -308,7 +327,7 @@ impl Backend { /// Returns `true` if the account is already impersonated pub async fn impersonate(&self, addr: Address) -> DatabaseResult { if self.cheats.impersonated_accounts().contains(&addr) { - return Ok(true) + return Ok(true); } // Ensure EIP-3607 is disabled let mut env = self.env.write(); @@ -341,7 +360,7 @@ impl Backend { /// Returns the `AccountInfo` from the database pub async fn get_account(&self, address: Address) -> DatabaseResult { - Ok(self.db.read().await.basic_ref(address.to_alloy())?.unwrap_or_default()) + Ok(self.db.read().await.basic_ref(address)?.unwrap_or_default()) } /// Whether we're forked off some remote client @@ -350,7 +369,7 @@ impl Backend { } pub fn precompiles(&self) -> Vec
{ - get_precompiles_for(self.env.read().cfg.spec_id) + get_precompiles_for(self.env.read().handler_cfg.spec_id) } /// Resets the fork to a fresh state @@ -359,10 +378,15 @@ impl Backend { if let Some(eth_rpc_url) = forking.clone().json_rpc_url { let mut env = self.env.read().clone(); - let mut node_config = self.node_config.write().await; + let (db, config) = { + let mut node_config = self.node_config.write().await; + + // we want to force the correct base fee for the next block during + // `setup_fork_db_config` + node_config.base_fee.take(); - let (db, config) = - node_config.setup_fork_db_config(eth_rpc_url, &mut env, &self.fees).await; + node_config.setup_fork_db_config(eth_rpc_url, &mut env, &self.fees).await + }; *self.db.write().await = Box::new(db); @@ -374,7 +398,7 @@ impl Backend { return Err(RpcError::invalid_params( "Forking not enabled and RPC URL not provided to start forking", ) - .into()) + .into()); } } @@ -394,20 +418,28 @@ impl Backend { env.cfg.chain_id = fork.chain_id(); env.block = BlockEnv { - number: rU256::from(fork_block_number), - timestamp: fork_block.timestamp.to_alloy(), - gas_limit: fork_block.gas_limit.to_alloy(), - difficulty: fork_block.difficulty.to_alloy(), - // ensures prevrandao is set - prevrandao: Some(fork_block.mix_hash.map(|h| h.to_alloy()).unwrap_or_default()), + number: U256::from(fork_block_number), + timestamp: U256::from(fork_block.header.timestamp), + gas_limit: U256::from(fork_block.header.gas_limit), + difficulty: fork_block.header.difficulty, + prevrandao: Some(fork_block.header.mix_hash.unwrap_or_default()), // Keep previous `coinbase` and `basefee` value coinbase: env.block.coinbase, basefee: env.block.basefee, ..env.block.clone() }; - self.time.reset((env.block.timestamp.to_ethers()).as_u64()); - self.fees.set_base_fee(env.block.basefee.to_ethers()); + self.time.reset(env.block.timestamp.to::()); + + // this is the base fee of the current block, but we need the base fee of + // the next block + let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( + fork_block.header.gas_used, + fork_block.header.gas_limit, + fork_block.header.base_fee_per_gas.unwrap_or_default(), + ); + + self.fees.set_base_fee(next_block_base_fee); // also reset the total difficulty self.blockchain.storage.write().total_difficulty = fork.total_difficulty(); @@ -460,34 +492,34 @@ impl Backend { } /// The env data of the blockchain - pub fn env(&self) -> &Arc> { + pub fn env(&self) -> &Arc> { &self.env } /// Returns the current best hash of the chain - pub fn best_hash(&self) -> H256 { + pub fn best_hash(&self) -> B256 { self.blockchain.storage.read().best_hash } /// Returns the current best number of the chain - pub fn best_number(&self) -> U64 { - self.env.read().block.number.saturating_to::().into() + pub fn best_number(&self) -> u64 { + self.blockchain.storage.read().best_number.try_into().unwrap_or(u64::MAX) } /// Sets the block number pub fn set_block_number(&self, number: U256) { let mut env = self.env.write(); - env.block.number = number.to_alloy(); + env.block.number = number; } /// Returns the client coinbase address. pub fn coinbase(&self) -> Address { - self.env.read().block.coinbase.to_ethers() + self.env.read().block.coinbase } /// Returns the client coinbase address. - pub fn chain_id(&self) -> u64 { - self.env.read().cfg.chain_id + pub fn chain_id(&self) -> U256 { + U256::from(self.env.read().cfg.chain_id) } pub fn set_chain_id(&self, chain_id: u64) { @@ -496,17 +528,17 @@ impl Backend { /// Returns balance of the given account. pub async fn current_balance(&self, address: Address) -> DatabaseResult { - Ok(self.get_account(address).await?.balance.to_ethers()) + Ok(self.get_account(address).await?.balance) } /// Returns balance of the given account. - pub async fn current_nonce(&self, address: Address) -> DatabaseResult { - Ok(self.get_account(address).await?.nonce.into()) + pub async fn current_nonce(&self, address: Address) -> DatabaseResult { + Ok(self.get_account(address).await?.nonce) } /// Sets the coinbase address pub fn set_coinbase(&self, address: Address) { - self.env.write().block.coinbase = address.to_alloy(); + self.env.write().block.coinbase = address; } /// Sets the nonce of the given address @@ -521,7 +553,7 @@ impl Backend { /// Sets the code of the given address pub async fn set_code(&self, address: Address, code: Bytes) -> DatabaseResult<()> { - self.db.write().await.set_code(address, code) + self.db.write().await.set_code(address, code.0.into()) } /// Sets the value for the given slot of the given address @@ -529,14 +561,14 @@ impl Backend { &self, address: Address, slot: U256, - val: H256, + val: B256, ) -> DatabaseResult<()> { - self.db.write().await.set_storage_at(address, slot, val.into_uint()) + self.db.write().await.set_storage_at(address, slot, U256::from_be_bytes(val.0)) } /// Returns the configured specid pub fn spec_id(&self) -> SpecId { - self.env.read().cfg.spec_id + self.env.read().handler_cfg.spec_id } /// Returns true for post London @@ -554,15 +586,20 @@ impl Backend { (self.spec_id() as u8) >= (SpecId::BERLIN as u8) } + /// Returns true for post Cancun + pub fn is_eip4844(&self) -> bool { + (self.spec_id() as u8) >= (SpecId::CANCUN as u8) + } + /// Returns true if op-stack deposits are active pub fn is_optimism(&self) -> bool { - self.env.read().cfg.optimism + self.env.read().handler_cfg.is_optimism } /// Returns an error if EIP1559 is not active (pre Berlin) pub fn ensure_eip1559_active(&self) -> Result<(), BlockchainError> { if self.is_eip1559() { - return Ok(()) + return Ok(()); } Err(BlockchainError::EIP1559TransactionUnsupportedAtHardfork) } @@ -570,11 +607,18 @@ impl Backend { /// Returns an error if EIP1559 is not active (pre muirGlacier) pub fn ensure_eip2930_active(&self) -> Result<(), BlockchainError> { if self.is_eip2930() { - return Ok(()) + return Ok(()); } Err(BlockchainError::EIP2930TransactionUnsupportedAtHardfork) } + pub fn ensure_eip4844_active(&self) -> Result<(), BlockchainError> { + if self.is_eip4844() { + return Ok(()); + } + Err(BlockchainError::EIP4844TransactionUnsupportedAtHardfork) + } + /// Returns an error if op-stack deposits are not active pub fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> { if self.is_optimism() { @@ -584,37 +628,41 @@ impl Backend { } /// Returns the block gas limit - pub fn gas_limit(&self) -> U256 { - self.env.read().block.gas_limit.to_ethers() + pub fn gas_limit(&self) -> u128 { + self.env.read().block.gas_limit.to() } /// Sets the block gas limit - pub fn set_gas_limit(&self, gas_limit: U256) { - self.env.write().block.gas_limit = gas_limit.to_alloy(); + pub fn set_gas_limit(&self, gas_limit: u128) { + self.env.write().block.gas_limit = U256::from(gas_limit); } /// Returns the current base fee - pub fn base_fee(&self) -> U256 { + pub fn base_fee(&self) -> u128 { self.fees.base_fee() } + pub fn excess_blob_gas_and_price(&self) -> Option { + self.fees.excess_blob_gas_and_price() + } + /// Sets the current basefee - pub fn set_base_fee(&self, basefee: U256) { + pub fn set_base_fee(&self, basefee: u128) { self.fees.set_base_fee(basefee) } /// Returns the current gas price - pub fn gas_price(&self) -> U256 { + pub fn gas_price(&self) -> u128 { self.fees.gas_price() } /// Returns the suggested fee cap - pub fn max_priority_fee_per_gas(&self) -> U256 { + pub fn max_priority_fee_per_gas(&self) -> u128 { self.fees.max_priority_fee_per_gas() } /// Sets the gas price - pub fn set_gas_price(&self, price: U256) { + pub fn set_gas_price(&self, price: u128) { self.fees.set_gas_price(price) } @@ -634,7 +682,7 @@ impl Backend { /// /// Returns the id of the snapshot created pub async fn create_snapshot(&self) -> U256 { - let num = self.best_number().as_u64(); + let num = self.best_number(); let hash = self.best_hash(); let id = self.db.write().await.snapshot(); trace!(target: "backend", "creating snapshot {} at {}", id, num); @@ -648,12 +696,12 @@ impl Backend { if let Some((num, hash)) = block { let best_block_hash = { // revert the storage that's newer than the snapshot - let current_height = self.best_number().as_u64(); + let current_height = self.best_number(); let mut storage = self.blockchain.storage.write(); for n in ((num + 1)..=current_height).rev() { trace!(target: "backend", "reverting block {}", n); - let n: U64 = n.into(); + let n = U64::from(n); if let Some(hash) = storage.hashes.remove(&n) { if let Some(block) = storage.blocks.remove(&hash) { for tx in block.transactions { @@ -663,24 +711,24 @@ impl Backend { } } - storage.best_number = num.into(); + storage.best_number = U64::from(num); storage.best_hash = hash; hash }; let block = self.block_by_hash(best_block_hash).await?.ok_or(BlockchainError::BlockNotFound)?; - let reset_time = block.timestamp.as_u64(); + let reset_time = block.header.timestamp; self.time.reset(reset_time); let mut env = self.env.write(); env.block = BlockEnv { - number: rU256::from(num), - timestamp: block.timestamp.to_alloy(), - difficulty: block.difficulty.to_alloy(), + number: U256::from(num), + timestamp: U256::from(block.header.timestamp), + difficulty: block.header.difficulty, // ensures prevrandao is set - prevrandao: Some(block.mix_hash.unwrap_or_default()).map(|h| h.to_alloy()), - gas_limit: block.gas_limit.to_alloy(), + prevrandao: Some(block.header.mix_hash.unwrap_or_default()), + gas_limit: U256::from(block.header.gas_limit), // Keep previous `coinbase` and `basefee` value coinbase: env.block.coinbase, basefee: env.block.basefee, @@ -690,13 +738,15 @@ impl Backend { Ok(self.db.write().await.revert(id, RevertSnapshotAction::RevertRemove)) } - pub fn list_snapshots(&self) -> BTreeMap { + pub fn list_snapshots(&self) -> BTreeMap { self.active_snapshots.lock().clone().into_iter().collect() } /// Get the current state. pub async fn serialized_state(&self) -> Result { - let state = self.db.read().await.dump_state()?; + let at = self.env.read().block.clone(); + let best_number = self.blockchain.storage.read().best_number; + let state = self.db.read().await.dump_state(at, best_number)?; state.ok_or_else(|| { RpcError::invalid_params("Dumping state not supported with the current configuration") .into() @@ -713,8 +763,30 @@ impl Backend { Ok(encoder.finish().unwrap_or_default().into()) } + /// Apply [SerializableState] data to the backend storage. + pub async fn load_state(&self, state: SerializableState) -> Result { + // reset the block env + if let Some(block) = state.block.clone() { + self.env.write().block = block.clone(); + + // Set the current best block number. + // Defaults to block number for compatibility with existing state files. + self.blockchain.storage.write().best_number = + state.best_block_number.unwrap_or(block.number.to::()); + } + + if !self.db.write().await.load_state(state)? { + Err(RpcError::invalid_params( + "Loading state not supported with the current configuration", + ) + .into()) + } else { + Ok(true) + } + } + /// Deserialize and add all chain data to the backend storage - pub async fn load_state(&self, buf: Bytes) -> Result { + pub async fn load_state_bytes(&self, buf: Bytes) -> Result { let orig_buf = &buf.0[..]; let mut decoder = GzDecoder::new(orig_buf); let mut decoded_data = Vec::new(); @@ -729,26 +801,37 @@ impl Backend { }) .map_err(|_| BlockchainError::FailedToDecodeStateDump)?; - if !self.db.write().await.load_state(state)? { - Err(RpcError::invalid_params( - "Loading state not supported with the current configuration", - ) - .into()) - } else { - Ok(true) - } + self.load_state(state).await } /// Returns the environment for the next block - fn next_env(&self) -> Env { + fn next_env(&self) -> EnvWithHandlerCfg { let mut env = self.env.read().clone(); // increase block number for this block - env.block.number = env.block.number.saturating_add(rU256::from(1)); - env.block.basefee = self.base_fee().to_alloy(); - env.block.timestamp = rU256::from(self.time.current_call_timestamp()); + env.block.number = env.block.number.saturating_add(U256::from(1)); + env.block.basefee = U256::from(self.base_fee()); + env.block.timestamp = U256::from(self.time.current_call_timestamp()); env } + /// Creates an EVM instance with optionally injected precompiles. + fn new_evm_with_inspector_ref( + &self, + db: DB, + env: EnvWithHandlerCfg, + inspector: I, + ) -> revm::Evm<'_, I, WrapDatabaseRef> + where + DB: revm::DatabaseRef, + I: InspectorExt>, + { + let mut evm = new_evm_with_inspector_ref(db, env, inspector); + if let Some(ref factory) = self.precompile_factory { + inject_precompiles(&mut evm, factory.precompiles()); + } + evm + } + /// executes the transactions without writing to the underlying database pub async fn inspect_tx( &self, @@ -759,31 +842,27 @@ impl Backend { > { let mut env = self.next_env(); env.tx = tx.pending_transaction.to_revm_tx_env(); + + if env.handler_cfg.is_optimism { + env.tx.optimism.enveloped_tx = + Some(alloy_rlp::encode(&tx.pending_transaction.transaction.transaction).into()); + } + let db = self.db.read().await; let mut inspector = Inspector::default(); - - let mut evm = revm::EVM::new(); - evm.env = env; - evm.database(&*db); - let result_and_state = match evm.inspect_ref(&mut inspector) { - Ok(res) => res, - Err(e) => return Err(e.into()), - }; - let state = result_and_state.state; - let state: revm::primitives::HashMap = - state.into_iter().map(|kv| (kv.0.to_ethers(), kv.1)).collect(); - let (exit_reason, gas_used, out, logs) = match result_and_state.result { + let mut evm = self.new_evm_with_inspector_ref(&*db, env, &mut inspector); + let ResultAndState { result, state } = evm.transact()?; + let (exit_reason, gas_used, out, logs) = match result { ExecutionResult::Success { reason, gas_used, logs, output, .. } => { - (eval_to_instruction_result(reason), gas_used, Some(output), Some(logs)) + (reason.into(), gas_used, Some(output), Some(logs)) } ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None) } - ExecutionResult::Halt { reason, gas_used } => { - (halt_to_instruction_result(reason), gas_used, None, None) - } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None, None), }; + drop(evm); inspector.print_logs(); Ok((exit_reason, out, gas_used, state, logs.unwrap_or_default())) @@ -805,7 +884,7 @@ impl Backend { f: F, ) -> T where - F: FnOnce(Box, BlockInfo) -> T, + F: FnOnce(Box, BlockInfo) -> T, { let db = self.db.read().await; let env = self.next_env(); @@ -814,15 +893,18 @@ impl Backend { let storage = self.blockchain.storage.read(); + let cfg_env = CfgEnvWithHandlerCfg::new(env.cfg.clone(), env.handler_cfg); let executor = TransactionExecutor { db: &mut cache_db, validator: self, pending: pool_transactions.into_iter(), block_env: env.block.clone(), - cfg_env: env.cfg, + cfg_env, parent_hash: storage.best_hash, - gas_used: U256::zero(), + gas_used: 0, + blob_gas_used: 0, enable_steps_tracing: self.enable_steps_tracing, + precompile_factory: self.precompile_factory.clone(), }; // create a new pending block @@ -849,19 +931,21 @@ impl Backend { let (outcome, header, block_hash) = { let current_base_fee = self.base_fee(); + let current_excess_blob_gas_and_price = self.excess_blob_gas_and_price(); let mut env = self.env.read().clone(); - if env.block.basefee == revm::primitives::U256::ZERO { + if env.block.basefee.is_zero() { // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee` // 0 is only possible if it's manually set env.cfg.disable_base_fee = true; } // increase block number for this block - env.block.number = env.block.number.saturating_add(rU256::from(1)); - env.block.basefee = current_base_fee.to_alloy(); - env.block.timestamp = rU256::from(self.time.next_timestamp()); + env.block.number = env.block.number.saturating_add(U256::from(1)); + env.block.basefee = U256::from(current_base_fee); + env.block.blob_excess_gas_and_price = current_excess_blob_gas_and_price; + env.block.timestamp = U256::from(self.time.next_timestamp()); let best_hash = self.blockchain.storage.read().best_hash; @@ -878,16 +962,18 @@ impl Backend { validator: self, pending: pool_transactions.into_iter(), block_env: env.block.clone(), - cfg_env: env.cfg.clone(), + cfg_env: CfgEnvWithHandlerCfg::new(env.cfg.clone(), env.handler_cfg), parent_hash: best_hash, - gas_used: U256::zero(), + gas_used: 0, + blob_gas_used: 0, enable_steps_tracing: self.enable_steps_tracing, + precompile_factory: self.precompile_factory.clone(), }; let executed_tx = executor.execute(); // we also need to update the new blockhash in the db itself - let block_hash = executed_tx.block.block.header.hash(); - db.insert_block_hash(executed_tx.block.block.header.number, block_hash); + let block_hash = executed_tx.block.block.header.hash_slow(); + db.insert_block_hash(U256::from(executed_tx.block.block.header.number), block_hash); (executed_tx, block_hash) }; @@ -896,8 +982,9 @@ impl Backend { let ExecutedTransactions { block, included, invalid } = executed_tx; let BlockInfo { block, transactions, receipts } = block; + let mut storage = self.blockchain.storage.write(); let header = block.header.clone(); - let block_number: U64 = (env.block.number.to_ethers()).as_u64().into(); + let block_number = storage.best_number.saturating_add(U64::from(1)); trace!( target: "backend", @@ -907,7 +994,6 @@ impl Backend { transactions.iter().map(|tx| tx.transaction_hash).collect::>() ); - let mut storage = self.blockchain.storage.write(); // update block metadata storage.best_number = block_number; storage.best_hash = block_hash; @@ -929,11 +1015,10 @@ impl Backend { if let Some(contract) = &info.contract_address { node_info!(" Contract created: {contract:?}"); } - node_info!(" Gas used: {}", receipt.gas_used()); + node_info!(" Gas used: {}", receipt.cumulative_gas_used()); if !info.exit.is_ok() { - let r = decode_revert( - info.out.as_deref().unwrap_or_default(), - None, + let r = RevertDecoder::new().decode( + info.out.as_ref().map(|b| &b[..]).unwrap_or_default(), Some(info.exit), ); node_info!(" Error: reverted with: {r}"); @@ -944,7 +1029,7 @@ impl Backend { info, receipt, block_hash, - block_number: block_number.as_u64(), + block_number: block_number.to::(), }; storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx); } @@ -953,14 +1038,14 @@ impl Backend { if let Some(transaction_block_keeper) = self.transaction_block_keeper { if storage.blocks.len() > transaction_block_keeper { let to_clear = block_number - .as_u64() + .to::() .saturating_sub(transaction_block_keeper.try_into().unwrap()); storage.remove_block_transactions_by_number(to_clear) } } // we intentionally set the difficulty to `0` for newer blocks - env.block.difficulty = rU256::from(0); + env.block.difficulty = U256::from(0); // update env with new values *self.env.write() = env; @@ -980,34 +1065,40 @@ impl Backend { header.gas_limit, header.base_fee_per_gas.unwrap_or_default(), ); + let next_block_excess_blob_gas = self.fees.get_next_block_blob_excess_gas( + header.excess_blob_gas.unwrap_or_default(), + header.blob_gas_used.unwrap_or_default(), + ); + + // update next base fee + self.fees.set_base_fee(next_block_base_fee); + self.fees + .set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new(next_block_excess_blob_gas)); // notify all listeners self.notify_on_new_block(header, block_hash); - // update next base fee - self.fees.set_base_fee(next_block_base_fee.into()); - outcome } - /// Executes the `EthTransactionRequest` without writing to the DB + /// Executes the [TransactionRequest] without writing to the DB /// /// # Errors /// /// Returns an error if the `block_number` is greater than the current height pub async fn call( &self, - request: EthTransactionRequest, + request: WithOtherFields, fee_details: FeeDetails, block_request: Option, overrides: Option, - ) -> Result<(InstructionResult, Option, u64, State), BlockchainError> { + ) -> Result<(InstructionResult, Option, u128, State), BlockchainError> { self.with_database_at(block_request, |state, block| { - let block_number = (block.number.to_ethers()).as_u64(); + let block_number = block.number.to::(); let (exit, out, gas, state) = match overrides { None => self.call_with_state(state, request, fee_details, block), Some(overrides) => { - let state = state::apply_state_override(overrides, state)?; + let state = state::apply_state_override(overrides.into_iter().collect(), state)?; self.call_with_state(state, request, fee_details, block) }, }?; @@ -1018,15 +1109,23 @@ impl Backend { fn build_call_env( &self, - request: EthTransactionRequest, + request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, - ) -> Env { - let EthTransactionRequest { from, to, gas, value, data, nonce, access_list, .. } = request; - - let FeeDetails { gas_price, max_fee_per_gas, max_priority_fee_per_gas } = fee_details; - - let gas_limit = gas.unwrap_or(block_env.gas_limit.to_ethers()); + ) -> EnvWithHandlerCfg { + let WithOtherFields:: { + inner: TransactionRequest { from, to, gas, value, input, nonce, access_list, .. }, + .. + } = request; + + let FeeDetails { + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + max_fee_per_blob_gas, + } = fee_details; + + let gas_limit = gas.unwrap_or(block_env.gas_limit.to()); let mut env = self.env.read().clone(); env.block = block_env; // we want to disable this in eth_call, since this is common practice used by other node @@ -1034,26 +1133,28 @@ impl Backend { env.cfg.disable_block_gas_limit = true; if let Some(base) = max_fee_per_gas { - env.block.basefee = base.to_alloy(); + env.block.basefee = U256::from(base); } let gas_price = gas_price.or(max_fee_per_gas).unwrap_or_else(|| self.gas_price()); let caller = from.unwrap_or_default(); - + let to = to.as_ref().and_then(TxKind::to); env.tx = TxEnv { - caller: caller.to_alloy(), - gas_limit: gas_limit.as_u64(), - gas_price: gas_price.to_alloy(), - gas_priority_fee: max_priority_fee_per_gas.map(|f| f.to_alloy()), + caller, + gas_limit: gas_limit as u64, + gas_price: U256::from(gas_price), + gas_priority_fee: max_priority_fee_per_gas.map(U256::from), + max_fee_per_blob_gas: max_fee_per_blob_gas.map(U256::from), transact_to: match to { - Some(addr) => TransactTo::Call(addr.to_alloy()), + Some(addr) => TransactTo::Call(*addr), None => TransactTo::Create(CreateScheme::Create), }, - value: value.unwrap_or_default().to_alloy(), - data: data.unwrap_or_default().to_vec().into(), + value: value.unwrap_or_default(), + data: input.into_input().unwrap_or_default(), chain_id: None, - nonce: nonce.map(|n| n.as_u64()), - access_list: to_revm_access_list(access_list.unwrap_or_default()), + nonce, + access_list: access_list.unwrap_or_default().flattened(), + optimism: OptimismFields { enveloped_tx: Some(Bytes::new()), ..Default::default() }, ..Default::default() }; @@ -1069,83 +1170,62 @@ impl Backend { pub fn call_with_state( &self, state: D, - request: EthTransactionRequest, + request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, - ) -> Result<(InstructionResult, Option, u64, State), BlockchainError> + ) -> Result<(InstructionResult, Option, u128, State), BlockchainError> where D: DatabaseRef, { let mut inspector = Inspector::default(); - let mut evm = revm::EVM::new(); - evm.env = self.build_call_env(request, fee_details, block_env); - evm.database(state); - let result_and_state = match evm.inspect_ref(&mut inspector) { - Ok(result_and_state) => result_and_state, - Err(e) => match e { - EVMError::Transaction(invalid_tx) => { - return Err(BlockchainError::InvalidTransaction(invalid_tx.into())) - } - EVMError::Database(e) => return Err(BlockchainError::DatabaseError(e)), - EVMError::Header(e) => match e { - InvalidHeader::ExcessBlobGasNotSet => { - return Err(BlockchainError::ExcessBlobGasNotSet) - } - InvalidHeader::PrevrandaoNotSet => { - return Err(BlockchainError::PrevrandaoNotSet) - } - }, - }, - }; - let state = result_and_state.state; - let state: revm::primitives::HashMap = - state.into_iter().map(|kv| (kv.0.to_ethers(), kv.1)).collect(); - let (exit_reason, gas_used, out) = match result_and_state.result { + + let env = self.build_call_env(request, fee_details, block_env); + let mut evm = self.new_evm_with_inspector_ref(state, env, &mut inspector); + let ResultAndState { result, state } = evm.transact()?; + let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { - (eval_to_instruction_result(reason), gas_used, Some(output)) + (reason.into(), gas_used, Some(output)) } ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output))) } - ExecutionResult::Halt { reason, gas_used } => { - (halt_to_instruction_result(reason), gas_used, None) - } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None), }; + drop(evm); inspector.print_logs(); - Ok((exit_reason, out, gas_used, state)) + Ok((exit_reason, out, gas_used as u128, state)) } pub async fn call_with_tracing( &self, - request: EthTransactionRequest, + request: WithOtherFields, fee_details: FeeDetails, block_request: Option, - opts: GethDebugTracingOptions, + opts: GethDefaultTracingOptions, ) -> Result { self.with_database_at(block_request, |state, block| { let mut inspector = Inspector::default().with_steps_tracing(); let block_number = block.number; - let mut evm = revm::EVM::new(); - evm.env = self.build_call_env(request, fee_details, block); - evm.database(state); - let result_and_state = - match evm.inspect_ref(&mut inspector) { - Ok(result_and_state) => result_and_state, - Err(e) => return Err(e.into()), - }; - let (exit_reason, gas_used, out, ) = match result_and_state.result { + + let env = self.build_call_env(request, fee_details, block); + let mut evm = self.new_evm_with_inspector_ref(state, env, &mut inspector); + let ResultAndState { result, state: _ } = evm.transact()?; + + let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { - (eval_to_instruction_result(reason), gas_used, Some(output), ) - }, - ExecutionResult::Revert { gas_used, output} => { + (reason.into(), gas_used, Some(output)) + } + ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output))) - }, - ExecutionResult::Halt { reason, gas_used } => { - (halt_to_instruction_result(reason), gas_used, None) - }, + } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None), }; - let res = inspector.tracer.unwrap_or_default().traces.geth_trace(rU256::from(gas_used), opts); - trace!(target: "backend", "trace call return {:?} out: {:?} gas {} on block {}", exit_reason, out, gas_used, block_number); + + drop(evm); + let tracer = inspector.tracer.expect("tracer disappeared"); + let return_value = out.as_ref().map(|o| o.data().clone()).unwrap_or_default(); + let res = tracer.into_geth_builder().geth_traces(gas_used, return_value, opts); + trace!(target: "backend", ?exit_reason, ?out, %gas_used, %block_number, "trace call"); Ok(res) }) .await? @@ -1154,47 +1234,42 @@ impl Backend { pub fn build_access_list_with_state( &self, state: D, - request: EthTransactionRequest, + request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, ) -> Result<(InstructionResult, Option, u64, AccessList), BlockchainError> where D: DatabaseRef, { - let from = request.from.unwrap_or_default().to_alloy(); - let to = if let Some(to) = request.to { - to.to_alloy() + let from = request.from.unwrap_or_default(); + let to = if let Some(TxKind::Call(to)) = request.to { + to } else { let nonce = state.basic_ref(from)?.unwrap_or_default().nonce; from.create(nonce) }; - let mut tracer = AccessListTracer::new( - AccessList(request.access_list.clone().unwrap_or_default()), + let mut inspector = AccessListInspector::new( + request.access_list.clone().unwrap_or_default(), from, to, - self.precompiles().into_iter().map(|p| p.to_alloy()).collect(), + self.precompiles(), ); - let mut evm = revm::EVM::new(); - evm.env = self.build_call_env(request, fee_details, block_env); - evm.database(state); - let result_and_state = match evm.inspect_ref(&mut tracer) { - Ok(result_and_state) => result_and_state, - Err(e) => return Err(e.into()), - }; - let (exit_reason, gas_used, out) = match result_and_state.result { + let env = self.build_call_env(request, fee_details, block_env); + let mut evm = self.new_evm_with_inspector_ref(state, env, &mut inspector); + let ResultAndState { result, state: _ } = evm.transact()?; + let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { - (eval_to_instruction_result(reason), gas_used, Some(output)) + (reason.into(), gas_used, Some(output)) } ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output))) } - ExecutionResult::Halt { reason, gas_used } => { - (halt_to_instruction_result(reason), gas_used, None) - } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None), }; - let access_list = tracer.access_list(); + drop(evm); + let access_list = inspector.access_list(); Ok((exit_reason, out, gas_used, access_list)) } @@ -1216,14 +1291,14 @@ impl Backend { async fn logs_for_block( &self, filter: Filter, - hash: H256, + hash: B256, ) -> Result, BlockchainError> { if let Some(block) = self.blockchain.get_block_by_hash(&hash) { - return Ok(self.mined_logs_for_block(filter, block)) + return Ok(self.mined_logs_for_block(filter, block)); } if let Some(fork) = self.get_fork() { - return Ok(fork.logs(&filter).await?) + return Ok(fork.logs(&filter).await?); } Ok(Vec::new()) @@ -1233,56 +1308,43 @@ impl Backend { fn mined_logs_for_block(&self, filter: Filter, block: Block) -> Vec { let params = FilteredParams::new(Some(filter.clone())); let mut all_logs = Vec::new(); - let block_hash = block.header.hash(); + let block_hash = block.header.hash_slow(); let mut block_log_index = 0u32; - let transactions: Vec<_> = { - let storage = self.blockchain.storage.read(); - block - .transactions - .iter() - .filter_map(|tx| storage.transactions.get(&tx.hash()).map(|tx| tx.info.clone())) - .collect() - }; + let storage = self.blockchain.storage.read(); - for transaction in transactions { - let logs = transaction.logs.clone(); - let transaction_hash = transaction.transaction_hash; - - for (log_idx, log) in logs.into_iter().enumerate() { - let mut log = Log { - address: log.address, - topics: log.topics, - data: log.data, - block_hash: None, - block_number: None, - transaction_hash: None, - transaction_index: None, - log_index: None, - transaction_log_index: None, - log_type: None, - removed: Some(false), - }; + for tx in block.transactions { + let Some(tx) = storage.transactions.get(&tx.hash()) else { + continue; + }; + let logs = tx.receipt.logs(); + let transaction_hash = tx.info.transaction_hash; + + for log in logs { let mut is_match: bool = true; - if filter.address.is_some() && filter.has_topics() { - if !params.filter_address(&log) || !params.filter_topics(&log) { + if !filter.address.is_empty() && filter.has_topics() { + if !params.filter_address(&log.address) || !params.filter_topics(log.topics()) { is_match = false; } - } else if filter.address.is_some() { - if !params.filter_address(&log) { + } else if !filter.address.is_empty() { + if !params.filter_address(&log.address) { is_match = false; } - } else if filter.has_topics() && !params.filter_topics(&log) { + } else if filter.has_topics() && !params.filter_topics(log.topics()) { is_match = false; } if is_match { - log.block_hash = Some(block_hash); - log.block_number = Some(block.header.number.as_u64().into()); - log.transaction_hash = Some(transaction_hash); - log.transaction_index = Some(transaction.transaction_index.into()); - log.log_index = Some(U256::from(block_log_index)); - log.transaction_log_index = Some(U256::from(log_idx)); + let log = Log { + inner: log.clone(), + block_hash: Some(block_hash), + block_number: Some(block.header.number), + block_timestamp: Some(block.header.timestamp), + transaction_hash: Some(transaction_hash), + transaction_index: Some(tx.info.transaction_index), + log_index: Some(block_log_index as u64), + removed: false, + }; all_logs.push(log); } block_log_index += 1; @@ -1335,31 +1397,28 @@ impl Backend { if let Some(hash) = filter.get_block_hash() { self.logs_for_block(filter, hash).await } else { - let best = self.best_number().as_u64(); + let best = self.best_number(); let to_block = self.convert_block_number(filter.block_option.get_to_block().copied()).min(best); let from_block = self.convert_block_number(filter.block_option.get_from_block().copied()); if from_block > best { // requested log range does not exist yet - return Ok(vec![]) + return Ok(vec![]); } self.logs_for_range(&filter, from_block, to_block).await } } - pub async fn block_by_hash( - &self, - hash: H256, - ) -> Result>, BlockchainError> { + pub async fn block_by_hash(&self, hash: B256) -> Result, BlockchainError> { trace!(target: "backend", "get block by hash {:?}", hash); if let tx @ Some(_) = self.mined_block_by_hash(hash) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { - return Ok(fork.block_by_hash(hash).await?) + return Ok(fork.block_by_hash(hash).await?); } Ok(None) @@ -1367,11 +1426,11 @@ impl Backend { pub async fn block_by_hash_full( &self, - hash: H256, - ) -> Result>, BlockchainError> { + hash: B256, + ) -> Result, BlockchainError> { trace!(target: "backend", "get block by hash {:?}", hash); if let tx @ Some(_) = self.get_full_block(hash) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { @@ -1381,7 +1440,7 @@ impl Backend { Ok(None) } - fn mined_block_by_hash(&self, hash: H256) -> Option> { + fn mined_block_by_hash(&self, hash: B256) -> Option { let block = self.blockchain.get_block_by_hash(&hash)?; Some(self.convert_block(block)) } @@ -1389,15 +1448,18 @@ impl Backend { pub(crate) async fn mined_transactions_by_block_number( &self, number: BlockNumber, - ) -> Option> { + ) -> Option>> { if let Some(block) = self.get_block(number) { - return self.mined_transactions_in_block(&block) + return self.mined_transactions_in_block(&block); } None } /// Returns all transactions given a block - pub(crate) fn mined_transactions_in_block(&self, block: &Block) -> Option> { + pub(crate) fn mined_transactions_in_block( + &self, + block: &Block, + ) -> Option>> { let mut transactions = Vec::with_capacity(block.transactions.len()); let base_fee = block.header.base_fee_per_gas; let storage = self.blockchain.storage.read(); @@ -1414,10 +1476,10 @@ impl Backend { pub async fn block_by_number( &self, number: BlockNumber, - ) -> Result>, BlockchainError> { + ) -> Result, BlockchainError> { trace!(target: "backend", "get block by number {:?}", number); if let tx @ Some(_) = self.mined_block_by_number(number) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { @@ -1433,10 +1495,10 @@ impl Backend { pub async fn block_by_number_full( &self, number: BlockNumber, - ) -> Result>, BlockchainError> { + ) -> Result, BlockchainError> { trace!(target: "backend", "get block by number {:?}", number); if let tx @ Some(_) = self.get_full_block(number) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { @@ -1451,15 +1513,15 @@ impl Backend { pub fn get_block(&self, id: impl Into) -> Option { let hash = match id.into() { - BlockId::Hash(hash) => hash, + BlockId::Hash(hash) => hash.block_hash, BlockId::Number(number) => { let storage = self.blockchain.storage.read(); - let slots_in_an_epoch = U64::from(32u64); + let slots_in_an_epoch = U64::from(self.slots_in_an_epoch); match number { BlockNumber::Latest => storage.best_hash, BlockNumber::Earliest => storage.genesis_hash, BlockNumber::Pending => return None, - BlockNumber::Number(num) => *storage.hashes.get(&num)?, + BlockNumber::Number(num) => *storage.hashes.get(&U64::from(num))?, BlockNumber::Safe => { if storage.best_number > (slots_in_an_epoch) { *storage.hashes.get(&(storage.best_number - (slots_in_an_epoch)))? @@ -1468,8 +1530,10 @@ impl Backend { } } BlockNumber::Finalized => { - if storage.best_number > (slots_in_an_epoch * 2) { - *storage.hashes.get(&(storage.best_number - (slots_in_an_epoch * 2)))? + if storage.best_number > (slots_in_an_epoch * U64::from(2)) { + *storage + .hashes + .get(&(storage.best_number - (slots_in_an_epoch * U64::from(2))))? } else { storage.genesis_hash } @@ -1480,28 +1544,31 @@ impl Backend { self.get_block_by_hash(hash) } - pub fn get_block_by_hash(&self, hash: H256) -> Option { + pub fn get_block_by_hash(&self, hash: B256) -> Option { self.blockchain.get_block_by_hash(&hash) } - pub fn mined_block_by_number(&self, number: BlockNumber) -> Option> { - Some(self.convert_block(self.get_block(number)?)) + pub fn mined_block_by_number(&self, number: BlockNumber) -> Option { + let block = self.get_block(number)?; + let mut block = self.convert_block(block); + block.transactions.convert_to_hashes(); + Some(block) } - pub fn get_full_block(&self, id: impl Into) -> Option> { + pub fn get_full_block(&self, id: impl Into) -> Option { let block = self.get_block(id)?; let transactions = self.mined_transactions_in_block(&block)?; let block = self.convert_block(block); - Some(block.into_full_block(transactions)) + Some(block.into_full_block(transactions.into_iter().map(|t| t.inner).collect())) } /// Takes a block as it's stored internally and returns the eth api conform block format - pub fn convert_block(&self, block: Block) -> EthersBlock { - let size = U256::from(rlp::encode(&block).len() as u32); + pub fn convert_block(&self, block: Block) -> AlloyBlock { + let size = U256::from(alloy_rlp::encode(&block).len() as u32); let Block { header, transactions, .. } = block; - let hash = header.hash(); + let hash = header.hash_slow(); let Header { parent_hash, ommers_hash, @@ -1519,33 +1586,44 @@ impl Backend { mix_hash, nonce, base_fee_per_gas, + withdrawals_root: _, + blob_gas_used, + excess_blob_gas, + parent_beacon_block_root, } = header; - EthersBlock { - hash: Some(hash), - parent_hash, - uncles_hash: ommers_hash, - author: Some(beneficiary), - state_root, - transactions_root, - receipts_root, - number: Some(number.as_u64().into()), - gas_used, - gas_limit, - extra_data, - logs_bloom: Some(logs_bloom), - timestamp: timestamp.into(), - difficulty, - total_difficulty: Some(self.total_difficulty()), - seal_fields: { vec![mix_hash.as_bytes().to_vec().into(), nonce.0.to_vec().into()] }, - uncles: vec![], - transactions: transactions.into_iter().map(|tx| tx.hash()).collect(), + AlloyBlock { + header: AlloyHeader { + hash: Some(hash), + parent_hash, + uncles_hash: ommers_hash, + miner: beneficiary, + state_root, + transactions_root, + receipts_root, + number: Some(number), + gas_used, + gas_limit, + extra_data: extra_data.0.into(), + logs_bloom, + timestamp, + total_difficulty: Some(self.total_difficulty()), + difficulty, + mix_hash: Some(mix_hash), + nonce: Some(nonce), + base_fee_per_gas, + withdrawals_root: None, + blob_gas_used, + excess_blob_gas, + parent_beacon_block_root, + }, size: Some(size), - mix_hash: Some(mix_hash), - nonce: Some(nonce), - base_fee_per_gas, + transactions: alloy_rpc_types::BlockTransactions::Hashes( + transactions.into_iter().map(|tx| tx.hash()).collect(), + ), + uncles: vec![], + withdrawals: None, other: Default::default(), - ..Default::default() } } @@ -1558,23 +1636,22 @@ impl Backend { &self, block_id: Option, ) -> Result { - let current = self.best_number().as_u64(); - let slots_in_an_epoch = 32u64; + let current = self.best_number(); let requested = match block_id.map(Into::into).unwrap_or(BlockId::Number(BlockNumber::Latest)) { BlockId::Hash(hash) => self - .block_by_hash(hash) + .block_by_hash(hash.block_hash) .await? .ok_or(BlockchainError::BlockNotFound)? + .header .number - .ok_or(BlockchainError::BlockNotFound)? - .as_u64(), + .ok_or(BlockchainError::BlockNotFound)?, BlockId::Number(num) => match num { - BlockNumber::Latest | BlockNumber::Pending => self.best_number().as_u64(), - BlockNumber::Earliest => 0, - BlockNumber::Number(num) => num.as_u64(), - BlockNumber::Safe => current.saturating_sub(slots_in_an_epoch), - BlockNumber::Finalized => current.saturating_sub(slots_in_an_epoch * 2), + BlockNumber::Latest | BlockNumber::Pending => self.best_number(), + BlockNumber::Earliest => U64::ZERO.to::(), + BlockNumber::Number(num) => num, + BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch), + BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2), }, }; @@ -1586,14 +1663,13 @@ impl Backend { } pub fn convert_block_number(&self, block: Option) -> u64 { - let current = self.best_number().as_u64(); - let slots_in_an_epoch = 32u64; + let current = self.best_number(); match block.unwrap_or(BlockNumber::Latest) { BlockNumber::Latest | BlockNumber::Pending => current, BlockNumber::Earliest => 0, - BlockNumber::Number(num) => num.as_u64(), - BlockNumber::Safe => current.saturating_sub(slots_in_an_epoch), - BlockNumber::Finalized => current.saturating_sub(slots_in_an_epoch * 2), + BlockNumber::Number(num) => num, + BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch), + BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2), } } @@ -1604,7 +1680,7 @@ impl Backend { f: F, ) -> Result where - F: FnOnce(Box, BlockEnv) -> T, + F: FnOnce(Box, BlockEnv) -> T, { let block_number = match block_request { Some(BlockRequest::Pending(pool_transactions)) => { @@ -1612,44 +1688,44 @@ impl Backend { .with_pending_block(pool_transactions, |state, block| { let block = block.block; let block = BlockEnv { - number: block.header.number.to_alloy(), - coinbase: block.header.beneficiary.to_alloy(), - timestamp: rU256::from(block.header.timestamp), - difficulty: block.header.difficulty.to_alloy(), - prevrandao: Some(block.header.mix_hash).map(|h| h.to_alloy()), - basefee: block.header.base_fee_per_gas.unwrap_or_default().to_alloy(), - gas_limit: block.header.gas_limit.to_alloy(), + number: U256::from(block.header.number), + coinbase: block.header.beneficiary, + timestamp: U256::from(block.header.timestamp), + difficulty: block.header.difficulty, + prevrandao: Some(block.header.mix_hash), + basefee: U256::from(block.header.base_fee_per_gas.unwrap_or_default()), + gas_limit: U256::from(block.header.gas_limit), ..Default::default() }; f(state, block) }) .await; - return Ok(result) + return Ok(result); } Some(BlockRequest::Number(bn)) => Some(BlockNumber::Number(bn)), None => None, }; - let block_number: U256 = self.convert_block_number(block_number).into(); + let block_number: U256 = U256::from(self.convert_block_number(block_number)); - if block_number.to_alloy() < self.env.read().block.number { + if block_number < self.env.read().block.number { { let mut states = self.states.write(); if let Some((state, block)) = self - .get_block(block_number.as_u64()) - .and_then(|block| Some((states.get(&block.header.hash())?, block))) + .get_block(block_number.to::()) + .and_then(|block| Some((states.get(&block.header.hash_slow())?, block))) { let block = BlockEnv { - number: block.header.number.to_alloy(), - coinbase: block.header.beneficiary.to_alloy(), - timestamp: rU256::from(block.header.timestamp), - difficulty: block.header.difficulty.to_alloy(), - prevrandao: Some(block.header.mix_hash).map(|h| h.to_alloy()), - basefee: block.header.base_fee_per_gas.unwrap_or_default().to_alloy(), - gas_limit: block.header.gas_limit.to_alloy(), + number: U256::from(block.header.number), + coinbase: block.header.beneficiary, + timestamp: U256::from(block.header.timestamp), + difficulty: block.header.difficulty, + prevrandao: Some(block.header.mix_hash), + basefee: U256::from(block.header.base_fee_per_gas.unwrap_or_default()), + gas_limit: U256::from(block.header.gas_limit), ..Default::default() }; - return Ok(f(Box::new(state), block)) + return Ok(f(Box::new(state), block)); } } @@ -1664,19 +1740,19 @@ impl Backend { let db = self.db.read().await; let gen_db = self.genesis.state_db_at_genesis(Box::new(&*db)); - block.number = block_number.to_alloy(); - block.timestamp = rU256::from(fork.timestamp()); - block.basefee = fork.base_fee().unwrap_or_default().to_alloy(); + block.number = block_number; + block.timestamp = U256::from(fork.timestamp()); + block.basefee = U256::from(fork.base_fee().unwrap_or_default()); - return Ok(f(Box::new(&gen_db), block)) + return Ok(f(Box::new(&gen_db), block)); } } warn!(target: "backend", "Not historic state found for block={}", block_number); return Err(BlockchainError::BlockOutOfRange( - self.env.read().block.number.to_ethers().as_u64(), - block_number.as_u64(), - )) + self.env.read().block.number.to::(), + block_number.to::(), + )); } let db = self.db.read().await; @@ -1689,11 +1765,11 @@ impl Backend { address: Address, index: U256, block_request: Option, - ) -> Result { + ) -> Result { self.with_database_at(block_request, |db, _| { trace!(target: "backend", "get storage for {:?} at {:?}", address, index); - let val = db.storage_ref(address.to_alloy(), index.to_alloy())?; - Ok(u256_to_h256_be(val.to_ethers())) + let val = db.storage_ref(address, index)?; + Ok(val.into()) }) .await? } @@ -1719,10 +1795,10 @@ impl Backend { D: DatabaseRef, { trace!(target: "backend", "get code for {:?}", address); - let account = state.basic_ref(address.to_alloy())?.unwrap_or_default(); + let account = state.basic_ref(address)?.unwrap_or_default(); if account.code_hash == KECCAK_EMPTY { // if the code hash is `KECCAK_EMPTY`, we check no further - return Ok(Default::default()) + return Ok(Default::default()); } let code = if let Some(code) = account.code { code @@ -1753,7 +1829,7 @@ impl Backend { D: DatabaseRef, { trace!(target: "backend", "get balance for {:?}", address); - Ok(state.basic_ref(address.to_alloy())?.unwrap_or_default().balance.to_ethers()) + Ok(state.basic_ref(address)?.unwrap_or_default().balance) } /// Returns the nonce of the address @@ -1762,29 +1838,32 @@ impl Backend { pub async fn get_nonce( &self, address: Address, - block_request: Option, - ) -> Result { - if let Some(BlockRequest::Pending(pool_transactions)) = block_request.as_ref() { + block_request: BlockRequest, + ) -> Result { + if let BlockRequest::Pending(pool_transactions) = &block_request { if let Some(value) = get_pool_transactions_nonce(pool_transactions, address) { - return Ok(value) + return Ok(value); } } let final_block_request = match block_request { - Some(BlockRequest::Pending(_)) => Some(BlockRequest::Number(self.best_number())), - Some(BlockRequest::Number(bn)) => Some(BlockRequest::Number(bn)), - None => None, + BlockRequest::Pending(_) => BlockRequest::Number(self.best_number()), + BlockRequest::Number(bn) => BlockRequest::Number(bn), }; - self.with_database_at(final_block_request, |db, _| { + + self.with_database_at(Some(final_block_request), |db, _| { trace!(target: "backend", "get nonce for {:?}", address); - Ok(db.basic_ref(address.to_alloy())?.unwrap_or_default().nonce.into()) + Ok(db.basic_ref(address)?.unwrap_or_default().nonce) }) .await? } /// Returns the traces for the given transaction - pub async fn trace_transaction(&self, hash: H256) -> Result, BlockchainError> { + pub async fn trace_transaction( + &self, + hash: B256, + ) -> Result, BlockchainError> { if let Some(traces) = self.mined_parity_trace_transaction(hash) { - return Ok(traces) + return Ok(traces); } if let Some(fork) = self.get_fork() { @@ -1795,17 +1874,23 @@ impl Backend { } /// Returns the traces for the given transaction - pub(crate) fn mined_parity_trace_transaction(&self, hash: H256) -> Option> { + pub(crate) fn mined_parity_trace_transaction( + &self, + hash: B256, + ) -> Option> { self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.parity_traces()) } /// Returns the traces for the given transaction - pub(crate) fn mined_transaction(&self, hash: H256) -> Option { + pub(crate) fn mined_transaction(&self, hash: B256) -> Option { self.blockchain.storage.read().transactions.get(&hash).cloned() } /// Returns the traces for the given block - pub(crate) fn mined_parity_trace_block(&self, block: u64) -> Option> { + pub(crate) fn mined_parity_trace_block( + &self, + block: u64, + ) -> Option> { let block = self.get_block(block)?; let mut traces = vec![]; let storage = self.blockchain.storage.read(); @@ -1818,33 +1903,36 @@ impl Backend { /// Returns the traces for the given transaction pub async fn debug_trace_transaction( &self, - hash: H256, + hash: B256, opts: GethDebugTracingOptions, ) -> Result { if let Some(traces) = self.mined_geth_trace_transaction(hash, opts.clone()) { - return Ok(GethTrace::Known(GethTraceFrame::Default(traces))) + return Ok(GethTrace::Default(traces)); } if let Some(fork) = self.get_fork() { return Ok(fork.debug_trace_transaction(hash, opts).await?) } - Ok(GethTrace::Known(GethTraceFrame::Default(Default::default()))) + Ok(GethTrace::Default(Default::default())) } fn mined_geth_trace_transaction( &self, - hash: H256, + hash: B256, opts: GethDebugTracingOptions, ) -> Option { - self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.geth_trace(opts)) + self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.geth_trace(opts.config)) } /// Returns the traces for the given block - pub async fn trace_block(&self, block: BlockNumber) -> Result, BlockchainError> { + pub async fn trace_block( + &self, + block: BlockNumber, + ) -> Result, BlockchainError> { let number = self.convert_block_number(Some(block)); if let Some(traces) = self.mined_parity_trace_block(number) { - return Ok(traces) + return Ok(traces); } if let Some(fork) = self.get_fork() { @@ -1858,20 +1946,20 @@ impl Backend { pub async fn transaction_receipt( &self, - hash: H256, - ) -> Result, BlockchainError> { + hash: B256, + ) -> Result, BlockchainError> { if let Some(receipt) = self.mined_transaction_receipt(hash) { - return Ok(Some(receipt.inner)) + return Ok(Some(receipt.inner)); } if let Some(fork) = self.get_fork() { let receipt = fork.transaction_receipt(hash).await?; let number = self.convert_block_number( - receipt.clone().and_then(|r| r.block_number).map(|n| BlockNumber::from(n.as_u64())), + receipt.clone().and_then(|r| r.block_number).map(BlockNumber::from), ); if fork.predates_fork_inclusive(number) { - return Ok(receipt) + return Ok(receipt); } } @@ -1879,118 +1967,146 @@ impl Backend { } /// Returns all receipts of the block - pub fn mined_receipts(&self, hash: H256) -> Option> { + pub fn mined_receipts(&self, hash: B256) -> Option> { let block = self.mined_block_by_hash(hash)?; let mut receipts = Vec::new(); let storage = self.blockchain.storage.read(); - for tx in block.transactions { - let receipt = storage.transactions.get(&tx)?.receipt.clone(); + for tx in block.transactions.hashes() { + let receipt = storage.transactions.get(tx)?.receipt.clone(); receipts.push(receipt); } Some(receipts) } - /// Returns the transaction receipt for the given hash - pub(crate) fn mined_transaction_receipt(&self, hash: H256) -> Option { - let MinedTransaction { info, receipt, block_hash, .. } = - self.blockchain.get_transaction_by_hash(&hash)?; - - let EIP658Receipt { status_code, gas_used, logs_bloom, logs } = receipt.into(); - - let index = info.transaction_index as usize; - - let block = self.blockchain.get_block_by_hash(&block_hash)?; - - // TODO store cumulative gas used in receipt instead - let receipts = self.get_receipts(block.transactions.iter().map(|tx| tx.hash())); + /// Returns all transaction receipts of the block + pub fn mined_block_receipts(&self, id: impl Into) -> Option> { + let mut receipts = Vec::new(); + let block = self.get_block(id)?; - let mut cumulative_gas_used = U256::zero(); - for receipt in receipts.iter().take(index + 1) { - cumulative_gas_used = cumulative_gas_used.saturating_add(receipt.gas_used()); + for transaction in block.transactions { + let receipt = self.mined_transaction_receipt(transaction.hash())?; + receipts.push(receipt.inner); } - // cumulative_gas_used = cumulative_gas_used.saturating_sub(gas_used); + Some(receipts) + } - let mut cumulative_receipts = receipts; - cumulative_receipts.truncate(index + 1); + /// Returns the transaction receipt for the given hash + pub(crate) fn mined_transaction_receipt(&self, hash: B256) -> Option { + let MinedTransaction { info, receipt: tx_receipt, block_hash, .. } = + self.blockchain.get_transaction_by_hash(&hash)?; + let index = info.transaction_index as usize; + let block = self.blockchain.get_block_by_hash(&block_hash)?; let transaction = block.transactions[index].clone(); - let transaction_type = transaction.transaction.r#type(); + // Cancun specific + let excess_blob_gas = block.header.excess_blob_gas; + let blob_gas_price = calc_blob_gasprice(excess_blob_gas.map_or(0, |g| g as u64)); + let blob_gas_used = transaction.blob_gas(); let effective_gas_price = match transaction.transaction { - TypedTransaction::Legacy(t) => t.gas_price, - TypedTransaction::EIP2930(t) => t.gas_price, + TypedTransaction::Legacy(t) => t.tx().gas_price, + TypedTransaction::EIP2930(t) => t.tx().gas_price, TypedTransaction::EIP1559(t) => block .header .base_fee_per_gas - .unwrap_or(self.base_fee()) - .checked_add(t.max_priority_fee_per_gas) - .unwrap_or_else(U256::max_value), - TypedTransaction::Deposit(_) => U256::from(0), + .unwrap_or_else(|| self.base_fee()) + .saturating_add(t.tx().max_priority_fee_per_gas), + TypedTransaction::EIP4844(t) => block + .header + .base_fee_per_gas + .unwrap_or_else(|| self.base_fee()) + .saturating_add(t.tx().tx().max_priority_fee_per_gas), + TypedTransaction::Deposit(_) => 0_u128, }; - let deposit_nonce = transaction_type.and_then(|x| (x == 0x7E).then_some(info.nonce)); + let receipts = self.get_receipts(block.transactions.iter().map(|tx| tx.hash())); + let next_log_index = receipts[..index].iter().map(|r| r.logs().len()).sum::(); + + let receipt = tx_receipt.as_receipt_with_bloom().receipt.clone(); + let receipt = Receipt { + status: receipt.status, + cumulative_gas_used: receipt.cumulative_gas_used, + logs: receipt + .logs + .into_iter() + .enumerate() + .map(|(index, log)| alloy_rpc_types::Log { + inner: log, + block_hash: Some(block_hash), + block_number: Some(block.header.number), + block_timestamp: Some(block.header.timestamp), + transaction_hash: Some(info.transaction_hash), + transaction_index: Some(info.transaction_index), + log_index: Some((next_log_index + index) as u64), + removed: false, + }) + .collect(), + }; + let receipt_with_bloom = + ReceiptWithBloom { receipt, logs_bloom: tx_receipt.as_receipt_with_bloom().logs_bloom }; + + let inner = match tx_receipt { + TypedReceipt::EIP1559(_) => TypedReceipt::EIP1559(receipt_with_bloom), + TypedReceipt::Legacy(_) => TypedReceipt::Legacy(receipt_with_bloom), + TypedReceipt::EIP2930(_) => TypedReceipt::EIP2930(receipt_with_bloom), + TypedReceipt::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom), + TypedReceipt::Deposit(r) => TypedReceipt::Deposit(DepositReceipt { + inner: receipt_with_bloom, + deposit_nonce: r.deposit_nonce, + deposit_nonce_version: r.deposit_nonce_version, + }), + }; let inner = TransactionReceipt { + inner, transaction_hash: info.transaction_hash, - transaction_index: info.transaction_index.into(), + transaction_index: Some(info.transaction_index), + block_number: Some(block.header.number), + gas_used: info.gas_used, + contract_address: info.contract_address, + effective_gas_price, block_hash: Some(block_hash), - block_number: Some(block.header.number.as_u64().into()), from: info.from, to: info.to, - cumulative_gas_used, - gas_used: Some(gas_used), - contract_address: info.contract_address, - logs: { - let mut pre_receipts_log_index = None; - if !cumulative_receipts.is_empty() { - cumulative_receipts.truncate(cumulative_receipts.len() - 1); - pre_receipts_log_index = - Some(cumulative_receipts.iter().map(|_r| logs.len() as u32).sum::()); - } - logs.iter() - .enumerate() - .map(|(i, log)| Log { - address: log.address, - topics: log.topics.clone(), - data: log.data.clone(), - block_hash: Some(block_hash), - block_number: Some(block.header.number.as_u64().into()), - transaction_hash: Some(info.transaction_hash), - transaction_index: Some(info.transaction_index.into()), - log_index: Some(U256::from( - (pre_receipts_log_index.unwrap_or(0)) + i as u32, - )), - transaction_log_index: Some(U256::from(i)), - log_type: None, - removed: Some(false), - }) - .collect() - }, - status: Some(status_code.into()), - root: None, - logs_bloom, - transaction_type: transaction_type.map(Into::into), - effective_gas_price: Some(effective_gas_price), - deposit_nonce, - l1_fee: None, - l1_fee_scalar: None, - l1_gas_price: None, - l1_gas_used: None, - other: OtherFields::default(), + state_root: Some(block.header.state_root), + blob_gas_price: Some(blob_gas_price), + blob_gas_used, }; - Some(MinedTransactionReceipt { inner, out: info.out }) + Some(MinedTransactionReceipt { inner, out: info.out.map(|o| o.0.into()) }) + } + + /// Returns the blocks receipts for the given number + pub async fn block_receipts( + &self, + number: BlockNumber, + ) -> Result>, BlockchainError> { + if let Some(receipts) = self.mined_block_receipts(number) { + return Ok(Some(receipts)); + } + + if let Some(fork) = self.get_fork() { + let number = self.convert_block_number(Some(number)); + + if fork.predates_fork_inclusive(number) { + let receipts = fork.block_receipts(number).await?; + + return Ok(receipts); + } + } + + Ok(None) } pub async fn transaction_by_block_number_and_index( &self, number: BlockNumber, index: Index, - ) -> Result, BlockchainError> { - if let Some(hash) = self.mined_block_by_number(number).and_then(|b| b.hash) { - return Ok(self.mined_transaction_by_block_hash_and_index(hash, index)) + ) -> Result>, BlockchainError> { + if let Some(hash) = self.mined_block_by_number(number).and_then(|b| b.header.hash) { + return Ok(self.mined_transaction_by_block_hash_and_index(hash, index)); } if let Some(fork) = self.get_fork() { @@ -2005,11 +2121,11 @@ impl Backend { pub async fn transaction_by_block_hash_and_index( &self, - hash: H256, + hash: B256, index: Index, - ) -> Result, BlockchainError> { + ) -> Result>, BlockchainError> { if let tx @ Some(_) = self.mined_transaction_by_block_hash_and_index(hash, index) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { @@ -2021,9 +2137,9 @@ impl Backend { fn mined_transaction_by_block_hash_and_index( &self, - block_hash: H256, + block_hash: B256, index: Index, - ) -> Option { + ) -> Option> { let (info, block, tx) = { let storage = self.blockchain.storage.read(); let block = storage.blocks.get(&block_hash).cloned()?; @@ -2044,21 +2160,21 @@ impl Backend { pub async fn transaction_by_hash( &self, - hash: H256, - ) -> Result, BlockchainError> { + hash: B256, + ) -> Result>, BlockchainError> { trace!(target: "backend", "transaction_by_hash={:?}", hash); if let tx @ Some(_) = self.mined_transaction_by_hash(hash) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { - return Ok(fork.transaction_by_hash(hash).await?) + return fork.transaction_by_hash(hash).await.map_err(BlockchainError::AlloyForkProvider) } Ok(None) } - fn mined_transaction_by_hash(&self, hash: H256) -> Option { + fn mined_transaction_by_hash(&self, hash: B256) -> Option> { let (info, block) = { let storage = self.blockchain.storage.read(); let MinedTransaction { info, block_hash, .. } = @@ -2083,77 +2199,44 @@ impl Backend { pub async fn prove_account_at( &self, address: Address, - keys: Vec, + keys: Vec, block_request: Option, ) -> Result { - let account_key = H256::from(keccak256(address.as_bytes())); let block_number = block_request.as_ref().map(|r| r.block_number()); self.with_database_at(block_request, |block_db, _| { trace!(target: "backend", "get proof for {:?} at {:?}", address, block_number); - let (db, root) = block_db.maybe_as_hash_db().ok_or(BlockchainError::DataUnavailable)?; + let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; + let account = db.get(&address).cloned().unwrap_or_default(); - let data: &dyn HashDB<_, _> = db.deref(); - let mut recorder = Recorder::new(); - let trie = RefTrieDB::new(&data, &root.0) - .map_err(|err| BlockchainError::TrieError(err.to_string()))?; + let mut builder = HashBuilder::default() + .with_proof_retainer(vec![Nibbles::unpack(keccak256(address))]); - let maybe_account: Option = { - let acc_decoder = |bytes: &[u8]| { - rlp::decode(bytes).unwrap_or_else(|_| { - panic!("prove_account_at, could not query trie for account={:?}", &address) - }) - }; - let query = (&mut recorder, acc_decoder); - trie.get_with(account_key.as_bytes(), query) - .map_err(|err| BlockchainError::TrieError(err.to_string()))? - }; - let account = maybe_account.unwrap_or_default(); + for (key, account) in trie_accounts(db) { + builder.add_leaf(key, &account); + } - let proof = recorder - .drain() - .into_iter() - .map(|r| r.data) - .map(|record| { - // proof is rlp encoded: - // - // - rlp::encode(&record).to_vec().into() - }) - .collect::>(); + let _ = builder.root(); - let account_db = - block_db.maybe_account_db(address).ok_or(BlockchainError::DataUnavailable)?; + let proof = builder.take_proofs().values().cloned().collect::>(); + let storage_proofs = prove_storage(&account.storage, &keys); let account_proof = AccountProof { address, - balance: account.balance, - nonce: account.nonce.as_u64().into(), - code_hash: account.code_hash, - storage_hash: account.storage_root, + balance: account.info.balance, + nonce: U64::from(account.info.nonce), + code_hash: account.info.code_hash, + storage_hash: storage_root(&account.storage), account_proof: proof, storage_proof: keys .into_iter() - .map(|storage_key| { - // the key that should be proofed is the keccak256 of the storage key - let key = H256::from(keccak256(storage_key)); - prove_storage(&account, &account_db.0, key).map( - |(storage_proof, storage_value)| StorageProof { - key: storage_key, - value: storage_value.into_uint(), - proof: storage_proof - .into_iter() - .map(|proof| { - // proof is rlp encoded: - // - // - rlp::encode(&proof).to_vec().into() - }) - .collect(), - }, - ) + .zip(storage_proofs) + .map(|(key, proof)| { + let storage_key: U256 = key.into(); + let value = account.storage.get(&storage_key).cloned().unwrap_or_default(); + StorageProof { key: JsonStorageKey(key), value, proof } }) - .collect::, _>>()?, + .collect(), }; Ok(account_proof) @@ -2170,7 +2253,7 @@ impl Backend { } /// Notifies all `new_block_listeners` about the new block - fn notify_on_new_block(&self, header: Header, hash: H256) { + fn notify_on_new_block(&self, header: Header, hash: B256) { // cleanup closed notification streams first, if the channel is closed we can remove the // sender half for the set self.new_block_listeners.lock().retain(|tx| !tx.is_closed()); @@ -2186,21 +2269,16 @@ impl Backend { /// Get max nonce from transaction pool by address fn get_pool_transactions_nonce( pool_transactions: &[Arc], - address: ethers::types::H160, -) -> Option { - let highest_nonce_tx = pool_transactions + address: Address, +) -> Option { + if let Some(highest_nonce) = pool_transactions .iter() .filter(|tx| *tx.pending_transaction.sender() == address) - .reduce(|accum, item| { - let nonce = item.pending_transaction.nonce(); - if nonce.gt(accum.pending_transaction.nonce()) { - item - } else { - accum - } - }); - if let Some(highest_nonce_tx) = highest_nonce_tx { - return Some(highest_nonce_tx.pending_transaction.nonce().saturating_add(U256::one())) + .map(|tx| tx.pending_transaction.nonce()) + .max() + { + let tx_count = highest_nonce.saturating_add(1); + return Some(tx_count) } None } @@ -2221,54 +2299,54 @@ impl TransactionValidator for Backend { &self, pending: &PendingTransaction, account: &AccountInfo, - env: &Env, + env: &EnvWithHandlerCfg, ) -> Result<(), InvalidTransactionError> { let tx = &pending.transaction; if let Some(tx_chain_id) = tx.chain_id() { let chain_id = self.chain_id(); - if chain_id != tx_chain_id { + if chain_id.to::() != tx_chain_id { if let Some(legacy) = tx.as_legacy() { // - if env.cfg.spec_id >= SpecId::SPURIOUS_DRAGON && !legacy.meets_eip155(chain_id) + if env.handler_cfg.spec_id >= SpecId::SPURIOUS_DRAGON && + !meets_eip155(chain_id.to::(), legacy.signature().v()) { warn!(target: "backend", ?chain_id, ?tx_chain_id, "incompatible EIP155-based V"); - return Err(InvalidTransactionError::IncompatibleEIP155) + return Err(InvalidTransactionError::IncompatibleEIP155); } } else { warn!(target: "backend", ?chain_id, ?tx_chain_id, "invalid chain id"); - return Err(InvalidTransactionError::InvalidChainId) + return Err(InvalidTransactionError::InvalidChainId); } } } if tx.gas_limit() < MIN_TRANSACTION_GAS { warn!(target: "backend", "[{:?}] gas too low", tx.hash()); - return Err(InvalidTransactionError::GasTooLow) + return Err(InvalidTransactionError::GasTooLow); } // Check gas limit, iff block gas limit is set. - if !env.cfg.disable_block_gas_limit && tx.gas_limit() > env.block.gas_limit.to_ethers() { + if !env.cfg.disable_block_gas_limit && tx.gas_limit() > env.block.gas_limit.to() { warn!(target: "backend", "[{:?}] gas too high", tx.hash()); return Err(InvalidTransactionError::GasTooHigh(ErrDetail { detail: String::from("tx.gas_limit > env.block.gas_limit"), - })) + })); } // check nonce let is_deposit_tx = matches!(&pending.transaction.transaction, TypedTransaction::Deposit(_)); - let nonce: u64 = - (*tx.nonce()).try_into().map_err(|_| InvalidTransactionError::NonceMaxValue)?; + let nonce = tx.nonce(); if nonce < account.nonce && !is_deposit_tx { warn!(target: "backend", "[{:?}] nonce too low", tx.hash()); - return Err(InvalidTransactionError::NonceTooLow) + return Err(InvalidTransactionError::NonceTooLow); } - if (env.cfg.spec_id as u8) >= (SpecId::LONDON as u8) { - if tx.gas_price() < env.block.basefee.to_ethers() && !is_deposit_tx { + if (env.handler_cfg.spec_id as u8) >= (SpecId::LONDON as u8) { + if tx.gas_price() < env.block.basefee.to() && !is_deposit_tx { warn!(target: "backend", "max fee per gas={}, too low, block basefee={}",tx.gas_price(), env.block.basefee); - return Err(InvalidTransactionError::FeeCapTooLow) + return Err(InvalidTransactionError::FeeCapTooLow); } if let (Some(max_priority_fee_per_gas), Some(max_fee_per_gas)) = @@ -2276,23 +2354,58 @@ impl TransactionValidator for Backend { { if max_priority_fee_per_gas > max_fee_per_gas { warn!(target: "backend", "max priority fee per gas={}, too high, max fee per gas={}", max_priority_fee_per_gas, max_fee_per_gas); - return Err(InvalidTransactionError::TipAboveFeeCap) + return Err(InvalidTransactionError::TipAboveFeeCap); } } } + // EIP-4844 Cancun hard fork validation steps + if env.spec_id() >= SpecId::CANCUN && tx.transaction.is_eip4844() { + // Light checks first: see if the blob fee cap is too low. + if let Some(max_fee_per_blob_gas) = tx.essentials().max_fee_per_blob_gas { + if let Some(blob_gas_and_price) = &env.block.blob_excess_gas_and_price { + if max_fee_per_blob_gas.to::() < blob_gas_and_price.blob_gasprice { + warn!(target: "backend", "max fee per blob gas={}, too low, block blob gas price={}", max_fee_per_blob_gas, blob_gas_and_price.blob_gasprice); + return Err(InvalidTransactionError::BlobFeeCapTooLow); + } + } + } + + // Heavy (blob validation) checks + let tx = match &tx.transaction { + TypedTransaction::EIP4844(tx) => tx.tx(), + _ => unreachable!(), + }; + + let blob_count = tx.tx().blob_versioned_hashes.len(); + + // Ensure there are blob hashes. + if blob_count == 0 { + return Err(InvalidTransactionError::NoBlobHashes) + } + + // Ensure the tx does not exceed the max blobs per block. + if blob_count > MAX_BLOBS_PER_BLOCK { + return Err(InvalidTransactionError::TooManyBlobs) + } + + // Check for any blob validation errors + if let Err(err) = tx.validate(env.cfg.kzg_settings.get()) { + return Err(InvalidTransactionError::BlobTransactionValidationError(err)) + } + } + let max_cost = tx.max_cost(); let value = tx.value(); // check sufficient funds: `gas * price + value` - let req_funds = max_cost.checked_add(value).ok_or_else(|| { + let req_funds = max_cost.checked_add(value.to()).ok_or_else(|| { warn!(target: "backend", "[{:?}] cost too high", tx.hash()); InvalidTransactionError::InsufficientFunds })?; - - if account.balance < req_funds.to_alloy() { + if account.balance < U256::from(req_funds) { warn!(target: "backend", "[{:?}] insufficient allowance={}, required={} account={:?}", tx.hash(), account.balance, req_funds, *pending.sender()); - return Err(InvalidTransactionError::InsufficientFunds) + return Err(InvalidTransactionError::InsufficientFunds); } Ok(()) } @@ -2301,11 +2414,11 @@ impl TransactionValidator for Backend { &self, tx: &PendingTransaction, account: &AccountInfo, - env: &Env, + env: &EnvWithHandlerCfg, ) -> Result<(), InvalidTransactionError> { self.validate_pool_transaction_for(tx, account, env)?; - if tx.nonce().as_u64() > account.nonce { - return Err(InvalidTransactionError::NonceTooHigh) + if tx.nonce() > account.nonce { + return Err(InvalidTransactionError::NonceTooHigh); } Ok(()) } @@ -2314,15 +2427,15 @@ impl TransactionValidator for Backend { /// Creates a `Transaction` as it's expected for the `eth` RPC api from storage data #[allow(clippy::too_many_arguments)] pub fn transaction_build( - tx_hash: Option, + tx_hash: Option, eth_transaction: MaybeImpersonatedTransaction, block: Option<&Block>, info: Option, - base_fee: Option, -) -> Transaction { + base_fee: Option, +) -> WithOtherFields { let mut transaction: Transaction = eth_transaction.clone().into(); - if info.is_some() && transaction.transaction_type.unwrap_or(U64::zero()).as_u64() == 0x7E { - transaction.nonce = U256::from(info.as_ref().unwrap().nonce); + if info.is_some() && transaction.transaction_type == Some(0x7E) { + transaction.nonce = info.as_ref().unwrap().nonce; } if eth_transaction.is_dynamic_fee() { @@ -2332,12 +2445,9 @@ pub fn transaction_build( } else { // if transaction is already mined, gas price is considered base fee + priority fee: the // effective gas price. - let base_fee = base_fee.unwrap_or(U256::zero()); - let max_priority_fee_per_gas = - transaction.max_priority_fee_per_gas.unwrap_or(U256::zero()); - transaction.gas_price = Some( - base_fee.checked_add(max_priority_fee_per_gas).unwrap_or_else(U256::max_value), - ); + let base_fee = base_fee.unwrap_or(0u128); + let max_priority_fee_per_gas = transaction.max_priority_fee_per_gas.unwrap_or(0); + transaction.gas_price = Some(base_fee.saturating_add(max_priority_fee_per_gas)); } } else { transaction.max_fee_per_gas = None; @@ -2345,11 +2455,11 @@ pub fn transaction_build( } transaction.block_hash = - block.as_ref().map(|block| H256::from(keccak256(&rlp::encode(&block.header)))); + block.as_ref().map(|block| B256::from(keccak256(alloy_rlp::encode(&block.header)))); - transaction.block_number = block.as_ref().map(|block| block.header.number.as_u64().into()); + transaction.block_number = block.as_ref().map(|block| block.header.number); - transaction.transaction_index = info.as_ref().map(|status| status.transaction_index.into()); + transaction.transaction_index = info.as_ref().map(|info| info.transaction_index); // need to check if the signature of the transaction is impersonated, if so then we // can't recover the sender, instead we use the sender from the executed transaction and set the @@ -2370,9 +2480,8 @@ pub fn transaction_build( transaction.hash = tx_hash; } - transaction.to = info.as_ref().map_or(eth_transaction.to().cloned(), |status| status.to); - - transaction + transaction.to = info.as_ref().map_or(eth_transaction.to(), |status| status.to); + WithOtherFields::new(transaction) } /// Prove a storage key's existence or nonexistence in the account's storage @@ -2380,24 +2489,29 @@ pub fn transaction_build( /// `storage_key` is the hash of the desired storage key, meaning /// this will only work correctly under a secure trie. /// `storage_key` == keccak(key) -pub fn prove_storage( - acc: &BasicAccount, - data: &AsHashDB, - storage_key: H256, -) -> Result<(Vec>, H256), BlockchainError> { - let data: &dyn HashDB<_, _> = data.deref(); - let mut recorder = Recorder::new(); - let trie = RefTrieDB::new(&data, &acc.storage_root.0) - .map_err(|err| BlockchainError::TrieError(err.to_string())) - .unwrap(); - - let item: U256 = { - let decode_value = |bytes: &[u8]| rlp::decode(bytes).expect("decoding db value failed"); - let query = (&mut recorder, decode_value); - trie.get_with(storage_key.as_bytes(), query) - .map_err(|err| BlockchainError::TrieError(err.to_string()))? - .unwrap_or_else(U256::zero) - }; - - Ok((recorder.drain().into_iter().map(|r| r.data).collect(), BigEndianHash::from_uint(&item))) +pub fn prove_storage(storage: &HashMap, keys: &[B256]) -> Vec> { + let keys: Vec<_> = keys.iter().map(|key| Nibbles::unpack(keccak256(key))).collect(); + + let mut builder = HashBuilder::default().with_proof_retainer(keys.clone()); + + for (key, value) in trie_storage(storage) { + builder.add_leaf(key, &value); + } + + let _ = builder.root(); + + let mut proofs = Vec::new(); + let all_proof_nodes = builder.take_proofs(); + + for proof_key in keys { + // Iterate over all proof nodes and find the matching ones. + // The filtered results are guaranteed to be in order. + let matching_proof_nodes = all_proof_nodes + .iter() + .filter(|(path, _)| proof_key.starts_with(path)) + .map(|(_, node)| node.clone()); + proofs.push(matching_proof_nodes.collect()); + } + + proofs } diff --git a/crates/anvil/src/eth/backend/mem/state.rs b/crates/anvil/src/eth/backend/mem/state.rs index bda955860d4a9..fb8be9b99cdc3 100644 --- a/crates/anvil/src/eth/backend/mem/state.rs +++ b/crates/anvil/src/eth/backend/mem/state.rs @@ -1,109 +1,73 @@ //! Support for generating the state root for memdb storage -use crate::eth::{backend::db::AsHashDB, error::BlockchainError}; -use alloy_primitives::{Address, Bytes, U256 as rU256}; -use anvil_core::eth::{state::StateOverride, trie::RefSecTrieDBMut}; -use ethers::{ - types::H256, - utils::{rlp, rlp::RlpStream}, -}; -use foundry_common::types::{ToAlloy, ToEthers}; +use crate::eth::error::BlockchainError; +use alloy_primitives::{keccak256, Address, B256, U256}; +use alloy_rlp::Encodable; +use alloy_rpc_types::state::StateOverride; +use alloy_trie::{HashBuilder, Nibbles}; use foundry_evm::{ backend::DatabaseError, - hashbrown::HashMap as Map, revm::{ db::{CacheDB, DatabaseRef, DbAccount}, - primitives::{AccountInfo, Bytecode, Log}, + primitives::{AccountInfo, Bytecode, HashMap}, }, }; -use memory_db::HashKey; -use trie_db::TrieMut; -/// Returns the log hash for all `logs` -/// -/// The log hash is `keccak(rlp(logs[]))`, -pub fn log_rlp_hash(logs: Vec) -> H256 { - let mut stream = RlpStream::new(); - stream.begin_unbounded_list(); - for log in logs { - let topics = log.topics.into_iter().map(|t| t.to_ethers()).collect::>(); - stream.begin_list(3); - stream.append(&(log.address.to_ethers())); - stream.append_list(&topics); - stream.append(&log.data.0); +pub fn build_root(values: impl IntoIterator)>) -> B256 { + let mut builder = HashBuilder::default(); + for (key, value) in values { + builder.add_leaf(key, value.as_ref()); } - stream.finalize_unbounded_list(); - let out = stream.out().freeze(); - - let out = ethers::utils::keccak256(out); - H256::from_slice(out.as_slice()) + builder.root() } -/// Returns storage trie of an account as `HashDB` -pub fn storage_trie_db(storage: &Map) -> (AsHashDB, H256) { - // Populate DB with full trie from entries. - let (db, root) = { - let mut db = , _>>::default(); - let mut root = Default::default(); - { - let mut trie = RefSecTrieDBMut::new(&mut db, &mut root); - for (k, v) in storage.iter().filter(|(_k, v)| *v != &rU256::from(0)) { - let mut temp: [u8; 32] = [0; 32]; - (*k).to_ethers().to_big_endian(&mut temp); - let key = H256::from(temp); - let value = rlp::encode(&(*v).to_ethers()); - trie.insert(key.as_bytes(), value.as_ref()).unwrap(); - } - } - (db, root) - }; - - (Box::new(db), H256::from(root)) +/// Builds state root from the given accounts +pub fn state_root(accounts: &HashMap) -> B256 { + build_root(trie_accounts(accounts)) } -/// Returns the account data as `HashDB` -pub fn trie_hash_db(accounts: &Map) -> (AsHashDB, H256) { - let accounts = trie_accounts(accounts); +/// Builds storage root from the given storage +pub fn storage_root(storage: &HashMap) -> B256 { + build_root(trie_storage(storage)) +} - // Populate DB with full trie from entries. - let (db, root) = { - let mut db = , _>>::default(); - let mut root = Default::default(); - { - let mut trie = RefSecTrieDBMut::new(&mut db, &mut root); - for (address, value) in accounts { - trie.insert(address.as_ref(), value.as_ref()).unwrap(); - } - } - (db, root) - }; +/// Builds iterator over stored key-value pairs ready for storage trie root calculation. +pub fn trie_storage(storage: &HashMap) -> Vec<(Nibbles, Vec)> { + let mut storage = storage + .iter() + .map(|(key, value)| { + let data = alloy_rlp::encode(value); + (Nibbles::unpack(keccak256(key.to_be_bytes::<32>())), data) + }) + .collect::>(); + storage.sort_by(|(key1, _), (key2, _)| key1.cmp(key2)); - (Box::new(db), H256::from(root)) + storage } -/// Returns all RLP-encoded Accounts -pub fn trie_accounts(accounts: &Map) -> Vec<(Address, Bytes)> { - accounts +/// Builds iterator over stored key-value pairs ready for account trie root calculation. +pub fn trie_accounts(accounts: &HashMap) -> Vec<(Nibbles, Vec)> { + let mut accounts = accounts .iter() .map(|(address, account)| { - let storage_root = trie_account_rlp(&account.info, &account.storage); - (*address, storage_root) + let data = trie_account_rlp(&account.info, &account.storage); + (Nibbles::unpack(keccak256(*address)), data) }) - .collect() -} + .collect::>(); + accounts.sort_by(|(key1, _), (key2, _)| key1.cmp(key2)); -pub fn state_merkle_trie_root(accounts: &Map) -> H256 { - trie_hash_db(accounts).1 + accounts } /// Returns the RLP for this account. -pub fn trie_account_rlp(info: &AccountInfo, storage: &Map) -> Bytes { - let mut stream = RlpStream::new_list(4); - stream.append(&info.nonce); - stream.append(&info.balance.to_ethers()); - stream.append(&storage_trie_db(storage).1); - stream.append(&info.code_hash.as_slice()); - stream.out().freeze().into() +pub fn trie_account_rlp(info: &AccountInfo, storage: &HashMap) -> Vec { + let mut out: Vec = Vec::new(); + let list: [&dyn Encodable; 4] = + [&info.nonce, &info.balance, &storage_root(storage), &info.code_hash]; + + alloy_rlp::encode_list::<_, dyn Encodable>(&list, &mut out); + + out } /// Applies the given state overrides to the state, returning a new CacheDB state @@ -116,19 +80,19 @@ where { let mut cache_db = CacheDB::new(state); for (account, account_overrides) in overrides.iter() { - let mut account_info = cache_db.basic_ref((*account).to_alloy())?.unwrap_or_default(); + let mut account_info = cache_db.basic_ref(*account)?.unwrap_or_default(); if let Some(nonce) = account_overrides.nonce { - account_info.nonce = nonce; + account_info.nonce = nonce.to::(); } if let Some(code) = &account_overrides.code { account_info.code = Some(Bytecode::new_raw(code.to_vec().into())); } if let Some(balance) = account_overrides.balance { - account_info.balance = balance.to_alloy(); + account_info.balance = balance; } - cache_db.insert_account_info((*account).to_alloy(), account_info); + cache_db.insert_account_info(*account, account_info); // We ensure that not both state and state_diff are set. // If state is set, we must mark the account as "NewlyCreated", so that the old storage @@ -142,20 +106,16 @@ where (None, None) => (), (Some(new_account_state), None) => { cache_db.replace_account_storage( - (*account).to_alloy(), + *account, new_account_state .iter() - .map(|(key, value)| (key.to_alloy().into(), (value.to_alloy().into()))) + .map(|(key, value)| ((*key).into(), (*value))) .collect(), )?; } (None, Some(account_state_diff)) => { for (key, value) in account_state_diff.iter() { - cache_db.insert_account_storage( - (*account).to_alloy(), - key.to_alloy().into(), - value.to_alloy().into(), - )?; + cache_db.insert_account_storage(*account, (*key).into(), *value)?; } } }; diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index 074ebeeced89a..434490de575a1 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -1,22 +1,25 @@ //! In-memory blockchain storage use crate::eth::{ backend::{ - db::{MaybeHashDatabase, StateDb}, + db::{MaybeFullDatabase, StateDb}, mem::cache::DiskStateCache, }, pool::transactions::PoolTransaction, }; +use alloy_primitives::{Bytes, TxHash, B256, U256, U64}; +use alloy_rpc_types::{BlockId, BlockNumberOrTag, TransactionInfo as RethTransactionInfo}; +use alloy_rpc_types_trace::{ + geth::{DefaultFrame, GethDefaultTracingOptions}, + parity::LocalizedTransactionTrace, +}; use anvil_core::eth::{ block::{Block, PartialHeader}, - receipt::TypedReceipt, - transaction::{MaybeImpersonatedTransaction, TransactionInfo}, + transaction::{MaybeImpersonatedTransaction, ReceiptResponse, TransactionInfo, TypedReceipt}, }; -use ethers::{ - prelude::{BlockId, BlockNumber, DefaultFrame, Trace, H256, H256 as TxHash, U64}, - types::{ActionType, Bytes, GethDebugTracingOptions, TransactionReceipt, U256}, +use foundry_evm::{ + revm::primitives::Env, + traces::{GethTraceBuilder, ParityTraceBuilder, TracingInspectorConfig}, }; -use foundry_common::types::{ToAlloy, ToEthers}; -use foundry_evm::revm::{interpreter::InstructionResult, primitives::Env}; use parking_lot::RwLock; use std::{ collections::{HashMap, VecDeque}, @@ -37,9 +40,9 @@ const MAX_ON_DISK_HISTORY_LIMIT: usize = 3_600; /// Represents the complete state of single block pub struct InMemoryBlockStates { /// The states at a certain block - states: HashMap, + states: HashMap, /// states which data is moved to disk - on_disk_states: HashMap, + on_disk_states: HashMap, /// How many states to store at most in_memory_limit: usize, /// minimum amount of states we keep in memory @@ -49,9 +52,9 @@ pub struct InMemoryBlockStates { /// Limiting the states will prevent disk blow up, especially in interval mining mode max_on_disk_limit: usize, /// the oldest states written to disk - oldest_on_disk: VecDeque, + oldest_on_disk: VecDeque, /// all states present, used to enforce `in_memory_limit` - present: VecDeque, + present: VecDeque, /// Stores old states on disk disk_cache: DiskStateCache, } @@ -109,7 +112,7 @@ impl InMemoryBlockStates { /// the number of states/blocks until we reached the `min_limit`. /// /// When a state that was previously written to disk is requested, it is simply read from disk. - pub fn insert(&mut self, hash: H256, state: StateDb) { + pub fn insert(&mut self, hash: B256, state: StateDb) { if !self.is_memory_only() && self.present.len() >= self.in_memory_limit { // once we hit the max limit we gradually decrease it self.in_memory_limit = @@ -153,7 +156,7 @@ impl InMemoryBlockStates { } /// Returns the state for the given `hash` if present - pub fn get(&mut self, hash: &H256) -> Option<&StateDb> { + pub fn get(&mut self, hash: &B256) -> Option<&StateDb> { self.states.get(hash).or_else(|| { if let Some(state) = self.on_disk_states.get_mut(hash) { if let Some(cached) = self.disk_cache.read(*hash) { @@ -204,15 +207,15 @@ impl Default for InMemoryBlockStates { #[derive(Clone)] pub struct BlockchainStorage { /// all stored blocks (block hash -> block) - pub blocks: HashMap, + pub blocks: HashMap, /// mapping from block number -> block hash - pub hashes: HashMap, + pub hashes: HashMap, /// The current best hash - pub best_hash: H256, + pub best_hash: B256, /// The current best block number pub best_number: U64, /// genesis hash of the chain - pub genesis_hash: H256, + pub genesis_hash: B256, /// Mapping from the transaction hash to a tuple containing the transaction as well as the /// transaction receipt pub transactions: HashMap, @@ -222,20 +225,20 @@ pub struct BlockchainStorage { impl BlockchainStorage { /// Creates a new storage with a genesis block - pub fn new(env: &Env, base_fee: Option, timestamp: u64) -> Self { + pub fn new(env: &Env, base_fee: Option, timestamp: u64) -> Self { // create a dummy genesis block let partial_header = PartialHeader { timestamp, base_fee, - gas_limit: env.block.gas_limit.to_ethers(), - beneficiary: env.block.coinbase.to_ethers(), - difficulty: env.block.difficulty.to_ethers(), + gas_limit: env.block.gas_limit.to::(), + beneficiary: env.block.coinbase, + difficulty: env.block.difficulty, ..Default::default() }; let block = Block::new::(partial_header, vec![], vec![]); - let genesis_hash = block.header.hash(); + let genesis_hash = block.header.hash_slow(); let best_hash = genesis_hash; - let best_number: U64 = 0u64.into(); + let best_number: U64 = U64::from(0u64); Self { blocks: HashMap::from([(genesis_hash, block)]), @@ -248,12 +251,12 @@ impl BlockchainStorage { } } - pub fn forked(block_number: u64, block_hash: H256, total_difficulty: U256) -> Self { + pub fn forked(block_number: u64, block_hash: B256, total_difficulty: U256) -> Self { BlockchainStorage { blocks: Default::default(), - hashes: HashMap::from([(block_number.into(), block_hash)]), + hashes: HashMap::from([(U64::from(block_number), block_hash)]), best_hash: block_hash, - best_number: block_number.into(), + best_number: U64::from(block_number), genesis_hash: Default::default(), transactions: Default::default(), total_difficulty, @@ -275,13 +278,13 @@ impl BlockchainStorage { /// Removes all stored transactions for the given block number pub fn remove_block_transactions_by_number(&mut self, num: u64) { - if let Some(hash) = self.hashes.get(&(num.into())).copied() { + if let Some(hash) = self.hashes.get(&(U64::from(num))).copied() { self.remove_block_transactions(hash); } } /// Removes all stored transactions for the given block hash - pub fn remove_block_transactions(&mut self, block_hash: H256) { + pub fn remove_block_transactions(&mut self, block_hash: B256) { if let Some(block) = self.blocks.get_mut(&block_hash) { for tx in block.transactions.iter() { self.transactions.remove(&tx.hash()); @@ -294,24 +297,26 @@ impl BlockchainStorage { // === impl BlockchainStorage === impl BlockchainStorage { - /// Returns the hash for [BlockNumber] - pub fn hash(&self, number: BlockNumber) -> Option { + /// Returns the hash for [BlockNumberOrTag] + pub fn hash(&self, number: BlockNumberOrTag) -> Option { let slots_in_an_epoch = U64::from(32u64); match number { - BlockNumber::Latest => Some(self.best_hash), - BlockNumber::Earliest => Some(self.genesis_hash), - BlockNumber::Pending => None, - BlockNumber::Number(num) => self.hashes.get(&num).copied(), - BlockNumber::Safe => { + BlockNumberOrTag::Latest => Some(self.best_hash), + BlockNumberOrTag::Earliest => Some(self.genesis_hash), + BlockNumberOrTag::Pending => None, + BlockNumberOrTag::Number(num) => self.hashes.get(&U64::from(num)).copied(), + BlockNumberOrTag::Safe => { if self.best_number > (slots_in_an_epoch) { self.hashes.get(&(self.best_number - (slots_in_an_epoch))).copied() } else { Some(self.genesis_hash) // treat the genesis block as safe "by definition" } } - BlockNumber::Finalized => { - if self.best_number > (slots_in_an_epoch * 2) { - self.hashes.get(&(self.best_number - (slots_in_an_epoch * 2))).copied() + BlockNumberOrTag::Finalized => { + if self.best_number > (slots_in_an_epoch * U64::from(2)) { + self.hashes + .get(&(self.best_number - (slots_in_an_epoch * U64::from(2)))) + .copied() } else { Some(self.genesis_hash) } @@ -331,11 +336,11 @@ pub struct Blockchain { impl Blockchain { /// Creates a new storage with a genesis block - pub fn new(env: &Env, base_fee: Option, timestamp: u64) -> Self { + pub fn new(env: &Env, base_fee: Option, timestamp: u64) -> Self { Self { storage: Arc::new(RwLock::new(BlockchainStorage::new(env, base_fee, timestamp))) } } - pub fn forked(block_number: u64, block_hash: H256, total_difficulty: U256) -> Self { + pub fn forked(block_number: u64, block_hash: B256, total_difficulty: U256) -> Self { Self { storage: Arc::new(RwLock::new(BlockchainStorage::forked( block_number, @@ -346,18 +351,18 @@ impl Blockchain { } /// returns the header hash of given block - pub fn hash(&self, id: BlockId) -> Option { + pub fn hash(&self, id: BlockId) -> Option { match id { - BlockId::Hash(h) => Some(h), + BlockId::Hash(h) => Some(h.block_hash), BlockId::Number(num) => self.storage.read().hash(num), } } - pub fn get_block_by_hash(&self, hash: &H256) -> Option { + pub fn get_block_by_hash(&self, hash: &B256) -> Option { self.storage.read().blocks.get(hash).cloned() } - pub fn get_transaction_by_hash(&self, hash: &H256) -> Option { + pub fn get_transaction_by_hash(&self, hash: &B256) -> Option { self.storage.read().transactions.get(hash).cloned() } @@ -368,7 +373,7 @@ impl Blockchain { } /// Represents the outcome of mining a new block -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct MinedBlockOutcome { /// The block that was mined pub block_number: U64, @@ -380,11 +385,11 @@ pub struct MinedBlockOutcome { } /// Container type for a mined transaction -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct MinedTransaction { pub info: TransactionInfo, pub receipt: TypedReceipt, - pub block_hash: H256, + pub block_hash: B256, pub block_number: u64, } @@ -392,46 +397,36 @@ pub struct MinedTransaction { impl MinedTransaction { /// Returns the traces of the transaction for `trace_transaction` - pub fn parity_traces(&self) -> Vec { - let mut traces = Vec::with_capacity(self.info.traces.arena.len()); - for (idx, node) in self.info.traces.arena.iter().cloned().enumerate() { - let action = node.parity_action(); - let result = node.parity_result(); - - let action_type = if node.status() == InstructionResult::SelfDestruct { - ActionType::Suicide - } else { - node.kind().into() - }; - - let trace = Trace { - action, - result: Some(result), - trace_address: self.info.trace_address(idx), - subtraces: node.children.len(), - transaction_position: Some(self.info.transaction_index as usize), - transaction_hash: Some(self.info.transaction_hash), - block_number: self.block_number, - block_hash: self.block_hash, - action_type, - error: None, - }; - traces.push(trace) - } - - traces + pub fn parity_traces(&self) -> Vec { + ParityTraceBuilder::new( + self.info.traces.clone(), + None, + TracingInspectorConfig::default_parity(), + ) + .into_localized_transaction_traces(RethTransactionInfo { + hash: Some(self.info.transaction_hash), + index: Some(self.info.transaction_index), + block_hash: Some(self.block_hash), + block_number: Some(self.block_number), + base_fee: None, + }) } - pub fn geth_trace(&self, opts: GethDebugTracingOptions) -> DefaultFrame { - self.info.traces.geth_trace(self.receipt.gas_used().to_alloy(), opts) + pub fn geth_trace(&self, opts: GethDefaultTracingOptions) -> DefaultFrame { + GethTraceBuilder::new(self.info.traces.clone(), TracingInspectorConfig::default_geth()) + .geth_traces( + self.receipt.cumulative_gas_used() as u64, + self.info.out.clone().unwrap_or_default().0.into(), + opts, + ) } } /// Intermediary Anvil representation of a receipt -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct MinedTransactionReceipt { /// The actual json rpc receipt object - pub inner: TransactionReceipt, + pub inner: ReceiptResponse, /// Output data fo the transaction pub out: Option, } @@ -440,8 +435,7 @@ pub struct MinedTransactionReceipt { mod tests { use super::*; use crate::eth::backend::db::Db; - use ethers::{abi::ethereum_types::BigEndianHash, types::Address}; - use foundry_common::types::ToAlloy; + use alloy_primitives::Address; use foundry_evm::{ backend::MemDb, revm::{ @@ -460,8 +454,8 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn can_read_write_cached_state() { let mut storage = InMemoryBlockStates::new(1); - let one = H256::from_uint(&U256::from(1)); - let two = H256::from_uint(&U256::from(2)); + let one = B256::from(U256::from(1)); + let two = B256::from(U256::from(2)); let mut state = MemDb::default(); let addr = Address::random(); @@ -474,11 +468,11 @@ mod tests { tokio::time::sleep(std::time::Duration::from_secs(2)).await; assert_eq!(storage.on_disk_states.len(), 1); - assert!(storage.on_disk_states.get(&one).is_some()); + assert!(storage.on_disk_states.contains_key(&one)); let loaded = storage.get(&one).unwrap(); - let acc = loaded.basic_ref(addr.to_alloy()).unwrap().unwrap(); + let acc = loaded.basic_ref(addr).unwrap().unwrap(); assert_eq!(acc.balance, rU256::from(1337u64)); } @@ -490,8 +484,8 @@ mod tests { let num_states = 30; for idx in 0..num_states { let mut state = MemDb::default(); - let hash = H256::from_uint(&U256::from(idx)); - let addr = Address::from(hash); + let hash = B256::from(U256::from(idx)); + let addr = Address::from_word(hash); let balance = (idx * 2) as u64; let info = AccountInfo::from_balance(rU256::from(balance)); state.insert_account(addr, info); @@ -505,10 +499,10 @@ mod tests { assert_eq!(storage.present.len(), storage.min_in_memory_limit); for idx in 0..num_states { - let hash = H256::from_uint(&U256::from(idx)); - let addr = Address::from(hash); + let hash = B256::from(U256::from(idx)); + let addr = Address::from_word(hash); let loaded = storage.get(&hash).unwrap(); - let acc = loaded.basic_ref(addr.to_alloy()).unwrap().unwrap(); + let acc = loaded.basic_ref(addr).unwrap().unwrap(); let balance = (idx * 2) as u64; assert_eq!(acc.balance, rU256::from(balance)); } diff --git a/crates/anvil/src/eth/backend/notifications.rs b/crates/anvil/src/eth/backend/notifications.rs index 7a5f428db7a41..795de0cca9a55 100644 --- a/crates/anvil/src/eth/backend/notifications.rs +++ b/crates/anvil/src/eth/backend/notifications.rs @@ -1,7 +1,7 @@ //! Notifications emitted from the backed -use anvil_core::eth::block::Header; -use ethers::types::H256; +use alloy_consensus::Header; +use alloy_primitives::B256; use futures::channel::mpsc::UnboundedReceiver; use std::sync::Arc; @@ -9,7 +9,7 @@ use std::sync::Arc; #[derive(Clone, Debug)] pub struct NewBlockNotification { /// Hash of the imported block - pub hash: H256, + pub hash: B256, /// block header pub header: Arc
, } diff --git a/crates/anvil/src/eth/backend/time.rs b/crates/anvil/src/eth/backend/time.rs index 783966dbe36b3..ce6900eb48f90 100644 --- a/crates/anvil/src/eth/backend/time.rs +++ b/crates/anvil/src/eth/backend/time.rs @@ -1,20 +1,17 @@ //! Manages the block time use crate::eth::error::BlockchainError; -use chrono::{DateTime, NaiveDateTime, Utc}; +use chrono::{DateTime, Utc}; use parking_lot::RwLock; use std::{sync::Arc, time::Duration}; /// Returns the `Utc` datetime for the given seconds since unix epoch pub fn utc_from_secs(secs: u64) -> DateTime { - DateTime::::from_naive_utc_and_offset( - NaiveDateTime::from_timestamp_opt(secs as i64, 0).unwrap(), - Utc, - ) + DateTime::from_timestamp(secs as i64, 0).unwrap() } /// Manages block time -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct TimeManager { /// tracks the overall applied timestamp offset offset: Arc>, diff --git a/crates/anvil/src/eth/backend/validate.rs b/crates/anvil/src/eth/backend/validate.rs index 3ea666f15a156..650ce24a55010 100644 --- a/crates/anvil/src/eth/backend/validate.rs +++ b/crates/anvil/src/eth/backend/validate.rs @@ -2,7 +2,7 @@ use crate::eth::error::{BlockchainError, InvalidTransactionError}; use anvil_core::eth::transaction::PendingTransaction; -use foundry_evm::revm::primitives::{AccountInfo, Env}; +use foundry_evm::revm::primitives::{AccountInfo, EnvWithHandlerCfg}; /// A trait for validating transactions #[async_trait::async_trait] @@ -22,7 +22,7 @@ pub trait TransactionValidator { &self, tx: &PendingTransaction, account: &AccountInfo, - env: &Env, + env: &EnvWithHandlerCfg, ) -> Result<(), InvalidTransactionError>; /// Validates the transaction against a specific account @@ -32,6 +32,6 @@ pub trait TransactionValidator { &self, tx: &PendingTransaction, account: &AccountInfo, - env: &Env, + env: &EnvWithHandlerCfg, ) -> Result<(), InvalidTransactionError>; } diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error.rs index db17c003b9b60..ce00da3194d2c 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error.rs @@ -1,19 +1,17 @@ //! Aggregated error type for this module use crate::eth::pool::transactions::PoolTransaction; +use alloy_primitives::{Bytes, SignatureError}; +use alloy_rpc_types::BlockNumberOrTag; +use alloy_signer::Error as SignerError; +use alloy_transport::TransportError; use anvil_rpc::{ error::{ErrorCode, RpcError}, response::ResponseResult, }; -use ethers::{ - abi::AbiDecode, - providers::ProviderError, - signers::WalletError, - types::{Bytes, SignatureError, U256}, -}; -use foundry_common::SELECTOR_LEN; use foundry_evm::{ backend::DatabaseError, + decode::RevertDecoder, revm::{ self, interpreter::InstructionResult, @@ -24,7 +22,7 @@ use serde::Serialize; pub(crate) type Result = std::result::Result; -#[derive(thiserror::Error, Debug)] +#[derive(Debug, thiserror::Error)] pub enum BlockchainError { #[error(transparent)] Pool(#[from] PoolError), @@ -40,6 +38,8 @@ pub enum BlockchainError { FailedToDecodeSignedTransaction, #[error("Failed to decode transaction")] FailedToDecodeTransaction, + #[error("Failed to decode receipt")] + FailedToDecodeReceipt, #[error("Failed to decode state")] FailedToDecodeStateDump, #[error("Prevrandao not in th EVM's environment after merge")] @@ -47,7 +47,7 @@ pub enum BlockchainError { #[error(transparent)] SignatureError(#[from] SignatureError), #[error(transparent)] - WalletError(#[from] WalletError), + SignerError(#[from] SignerError), #[error("Rpc Endpoint not implemented")] RpcUnimplemented, #[error("Rpc error {0:?}")] @@ -57,7 +57,7 @@ pub enum BlockchainError { #[error(transparent)] FeeHistory(#[from] FeeHistoryError), #[error(transparent)] - ForkProvider(#[from] ProviderError), + AlloyForkProvider(#[from] TransportError), #[error("EVM error {0:?}")] EvmError(InstructionResult), #[error("Invalid url {0:?}")] @@ -84,10 +84,14 @@ pub enum BlockchainError { EIP1559TransactionUnsupportedAtHardfork, #[error("Access list received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork berlin' or later.")] EIP2930TransactionUnsupportedAtHardfork, + #[error("EIP-4844 fields received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork cancun' or later.")] + EIP4844TransactionUnsupportedAtHardfork, #[error("op-stack deposit tx received but is not supported.\n\nYou can use it by running anvil with '--optimism'.")] DepositTransactionUnsupported, #[error("Excess blob gas not set.")] ExcessBlobGasNotSet, + #[error("{0}")] + Message(String), } impl From for BlockchainError { @@ -108,12 +112,13 @@ where InvalidHeader::PrevrandaoNotSet => BlockchainError::PrevrandaoNotSet, }, EVMError::Database(err) => err.into(), + EVMError::Custom(err) => BlockchainError::Message(err), } } } /// Errors that can occur in the transaction pool -#[derive(thiserror::Error, Debug)] +#[derive(Debug, thiserror::Error)] pub enum PoolError { #[error("Transaction with cyclic dependent transactions")] CyclicTransaction, @@ -125,10 +130,12 @@ pub enum PoolError { } /// Errors that can occur with `eth_feeHistory` -#[derive(thiserror::Error, Debug)] +#[derive(Debug, thiserror::Error)] pub enum FeeHistoryError { - #[error("Requested block range is out of bounds")] + #[error("requested block range is out of bounds")] InvalidBlockRange, + #[error("could not find newest block number requested: {0}")] + BlockNotFound(BlockNumberOrTag), } #[derive(Debug)] @@ -137,7 +144,7 @@ pub struct ErrDetail { } /// An error due to invalid transaction -#[derive(thiserror::Error, Debug)] +#[derive(Debug, thiserror::Error)] pub enum InvalidTransactionError { /// returned if the nonce of a transaction is lower than the one present in the local chain. #[error("nonce too low")] @@ -178,7 +185,7 @@ pub enum InvalidTransactionError { FeeCapTooLow, /// Thrown during estimate if caller has insufficient funds to cover the tx. #[error("Out of gas: gas required exceeds allowance: {0:?}")] - BasicOutOfGas(U256), + BasicOutOfGas(u128), /// Thrown if executing a transaction failed during estimate/call #[error("execution reverted: {0:?}")] Revert(Option), @@ -197,7 +204,7 @@ pub enum InvalidTransactionError { /// Thrown when the block's `blob_gas_price` is greater than tx-specified /// `max_fee_per_blob_gas` after Cancun. #[error("Block `blob_gas_price` is greater than tx-specified `max_fee_per_blob_gas`")] - BlobGasPriceGreaterThanMax, + BlobFeeCapTooLow, /// Thrown when we receive a tx with `blob_versioned_hashes` and we're not on the Cancun hard /// fork. #[error("Block `blob_versioned_hashes` is not supported before the Cancun hardfork")] @@ -205,6 +212,23 @@ pub enum InvalidTransactionError { /// Thrown when `max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork. #[error("`max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.")] MaxFeePerBlobGasNotSupported, + /// Thrown when there are no `blob_hashes` in the transaction, and it is an EIP-4844 tx. + #[error("`blob_hashes` are required for EIP-4844 transactions")] + NoBlobHashes, + #[error("too many blobs in one transaction")] + TooManyBlobs, + /// Thrown when there's a blob validation error + #[error(transparent)] + BlobTransactionValidationError(#[from] alloy_consensus::BlobTransactionValidationError), + /// Thrown when Blob transaction is a create transaction. `to` must be present. + #[error("Blob transaction can't be a create transaction. `to` must be present.")] + BlobCreateTransaction, + /// Thrown when Blob transaction contains a versioned hash with an incorrect version. + #[error("Blob transaction contains a versioned hash with an incorrect version")] + BlobVersionNotSupported, + /// Thrown when there are no `blob_hashes` in the transaction. + #[error("There should be at least one blob in a Blob transaction.")] + EmptyBlobs, } impl From for InvalidTransactionError { @@ -236,7 +260,7 @@ impl From for InvalidTransactionError { InvalidTransaction::NonceOverflowInTransaction => { InvalidTransactionError::NonceMaxValue } - InvalidTransaction::CreateInitcodeSizeLimit => { + InvalidTransaction::CreateInitCodeSizeLimit => { InvalidTransactionError::MaxInitCodeSizeExceeded } InvalidTransaction::NonceTooHigh { .. } => InvalidTransactionError::NonceTooHigh, @@ -245,7 +269,7 @@ impl From for InvalidTransactionError { InvalidTransactionError::AccessListNotSupported } InvalidTransaction::BlobGasPriceGreaterThanMax => { - InvalidTransactionError::BlobGasPriceGreaterThanMax + InvalidTransactionError::BlobFeeCapTooLow } InvalidTransaction::BlobVersionedHashesNotSupported => { InvalidTransactionError::BlobVersionedHashesNotSupported @@ -253,24 +277,19 @@ impl From for InvalidTransactionError { InvalidTransaction::MaxFeePerBlobGasNotSupported => { InvalidTransactionError::MaxFeePerBlobGasNotSupported } - // TODO: Blob-related errors should be handled once the Reth migration is done and code - // is moved over. + InvalidTransaction::BlobCreateTransaction => { + InvalidTransactionError::BlobCreateTransaction + } + InvalidTransaction::BlobVersionNotSupported => { + InvalidTransactionError::BlobVersionedHashesNotSupported + } + InvalidTransaction::EmptyBlobs => InvalidTransactionError::EmptyBlobs, + InvalidTransaction::TooManyBlobs => InvalidTransactionError::TooManyBlobs, _ => todo!(), } } } -/// Returns the revert reason from the `revm::TransactOut` data, if it's an abi encoded String. -/// -/// **Note:** it's assumed the `out` buffer starts with the call's signature -pub(crate) fn decode_revert_reason(out: impl AsRef<[u8]>) -> Option { - let out = out.as_ref(); - if out.len() < SELECTOR_LEN { - return None - } - String::decode(&out[SELECTOR_LEN..]).ok() -} - /// Helper trait to easily convert results to rpc results pub(crate) trait ToRpcResponseResult { fn to_rpc_result(self) -> ResponseResult; @@ -316,7 +335,10 @@ impl ToRpcResponseResult for Result { InvalidTransactionError::Revert(data) => { // this mimics geth revert error let mut msg = "execution reverted".to_string(); - if let Some(reason) = data.as_ref().and_then(decode_revert_reason) { + if let Some(reason) = data + .as_ref() + .and_then(|data| RevertDecoder::new().maybe_decode(data, None)) + { msg = format!("{msg}: {reason}"); } RpcError { @@ -354,11 +376,14 @@ impl ToRpcResponseResult for Result { BlockchainError::FailedToDecodeTransaction => { RpcError::invalid_params("Failed to decode transaction") } + BlockchainError::FailedToDecodeReceipt => { + RpcError::invalid_params("Failed to decode receipt") + } BlockchainError::FailedToDecodeStateDump => { RpcError::invalid_params("Failed to decode state dump") } + BlockchainError::SignerError(err) => RpcError::invalid_params(err.to_string()), BlockchainError::SignatureError(err) => RpcError::invalid_params(err.to_string()), - BlockchainError::WalletError(err) => RpcError::invalid_params(err.to_string()), BlockchainError::RpcUnimplemented => { RpcError::internal_error_with("Not implemented") } @@ -367,9 +392,16 @@ impl ToRpcResponseResult for Result { BlockchainError::InvalidFeeInput => RpcError::invalid_params( "Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`", ), - BlockchainError::ForkProvider(err) => { - error!(%err, "fork provider error"); - RpcError::internal_error_with(format!("Fork Error: {err:?}")) + BlockchainError::AlloyForkProvider(err) => { + error!(target: "backend", %err, "fork provider error"); + match err { + TransportError::ErrorResp(err) => RpcError { + code: ErrorCode::from(err.code), + message: err.message.into(), + data: err.data.and_then(|data| serde_json::to_value(data).ok()), + }, + err => RpcError::internal_error_with(format!("Fork Error: {err:?}")), + } } err @ BlockchainError::EvmError(_) => { RpcError::internal_error_with(err.to_string()) @@ -407,12 +439,16 @@ impl ToRpcResponseResult for Result { err @ BlockchainError::EIP2930TransactionUnsupportedAtHardfork => { RpcError::invalid_params(err.to_string()) } + err @ BlockchainError::EIP4844TransactionUnsupportedAtHardfork => { + RpcError::invalid_params(err.to_string()) + } err @ BlockchainError::DepositTransactionUnsupported => { RpcError::invalid_params(err.to_string()) } err @ BlockchainError::ExcessBlobGasNotSet => { RpcError::invalid_params(err.to_string()) } + err @ BlockchainError::Message(_) => RpcError::internal_error_with(err.to_string()), } .into(), } diff --git a/crates/anvil/src/eth/fees.rs b/crates/anvil/src/eth/fees.rs index 67bc73740c4f0..f0a614c35c6b8 100644 --- a/crates/anvil/src/eth/fees.rs +++ b/crates/anvil/src/eth/fees.rs @@ -2,9 +2,12 @@ use crate::eth::{ backend::{info::StorageInfo, notifications::NewBlockNotifications}, error::BlockchainError, }; +use alloy_eips::{ + calc_next_block_base_fee, eip1559::BaseFeeParams, eip4844::MAX_DATA_GAS_PER_BLOCK, +}; +use alloy_primitives::B256; use anvil_core::eth::transaction::TypedTransaction; -use ethers::types::{H256, U256}; -use foundry_evm::revm::primitives::SpecId; +use foundry_evm::revm::primitives::{BlobExcessGasAndPrice, SpecId}; use futures::StreamExt; use parking_lot::{Mutex, RwLock}; use std::{ @@ -20,45 +23,52 @@ use std::{ pub const MAX_FEE_HISTORY_CACHE_SIZE: u64 = 2048u64; /// Initial base fee for EIP-1559 blocks. -pub const INITIAL_BASE_FEE: u64 = 1_000_000_000; +pub const INITIAL_BASE_FEE: u128 = 1_000_000_000; /// Initial default gas price for the first block -pub const INITIAL_GAS_PRICE: u64 = 1_875_000_000; +pub const INITIAL_GAS_PRICE: u128 = 1_875_000_000; /// Bounds the amount the base fee can change between blocks. -pub const BASE_FEE_CHANGE_DENOMINATOR: u64 = 8; - -/// Elasticity multiplier as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) -pub const EIP1559_ELASTICITY_MULTIPLIER: u64 = 2; +pub const BASE_FEE_CHANGE_DENOMINATOR: u128 = 8; pub fn default_elasticity() -> f64 { - 1f64 / BASE_FEE_CHANGE_DENOMINATOR as f64 + 1f64 / BaseFeeParams::ethereum().elasticity_multiplier as f64 } /// Stores the fee related information -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct FeeManager { /// Hardfork identifier spec_id: SpecId, /// Tracks the base fee for the next block post London /// /// This value will be updated after a new block was mined - base_fee: Arc>, + base_fee: Arc>, + /// Tracks the excess blob gas, and the base fee, for the next block post Cancun + /// + /// This value will be updated after a new block was mined + blob_excess_gas_and_price: Arc>, /// The base price to use Pre London /// /// This will be constant value unless changed manually - gas_price: Arc>, + gas_price: Arc>, elasticity: Arc>, } // === impl FeeManager === impl FeeManager { - pub fn new(spec_id: SpecId, base_fee: U256, gas_price: U256) -> Self { + pub fn new( + spec_id: SpecId, + base_fee: u128, + gas_price: u128, + blob_excess_gas_and_price: BlobExcessGasAndPrice, + ) -> Self { Self { spec_id, base_fee: Arc::new(RwLock::new(base_fee)), gas_price: Arc::new(RwLock::new(gas_price)), + blob_excess_gas_and_price: Arc::new(RwLock::new(blob_excess_gas_and_price)), elasticity: Arc::new(RwLock::new(default_elasticity())), } } @@ -72,8 +82,12 @@ impl FeeManager { (self.spec_id as u8) >= (SpecId::LONDON as u8) } + pub fn is_eip4844(&self) -> bool { + (self.spec_id as u8) >= (SpecId::CANCUN as u8) + } + /// Calculates the current gas price - pub fn gas_price(&self) -> U256 { + pub fn gas_price(&self) -> u128 { if self.is_eip1559() { self.base_fee().saturating_add(self.suggested_priority_fee()) } else { @@ -81,87 +95,108 @@ impl FeeManager { } } + /// Calculates the current blob gas price + pub fn blob_gas_price(&self) -> u128 { + if self.is_eip4844() { + self.base_fee_per_blob_gas() + } else { + 0 + } + } + /// Suggested priority fee to add to the base fee - pub fn suggested_priority_fee(&self) -> U256 { - U256::from(1e9 as u64) + pub fn suggested_priority_fee(&self) -> u128 { + 1e9 as u128 } - pub fn base_fee(&self) -> U256 { + pub fn base_fee(&self) -> u128 { if self.is_eip1559() { *self.base_fee.read() } else { - U256::zero() + 0 + } + } + + pub fn excess_blob_gas_and_price(&self) -> Option { + if self.is_eip4844() { + Some(self.blob_excess_gas_and_price.read().clone()) + } else { + None + } + } + + pub fn base_fee_per_blob_gas(&self) -> u128 { + if self.is_eip4844() { + self.blob_excess_gas_and_price.read().blob_gasprice + } else { + 0 } } /// Returns the suggested fee cap /// - /// This mirrors geth's auto values for `SuggestGasTipCap` which is: `priority fee + 2x current - /// basefee`. - pub fn max_priority_fee_per_gas(&self) -> U256 { - self.suggested_priority_fee() + *self.base_fee.read() * 2 + /// Note: This currently returns a constant value: [Self::suggested_priority_fee] + pub fn max_priority_fee_per_gas(&self) -> u128 { + self.suggested_priority_fee() } /// Returns the current gas price - pub fn set_gas_price(&self, price: U256) { + pub fn set_gas_price(&self, price: u128) { let mut gas = self.gas_price.write(); *gas = price; } /// Returns the current base fee - pub fn set_base_fee(&self, fee: U256) { + pub fn set_base_fee(&self, fee: u128) { trace!(target: "backend::fees", "updated base fee {:?}", fee); let mut base = self.base_fee.write(); *base = fee; } + /// Sets the current blob excess gas and price + pub fn set_blob_excess_gas_and_price(&self, blob_excess_gas_and_price: BlobExcessGasAndPrice) { + trace!(target: "backend::fees", "updated blob base fee {:?}", blob_excess_gas_and_price); + let mut base = self.blob_excess_gas_and_price.write(); + *base = blob_excess_gas_and_price; + } + /// Calculates the base fee for the next block pub fn get_next_block_base_fee_per_gas( &self, - gas_used: U256, - gas_limit: U256, - last_fee_per_gas: U256, - ) -> u64 { + gas_used: u128, + gas_limit: u128, + last_fee_per_gas: u128, + ) -> u128 { // It's naturally impossible for base fee to be 0; // It means it was set by the user deliberately and therefore we treat it as a constant. // Therefore, we skip the base fee calculation altogether and we return 0. - if self.base_fee() == U256::zero() { + if self.base_fee() == 0 { return 0 } - calculate_next_block_base_fee( - gas_used.as_u64(), - gas_limit.as_u64(), - last_fee_per_gas.as_u64(), - ) + calculate_next_block_base_fee(gas_used, gas_limit, last_fee_per_gas) } -} - -/// Calculate base fee for next block. [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) spec -pub fn calculate_next_block_base_fee(gas_used: u64, gas_limit: u64, base_fee: u64) -> u64 { - let gas_target = gas_limit / EIP1559_ELASTICITY_MULTIPLIER; - if gas_used == gas_target { - return base_fee + /// Calculates the next block blob base fee, using the provided excess blob gas + pub fn get_next_block_blob_base_fee_per_gas(&self, excess_blob_gas: u128) -> u128 { + crate::revm::primitives::calc_blob_gasprice(excess_blob_gas as u64) } - if gas_used > gas_target { - let gas_used_delta = gas_used - gas_target; - let base_fee_delta = std::cmp::max( - 1, - base_fee as u128 * gas_used_delta as u128 / - gas_target as u128 / - BASE_FEE_CHANGE_DENOMINATOR as u128, - ); - base_fee + (base_fee_delta as u64) - } else { - let gas_used_delta = gas_target - gas_used; - let base_fee_per_gas_delta = base_fee as u128 * gas_used_delta as u128 / - gas_target as u128 / - BASE_FEE_CHANGE_DENOMINATOR as u128; - - base_fee.saturating_sub(base_fee_per_gas_delta as u64) + + /// Calculates the next block blob excess gas, using the provided parent blob gas used and + /// parent blob excess gas + pub fn get_next_block_blob_excess_gas( + &self, + blob_gas_used: u128, + blob_excess_gas: u128, + ) -> u64 { + crate::revm::primitives::calc_excess_blob_gas(blob_gas_used as u64, blob_excess_gas as u64) } } +/// Calculate base fee for next block. [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) spec +pub fn calculate_next_block_base_fee(gas_used: u128, gas_limit: u128, base_fee: u128) -> u128 { + calc_next_block_base_fee(gas_used, gas_limit, base_fee, BaseFeeParams::ethereum()) +} + /// An async service that takes care of the `FeeHistory` cache pub struct FeeHistoryService { /// incoming notifications about new blocks @@ -199,12 +234,14 @@ impl FeeHistoryService { self.fee_history_limit } + /// Inserts a new cache entry for the given block + pub(crate) fn insert_cache_entry_for_block(&self, hash: B256) { + let (result, block_number) = self.create_cache_entry(hash); + self.insert_cache_entry(result, block_number); + } + /// Create a new history entry for the block - fn create_cache_entry( - &self, - hash: H256, - elasticity: f64, - ) -> (FeeHistoryCacheItem, Option) { + fn create_cache_entry(&self, hash: B256) -> (FeeHistoryCacheItem, Option) { // percentile list from 0.0 to 100.0 with a 0.5 resolution. // this will create 200 percentile points let reward_percentiles: Vec = { @@ -220,42 +257,53 @@ impl FeeHistoryService { let mut block_number: Option = None; let base_fee = self.fees.base_fee(); + let excess_blob_gas_and_price = self.fees.excess_blob_gas_and_price(); let mut item = FeeHistoryCacheItem { - base_fee: base_fee.as_u64(), + base_fee, gas_used_ratio: 0f64, + blob_gas_used_ratio: 0f64, rewards: Vec::new(), + excess_blob_gas: excess_blob_gas_and_price.as_ref().map(|g| g.excess_blob_gas as u128), + base_fee_per_blob_gas: excess_blob_gas_and_price.as_ref().map(|g| g.blob_gasprice), + blob_gas_used: excess_blob_gas_and_price.as_ref().map(|_| 0), }; let current_block = self.storage_info.block(hash); let current_receipts = self.storage_info.receipts(hash); if let (Some(block), Some(receipts)) = (current_block, current_receipts) { - block_number = Some(block.header.number.as_u64()); - - let gas_used = block.header.gas_used.as_u64() as f64; - let gas_limit = block.header.gas_limit.as_u64() as f64; + block_number = Some(block.header.number); - let gas_target = gas_limit / elasticity; - item.gas_used_ratio = gas_used / (gas_target * elasticity); + let gas_used = block.header.gas_used as f64; + let blob_gas_used = block.header.blob_gas_used.map(|g| g as f64); + item.gas_used_ratio = gas_used / block.header.gas_limit as f64; + item.blob_gas_used_ratio = + blob_gas_used.map(|g| g / MAX_DATA_GAS_PER_BLOCK as f64).unwrap_or(0 as f64); // extract useful tx info (gas_used, effective_reward) - let mut transactions: Vec<(u64, u64)> = receipts + let mut transactions: Vec<(u128, u128)> = receipts .iter() .enumerate() .map(|(i, receipt)| { - let gas_used = receipt.gas_used().as_u64(); + let gas_used = receipt.cumulative_gas_used(); let effective_reward = match block.transactions.get(i).map(|tx| &tx.transaction) { Some(TypedTransaction::Legacy(t)) => { - t.gas_price.saturating_sub(base_fee).as_u64() + t.tx().gas_price.saturating_sub(base_fee) } Some(TypedTransaction::EIP2930(t)) => { - t.gas_price.saturating_sub(base_fee).as_u64() + t.tx().gas_price.saturating_sub(base_fee) } Some(TypedTransaction::EIP1559(t)) => t + .tx() .max_priority_fee_per_gas - .min(t.max_fee_per_gas.saturating_sub(base_fee)) - .as_u64(), + .min(t.tx().max_fee_per_gas.saturating_sub(base_fee)), + // TODO: This probably needs to be extended to extract 4844 info. + Some(TypedTransaction::EIP4844(t)) => t + .tx() + .tx() + .max_priority_fee_per_gas + .min(t.tx().tx().max_fee_per_gas.saturating_sub(base_fee)), Some(TypedTransaction::Deposit(_)) => 0, None => 0, }; @@ -271,7 +319,7 @@ impl FeeHistoryService { item.rewards = reward_percentiles .into_iter() .filter_map(|p| { - let target_gas = (p * gas_used / 100f64) as u64; + let target_gas = (p * gas_used / 100f64) as u128; let mut sum_gas = 0; for (gas_used, effective_reward) in transactions.iter().cloned() { sum_gas += gas_used; @@ -315,11 +363,9 @@ impl Future for FeeHistoryService { while let Poll::Ready(Some(notification)) = pin.new_blocks.poll_next_unpin(cx) { let hash = notification.hash; - let elasticity = default_elasticity(); // add the imported block. - let (result, block_number) = pin.create_cache_entry(hash, elasticity); - pin.insert_cache_entry(result, block_number) + pin.insert_cache_entry_for_block(hash); } Poll::Pending @@ -329,63 +375,94 @@ impl Future for FeeHistoryService { pub type FeeHistoryCache = Arc>>; /// A single item in the whole fee history cache -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct FeeHistoryCacheItem { - pub base_fee: u64, + pub base_fee: u128, pub gas_used_ratio: f64, - pub rewards: Vec, + pub base_fee_per_blob_gas: Option, + pub blob_gas_used_ratio: f64, + pub excess_blob_gas: Option, + pub blob_gas_used: Option, + pub rewards: Vec, } -#[derive(Default, Clone)] +#[derive(Clone, Default)] pub struct FeeDetails { - pub gas_price: Option, - pub max_fee_per_gas: Option, - pub max_priority_fee_per_gas: Option, + pub gas_price: Option, + pub max_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, + pub max_fee_per_blob_gas: Option, } impl FeeDetails { /// All values zero pub fn zero() -> Self { Self { - gas_price: Some(U256::zero()), - max_fee_per_gas: Some(U256::zero()), - max_priority_fee_per_gas: Some(U256::zero()), + gas_price: Some(0), + max_fee_per_gas: Some(0), + max_priority_fee_per_gas: Some(0), + max_fee_per_blob_gas: None, } } /// If neither `gas_price` nor `max_fee_per_gas` is `Some`, this will set both to `0` pub fn or_zero_fees(self) -> Self { - let FeeDetails { gas_price, max_fee_per_gas, max_priority_fee_per_gas } = self; + let FeeDetails { + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + max_fee_per_blob_gas, + } = self; let no_fees = gas_price.is_none() && max_fee_per_gas.is_none(); - let gas_price = if no_fees { Some(U256::zero()) } else { gas_price }; - let max_fee_per_gas = if no_fees { Some(U256::zero()) } else { max_fee_per_gas }; + let gas_price = if no_fees { Some(0) } else { gas_price }; + let max_fee_per_gas = if no_fees { Some(0) } else { max_fee_per_gas }; + let max_fee_per_blob_gas = if no_fees { None } else { max_fee_per_blob_gas }; - Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas } + Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } } /// Turns this type into a tuple - pub fn split(self) -> (Option, Option, Option) { - let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas } = self; - (gas_price, max_fee_per_gas, max_priority_fee_per_gas) + pub fn split(self) -> (Option, Option, Option, Option) { + let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } = + self; + (gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas) } /// Creates a new instance from the request's gas related values pub fn new( - request_gas_price: Option, - request_max_fee: Option, - request_priority: Option, + request_gas_price: Option, + request_max_fee: Option, + request_priority: Option, + max_fee_per_blob_gas: Option, ) -> Result { - match (request_gas_price, request_max_fee, request_priority) { - (gas_price, None, None) => { + match (request_gas_price, request_max_fee, request_priority, max_fee_per_blob_gas) { + (gas_price, None, None, None) => { // Legacy request, all default to gas price. Ok(FeeDetails { gas_price, max_fee_per_gas: gas_price, max_priority_fee_per_gas: gas_price, + max_fee_per_blob_gas: None, + }) + } + (_, max_fee, max_priority, None) => { + // eip-1559 + // Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`. + if let Some(max_priority) = max_priority { + let max_fee = max_fee.unwrap_or_default(); + if max_priority > max_fee { + return Err(BlockchainError::InvalidFeeInput) + } + } + Ok(FeeDetails { + gas_price: max_fee, + max_fee_per_gas: max_fee, + max_priority_fee_per_gas: max_priority, + max_fee_per_blob_gas: None, }) } - (_, max_fee, max_priority) => { + (_, max_fee, max_priority, max_fee_per_blob_gas) => { // eip-1559 // Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`. if let Some(max_priority) = max_priority { @@ -398,6 +475,7 @@ impl FeeDetails { gas_price: max_fee, max_fee_per_gas: max_fee, max_priority_fee_per_gas: max_priority, + max_fee_per_blob_gas, }) } } diff --git a/crates/anvil/src/eth/miner.rs b/crates/anvil/src/eth/miner.rs index 0c1d1758db214..2199a50613511 100644 --- a/crates/anvil/src/eth/miner.rs +++ b/crates/anvil/src/eth/miner.rs @@ -1,7 +1,7 @@ //! Mines transactions use crate::eth::pool::{transactions::PoolTransaction, Pool}; -use ethers::prelude::TxHash; +use alloy_primitives::TxHash; use futures::{ channel::mpsc::Receiver, stream::{Fuse, Stream, StreamExt}, @@ -17,7 +17,7 @@ use std::{ }; use tokio::time::Interval; -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct Miner { /// The mode this miner currently operates in mode: Arc>, diff --git a/crates/anvil/src/eth/mod.rs b/crates/anvil/src/eth/mod.rs index a9acd1d9af4cd..393a9ff213306 100644 --- a/crates/anvil/src/eth/mod.rs +++ b/crates/anvil/src/eth/mod.rs @@ -1,5 +1,6 @@ pub mod api; pub mod otterscan; +pub mod sign; pub use api::EthApi; pub mod backend; @@ -10,5 +11,4 @@ pub mod fees; pub(crate) mod macros; pub mod miner; pub mod pool; -pub mod sign; pub mod util; diff --git a/crates/anvil/src/eth/otterscan/api.rs b/crates/anvil/src/eth/otterscan/api.rs index 95a255051ea9c..d2a1c7f0c1979 100644 --- a/crates/anvil/src/eth/otterscan/api.rs +++ b/crates/anvil/src/eth/otterscan/api.rs @@ -7,10 +7,9 @@ use crate::eth::{ macros::node_info, EthApi, }; -use ethers::types::{ - Action, Address, Block, BlockId, BlockNumber, Bytes, Call, Create, CreateResult, Res, Reward, - Transaction, TxHash, H256, U256, U64, -}; +use alloy_primitives::{Address, Bytes, B256, U256}; +use alloy_rpc_types::{Block, BlockId, BlockNumberOrTag as BlockNumber}; +use alloy_rpc_types_trace::parity::{Action, CreateAction, CreateOutput, TraceOutput}; use itertools::Itertools; impl EthApi { @@ -19,10 +18,7 @@ impl EthApi { /// /// As a faster alternative to eth_getBlockByNumber (by excluding uncle block /// information), which is not relevant in the context of an anvil node - pub async fn erigon_get_header_by_number( - &self, - number: BlockNumber, - ) -> Result>> { + pub async fn erigon_get_header_by_number(&self, number: BlockNumber) -> Result> { node_info!("ots_getApiLevel"); self.backend.block_by_number(number).await @@ -41,7 +37,7 @@ impl EthApi { /// certain transaction. pub async fn ots_get_internal_operations( &self, - hash: H256, + hash: B256, ) -> Result> { node_info!("ots_getInternalOperations"); @@ -59,23 +55,23 @@ impl EthApi { } /// Trace a transaction and generate a trace call tree. - pub async fn ots_trace_transaction(&self, hash: H256) -> Result> { + pub async fn ots_trace_transaction(&self, hash: B256) -> Result> { node_info!("ots_traceTransaction"); Ok(OtsTrace::batch_build(self.backend.trace_transaction(hash).await?)) } /// Given a transaction hash, returns its raw revert reason. - pub async fn ots_get_transaction_error(&self, hash: H256) -> Result> { + pub async fn ots_get_transaction_error(&self, hash: B256) -> Result { node_info!("ots_getTransactionError"); if let Some(receipt) = self.backend.mined_transaction_receipt(hash) { - if receipt.inner.status == Some(U64::zero()) { - return Ok(receipt.out) + if !receipt.inner.inner.as_receipt_with_bloom().receipt.status { + return Ok(receipt.out.map(|b| b.0.into()).unwrap_or(Bytes::default())) } } - Ok(Default::default()) + Ok(Bytes::default()) } /// For simplicity purposes, we return the entire block instead of emptying the values that @@ -96,7 +92,7 @@ impl EthApi { /// For simplicity purposes, we return the entire block instead of emptying the values that /// Otterscan doesn't want. This is the original purpose of the endpoint (to save bandwidth), /// but it doesn't seem necessary in the context of an anvil node - pub async fn ots_get_block_details_by_hash(&self, hash: H256) -> Result { + pub async fn ots_get_block_details_by_hash(&self, hash: B256) -> Result { node_info!("ots_getBlockDetailsByHash"); if let Some(block) = self.backend.block_by_hash(hash).await? { @@ -133,39 +129,34 @@ impl EthApi { ) -> Result { node_info!("ots_searchTransactionsBefore"); - let best = self.backend.best_number().as_u64(); + let best = self.backend.best_number(); // we go from given block (defaulting to best) down to first block // considering only post-fork - let from = if block_number == 0 { best } else { block_number }; + let from = if block_number == 0 { best } else { block_number - 1 }; let to = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1); - let first_page = from == best; + let first_page = from >= best; let mut last_page = false; let mut res: Vec<_> = vec![]; for n in (to..=from).rev() { - if n == to { - last_page = true; - } - if let Some(traces) = self.backend.mined_parity_trace_block(n) { let hashes = traces .into_iter() .rev() - .filter_map(|trace| match trace.action { - Action::Call(Call { from, to, .. }) if from == address || to == address => { - trace.transaction_hash - } - _ => None, - }) + .filter_map(|trace| OtsSearchTransactions::mentions_address(trace, address)) .unique(); - res.extend(hashes); - if res.len() >= page_size { break } + + res.extend(hashes); + } + + if n == to { + last_page = true; } } @@ -181,22 +172,19 @@ impl EthApi { ) -> Result { node_info!("ots_searchTransactionsAfter"); - let best = self.backend.best_number().as_u64(); + let best = self.backend.best_number(); // we go from the first post-fork block, up to the tip - let from = if block_number == 0 { - self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1) - } else { - block_number - }; + let first_block = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1); + let from = if block_number == 0 { first_block } else { block_number + 1 }; let to = best; - let first_page = from == best; + let mut first_page = from >= best; let mut last_page = false; let mut res: Vec<_> = vec![]; for n in from..=to { - if n == to { + if n == first_block { last_page = true; } @@ -204,28 +192,23 @@ impl EthApi { let hashes = traces .into_iter() .rev() - .filter_map(|trace| match trace.action { - Action::Call(Call { from, to, .. }) if from == address || to == address => { - trace.transaction_hash - } - Action::Create(Create { from, .. }) if from == address => { - trace.transaction_hash - } - Action::Reward(Reward { author, .. }) if author == address => { - trace.transaction_hash - } - _ => None, - }) + .filter_map(|trace| OtsSearchTransactions::mentions_address(trace, address)) .unique(); - res.extend(hashes); - if res.len() >= page_size { break } + + res.extend(hashes); + } + + if n == to { + first_page = true; } } + // Results are always sent in reverse chronological order, according to the Otterscan spec + res.reverse(); OtsSearchTransactions::build(res, &self.backend, first_page, last_page).await } @@ -236,17 +219,17 @@ impl EthApi { &self, address: Address, nonce: U256, - ) -> Result> { + ) -> Result> { node_info!("ots_getTransactionBySenderAndNonce"); let from = self.get_fork().map(|f| f.block_number() + 1).unwrap_or_default(); - let to = self.backend.best_number().as_u64(); + let to = self.backend.best_number(); for n in (from..=to).rev() { if let Some(txs) = self.backend.mined_transactions_by_block_number(n.into()).await { for tx in txs { - if tx.nonce == nonce && tx.from == address { - return Ok(Some(tx)) + if U256::from(tx.nonce) == nonce && tx.from == address { + return Ok(Some(tx.hash)) } } } @@ -264,16 +247,16 @@ impl EthApi { node_info!("ots_getContractCreator"); let from = self.get_fork().map(|f| f.block_number()).unwrap_or_default(); - let to = self.backend.best_number().as_u64(); + let to = self.backend.best_number(); // loop in reverse, since we want the latest deploy to the address for n in (from..=to).rev() { if let Some(traces) = self.backend.mined_parity_trace_block(n) { for trace in traces.into_iter().rev() { - match (trace.action, trace.result) { + match (trace.trace.action, trace.trace.result) { ( - Action::Create(Create { from, .. }), - Some(Res::Create(CreateResult { address, .. })), + Action::Create(CreateAction { from, .. }), + Some(TraceOutput::Create(CreateOutput { address, .. })), ) if address == addr => { return Ok(Some(OtsContractCreator { hash: trace.transaction_hash.unwrap(), diff --git a/crates/anvil/src/eth/otterscan/types.rs b/crates/anvil/src/eth/otterscan/types.rs index 9843e0536549f..7fe337d97a04a 100644 --- a/crates/anvil/src/eth/otterscan/types.rs +++ b/crates/anvil/src/eth/otterscan/types.rs @@ -2,36 +2,38 @@ use crate::eth::{ backend::mem::{storage::MinedTransaction, Backend}, error::{BlockchainError, Result}, }; -use alloy_primitives::U256 as rU256; -use ethers::types::{ - Action, Address, Block, Bytes, CallType, Trace, Transaction, TransactionReceipt, H256, U256, +use alloy_primitives::{Address, Bytes, FixedBytes, B256, U256 as rU256, U256}; +use alloy_rpc_types::{Block, BlockTransactions, Transaction, WithOtherFields}; +use alloy_rpc_types_trace::parity::{ + Action, CallAction, CallType, CreateAction, CreateOutput, LocalizedTransactionTrace, + RewardAction, TraceOutput, }; -use foundry_common::types::ToEthers; -use foundry_evm::{revm::interpreter::InstructionResult, utils::CallKind}; +use anvil_core::eth::transaction::ReceiptResponse; +use foundry_evm::{revm::interpreter::InstructionResult, traces::CallKind}; use futures::future::join_all; -use serde::{de::DeserializeOwned, Serialize}; +use serde::Serialize; use serde_repr::Serialize_repr; /// Patched Block struct, to include the additional `transactionCount` field expected by Otterscan -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase", bound = "TX: Serialize + DeserializeOwned")] -pub struct OtsBlock { +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OtsBlock { #[serde(flatten)] - pub block: Block, + pub block: Block, pub transaction_count: usize, } /// Block structure with additional details regarding fees and issuance -#[derive(Serialize, Debug)] +#[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct OtsBlockDetails { - pub block: OtsBlock, + pub block: OtsBlock, pub total_fees: U256, pub issuance: Issuance, } /// Issuance information for a block. Expected by Otterscan in ots_getBlockDetails calls -#[derive(Debug, Serialize, Default)] +#[derive(Debug, Default, Serialize)] pub struct Issuance { block_reward: U256, uncle_reward: U256, @@ -39,10 +41,10 @@ pub struct Issuance { } /// Holds both transactions and receipts for a block -#[derive(Serialize, Debug)] +#[derive(Clone, Serialize, Debug)] pub struct OtsBlockTransactions { - pub fullblock: OtsBlock, - pub receipts: Vec, + pub fullblock: OtsBlock, + pub receipts: Vec, } /// Patched Receipt struct, to include the additional `timestamp` field expected by Otterscan @@ -50,29 +52,29 @@ pub struct OtsBlockTransactions { #[serde(rename_all = "camelCase")] pub struct OtsTransactionReceipt { #[serde(flatten)] - receipt: TransactionReceipt, + receipt: ReceiptResponse, timestamp: u64, } /// Information about the creator address and transaction for a contract -#[derive(Serialize, Debug)] +#[derive(Debug, Serialize)] pub struct OtsContractCreator { - pub hash: H256, + pub hash: B256, pub creator: Address, } /// Paginated search results of an account's history -#[derive(Serialize, Debug)] +#[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct OtsSearchTransactions { - pub txs: Vec, + pub txs: Vec>, pub receipts: Vec, pub first_page: bool, pub last_page: bool, } /// Otterscan format for listing relevant internal operations -#[derive(Serialize, Debug, PartialEq)] +#[derive(Debug, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct OtsInternalOperation { pub r#type: OtsInternalOperationType, @@ -82,7 +84,7 @@ pub struct OtsInternalOperation { } /// Types of internal operations recognized by Otterscan -#[derive(Serialize_repr, Debug, PartialEq)] +#[derive(Debug, PartialEq, Serialize_repr)] #[repr(u8)] pub enum OtsInternalOperationType { Transfer = 0, @@ -92,7 +94,7 @@ pub enum OtsInternalOperationType { } /// Otterscan's representation of a trace -#[derive(Serialize, Debug, PartialEq)] +#[derive(Debug, PartialEq, Serialize)] pub struct OtsTrace { pub r#type: OtsTraceType, pub depth: usize, @@ -104,7 +106,7 @@ pub struct OtsTrace { /// The type of call being described by an Otterscan trace. Only CALL, STATICCALL and DELEGATECALL /// are represented -#[derive(Serialize, Debug, PartialEq)] +#[derive(Debug, PartialEq, Serialize)] #[serde(rename_all = "UPPERCASE")] pub enum OtsTraceType { Call, @@ -124,27 +126,32 @@ impl OtsBlockDetails { /// - It breaks the abstraction built in `OtsBlock` which computes `transaction_count` /// based on the existing list. /// Therefore we keep it simple by keeping the data in the response - pub async fn build(block: Block, backend: &Backend) -> Result { + pub async fn build(block: Block, backend: &Backend) -> Result { + let block_txs = match block.transactions.clone() { + BlockTransactions::Full(txs) => txs.into_iter().map(|tx| tx.hash).collect(), + BlockTransactions::Hashes(txs) => txs, + BlockTransactions::Uncle => return Err(BlockchainError::DataUnavailable), + }; let receipts_futs = - block.transactions.iter().map(|tx| async { backend.transaction_receipt(*tx).await }); + block_txs.iter().map(|tx| async { backend.transaction_receipt(*tx).await }); // fetch all receipts - let receipts: Vec = join_all(receipts_futs) + let receipts = join_all(receipts_futs) .await .into_iter() .map(|r| match r { Ok(Some(r)) => Ok(r), _ => Err(BlockchainError::DataUnavailable), }) - .collect::>()?; + .collect::>>()?; - let total_fees = receipts.iter().fold(U256::zero(), |acc, receipt| { - acc + receipt.gas_used.unwrap() * (receipt.effective_gas_price.unwrap()) - }); + let total_fees = receipts + .iter() + .fold(0, |acc, receipt| acc + receipt.gas_used * receipt.effective_gas_price); Ok(Self { block: block.into(), - total_fees, + total_fees: U256::from(total_fees), // issuance has no meaningful value in anvil's backend. just default to 0 issuance: Default::default(), }) @@ -153,9 +160,13 @@ impl OtsBlockDetails { /// Converts a regular block into the patched OtsBlock format /// which includes the `transaction_count` field -impl From> for OtsBlock { - fn from(block: Block) -> Self { - let transaction_count = block.transactions.len(); +impl From for OtsBlock { + fn from(block: Block) -> Self { + let transaction_count = match block.transactions { + BlockTransactions::Full(ref txs) => txs.len(), + BlockTransactions::Hashes(ref txs) => txs.len(), + BlockTransactions::Uncle => 0, + }; Self { block, transaction_count } } @@ -164,20 +175,34 @@ impl From> for OtsBlock { impl OtsBlockTransactions { /// Fetches all receipts for the blocks's transactions, as required by the [`ots_getBlockTransactions`](https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_getblockdetails) endpoint spec, and returns the final response object. pub async fn build( - mut block: Block, + mut block: Block, backend: &Backend, page: usize, page_size: usize, ) -> Result { - block.transactions = - block.transactions.into_iter().skip(page * page_size).take(page_size).collect(); + let block_txs = match block.transactions.clone() { + BlockTransactions::Full(txs) => txs.into_iter().map(|tx| tx.hash).collect(), + BlockTransactions::Hashes(txs) => txs, + BlockTransactions::Uncle => return Err(BlockchainError::DataUnavailable), + }; - let receipt_futs = block - .transactions - .iter() - .map(|tx| async { backend.transaction_receipt(tx.hash).await }); + let block_txs = + block_txs.into_iter().skip(page * page_size).take(page_size).collect::>(); + + block.transactions = match block.transactions { + BlockTransactions::Full(txs) => BlockTransactions::Full( + txs.into_iter().skip(page * page_size).take(page_size).collect(), + ), + BlockTransactions::Hashes(txs) => BlockTransactions::Hashes( + txs.into_iter().skip(page * page_size).take(page_size).collect(), + ), + BlockTransactions::Uncle => return Err(BlockchainError::DataUnavailable), + }; - let receipts: Vec = join_all(receipt_futs) + let receipt_futs = + block_txs.iter().map(|tx| async { backend.transaction_receipt(*tx).await }); + + let receipts = join_all(receipt_futs) .await .into_iter() .map(|r| match r { @@ -186,7 +211,7 @@ impl OtsBlockTransactions { }) .collect::>()?; - let fullblock: OtsBlock<_> = block.into(); + let fullblock: OtsBlock = block.into(); Ok(Self { fullblock, receipts }) } @@ -197,14 +222,14 @@ impl OtsSearchTransactions { /// `ots_searchTransactionsAfter`](lrequires not only the transactions, but also the /// corresponding receipts, which are fetched here before constructing the final) pub async fn build( - hashes: Vec, + hashes: Vec, backend: &Backend, first_page: bool, last_page: bool, ) -> Result { let txs_futs = hashes.iter().map(|hash| async { backend.transaction_by_hash(*hash).await }); - let txs: Vec = join_all(txs_futs) + let txs: Vec<_> = join_all(txs_futs) .await .into_iter() .map(|t| match t { @@ -229,6 +254,29 @@ impl OtsSearchTransactions { .collect::>>() .map(|receipts| Self { txs, receipts, first_page, last_page }) } + + pub fn mentions_address( + trace: LocalizedTransactionTrace, + address: Address, + ) -> Option> { + match (trace.trace.action, trace.trace.result) { + (Action::Call(CallAction { from, to, .. }), _) if from == address || to == address => { + trace.transaction_hash + } + (_, Some(TraceOutput::Create(CreateOutput { address: created_address, .. }))) + if created_address == address => + { + trace.transaction_hash + } + (Action::Create(CreateAction { from, .. }), _) if from == address => { + trace.transaction_hash + } + (Action::Reward(RewardAction { author, .. }), _) if author == address => { + trace.transaction_hash + } + _ => None, + } + } } impl OtsInternalOperation { @@ -238,35 +286,34 @@ impl OtsInternalOperation { traces .info .traces - .arena .iter() .filter_map(|node| { - match (node.kind(), node.status()) { + match (node.trace.kind, node.trace.status) { (CallKind::Call, _) if node.trace.value != rU256::ZERO => Some(Self { r#type: OtsInternalOperationType::Transfer, - from: node.trace.caller.to_ethers(), - to: node.trace.address.to_ethers(), - value: node.trace.value.to_ethers(), + from: node.trace.caller, + to: node.trace.address, + value: node.trace.value, }), (CallKind::Create, _) => Some(Self { r#type: OtsInternalOperationType::Create, - from: node.trace.caller.to_ethers(), - to: node.trace.address.to_ethers(), - value: node.trace.value.to_ethers(), + from: node.trace.caller, + to: node.trace.address, + value: node.trace.value, }), (CallKind::Create2, _) => Some(Self { r#type: OtsInternalOperationType::Create2, - from: node.trace.caller.to_ethers(), - to: node.trace.address.to_ethers(), - value: node.trace.value.to_ethers(), + from: node.trace.caller, + to: node.trace.address, + value: node.trace.value, }), (_, InstructionResult::SelfDestruct) => { Some(Self { r#type: OtsInternalOperationType::SelfDestruct, - from: node.trace.address.to_ethers(), + from: node.trace.address, // the foundry CallTraceNode doesn't have a refund address to: Default::default(), - value: node.trace.value.to_ethers(), + value: node.trace.value, }) } _ => None, @@ -279,26 +326,26 @@ impl OtsInternalOperation { impl OtsTrace { /// Converts the list of traces for a transaction into the expected Otterscan format, as /// specified in the [`ots_traceTransaction`](https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_tracetransaction) spec - pub fn batch_build(traces: Vec) -> Vec { + pub fn batch_build(traces: Vec) -> Vec { traces .into_iter() - .filter_map(|trace| match trace.action { + .filter_map(|trace| match trace.trace.action { Action::Call(call) => { if let Ok(ots_type) = call.call_type.try_into() { Some(OtsTrace { r#type: ots_type, - depth: trace.trace_address.len(), + depth: trace.trace.trace_address.len(), from: call.from, to: call.to, value: call.value, - input: call.input, + input: call.input.0.into(), }) } else { None } } Action::Create(_) => None, - Action::Suicide(_) => None, + Action::Selfdestruct(_) => None, Action::Reward(_) => None, }) .collect() diff --git a/crates/anvil/src/eth/pool/mod.rs b/crates/anvil/src/eth/pool/mod.rs index 4fca41febccf6..c94fafaca5780 100644 --- a/crates/anvil/src/eth/pool/mod.rs +++ b/crates/anvil/src/eth/pool/mod.rs @@ -36,11 +36,9 @@ use crate::{ }, mem::storage::MinedBlockOutcome, }; +use alloy_primitives::{Address, TxHash, U64}; +use alloy_rpc_types::txpool::TxpoolStatus; use anvil_core::eth::transaction::PendingTransaction; -use ethers::{ - prelude::TxpoolStatus, - types::{TxHash, U64}, -}; use futures::channel::mpsc::{channel, Receiver, Sender}; use parking_lot::{Mutex, RwLock}; use std::{collections::VecDeque, fmt, sync::Arc}; @@ -77,8 +75,8 @@ impl Pool { /// Returns the number of tx that are ready and queued for further execution pub fn txpool_status(&self) -> TxpoolStatus { // Note: naming differs here compared to geth's `TxpoolStatus` - let pending = self.ready_transactions().count().into(); - let queued = self.inner.read().pending_transactions.len().into(); + let pending: u64 = self.ready_transactions().count().try_into().unwrap_or(0); + let queued: u64 = self.inner.read().pending_transactions.len().try_into().unwrap_or(0); TxpoolStatus { pending, queued } } @@ -89,7 +87,7 @@ impl Pool { let MinedBlockOutcome { block_number, included, invalid } = outcome; // remove invalid transactions from the pool - self.remove_invalid(invalid.into_iter().map(|tx| *tx.hash()).collect()); + self.remove_invalid(invalid.into_iter().map(|tx| tx.hash()).collect()); // prune all the markers the mined transactions provide let res = self @@ -143,6 +141,11 @@ impl Pool { self.inner.write().remove_invalid(tx_hashes) } + /// Remove transactions by sender + pub fn remove_transactions_by_address(&self, sender: Address) -> Vec> { + self.inner.write().remove_transactions_by_address(sender) + } + /// Removes a single transaction from the pool /// /// This is similar to `[Pool::remove_invalid()]` but for a single transaction. @@ -163,6 +166,12 @@ impl Pool { dropped } + /// Removes all transactions from the pool + pub fn clear(&self) { + let mut pool = self.inner.write(); + pool.clear(); + } + /// notifies all listeners about the transaction fn notify_listener(&self, hash: TxHash) { let mut listener = self.transaction_listener.lock(); @@ -208,6 +217,12 @@ impl PoolInner { self.ready_transactions.get_transactions() } + /// Clears + fn clear(&mut self) { + self.ready_transactions.clear(); + self.pending_transactions.clear(); + } + /// checks both pools for the matching transaction /// /// Returns `None` if the transaction does not exist in the pool @@ -220,13 +235,31 @@ impl PoolInner { ) } + /// Returns an iterator over all transactions in the pool filtered by the sender + pub fn transactions_by_sender( + &self, + sender: Address, + ) -> impl Iterator> + '_ { + let pending_txs = self + .pending_transactions + .transactions() + .filter(move |tx| tx.pending_transaction.sender().eq(&sender)); + + let ready_txs = self + .ready_transactions + .get_transactions() + .filter(move |tx| tx.pending_transaction.sender().eq(&sender)); + + pending_txs.chain(ready_txs) + } + /// Returns true if this pool already contains the transaction fn contains(&self, tx_hash: &TxHash) -> bool { self.pending_transactions.contains(tx_hash) || self.ready_transactions.contains(tx_hash) } fn add_transaction(&mut self, tx: PoolTransaction) -> Result { - if self.contains(tx.hash()) { + if self.contains(&tx.hash()) { warn!(target: "txpool", "[{:?}] Already imported", tx.hash()); return Err(PoolError::AlreadyImported(Box::new(tx))) } @@ -236,7 +269,7 @@ impl PoolInner { // If all markers are not satisfied import to future if !tx.is_ready() { - let hash = *tx.transaction.hash(); + let hash = tx.transaction.hash(); self.pending_transactions.add_transaction(tx)?; return Ok(AddedTransaction::Pending { hash }) } @@ -248,7 +281,7 @@ impl PoolInner { &mut self, tx: PendingPoolTransaction, ) -> Result { - let hash = *tx.transaction.hash(); + let hash = tx.transaction.hash(); trace!(target: "txpool", "adding ready transaction [{:?}]", hash); let mut ready = ReadyTransaction::new(hash); @@ -263,7 +296,7 @@ impl PoolInner { self.pending_transactions.mark_and_unlock(¤t_tx.transaction.provides), ); - let current_hash = *current_tx.transaction.hash(); + let current_hash = current_tx.transaction.hash(); // try to add the transaction to the ready pool match self.ready_transactions.add_transaction(current_tx) { Ok(replaced_transactions) => { @@ -316,7 +349,7 @@ impl PoolInner { let mut promoted = vec![]; let mut failed = vec![]; for tx in imports { - let hash = *tx.transaction.hash(); + let hash = tx.transaction.hash(); match self.add_ready_transaction(tx) { Ok(res) => promoted.push(res), Err(e) => { @@ -344,6 +377,25 @@ impl PoolInner { removed } + + /// Remove transactions by sender address + pub fn remove_transactions_by_address(&mut self, sender: Address) -> Vec> { + let tx_hashes = + self.transactions_by_sender(sender).map(move |tx| tx.hash()).collect::>(); + + if tx_hashes.is_empty() { + return vec![] + } + + trace!(target: "txpool", "Removing transactions: {:?}", tx_hashes); + + let mut removed = self.ready_transactions.remove_with_markers(tx_hashes.clone(), None); + removed.extend(self.pending_transactions.remove(tx_hashes)); + + trace!(target: "txpool", "Removed transactions: {:?}", removed); + + removed + } } /// Represents the outcome of a prune @@ -375,7 +427,7 @@ impl fmt::Debug for PruneResult { } } -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ReadyTransaction { /// the hash of the submitted transaction hash: TxHash, @@ -398,7 +450,7 @@ impl ReadyTransaction { } } -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub enum AddedTransaction { /// transaction was successfully added and being processed Ready(ReadyTransaction), diff --git a/crates/anvil/src/eth/pool/transactions.rs b/crates/anvil/src/eth/pool/transactions.rs index e6b7890f6c8d9..9a229001d4054 100644 --- a/crates/anvil/src/eth/pool/transactions.rs +++ b/crates/anvil/src/eth/pool/transactions.rs @@ -1,6 +1,6 @@ use crate::eth::{error::PoolError, util::hex_fmt_many}; +use alloy_primitives::{Address, TxHash}; use anvil_core::eth::transaction::{PendingTransaction, TypedTransaction}; -use ethers::types::{Address, TxHash, U256}; use parking_lot::RwLock; use std::{ cmp::Ordering, @@ -25,7 +25,7 @@ pub fn to_marker(nonce: u64, from: Address) -> TxMarker { /// Modes that determine the transaction ordering of the mempool /// /// This type controls the transaction order via the priority metric of a transaction -#[derive(Debug, Clone, Eq, PartialEq, Copy, serde::Serialize, serde::Deserialize, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum TransactionOrder { /// Keep the pool transaction transactions sorted in the order they arrive. /// @@ -65,10 +65,10 @@ impl FromStr for TransactionOrder { /// Metric value for the priority of a transaction. /// -/// The `TransactionPriority` determines the ordering of two transactions that have all their +/// The `TransactionPriority` determines the ordering of two transactions that have all their /// markers satisfied. -#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default)] -pub struct TransactionPriority(pub U256); +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct TransactionPriority(pub u128); /// Internal Transaction type #[derive(Clone, PartialEq, Eq)] @@ -87,12 +87,12 @@ pub struct PoolTransaction { impl PoolTransaction { /// Returns the hash of this transaction - pub fn hash(&self) -> &TxHash { - self.pending_transaction.hash() + pub fn hash(&self) -> TxHash { + *self.pending_transaction.hash() } /// Returns the gas pric of this transaction - pub fn gas_price(&self) -> U256 { + pub fn gas_price(&self) -> u128 { self.pending_transaction.transaction.gas_price() } } @@ -112,7 +112,7 @@ impl fmt::Debug for PoolTransaction { /// A waiting pool of transaction that are pending, but not yet ready to be included in a new block. /// /// Keeps a set of transactions that are waiting for other transactions -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct PendingTransactions { /// markers that aren't yet provided by any transaction required_markers: HashMap>, @@ -134,6 +134,13 @@ impl PendingTransactions { self.waiting_queue.is_empty() } + /// Clears internal state + pub fn clear(&mut self) { + self.required_markers.clear(); + self.waiting_markers.clear(); + self.waiting_queue.clear(); + } + /// Returns an iterator over all transactions in the waiting pool pub fn transactions(&self) -> impl Iterator> + '_ { self.waiting_queue.values().map(|tx| tx.transaction.clone()) @@ -143,7 +150,7 @@ impl PendingTransactions { pub fn add_transaction(&mut self, tx: PendingPoolTransaction) -> Result<(), PoolError> { assert!(!tx.is_ready(), "transaction must not be ready"); assert!( - !self.waiting_queue.contains_key(tx.transaction.hash()), + !self.waiting_queue.contains_key(&tx.transaction.hash()), "transaction is already added" ); @@ -163,13 +170,13 @@ impl PendingTransactions { // add all missing markers for marker in &tx.missing_markers { - self.required_markers.entry(marker.clone()).or_default().insert(*tx.transaction.hash()); + self.required_markers.entry(marker.clone()).or_default().insert(tx.transaction.hash()); } // also track identifying markers - self.waiting_markers.insert(tx.transaction.provides.clone(), *tx.transaction.hash()); + self.waiting_markers.insert(tx.transaction.provides.clone(), tx.transaction.hash()); // add tx to the queue - self.waiting_queue.insert(*tx.transaction.hash(), tx); + self.waiting_queue.insert(tx.transaction.hash(), tx); Ok(()) } @@ -309,7 +316,7 @@ impl TransactionsIterator { self.independent.insert(tx_ref); } else { // otherwise we're still awaiting for some deps - self.awaiting.insert(*tx_ref.transaction.hash(), (satisfied, tx_ref)); + self.awaiting.insert(tx_ref.transaction.hash(), (satisfied, tx_ref)); } } } @@ -324,7 +331,7 @@ impl Iterator for TransactionsIterator { let hash = best.transaction.hash(); let ready = - if let Some(ready) = self.all.get(hash).cloned() { ready } else { continue }; + if let Some(ready) = self.all.get(&hash).cloned() { ready } else { continue }; // Insert transactions that just got unlocked. for hash in &ready.unlocks { @@ -349,7 +356,7 @@ impl Iterator for TransactionsIterator { } /// transactions that are ready to be included in a block. -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct ReadyTransactions { /// keeps track of transactions inserted in the pool /// @@ -377,6 +384,13 @@ impl ReadyTransactions { } } + /// Clears the internal state + pub fn clear(&mut self) { + self.provided_markers.clear(); + self.ready_tx.write().clear(); + self.independent_transactions.clear(); + } + /// Returns true if the transaction is part of the queue. pub fn contains(&self, hash: &TxHash) -> bool { self.ready_tx.read().contains_key(hash) @@ -409,14 +423,14 @@ impl ReadyTransactions { ) -> Result>, PoolError> { assert!(tx.is_ready(), "transaction must be ready",); assert!( - !self.ready_tx.read().contains_key(tx.transaction.hash()), + !self.ready_tx.read().contains_key(&tx.transaction.hash()), "transaction already included" ); let (replaced_tx, unlocks) = self.replaced_transactions(&tx.transaction)?; let id = self.next_id(); - let hash = *tx.transaction.hash(); + let hash = tx.transaction.hash(); let mut independent = true; let mut requires_offset = 0; @@ -473,7 +487,7 @@ impl ReadyTransactions { // construct a list of unlocked transactions // also check for transactions that shouldn't be replaced because underpriced let ready = self.ready_tx.read(); - for to_remove in remove_hashes.iter().filter_map(|hash| ready.get(hash)) { + for to_remove in remove_hashes.iter().filter_map(|hash| ready.get(*hash)) { // if we're attempting to replace a transaction that provides the exact same markers // (addr + nonce) then we check for gas price if to_remove.provides() == tx.provides { @@ -534,7 +548,7 @@ impl ReadyTransactions { let prev_hash = self.provided_markers.get(marker)?; let tx2 = ready.get_mut(prev_hash)?; // remove hash - if let Some(idx) = tx2.unlocks.iter().position(|i| i == hash) { + if let Some(idx) = tx2.unlocks.iter().position(|i| i == &hash) { tx2.unlocks.swap_remove(idx); } if tx2.unlocks.is_empty() { @@ -566,7 +580,7 @@ impl ReadyTransactions { for marker in &tx.provides { let removed = self.provided_markers.remove(marker); assert_eq!( - removed.as_ref(), + removed, if current_marker == marker { None } else { Some(tx.hash()) }, "The pool contains exactly one transaction providing given tag; the removed transaction claims to provide that tag, so it has to be mapped to it's hash; qed" @@ -631,7 +645,7 @@ impl ReadyTransactions { } /// A reference to a transaction in the pool -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct PoolTransactionRef { /// actual transaction pub transaction: Arc, @@ -662,7 +676,7 @@ impl Ord for PoolTransactionRef { } } -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ReadyTransaction { /// ref to the actual transaction pub transaction: PoolTransactionRef, @@ -679,7 +693,7 @@ impl ReadyTransaction { &self.transaction.transaction.provides } - pub fn gas_price(&self) -> U256 { + pub fn gas_price(&self) -> u128 { self.transaction.transaction.gas_price() } } diff --git a/crates/anvil/src/eth/sign.rs b/crates/anvil/src/eth/sign.rs index 112f8c7ab46b6..949a517fa7690 100644 --- a/crates/anvil/src/eth/sign.rs +++ b/crates/anvil/src/eth/sign.rs @@ -1,20 +1,14 @@ use crate::eth::error::BlockchainError; +use alloy_consensus::{SignableTransaction, Signed}; +use alloy_dyn_abi::TypedData; +use alloy_network::TxSignerSync; +use alloy_primitives::{Address, Signature, B256}; +use alloy_signer::Signer as AlloySigner; +use alloy_signer_wallet::LocalWallet; use anvil_core::eth::transaction::{ - DepositTransaction, DepositTransactionRequest, EIP1559Transaction, EIP1559TransactionRequest, - EIP2930Transaction, EIP2930TransactionRequest, LegacyTransaction, LegacyTransactionRequest, + optimism::{DepositTransaction, DepositTransactionRequest}, TypedTransaction, TypedTransactionRequest, }; -use ethers::{ - core::k256::ecdsa::SigningKey, - prelude::{Address, Wallet}, - signers::Signer as EthersSigner, - types::{ - transaction::{ - eip2718::TypedTransaction as EthersTypedTransactionRequest, eip712::TypedData, - }, - Signature, H256, U256, - }, -}; use std::collections::HashMap; /// A transaction signer @@ -31,13 +25,17 @@ pub trait Signer: Send + Sync { /// Returns the signature async fn sign(&self, address: Address, message: &[u8]) -> Result; - /// Encodes and signs the typed data according EIP-712. Payload must implement Eip712 trait. + /// Encodes and signs the typed data according EIP-712. Payload must conform to the EIP-712 + /// standard. async fn sign_typed_data( &self, address: Address, payload: &TypedData, ) -> Result; + /// Signs the given hash. + async fn sign_hash(&self, address: Address, hash: B256) -> Result; + /// signs a transaction request using the given account in request fn sign_transaction( &self, @@ -49,11 +47,11 @@ pub trait Signer: Send + Sync { /// Maintains developer keys pub struct DevSigner { addresses: Vec
, - accounts: HashMap>, + accounts: HashMap, } impl DevSigner { - pub fn new(accounts: Vec>) -> Self { + pub fn new(accounts: Vec) -> Self { let addresses = accounts.iter().map(|wallet| wallet.address()).collect::>(); let accounts = addresses.iter().cloned().zip(accounts).collect(); Self { addresses, accounts } @@ -81,8 +79,20 @@ impl Signer for DevSigner { address: Address, payload: &TypedData, ) -> Result { + let mut signer = + self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?.to_owned(); + + // Explicitly set chainID as none, to avoid any EIP-155 application to `v` when signing + // typed data. + signer.set_chain_id(None); + + Ok(signer.sign_dynamic_typed_data(payload).await?) + } + + async fn sign_hash(&self, address: Address, hash: B256) -> Result { let signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?; - Ok(signer.sign_typed_data(payload).await?) + + Ok(signer.sign_hash(&hash).await?) } fn sign_transaction( @@ -91,9 +101,13 @@ impl Signer for DevSigner { address: &Address, ) -> Result { let signer = self.accounts.get(address).ok_or(BlockchainError::NoSignerAvailable)?; - let ethers_tx: EthersTypedTransactionRequest = request.into(); - - Ok(signer.sign_transaction_sync(ðers_tx)?) + match request { + TypedTransactionRequest::Legacy(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), + TypedTransactionRequest::EIP2930(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), + TypedTransactionRequest::EIP1559(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), + TypedTransactionRequest::EIP4844(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), + TypedTransactionRequest::Deposit(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), + } } } @@ -108,92 +122,20 @@ pub fn build_typed_transaction( ) -> Result { let tx = match request { TypedTransactionRequest::Legacy(tx) => { - let LegacyTransactionRequest { - nonce, gas_price, gas_limit, kind, value, input, .. - } = tx; - TypedTransaction::Legacy(LegacyTransaction { - nonce, - gas_price, - gas_limit, - kind, - value, - input, - signature, - }) + let sighash = tx.signature_hash(); + TypedTransaction::Legacy(Signed::new_unchecked(tx, signature, sighash)) } TypedTransactionRequest::EIP2930(tx) => { - let EIP2930TransactionRequest { - chain_id, - nonce, - gas_price, - gas_limit, - kind, - value, - input, - access_list, - } = tx; - - let recid: u8 = signature.recovery_id()?.into(); - - TypedTransaction::EIP2930(EIP2930Transaction { - chain_id, - nonce, - gas_price, - gas_limit, - kind, - value, - input, - access_list: access_list.into(), - odd_y_parity: recid != 0, - r: { - let mut rarr = [0_u8; 32]; - signature.r.to_big_endian(&mut rarr); - H256::from(rarr) - }, - s: { - let mut sarr = [0_u8; 32]; - signature.s.to_big_endian(&mut sarr); - H256::from(sarr) - }, - }) + let sighash = tx.signature_hash(); + TypedTransaction::EIP2930(Signed::new_unchecked(tx, signature, sighash)) } TypedTransactionRequest::EIP1559(tx) => { - let EIP1559TransactionRequest { - chain_id, - nonce, - max_priority_fee_per_gas, - max_fee_per_gas, - gas_limit, - kind, - value, - input, - access_list, - } = tx; - - let recid: u8 = signature.recovery_id()?.into(); - - TypedTransaction::EIP1559(EIP1559Transaction { - chain_id, - nonce, - max_priority_fee_per_gas, - max_fee_per_gas, - gas_limit, - kind, - value, - input, - access_list: access_list.into(), - odd_y_parity: recid != 0, - r: { - let mut rarr = [0u8; 32]; - signature.r.to_big_endian(&mut rarr); - H256::from(rarr) - }, - s: { - let mut sarr = [0u8; 32]; - signature.s.to_big_endian(&mut sarr); - H256::from(sarr) - }, - }) + let sighash = tx.signature_hash(); + TypedTransaction::EIP1559(Signed::new_unchecked(tx, signature, sighash)) + } + TypedTransactionRequest::EIP4844(tx) => { + let sighash = tx.signature_hash(); + TypedTransaction::EIP4844(Signed::new_unchecked(tx, signature, sighash)) } TypedTransactionRequest::Deposit(tx) => { let DepositTransactionRequest { @@ -216,7 +158,7 @@ pub fn build_typed_transaction( source_hash, mint, is_system_tx, - nonce: U256::zero(), + nonce: 0, }) } }; diff --git a/crates/anvil/src/eth/util.rs b/crates/anvil/src/eth/util.rs index d3c6e9d7e97f5..07ea1d06537ce 100644 --- a/crates/anvil/src/eth/util.rs +++ b/crates/anvil/src/eth/util.rs @@ -1,15 +1,12 @@ -use ethers::abi::Address; -use foundry_common::types::ToEthers; -use foundry_evm::revm::{self, precompile::Precompiles, primitives::SpecId}; +use alloy_primitives::Address; +use foundry_evm::revm::{ + precompile::{PrecompileSpecId, Precompiles}, + primitives::SpecId, +}; use std::fmt; pub fn get_precompiles_for(spec_id: SpecId) -> Vec
{ - Precompiles::new(to_precompile_id(spec_id)) - .addresses() - .into_iter() - .copied() - .map(|item| item.to_ethers()) - .collect() + Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)).addresses().copied().collect() } /// wrapper type that displays byte as hex @@ -58,31 +55,4 @@ impl<'a> fmt::Debug for HexDisplay<'a> { } Ok(()) } -} - -pub fn to_precompile_id(spec_id: SpecId) -> revm::precompile::SpecId { - match spec_id { - SpecId::FRONTIER | - SpecId::FRONTIER_THAWING | - SpecId::HOMESTEAD | - SpecId::DAO_FORK | - SpecId::TANGERINE | - SpecId::SPURIOUS_DRAGON => revm::precompile::SpecId::HOMESTEAD, - SpecId::BYZANTIUM | SpecId::CONSTANTINOPLE | SpecId::PETERSBURG => { - revm::precompile::SpecId::BYZANTIUM - } - SpecId::ISTANBUL | SpecId::MUIR_GLACIER => revm::precompile::SpecId::ISTANBUL, - SpecId::BERLIN | - SpecId::LONDON | - SpecId::ARROW_GLACIER | - SpecId::GRAY_GLACIER | - SpecId::MERGE | - SpecId::SHANGHAI | - SpecId::CANCUN | - SpecId::PRAGUE | - SpecId::BEDROCK | - SpecId::REGOLITH | - SpecId::CANYON | - SpecId::LATEST => revm::precompile::SpecId::BERLIN, - } -} +} \ No newline at end of file diff --git a/crates/anvil/src/evm.rs b/crates/anvil/src/evm.rs new file mode 100644 index 0000000000000..de1dfbd51e2a9 --- /dev/null +++ b/crates/anvil/src/evm.rs @@ -0,0 +1,92 @@ +use std::{fmt::Debug, sync::Arc}; + +use alloy_primitives::Address; +use foundry_evm::revm::{self, precompile::Precompile, ContextPrecompile, ContextPrecompiles}; + +/// Object-safe trait that enables injecting extra precompiles when using +/// `anvil` as a library. +pub trait PrecompileFactory: Send + Sync + Unpin + Debug { + /// Returns a set of precompiles to extend the EVM with. + fn precompiles(&self) -> Vec<(Address, Precompile)>; +} + +/// Appends a handler register to `evm` that injects the given `precompiles`. +/// +/// This will add an additional handler that extends the default precompiles with the given set of +/// precompiles. +pub fn inject_precompiles( + evm: &mut revm::Evm<'_, I, DB>, + precompiles: Vec<(Address, Precompile)>, +) where + DB: revm::Database, +{ + evm.handler.append_handler_register_box(Box::new(move |handler| { + let precompiles = precompiles.clone(); + let loaded_precompiles = handler.pre_execution().load_precompiles(); + handler.pre_execution.load_precompiles = Arc::new(move || { + let mut loaded_precompiles = loaded_precompiles.clone(); + loaded_precompiles.extend( + precompiles + .clone() + .into_iter() + .map(|(addr, p)| (addr, ContextPrecompile::Ordinary(p))), + ); + let mut default_precompiles = ContextPrecompiles::default(); + default_precompiles.extend(loaded_precompiles); + default_precompiles + }); + })); +} + +#[cfg(test)] +mod tests { + use crate::{evm::inject_precompiles, PrecompileFactory}; + use alloy_primitives::Address; + use foundry_evm::revm::{ + self, + primitives::{address, Bytes, Precompile, PrecompileResult, SpecId}, + }; + + #[test] + fn build_evm_with_extra_precompiles() { + const PRECOMPILE_ADDR: Address = address!("0000000000000000000000000000000000000071"); + fn my_precompile(_bytes: &Bytes, _gas_limit: u64) -> PrecompileResult { + Ok((0, Bytes::new())) + } + + #[derive(Debug)] + struct CustomPrecompileFactory; + + impl PrecompileFactory for CustomPrecompileFactory { + fn precompiles(&self) -> Vec<(Address, Precompile)> { + vec![(PRECOMPILE_ADDR, Precompile::Standard(my_precompile))] + } + } + + let db = revm::db::EmptyDB::default(); + let env = Box::::default(); + let spec = SpecId::LATEST; + let handler_cfg = revm::primitives::HandlerCfg::new(spec); + let inspector = revm::inspectors::NoOpInspector; + let context = revm::Context::new(revm::EvmContext::new_with_env(db, env), inspector); + let handler = revm::Handler::new(handler_cfg); + let mut evm = revm::Evm::new(context, handler); + assert!(!evm + .handler + .pre_execution() + .load_precompiles() + .addresses() + .any(|&addr| addr == PRECOMPILE_ADDR)); + + inject_precompiles(&mut evm, CustomPrecompileFactory.precompiles()); + assert!(evm + .handler + .pre_execution() + .load_precompiles() + .addresses() + .any(|&addr| addr == PRECOMPILE_ADDR)); + + let result = evm.transact().unwrap(); + assert!(result.result.is_success()); + } +} diff --git a/crates/anvil/src/filter.rs b/crates/anvil/src/filter.rs index e6083df27b423..268a8f46eb58d 100644 --- a/crates/anvil/src/filter.rs +++ b/crates/anvil/src/filter.rs @@ -4,12 +4,10 @@ use crate::{ pubsub::filter_logs, StorageInfo, }; +use alloy_primitives::TxHash; +use alloy_rpc_types::{Filter, FilteredParams, Log}; use anvil_core::eth::subscription::SubscriptionId; use anvil_rpc::response::ResponseResult; -use ethers::{ - prelude::{Log as EthersLog, H256 as TxHash}, - types::{Filter, FilteredParams}, -}; use futures::{channel::mpsc::Receiver, Stream, StreamExt}; use std::{ collections::HashMap, @@ -27,7 +25,7 @@ type FilterMap = Arc>>; pub const ACTIVE_FILTER_TIMEOUT_SECS: u64 = 60 * 5; /// Contains all registered filters -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct Filters { /// all currently active filters active_filters: FilterMap, @@ -164,14 +162,14 @@ pub struct LogsFilter { /// existing logs that matched the filter when the listener was installed /// /// They'll be returned on the first pill - pub historic: Option>, + pub historic: Option>, } // === impl LogsFilter === impl LogsFilter { /// Returns all the logs since the last time this filter was polled - pub fn poll(&mut self, cx: &mut Context<'_>) -> Vec { + pub fn poll(&mut self, cx: &mut Context<'_>) -> Vec { let mut logs = self.historic.take().unwrap_or_default(); while let Poll::Ready(Some(block)) = self.blocks.poll_next_unpin(cx) { let b = self.storage.block(block.hash); diff --git a/crates/anvil/src/genesis.rs b/crates/anvil/src/genesis.rs deleted file mode 100644 index 43ba7030b8013..0000000000000 --- a/crates/anvil/src/genesis.rs +++ /dev/null @@ -1,300 +0,0 @@ -//! Bindings for geth's `genesis.json` format -use crate::revm::primitives::AccountInfo; -use ethers::{ - signers::LocalWallet, - types::{serde_helpers::*, Address, Bytes, H256, U256}, -}; -use foundry_common::{errors::FsPathError, types::ToAlloy}; -use foundry_evm::revm::primitives::{Bytecode, Env, KECCAK_EMPTY, U256 as rU256}; -use serde::{Deserialize, Serialize}; -use std::{ - collections::{BTreeMap, HashMap}, - path::Path, -}; - -/// Genesis specifies the header fields, state of a genesis block. It also defines hard fork -/// switch-over blocks through the chain configuration See also: -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Genesis { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub config: Option, - #[serde( - default, - deserialize_with = "deserialize_stringified_u64_opt", - skip_serializing_if = "Option::is_none" - )] - pub nonce: Option, - #[serde( - default, - deserialize_with = "deserialize_stringified_u64_opt", - skip_serializing_if = "Option::is_none" - )] - pub timestamp: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extra_data: Option, - #[serde(deserialize_with = "deserialize_stringified_u64")] - pub gas_limit: u64, - #[serde(deserialize_with = "deserialize_stringified_u64")] - pub difficulty: u64, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub mix_hash: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub coinbase: Option
, - #[serde(default)] - pub alloc: Alloc, - #[serde( - default, - deserialize_with = "deserialize_stringified_u64_opt", - skip_serializing_if = "Option::is_none" - )] - pub number: Option, - #[serde( - default, - deserialize_with = "deserialize_stringified_u64_opt", - skip_serializing_if = "Option::is_none" - )] - pub gas_used: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub parent_hash: Option, - #[serde( - default, - deserialize_with = "deserialize_stringified_numeric_opt", - skip_serializing_if = "Option::is_none" - )] - pub base_fee_per_gas: Option, -} - -impl Genesis { - /// Loads the `Genesis` object from the given json file path - pub fn load(path: impl AsRef) -> Result { - foundry_common::fs::read_json_file(path.as_ref()) - } - - /// The clap `value_parser` function - pub(crate) fn parse(path: &str) -> Result { - Self::load(path).map_err(|err| err.to_string()) - } - - pub fn chain_id(&self) -> Option { - self.config.as_ref().and_then(|c| c.chain_id) - } - - /// Applies all settings to the given `env` - pub fn apply(&self, env: &mut Env) { - if let Some(chain_id) = self.chain_id() { - env.cfg.chain_id = chain_id; - } - if let Some(timestamp) = self.timestamp { - env.block.timestamp = rU256::from(timestamp); - } - if let Some(base_fee) = self.base_fee_per_gas { - env.block.basefee = base_fee.to_alloy(); - } - if let Some(number) = self.number { - env.block.number = rU256::from(number); - } - if let Some(coinbase) = self.coinbase { - env.block.coinbase = coinbase.to_alloy(); - } - env.block.difficulty = rU256::from(self.difficulty); - env.block.gas_limit = rU256::from(self.gas_limit); - } - - /// Returns all private keys from the genesis accounts, if they exist - pub fn private_keys(&self) -> Vec { - self.alloc.accounts.values().filter_map(|acc| acc.private_key.clone()).collect() - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, Default)] -#[serde(transparent)] -pub struct Alloc { - pub accounts: BTreeMap, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct GenesisAccount { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub code: Option, - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - pub storage: HashMap, - #[serde(deserialize_with = "deserialize_stringified_numeric")] - pub balance: U256, - #[serde( - default, - deserialize_with = "deserialize_stringified_u64_opt", - skip_serializing_if = "Option::is_none" - )] - pub nonce: Option, - #[serde( - rename = "secretKey", - default, - skip_serializing_if = "Option::is_none", - with = "secret_key" - )] - pub private_key: Option, -} - -impl From for AccountInfo { - fn from(acc: GenesisAccount) -> Self { - let GenesisAccount { code, balance, nonce, .. } = acc; - let code = code.map(|code| Bytecode::new_raw(code.to_vec().into())); - AccountInfo { - balance: balance.to_alloy(), - nonce: nonce.unwrap_or_default(), - code_hash: code.as_ref().map(|code| code.hash_slow()).unwrap_or(KECCAK_EMPTY), - code, - } - } -} - -/// ChainConfig is the core config which determines the blockchain settings. -/// -/// ChainConfig is stored in the database on a per block basis. This means -/// that any network, identified by its genesis block, can have its own -/// set of configuration options. -/// <(https://github.com/ethereum/go-ethereum/blob/0ce494b60cd00d70f1f9f2dd0b9bfbd76204168a/params/config.go#L342-L387> -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Config { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub chain_id: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub homestead_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub dao_fork_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub dao_fork_support: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub eip150_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub eip150_hash: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub eip155_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub eip158_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub byzantium_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub constantinople_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub petersburg_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub istanbul_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub muir_glacier_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub berlin_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub london_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub arrow_glacier_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub gray_glacier_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub merge_netsplit_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub shanghai_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub cancun_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub prague_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub terminal_total_difficulty: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub terminal_total_difficulty_passed: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub ethash: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub clique: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct EthashConfig {} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct CliqueConfig { - pub period: u64, - pub epoch: u64, -} - -/// serde support for `secretKey` in genesis - -pub mod secret_key { - use ethers::{core::k256::SecretKey, signers::LocalWallet, types::Bytes}; - use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; - - pub fn serialize(value: &Option, serializer: S) -> Result - where - S: Serializer, - { - if let Some(wallet) = value { - Bytes::from(wallet.signer().to_bytes().as_ref()).serialize(serializer) - } else { - serializer.serialize_none() - } - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - if let Some(s) = Option::::deserialize(deserializer)? { - if s.is_empty() { - return Ok(None) - } - SecretKey::from_bytes(s.as_ref().into()) - .map_err(de::Error::custom) - .map(Into::into) - .map(Some) - } else { - Ok(None) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_parse_genesis_json() { - let s = r#"{ - "config": { - "chainId": 19763, - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "ethash": {} - }, - "nonce": "0xdeadbeefdeadbeef", - "timestamp": "0x0", - "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x80000000", - "difficulty": "0x20000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", - "alloc": { - "71562b71999873db5b286df957af199ec94617f7": { - "balance": "0xffffffffffffffffffffffffff", - "secretkey": "0x305b526d493844b63466be6d48a424ab83f5216011eef860acc6db4c1821adc9" - } - }, - "number": "0x0", - "gasUsed": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" -} -"#; - - let gen: Genesis = serde_json::from_str(s).unwrap(); - assert_eq!(gen.nonce, Some(16045690984833335023)); - assert_eq!(gen.gas_limit, 2147483648); - assert_eq!(gen.difficulty, 131072); - assert_eq!(gen.alloc.accounts.len(), 1); - let config = gen.config.unwrap(); - assert_eq!(config.chain_id, Some(19763)); - } -} diff --git a/crates/anvil/src/hardfork.rs b/crates/anvil/src/hardfork.rs index 62e20c3e9e5d6..25042b1fdbb49 100644 --- a/crates/anvil/src/hardfork.rs +++ b/crates/anvil/src/hardfork.rs @@ -1,9 +1,9 @@ +use alloy_rpc_types::BlockNumberOrTag; use ethereum_forkid::{ForkHash, ForkId}; -use ethers::types::BlockNumber; use foundry_evm::revm::primitives::SpecId; use std::str::FromStr; -#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum Hardfork { Frontier, Homestead, @@ -45,10 +45,10 @@ impl Hardfork { Hardfork::ArrowGlacier => 13773000, Hardfork::GrayGlacier => 15050000, Hardfork::Paris => 15537394, - Hardfork::Shanghai | Hardfork::Latest => 17034870, + Hardfork::Shanghai => 17034870, + Hardfork::Cancun | Hardfork::Latest => 19426587, - // TODO: set block number after activation - Hardfork::Cancun => unreachable!(), + // TODO: set fork block num once known Hardfork::Prague => unreachable!(), } } @@ -96,7 +96,7 @@ impl Hardfork { Hardfork::Paris => ForkId { hash: ForkHash([0x4f, 0xb8, 0xa8, 0x72]), next: 17034870 }, Hardfork::Shanghai | Hardfork::Latest => { // update `next` when another fork block num is known - ForkId { hash: ForkHash([0xc1, 0xfd, 0xf1, 0x81]), next: 0 } + ForkId { hash: ForkHash([0xc1, 0xfd, 0xf1, 0x81]), next: 19426587 } } Hardfork::Cancun => { // TODO: set fork hash once known @@ -159,20 +159,18 @@ impl From for SpecId { Hardfork::ArrowGlacier => SpecId::LONDON, Hardfork::GrayGlacier => SpecId::GRAY_GLACIER, Hardfork::Paris => SpecId::MERGE, - Hardfork::Shanghai | Hardfork::Latest => SpecId::SHANGHAI, - - // TODO: switch to latest after activation - Hardfork::Cancun => SpecId::CANCUN, + Hardfork::Shanghai => SpecId::SHANGHAI, Hardfork::Prague => SpecId::PRAGUE, + Hardfork::Cancun | Hardfork::Latest => SpecId::CANCUN, } } } -impl> From for Hardfork { - fn from(block: T) -> Hardfork { +impl> From for Hardfork { + fn from(block: T) -> Self { let num = match block.into() { - BlockNumber::Earliest => 0, - BlockNumber::Number(num) => num.as_u64(), + BlockNumberOrTag::Earliest => 0, + BlockNumberOrTag::Number(num) => num, _ => u64::MAX, }; @@ -190,6 +188,7 @@ impl> From for Hardfork { _i if num < 13_773_000 => Hardfork::London, _i if num < 15_050_000 => Hardfork::ArrowGlacier, _i if num < 17_034_870 => Hardfork::Paris, + _i if num < 19_426_587 => Hardfork::Shanghai, _ => Hardfork::Latest, } } @@ -198,8 +197,8 @@ impl> From for Hardfork { #[cfg(test)] mod tests { use crate::Hardfork; + use alloy_primitives::hex; use crc::{Crc, CRC_32_ISO_HDLC}; - use ethers::utils::hex; #[test] fn test_hardfork_blocks() { diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index 4615f6ca7f28c..ce6a85411e8e1 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -16,14 +16,10 @@ use crate::{ shutdown::Signal, tasks::TaskManager, }; +use alloy_primitives::{Address, U256}; +use alloy_signer_wallet::LocalWallet; use eth::backend::fork::ClientFork; -use ethers::{ - core::k256::ecdsa::SigningKey, - prelude::Wallet, - signers::Signer, - types::{Address, U256}, -}; -use foundry_common::{ProviderBuilder, RetryProvider}; +use foundry_common::provider::{ProviderBuilder, RetryProvider}; use foundry_evm::revm; use futures::{FutureExt, TryFutureExt}; use parking_lot::Mutex; @@ -34,7 +30,6 @@ use std::{ pin::Pin, sync::Arc, task::{Context, Poll}, - time::Duration, }; use tokio::{ runtime::Handle, @@ -55,10 +50,11 @@ pub use hardfork::Hardfork; /// ethereum related implementations pub mod eth; +/// Evm related abstractions +mod evm; +pub use evm::{inject_precompiles, PrecompileFactory}; /// support for polling filters pub mod filter; -/// support for handling `genesis.json` files -pub mod genesis; /// commandline output pub mod logging; /// types for subscriptions @@ -74,26 +70,56 @@ mod tasks; #[cfg(feature = "cmd")] pub mod cmd; -/// Creates the node and runs the server +/// Creates the node and runs the server. /// /// Returns the [EthApi] that can be used to interact with the node and the [JoinHandle] of the /// task. /// -/// # Example +/// # Panics +/// +/// Panics if any error occurs. For a non-panicking version, use [`try_spawn`]. /// -/// ```rust +/// +/// # Examples +/// +/// ```no_run /// # use anvil::NodeConfig; -/// # async fn spawn() { +/// # async fn spawn() -> eyre::Result<()> { /// let config = NodeConfig::default(); /// let (api, handle) = anvil::spawn(config).await; /// /// // use api /// /// // wait forever -/// handle.await.unwrap(); +/// handle.await.unwrap().unwrap(); +/// # Ok(()) +/// # } +/// ``` +pub async fn spawn(config: NodeConfig) -> (EthApi, NodeHandle) { + try_spawn(config).await.expect("failed to spawn node") +} + +/// Creates the node and runs the server +/// +/// Returns the [EthApi] that can be used to interact with the node and the [JoinHandle] of the +/// task. +/// +/// # Examples +/// +/// ```no_run +/// # use anvil::NodeConfig; +/// # async fn spawn() -> eyre::Result<()> { +/// let config = NodeConfig::default(); +/// let (api, handle) = anvil::try_spawn(config).await?; +/// +/// // use api +/// +/// // wait forever +/// handle.await??; +/// # Ok(()) /// # } /// ``` -pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) { +pub async fn try_spawn(mut config: NodeConfig) -> io::Result<(EthApi, NodeHandle)> { let logger = if config.enable_tracing { init_tracing() } else { Default::default() }; logger.set_enabled(!config.silent); @@ -133,11 +159,14 @@ pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) { let dev_signer: Box = Box::new(DevSigner::new(signer_accounts)); let mut signers = vec![dev_signer]; if let Some(genesis) = genesis { - // include all signers from genesis.json if any - let genesis_signers = genesis.private_keys(); + let genesis_signers = genesis + .alloc + .values() + .filter_map(|acc| acc.private_key) + .flat_map(|k| LocalWallet::from_bytes(&k)) + .collect::>(); if !genesis_signers.is_empty() { - let genesis_signers: Box = Box::new(DevSigner::new(genesis_signers)); - signers.push(genesis_signers); + signers.push(Box::new(DevSigner::new(genesis_signers))); } } @@ -149,6 +178,12 @@ pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) { fees, StorageInfo::new(Arc::clone(&backend)), ); + // create an entry for the best block + if let Some(best_block) = + backend.get_block(backend.best_number()).map(|block| block.header.hash_slow()) + { + fee_history_service.insert_cache_entry_for_block(best_block); + } let filters = Filters::default(); @@ -169,18 +204,19 @@ pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) { let node_service = tokio::task::spawn(NodeService::new(pool, backend, miner, fee_history_service, filters)); - let mut servers = Vec::new(); - let mut addresses = Vec::new(); + let mut servers = Vec::with_capacity(config.host.len()); + let mut addresses = Vec::with_capacity(config.host.len()); - for addr in config.host.iter() { - let sock_addr = SocketAddr::new(addr.to_owned(), port); - let srv = server::serve(sock_addr, api.clone(), server_config.clone()); + for addr in &config.host { + let sock_addr = SocketAddr::new(*addr, port); - addresses.push(srv.local_addr()); + // Create a TCP listener. + let tcp_listener = tokio::net::TcpListener::bind(sock_addr).await?; + addresses.push(tcp_listener.local_addr()?); - // spawn the server on a new task - let srv = tokio::task::spawn(srv.map_err(NodeError::from)); - servers.push(srv); + // Spawn the server future on a new task. + let srv = server::serve_on(tcp_listener, api.clone(), server_config.clone()); + servers.push(tokio::task::spawn(srv.map_err(Into::into))); } let tokio_handle = Handle::current(); @@ -201,10 +237,10 @@ pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) { handle.print(fork.as_ref()); - (api, handle) + Ok((api, handle)) } -type IpcTask = JoinHandle>; +type IpcTask = JoinHandle<()>; /// A handle to the spawned node and server tasks /// @@ -235,6 +271,9 @@ impl NodeHandle { pub(crate) fn print(&self, fork: Option<&ClientFork>) { self.config.print(fork); if !self.config.silent { + if let Some(ipc_path) = self.ipc_path() { + println!("IPC path: {}", ipc_path); + } println!( "Listening on {}", self.addresses @@ -242,7 +281,7 @@ impl NodeHandle { .map(|addr| { addr.to_string() }) .collect::>() .join(", ") - ) + ); } } @@ -271,10 +310,8 @@ impl NodeHandle { /// Constructs a [`RetryProvider`] for this handle's HTTP endpoint. pub fn http_provider(&self) -> RetryProvider { - ProviderBuilder::new(&self.http_endpoint()) - .build() - .expect("failed to build HTTP provider") - .interval(Duration::from_millis(500)) + ProviderBuilder::new(&self.http_endpoint()).build().expect("failed to build HTTP provider") + // .interval(Duration::from_millis(500)) } /// Constructs a [`RetryProvider`] for this handle's WS endpoint. @@ -293,7 +330,7 @@ impl NodeHandle { } /// Signer accounts that can sign messages/transactions from the EVM node - pub fn dev_wallets(&self) -> impl Iterator> + '_ { + pub fn dev_wallets(&self) -> impl Iterator + '_ { self.config.signer_accounts.iter().cloned() } @@ -308,7 +345,7 @@ impl NodeHandle { } /// Default gas price for all txs - pub fn gas_price(&self) -> U256 { + pub fn gas_price(&self) -> u128 { self.config.get_gas_price() } @@ -353,7 +390,7 @@ impl Future for NodeHandle { // poll the ipc task if let Some(mut ipc) = pin.ipc_task.take() { if let Poll::Ready(res) = ipc.poll_unpin(cx) { - return Poll::Ready(res.map(|res| res.map_err(NodeError::from))) + return Poll::Ready(res.map(|()| Ok(()))); } else { pin.ipc_task = Some(ipc); } @@ -361,13 +398,13 @@ impl Future for NodeHandle { // poll the node service task if let Poll::Ready(res) = pin.node_service.poll_unpin(cx) { - return Poll::Ready(res) + return Poll::Ready(res); } // poll the axum server handles for server in pin.servers.iter_mut() { if let Poll::Ready(res) = server.poll_unpin(cx) { - return Poll::Ready(res) + return Poll::Ready(res); } } diff --git a/crates/anvil/src/logging.rs b/crates/anvil/src/logging.rs index ecab2000ec5ae..5819aafc99c2d 100644 --- a/crates/anvil/src/logging.rs +++ b/crates/anvil/src/logging.rs @@ -12,7 +12,7 @@ pub(crate) const NODE_USER_LOG_TARGET: &str = "node::user"; /// /// This layer is intended to be used as filter for `NODE_USER_LOG_TARGET` events that will /// eventually be logged to stdout -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct NodeLogLayer { state: LoggingManager, } @@ -45,7 +45,7 @@ where } /// Contains the configuration of the logger -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct LoggingManager { /// Whether the logger is currently enabled pub enabled: Arc>, diff --git a/crates/anvil/src/pubsub.rs b/crates/anvil/src/pubsub.rs index 0c58327959315..a063ee6697b2f 100644 --- a/crates/anvil/src/pubsub.rs +++ b/crates/anvil/src/pubsub.rs @@ -1,17 +1,11 @@ use crate::{ eth::{backend::notifications::NewBlockNotifications, error::to_rpc_result}, - StorageInfo, U256, -}; -use anvil_core::eth::{ - block::Block, - receipt::{EIP658Receipt, Log, TypedReceipt}, - subscription::{SubscriptionId, SubscriptionResult}, + StorageInfo, }; +use alloy_primitives::{TxHash, B256}; +use alloy_rpc_types::{pubsub::SubscriptionResult, FilteredParams, Log}; +use anvil_core::eth::{block::Block, subscription::SubscriptionId, transaction::TypedReceipt}; use anvil_rpc::{request::Version, response::ResponseResult}; -use ethers::{ - prelude::{Log as EthersLog, H256, H256 as TxHash, U64}, - types::FilteredParams, -}; use futures::{channel::mpsc::Receiver, ready, Stream, StreamExt}; use serde::Serialize; use std::{ @@ -26,7 +20,7 @@ pub struct LogsSubscription { pub blocks: NewBlockNotifications, pub storage: StorageInfo, pub filter: FilteredParams, - pub queued: VecDeque, + pub queued: VecDeque, pub id: SubscriptionId, } @@ -40,7 +34,7 @@ impl LogsSubscription { subscription: self.id.clone(), result: to_rpc_result(log), }; - return Poll::Ready(Some(EthSubscriptionResponse::new(params))) + return Poll::Ready(Some(EthSubscriptionResponse::new(params))); } if let Some(block) = ready!(self.blocks.poll_next_unpin(cx)) { @@ -52,22 +46,22 @@ impl LogsSubscription { // this ensures we poll the receiver until it is pending, in which case the // underlying `UnboundedReceiver` will register the new waker, see // [`futures::channel::mpsc::UnboundedReceiver::poll_next()`] - continue + continue; } self.queued.extend(logs) } } else { - return Poll::Ready(None) + return Poll::Ready(None); } if self.queued.is_empty() { - return Poll::Pending + return Poll::Pending; } } } } -#[derive(Debug, PartialEq, Eq, Clone, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct EthSubscriptionResponse { jsonrpc: Version, method: &'static str, @@ -83,7 +77,7 @@ impl EthSubscriptionResponse { } /// Represents the `params` field of an `eth_subscription` event -#[derive(Debug, PartialEq, Eq, Clone, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct EthSubscriptionParams { subscription: SubscriptionId, #[serde(flatten)] @@ -115,10 +109,10 @@ impl EthSubscription { subscription: id.clone(), result: to_rpc_result(block), }; - return Poll::Ready(Some(EthSubscriptionResponse::new(params))) + return Poll::Ready(Some(EthSubscriptionResponse::new(params))); } } else { - return Poll::Ready(None) + return Poll::Ready(None); } } } @@ -149,64 +143,43 @@ impl Stream for EthSubscription { } /// Returns all the logs that match the given filter -pub fn filter_logs( - block: Block, - receipts: Vec, - filter: &FilteredParams, -) -> Vec { +pub fn filter_logs(block: Block, receipts: Vec, filter: &FilteredParams) -> Vec { /// Determines whether to add this log - fn add_log(block_hash: H256, l: &Log, block: &Block, params: &FilteredParams) -> bool { - let log = EthersLog { - address: l.address, - topics: l.topics.clone(), - data: l.data.clone(), - block_hash: None, - block_number: None, - transaction_hash: None, - transaction_index: None, - log_index: None, - transaction_log_index: None, - log_type: None, - removed: Some(false), - }; + fn add_log( + block_hash: B256, + l: &alloy_primitives::Log, + block: &Block, + params: &FilteredParams, + ) -> bool { if params.filter.is_some() { - let block_number = block.header.number.as_u64(); + let block_number = block.header.number; if !params.filter_block_range(block_number) || !params.filter_block_hash(block_hash) || - !params.filter_address(&log) || - !params.filter_topics(&log) + !params.filter_address(&l.address) || + !params.filter_topics(l.topics()) { - return false + return false; } } true } - let block_hash = block.header.hash(); + let block_hash = block.header.hash_slow(); let mut logs = vec![]; let mut log_index: u32 = 0; for (receipt_index, receipt) in receipts.into_iter().enumerate() { - let receipt: EIP658Receipt = receipt.into(); - let receipt_logs = receipt.logs; - let transaction_hash: Option = if !receipt_logs.is_empty() { - Some(block.transactions[receipt_index].hash()) - } else { - None - }; - for (transaction_log_index, log) in receipt_logs.into_iter().enumerate() { - if add_log(block_hash, &log, &block, filter) { - logs.push(EthersLog { - address: log.address, - topics: log.topics, - data: log.data, + let transaction_hash = block.transactions[receipt_index].hash(); + for log in receipt.logs() { + if add_log(block_hash, log, &block, filter) { + logs.push(Log { + inner: log.clone(), block_hash: Some(block_hash), - block_number: Some(block.header.number.as_u64().into()), - transaction_hash, - transaction_index: Some(U64::from(receipt_index)), - log_index: Some(U256::from(log_index)), - transaction_log_index: Some(U256::from(transaction_log_index)), - log_type: None, - removed: Some(false), + block_number: Some(block.header.number), + transaction_hash: Some(transaction_hash), + transaction_index: Some(receipt_index as u64), + log_index: Some(log_index as u64), + removed: false, + block_timestamp: Some(block.header.timestamp), }); } log_index += 1; diff --git a/crates/anvil/src/server/handler.rs b/crates/anvil/src/server/handler.rs index 7306d603430c8..2cab0d5b7ae05 100644 --- a/crates/anvil/src/server/handler.rs +++ b/crates/anvil/src/server/handler.rs @@ -4,13 +4,13 @@ use crate::{ pubsub::{EthSubscription, LogsSubscription}, EthApi, }; -use anvil_core::eth::{ - subscription::{SubscriptionId, SubscriptionKind}, - EthPubSub, EthRequest, EthRpcCall, +use alloy_rpc_types::{ + pubsub::{Params, SubscriptionKind}, + FilteredParams, }; +use anvil_core::eth::{subscription::SubscriptionId, EthPubSub, EthRequest, EthRpcCall}; use anvil_rpc::{error::RpcError, response::ResponseResult}; use anvil_server::{PubSubContext, PubSubRpcHandler, RpcHandler}; -use ethers::types::FilteredParams; /// A `RpcHandler` that expects `EthRequest` rpc calls via http #[derive(Clone)] @@ -61,7 +61,16 @@ impl PubSubEthRpcHandler { ResponseResult::Success(canceled.into()) } EthPubSub::EthSubscribe(kind, params) => { - let params = FilteredParams::new(params.filter); + let filter = match *params { + Params::None => None, + Params::Logs(filter) => Some(*filter), + Params::Bool(_) => { + return ResponseResult::Error(RpcError::invalid_params( + "Expected params for logs subscription", + )) + } + }; + let params = FilteredParams::new(filter); let subscription = match kind { SubscriptionKind::Logs => { diff --git a/crates/anvil/src/server/mod.rs b/crates/anvil/src/server/mod.rs index 10d86f6713d5f..c488bcdc15ca3 100644 --- a/crates/anvil/src/server/mod.rs +++ b/crates/anvil/src/server/mod.rs @@ -1,48 +1,67 @@ -//! Contains the code to launch an ethereum RPC-Server -use crate::EthApi; -use anvil_server::{ipc::IpcEndpoint, AnvilServer, ServerConfig}; +//! Contains the code to launch an Ethereum RPC server. + +use crate::{EthApi, IpcTask}; +use anvil_server::{ipc::IpcEndpoint, ServerConfig}; +use axum::Router; use futures::StreamExt; use handler::{HttpEthRpcHandler, PubSubEthRpcHandler}; -use std::net::SocketAddr; -use tokio::{io, task::JoinHandle}; +use std::{future::Future, io, net::SocketAddr, pin::pin}; +use tokio::net::TcpListener; +pub mod error; mod handler; -pub mod error; +/// Configures a server that handles [`EthApi`] related JSON-RPC calls via HTTP and WS. +/// +/// The returned future creates a new server, binding it to the given address, which returns another +/// future that runs it. +pub async fn serve( + addr: SocketAddr, + api: EthApi, + config: ServerConfig, +) -> io::Result>> { + let tcp_listener = TcpListener::bind(addr).await?; + Ok(serve_on(tcp_listener, api, config)) +} -/// Configures an [axum::Server] that handles [EthApi] related JSON-RPC calls via HTTP and WS -pub fn serve(addr: SocketAddr, api: EthApi, config: ServerConfig) -> AnvilServer { +/// Configures a server that handles [`EthApi`] related JSON-RPC calls via HTTP and WS. +pub async fn serve_on( + tcp_listener: TcpListener, + api: EthApi, + config: ServerConfig, +) -> io::Result<()> { + axum::serve(tcp_listener, router(api, config).into_make_service()).await +} + +/// Configures an [`axum::Router`] that handles [`EthApi`] related JSON-RPC calls via HTTP and WS. +pub fn router(api: EthApi, config: ServerConfig) -> Router { let http = HttpEthRpcHandler::new(api.clone()); let ws = PubSubEthRpcHandler::new(api); - anvil_server::serve_http_ws(addr, config, http, ws) + anvil_server::http_ws_router(config, http, ws) } /// Launches an ipc server at the given path in a new task /// /// # Panics /// -/// if setting up the ipc connection was unsuccessful -pub fn spawn_ipc(api: EthApi, path: impl Into) -> JoinHandle> { +/// Panics if setting up the IPC connection was unsuccessful. +#[track_caller] +pub fn spawn_ipc(api: EthApi, path: String) -> IpcTask { try_spawn_ipc(api, path).expect("failed to establish ipc connection") } -/// Launches an ipc server at the given path in a new task -pub fn try_spawn_ipc( - api: EthApi, - path: impl Into, -) -> io::Result>> { - let path = path.into(); +/// Launches an ipc server at the given path in a new task. +pub fn try_spawn_ipc(api: EthApi, path: String) -> io::Result { let handler = PubSubEthRpcHandler::new(api); let ipc = IpcEndpoint::new(handler, path); let incoming = ipc.incoming()?; let task = tokio::task::spawn(async move { - tokio::pin!(incoming); + let mut incoming = pin!(incoming); while let Some(stream) = incoming.next().await { trace!(target: "ipc", "new ipc connection"); tokio::task::spawn(stream); } - Ok(()) }); Ok(task) diff --git a/crates/anvil/src/service.rs b/crates/anvil/src/service.rs index 18ee20d998596..71cfdfecbd8d0 100644 --- a/crates/anvil/src/service.rs +++ b/crates/anvil/src/service.rs @@ -18,7 +18,7 @@ use std::{ sync::Arc, task::{Context, Poll}, }; -use tokio::time::Interval; +use tokio::{task::JoinHandle, time::Interval}; /// The type that drives the blockchain's state /// @@ -101,17 +101,13 @@ impl Future for NodeService { } } -// The type of the future that mines a new block -type BlockMiningFuture = - Pin)> + Send + Sync>>; - /// A type that exclusively mines one block at a time #[must_use = "streams do nothing unless polled"] struct BlockProducer { /// Holds the backend if no block is being mined idle_backend: Option>, /// Single active future that mines a new block - block_mining: Option, + block_mining: Option)>>, /// backlog of sets of transactions ready to be mined queued: VecDeque>>, } @@ -133,19 +129,33 @@ impl Stream for BlockProducer { if !pin.queued.is_empty() { if let Some(backend) = pin.idle_backend.take() { let transactions = pin.queued.pop_front().expect("not empty; qed"); - pin.block_mining = Some(Box::pin(async move { - trace!(target: "miner", "creating new block"); - let block = backend.mine_block(transactions).await; - trace!(target: "miner", "created new block: {}", block.block_number); - (block, backend) - })); + + // we spawn this on as blocking task because in this can be blocking for a while in + // forking mode, because of all the rpc calls to fetch the required state + let handle = tokio::runtime::Handle::current(); + let mining = tokio::task::spawn_blocking(move || { + handle.block_on(async move { + trace!(target: "miner", "creating new block"); + let block = backend.mine_block(transactions).await; + trace!(target: "miner", "created new block: {}", block.block_number); + (block, backend) + }) + }); + pin.block_mining = Some(mining); } } if let Some(mut mining) = pin.block_mining.take() { - if let Poll::Ready((outcome, backend)) = mining.poll_unpin(cx) { - pin.idle_backend = Some(backend); - return Poll::Ready(Some(outcome)) + if let Poll::Ready(res) = mining.poll_unpin(cx) { + return match res { + Ok((outcome, backend)) => { + pin.idle_backend = Some(backend); + Poll::Ready(Some(outcome)) + } + Err(err) => { + panic!("miner task failed: {}", err); + } + } } else { pin.block_mining = Some(mining) } diff --git a/crates/anvil/src/tasks/mod.rs b/crates/anvil/src/tasks/mod.rs index 429f8d5d32919..e42b0437ca68a 100644 --- a/crates/anvil/src/tasks/mod.rs +++ b/crates/anvil/src/tasks/mod.rs @@ -1,12 +1,13 @@ //! Task management support use crate::{shutdown::Shutdown, tasks::block_listener::BlockListener, EthApi}; +use alloy_network::AnyNetwork; +use alloy_primitives::B256; +use alloy_provider::Provider; +use alloy_rpc_types::Block; +use alloy_transport::Transport; use anvil_core::types::Forking; -use ethers::{ - prelude::Middleware, - providers::{JsonRpcClient, PubsubClient}, - types::{Block, H256}, -}; +use futures::StreamExt; use std::{fmt, future::Future}; use tokio::{runtime::Handle, task::JoinHandle}; @@ -51,32 +52,33 @@ impl TaskManager { /// block /// /// ``` + /// use alloy_network::Ethereum; + /// use alloy_provider::RootProvider; /// use anvil::{spawn, NodeConfig}; - /// use ethers::providers::Provider; - /// use std::sync::Arc; + /// /// # async fn t() { /// let endpoint = "http://...."; /// let (api, handle) = spawn(NodeConfig::default().with_eth_rpc_url(Some(endpoint))).await; /// - /// let provider = Arc::new(Provider::try_from(endpoint).unwrap()); + /// let provider = RootProvider::connect_builtin(endpoint).await.unwrap(); /// /// handle.task_manager().spawn_reset_on_new_polled_blocks(provider, api); /// # } /// ``` - pub fn spawn_reset_on_new_polled_blocks

(&self, provider: P, api: EthApi) + pub fn spawn_reset_on_new_polled_blocks(&self, provider: P, api: EthApi) where - P: Middleware + Clone + Unpin + 'static + Send + Sync, -

::Provider: JsonRpcClient, + P: Provider + Clone + Unpin + 'static, + T: Transport + Clone, { self.spawn_block_poll_listener(provider.clone(), move |hash| { let provider = provider.clone(); let api = api.clone(); async move { - if let Ok(Some(block)) = provider.get_block(hash).await { + if let Ok(Some(block)) = provider.get_block(hash.into(), false).await { let _ = api .anvil_reset(Some(Forking { json_rpc_url: None, - block_number: block.number.map(|b| b.as_u64()), + block_number: block.header.number, })) .await; } @@ -87,16 +89,21 @@ impl TaskManager { /// Spawns a new [`BlockListener`] task that listens for new blocks (poll-based) See also /// [`Provider::watch_blocks`] and executes the future the `task_factory` returns for the new /// block hash - pub fn spawn_block_poll_listener(&self, provider: P, task_factory: F) + pub fn spawn_block_poll_listener(&self, provider: P, task_factory: F) where - P: Middleware + Unpin + 'static, -

::Provider: JsonRpcClient, - F: Fn(H256) -> Fut + Unpin + Send + Sync + 'static, + P: Provider + 'static, + T: Transport + Clone, + F: Fn(B256) -> Fut + Unpin + Send + Sync + 'static, Fut: Future + Send, { let shutdown = self.on_shutdown.clone(); self.spawn(async move { - let blocks = provider.watch_blocks().await.unwrap(); + let blocks = provider + .watch_blocks() + .await + .unwrap() + .into_stream() + .flat_map(futures::stream::iter); BlockListener::new(shutdown, blocks, task_factory).await; }); } @@ -105,21 +112,23 @@ impl TaskManager { /// block /// /// ``` + /// use alloy_network::Ethereum; + /// use alloy_provider::RootProvider; /// use anvil::{spawn, NodeConfig}; - /// use ethers::providers::Provider; + /// /// # async fn t() { /// let (api, handle) = spawn(NodeConfig::default().with_eth_rpc_url(Some("http://...."))).await; /// - /// let provider = Provider::connect("ws://...").await.unwrap(); + /// let provider = RootProvider::connect_builtin("ws://...").await.unwrap(); /// /// handle.task_manager().spawn_reset_on_subscribed_blocks(provider, api); /// /// # } /// ``` - pub fn spawn_reset_on_subscribed_blocks

(&self, provider: P, api: EthApi) + pub fn spawn_reset_on_subscribed_blocks(&self, provider: P, api: EthApi) where - P: Middleware + Unpin + 'static + Send + Sync, -

::Provider: PubsubClient, + P: Provider + 'static, + T: Transport + Clone, { self.spawn_block_subscription(provider, move |block| { let api = api.clone(); @@ -127,7 +136,7 @@ impl TaskManager { let _ = api .anvil_reset(Some(Forking { json_rpc_url: None, - block_number: block.number.map(|b| b.as_u64()), + block_number: block.header.number, })) .await; } @@ -137,16 +146,16 @@ impl TaskManager { /// Spawns a new [`BlockListener`] task that listens for new blocks (via subscription) See also /// [`Provider::subscribe_blocks()`] and executes the future the `task_factory` returns for the /// new block hash - pub fn spawn_block_subscription(&self, provider: P, task_factory: F) + pub fn spawn_block_subscription(&self, provider: P, task_factory: F) where - P: Middleware + Unpin + 'static, -

::Provider: PubsubClient, - F: Fn(Block) -> Fut + Unpin + Send + Sync + 'static, + P: Provider + 'static, + T: Transport + Clone, + F: Fn(Block) -> Fut + Unpin + Send + Sync + 'static, Fut: Future + Send, { let shutdown = self.on_shutdown.clone(); self.spawn(async move { - let blocks = provider.subscribe_blocks().await.unwrap(); + let blocks = provider.subscribe_blocks().await.unwrap().into_stream(); BlockListener::new(shutdown, blocks, task_factory).await; }); } diff --git a/crates/anvil/test-data/SimpleStorage.json b/crates/anvil/test-data/SimpleStorage.json index 3d4a8b81aa5b9..8ff4ab3812fe3 100644 --- a/crates/anvil/test-data/SimpleStorage.json +++ b/crates/anvil/test-data/SimpleStorage.json @@ -1,117 +1 @@ -{ - "abi": [ - { - "inputs": [ - { - "internalType": "string", - "name": "value", - "type": "string" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "author", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "oldAuthor", - "type": "address" - }, - { - "indexed": false, - "internalType": "string", - "name": "oldValue", - "type": "string" - }, - { - "indexed": false, - "internalType": "string", - "name": "newValue", - "type": "string" - } - ], - "name": "ValueChanged", - "type": "event" - }, - { - "inputs": [], - "name": "_hashPuzzle", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getValue", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "lastSender", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "value", - "type": "string" - } - ], - "name": "setValue", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "value", - "type": "string" - }, - { - "internalType": "string", - "name": "value2", - "type": "string" - } - ], - "name": "setValues", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bin": "60806040523480156200001157600080fd5b5060405162000d6a38038062000d6a83398181016040528101906200003791906200030f565b600073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f999b6d464c4e3383c341bdd3a22b02dda8a7e1d69c069d252e35cb2ee2f4a3c36001846040516200009a929190620004c3565b60405180910390a38060019080519060200190620000ba929190620000c2565b5050620004fe565b828054620000d0906200038f565b90600052602060002090601f016020900481019282620000f4576000855562000140565b82601f106200010f57805160ff191683800117855562000140565b8280016001018555821562000140579182015b828111156200013f57825182559160200191906001019062000122565b5b5090506200014f919062000153565b5090565b5b808211156200016e57600081600090555060010162000154565b5090565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620001db8262000190565b810181811067ffffffffffffffff82111715620001fd57620001fc620001a1565b5b80604052505050565b60006200021262000172565b9050620002208282620001d0565b919050565b600067ffffffffffffffff821115620002435762000242620001a1565b5b6200024e8262000190565b9050602081019050919050565b60005b838110156200027b5780820151818401526020810190506200025e565b838111156200028b576000848401525b50505050565b6000620002a8620002a28462000225565b62000206565b905082815260208101848484011115620002c757620002c66200018b565b5b620002d48482856200025b565b509392505050565b600082601f830112620002f457620002f362000186565b5b81516200030684826020860162000291565b91505092915050565b6000602082840312156200032857620003276200017c565b5b600082015167ffffffffffffffff81111562000349576200034862000181565b5b6200035784828501620002dc565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620003a857607f821691505b60208210811415620003bf57620003be62000360565b5b50919050565b600082825260208201905092915050565b60008190508160005260206000209050919050565b60008154620003fa816200038f565b620004068186620003c5565b9450600182166000811462000424576001811462000437576200046e565b60ff19831686526020860193506200046e565b6200044285620003d6565b60005b83811015620004665781548189015260018201915060208101905062000445565b808801955050505b50505092915050565b600081519050919050565b60006200048f8262000477565b6200049b8185620003c5565b9350620004ad8185602086016200025b565b620004b88162000190565b840191505092915050565b60006040820190508181036000830152620004df8185620003eb565b90508181036020830152620004f5818462000482565b90509392505050565b61085c806200050e6000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063018ba9911461005c578063209652551461007a578063256fec88146100985780637ffaa4b6146100b657806393a09352146100d2575b600080fd5b6100646100ee565b60405161007191906103bd565b60405180910390f35b6100826100f7565b60405161008f9190610471565b60405180910390f35b6100a0610189565b6040516100ad91906104d4565b60405180910390f35b6100d060048036038101906100cb9190610638565b6101ad565b005b6100ec60048036038101906100e791906106b0565b61021f565b005b60006064905090565b60606001805461010690610728565b80601f016020809104026020016040519081016040528092919081815260200182805461013290610728565b801561017f5780601f106101545761010080835404028352916020019161017f565b820191906000526020600020905b81548152906001019060200180831161016257829003601f168201915b5050505050905090565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b81600190805190602001906101c3929190610301565b5080600290805190602001906101da929190610301565b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f999b6d464c4e3383c341bdd3a22b02dda8a7e1d69c069d252e35cb2ee2f4a3c360018460405161029f9291906107ef565b60405180910390a380600190805190602001906102bd929190610301565b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b82805461030d90610728565b90600052602060002090601f01602090048101928261032f5760008555610376565b82601f1061034857805160ff1916838001178555610376565b82800160010185558215610376579182015b8281111561037557825182559160200191906001019061035a565b5b5090506103839190610387565b5090565b5b808211156103a0576000816000905550600101610388565b5090565b6000819050919050565b6103b7816103a4565b82525050565b60006020820190506103d260008301846103ae565b92915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156104125780820151818401526020810190506103f7565b83811115610421576000848401525b50505050565b6000601f19601f8301169050919050565b6000610443826103d8565b61044d81856103e3565b935061045d8185602086016103f4565b61046681610427565b840191505092915050565b6000602082019050818103600083015261048b8184610438565b905092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104be82610493565b9050919050565b6104ce816104b3565b82525050565b60006020820190506104e960008301846104c5565b92915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61054582610427565b810181811067ffffffffffffffff821117156105645761056361050d565b5b80604052505050565b60006105776104ef565b9050610583828261053c565b919050565b600067ffffffffffffffff8211156105a3576105a261050d565b5b6105ac82610427565b9050602081019050919050565b82818337600083830152505050565b60006105db6105d684610588565b61056d565b9050828152602081018484840111156105f7576105f6610508565b5b6106028482856105b9565b509392505050565b600082601f83011261061f5761061e610503565b5b813561062f8482602086016105c8565b91505092915050565b6000806040838503121561064f5761064e6104f9565b5b600083013567ffffffffffffffff81111561066d5761066c6104fe565b5b6106798582860161060a565b925050602083013567ffffffffffffffff81111561069a576106996104fe565b5b6106a68582860161060a565b9150509250929050565b6000602082840312156106c6576106c56104f9565b5b600082013567ffffffffffffffff8111156106e4576106e36104fe565b5b6106f08482850161060a565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061074057607f821691505b60208210811415610754576107536106f9565b5b50919050565b60008190508160005260206000209050919050565b6000815461077c81610728565b61078681866103e3565b945060018216600081146107a157600181146107b3576107e6565b60ff19831686526020860193506107e6565b6107bc8561075a565b60005b838110156107de578154818901526001820191506020810190506107bf565b808801955050505b50505092915050565b60006040820190508181036000830152610809818561076f565b9050818103602083015261081d8184610438565b9050939250505056fea2646970667358221220e37ed4b56859ad0b3a3773f57ff7b4e7a99406933fc4ff9f8ae053c52cdf3e3264736f6c63430008090033" -} \ No newline at end of file +{"abi":[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":true,"internalType":"address","name":"oldAuthor","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"_hashPuzzle","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastSender","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"},{"internalType":"string","name":"value2","type":"string"}],"name":"setValues","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bin":"60806040523480156200001157600080fd5b5060405162000d6a38038062000d6a83398181016040528101906200003791906200030f565b600073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f999b6d464c4e3383c341bdd3a22b02dda8a7e1d69c069d252e35cb2ee2f4a3c36001846040516200009a929190620004c3565b60405180910390a38060019080519060200190620000ba929190620000c2565b5050620004fe565b828054620000d0906200038f565b90600052602060002090601f016020900481019282620000f4576000855562000140565b82601f106200010f57805160ff191683800117855562000140565b8280016001018555821562000140579182015b828111156200013f57825182559160200191906001019062000122565b5b5090506200014f919062000153565b5090565b5b808211156200016e57600081600090555060010162000154565b5090565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620001db8262000190565b810181811067ffffffffffffffff82111715620001fd57620001fc620001a1565b5b80604052505050565b60006200021262000172565b9050620002208282620001d0565b919050565b600067ffffffffffffffff821115620002435762000242620001a1565b5b6200024e8262000190565b9050602081019050919050565b60005b838110156200027b5780820151818401526020810190506200025e565b838111156200028b576000848401525b50505050565b6000620002a8620002a28462000225565b62000206565b905082815260208101848484011115620002c757620002c66200018b565b5b620002d48482856200025b565b509392505050565b600082601f830112620002f457620002f362000186565b5b81516200030684826020860162000291565b91505092915050565b6000602082840312156200032857620003276200017c565b5b600082015167ffffffffffffffff81111562000349576200034862000181565b5b6200035784828501620002dc565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620003a857607f821691505b60208210811415620003bf57620003be62000360565b5b50919050565b600082825260208201905092915050565b60008190508160005260206000209050919050565b60008154620003fa816200038f565b620004068186620003c5565b9450600182166000811462000424576001811462000437576200046e565b60ff19831686526020860193506200046e565b6200044285620003d6565b60005b83811015620004665781548189015260018201915060208101905062000445565b808801955050505b50505092915050565b600081519050919050565b60006200048f8262000477565b6200049b8185620003c5565b9350620004ad8185602086016200025b565b620004b88162000190565b840191505092915050565b60006040820190508181036000830152620004df8185620003eb565b90508181036020830152620004f5818462000482565b90509392505050565b61085c806200050e6000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063018ba9911461005c578063209652551461007a578063256fec88146100985780637ffaa4b6146100b657806393a09352146100d2575b600080fd5b6100646100ee565b60405161007191906103bd565b60405180910390f35b6100826100f7565b60405161008f9190610471565b60405180910390f35b6100a0610189565b6040516100ad91906104d4565b60405180910390f35b6100d060048036038101906100cb9190610638565b6101ad565b005b6100ec60048036038101906100e791906106b0565b61021f565b005b60006064905090565b60606001805461010690610728565b80601f016020809104026020016040519081016040528092919081815260200182805461013290610728565b801561017f5780601f106101545761010080835404028352916020019161017f565b820191906000526020600020905b81548152906001019060200180831161016257829003601f168201915b5050505050905090565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b81600190805190602001906101c3929190610301565b5080600290805190602001906101da929190610301565b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f999b6d464c4e3383c341bdd3a22b02dda8a7e1d69c069d252e35cb2ee2f4a3c360018460405161029f9291906107ef565b60405180910390a380600190805190602001906102bd929190610301565b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b82805461030d90610728565b90600052602060002090601f01602090048101928261032f5760008555610376565b82601f1061034857805160ff1916838001178555610376565b82800160010185558215610376579182015b8281111561037557825182559160200191906001019061035a565b5b5090506103839190610387565b5090565b5b808211156103a0576000816000905550600101610388565b5090565b6000819050919050565b6103b7816103a4565b82525050565b60006020820190506103d260008301846103ae565b92915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156104125780820151818401526020810190506103f7565b83811115610421576000848401525b50505050565b6000601f19601f8301169050919050565b6000610443826103d8565b61044d81856103e3565b935061045d8185602086016103f4565b61046681610427565b840191505092915050565b6000602082019050818103600083015261048b8184610438565b905092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104be82610493565b9050919050565b6104ce816104b3565b82525050565b60006020820190506104e960008301846104c5565b92915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61054582610427565b810181811067ffffffffffffffff821117156105645761056361050d565b5b80604052505050565b60006105776104ef565b9050610583828261053c565b919050565b600067ffffffffffffffff8211156105a3576105a261050d565b5b6105ac82610427565b9050602081019050919050565b82818337600083830152505050565b60006105db6105d684610588565b61056d565b9050828152602081018484840111156105f7576105f6610508565b5b6106028482856105b9565b509392505050565b600082601f83011261061f5761061e610503565b5b813561062f8482602086016105c8565b91505092915050565b6000806040838503121561064f5761064e6104f9565b5b600083013567ffffffffffffffff81111561066d5761066c6104fe565b5b6106798582860161060a565b925050602083013567ffffffffffffffff81111561069a576106996104fe565b5b6106a68582860161060a565b9150509250929050565b6000602082840312156106c6576106c56104f9565b5b600082013567ffffffffffffffff8111156106e4576106e36104fe565b5b6106f08482850161060a565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061074057607f821691505b60208210811415610754576107536106f9565b5b50919050565b60008190508160005260206000209050919050565b6000815461077c81610728565b61078681866103e3565b945060018216600081146107a157600181146107b3576107e6565b60ff19831686526020860193506107e6565b6107bc8561075a565b60005b838110156107de578154818901526001820191506020810190506107bf565b808801955050505b50505092915050565b60006040820190508181036000830152610809818561076f565b9050818103602083015261081d8184610438565b9050939250505056fea2646970667358221220e37ed4b56859ad0b3a3773f57ff7b4e7a99406933fc4ff9f8ae053c52cdf3e3264736f6c63430008090033"} \ No newline at end of file diff --git a/crates/anvil/test-data/emit_logs.json b/crates/anvil/test-data/emit_logs.json index 0113e1c49d926..635019ae3ab33 100644 --- a/crates/anvil/test-data/emit_logs.json +++ b/crates/anvil/test-data/emit_logs.json @@ -1,67 +1 @@ -{ - "abi": [ - { - "inputs": [ - { - "internalType": "string", - "name": "value", - "type": "string" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "author", - "type": "address" - }, - { - "indexed": false, - "internalType": "string", - "name": "oldValue", - "type": "string" - }, - { - "indexed": false, - "internalType": "string", - "name": "newValue", - "type": "string" - } - ], - "name": "ValueChanged", - "type": "event" - }, - { - "inputs": [], - "name": "getValue", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "value", - "type": "string" - } - ], - "name": "setValue", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bin": "608060405234801561001057600080fd5b506040516105a63803806105a68339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825164010000000081118282018810171561008257600080fd5b82525081516020918201929091019080838360005b838110156100af578181015183820152602001610097565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b50604052505081516100f6915060009060208401906100fd565b505061019e565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826101335760008555610179565b82601f1061014c57805160ff1916838001178555610179565b82800160010185558215610179579182015b8281111561017957825182559160200191906001019061015e565b50610185929150610189565b5090565b5b80821115610185576000815560010161018a565b6103f9806101ad6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063209652551461003b57806393a09352146100b8575b600080fd5b610043610160565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561007d578181015183820152602001610065565b50505050905090810190601f1680156100aa5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61015e600480360360208110156100ce57600080fd5b8101906020810181356401000000008111156100e957600080fd5b8201836020820111156100fb57600080fd5b8035906020019184600183028401116401000000008311171561011d57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506101f6945050505050565b005b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101ec5780601f106101c1576101008083540402835291602001916101ec565b820191906000526020600020905b8154815290600101906020018083116101cf57829003601f168201915b5050505050905090565b60408051818152600080546002600019610100600184161502019091160492820183905233927fe826f71647b8486f2bae59832124c70792fba044036720a54ec8dacdd5df4fcb9285918190602082019060608301908690801561029b5780601f106102705761010080835404028352916020019161029b565b820191906000526020600020905b81548152906001019060200180831161027e57829003601f168201915b5050838103825284518152845160209182019186019080838360005b838110156102cf5781810151838201526020016102b7565b50505050905090810190601f1680156102fc5780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a2805161031e906000906020840190610322565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282610358576000855561039e565b82601f1061037157805160ff191683800117855561039e565b8280016001018555821561039e579182015b8281111561039e578251825591602001919060010190610383565b506103aa9291506103ae565b5090565b5b808211156103aa57600081556001016103af56fea2646970667358221220c1367a0db85dfe60814cdfc5141a8fe8b95c9d051a6824343085c3ba9697244a64736f6c63430007060033" -} \ No newline at end of file +{"abi":[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bin":"608060405234801561001057600080fd5b506040516105a63803806105a68339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825164010000000081118282018810171561008257600080fd5b82525081516020918201929091019080838360005b838110156100af578181015183820152602001610097565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b50604052505081516100f6915060009060208401906100fd565b505061019e565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826101335760008555610179565b82601f1061014c57805160ff1916838001178555610179565b82800160010185558215610179579182015b8281111561017957825182559160200191906001019061015e565b50610185929150610189565b5090565b5b80821115610185576000815560010161018a565b6103f9806101ad6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063209652551461003b57806393a09352146100b8575b600080fd5b610043610160565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561007d578181015183820152602001610065565b50505050905090810190601f1680156100aa5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61015e600480360360208110156100ce57600080fd5b8101906020810181356401000000008111156100e957600080fd5b8201836020820111156100fb57600080fd5b8035906020019184600183028401116401000000008311171561011d57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506101f6945050505050565b005b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101ec5780601f106101c1576101008083540402835291602001916101ec565b820191906000526020600020905b8154815290600101906020018083116101cf57829003601f168201915b5050505050905090565b60408051818152600080546002600019610100600184161502019091160492820183905233927fe826f71647b8486f2bae59832124c70792fba044036720a54ec8dacdd5df4fcb9285918190602082019060608301908690801561029b5780601f106102705761010080835404028352916020019161029b565b820191906000526020600020905b81548152906001019060200180831161027e57829003601f168201915b5050838103825284518152845160209182019186019080838360005b838110156102cf5781810151838201526020016102b7565b50505050905090810190601f1680156102fc5780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a2805161031e906000906020840190610322565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282610358576000855561039e565b82601f1061037157805160ff191683800117855561039e565b8280016001018555821561039e579182015b8281111561039e578251825591602001919060010190610383565b506103aa9291506103ae565b5090565b5b808211156103aa57600081556001016103af56fea2646970667358221220c1367a0db85dfe60814cdfc5141a8fe8b95c9d051a6824343085c3ba9697244a64736f6c63430007060033"} \ No newline at end of file diff --git a/crates/anvil/test-data/greeter.json b/crates/anvil/test-data/greeter.json index 0892e4d43f920..93c50f01edb8f 100644 --- a/crates/anvil/test-data/greeter.json +++ b/crates/anvil/test-data/greeter.json @@ -1,44 +1 @@ -{ - "bytecode": { - "object": "608060405234801561001057600080fd5b506040516104913803806104918339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825164010000000081118282018810171561008257600080fd5b82525081516020918201929091019080838360005b838110156100af578181015183820152602001610097565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b50604052505081516100f6915060009060208401906100fd565b505061019e565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826101335760008555610179565b82601f1061014c57805160ff1916838001178555610179565b82800160010185558215610179579182015b8281111561017957825182559160200191906001019061015e565b50610185929150610189565b5090565b5b80821115610185576000815560010161018a565b6102e4806101ad6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae3217146100e3575b600080fd5b6100e16004803603602081101561005157600080fd5b81019060208101813564010000000081111561006c57600080fd5b82018360208201111561007e57600080fd5b803590602001918460018302840111640100000000831117156100a057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610160945050505050565b005b6100eb610177565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561012557818101518382015260200161010d565b50505050905090810190601f1680156101525780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b805161017390600090602084019061020d565b5050565b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156102035780601f106101d857610100808354040283529160200191610203565b820191906000526020600020905b8154815290600101906020018083116101e657829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826102435760008555610289565b82601f1061025c57805160ff1916838001178555610289565b82800160010185558215610289579182015b8281111561028957825182559160200191906001019061026e565b50610295929150610299565b5090565b5b80821115610295576000815560010161029a56fea26469706673582212208b9161dfd195d53618942a72a3b481d61a7b142de919925a0b34f9c986e5707e64736f6c63430007060033" - }, - "abi": [ - { - "inputs": [ - { - "internalType": "string", - "name": "_greeting", - "type": "string" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "greet", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_greeting", - "type": "string" - } - ], - "name": "setGreeting", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ] -} \ No newline at end of file +{"bytecode":{"object":"608060405234801561001057600080fd5b506040516104913803806104918339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825164010000000081118282018810171561008257600080fd5b82525081516020918201929091019080838360005b838110156100af578181015183820152602001610097565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b50604052505081516100f6915060009060208401906100fd565b505061019e565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826101335760008555610179565b82601f1061014c57805160ff1916838001178555610179565b82800160010185558215610179579182015b8281111561017957825182559160200191906001019061015e565b50610185929150610189565b5090565b5b80821115610185576000815560010161018a565b6102e4806101ad6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae3217146100e3575b600080fd5b6100e16004803603602081101561005157600080fd5b81019060208101813564010000000081111561006c57600080fd5b82018360208201111561007e57600080fd5b803590602001918460018302840111640100000000831117156100a057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610160945050505050565b005b6100eb610177565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561012557818101518382015260200161010d565b50505050905090810190601f1680156101525780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b805161017390600090602084019061020d565b5050565b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156102035780601f106101d857610100808354040283529160200191610203565b820191906000526020600020905b8154815290600101906020018083116101e657829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826102435760008555610289565b82601f1061025c57805160ff1916838001178555610289565b82800160010185558215610289579182015b8281111561028957825182559160200191906001019061026e565b50610295929150610299565b5090565b5b80821115610295576000815560010161029a56fea26469706673582212208b9161dfd195d53618942a72a3b481d61a7b142de919925a0b34f9c986e5707e64736f6c63430007060033"},"abi":[{"inputs":[{"internalType":"string","name":"_greeting","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"greet","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_greeting","type":"string"}],"name":"setGreeting","outputs":[],"stateMutability":"nonpayable","type":"function"}]} \ No newline at end of file diff --git a/crates/anvil/test-data/multicall.json b/crates/anvil/test-data/multicall.json index e0be8fc5df4e2..e7f7d9f1138f7 100644 --- a/crates/anvil/test-data/multicall.json +++ b/crates/anvil/test-data/multicall.json @@ -1,144 +1 @@ -{ - "abi": [ - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "target", - "type": "address" - }, - { - "internalType": "bytes", - "name": "callData", - "type": "bytes" - } - ], - "internalType": "struct Multicall.Call[]", - "name": "calls", - "type": "tuple[]" - } - ], - "name": "aggregate", - "outputs": [ - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - }, - { - "internalType": "bytes[]", - "name": "returnData", - "type": "bytes[]" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - } - ], - "name": "getBlockHash", - "outputs": [ - { - "internalType": "bytes32", - "name": "blockHash", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getCurrentBlockCoinbase", - "outputs": [ - { - "internalType": "address", - "name": "coinbase", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getCurrentBlockDifficulty", - "outputs": [ - { - "internalType": "uint256", - "name": "difficulty", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getCurrentBlockGasLimit", - "outputs": [ - { - "internalType": "uint256", - "name": "gaslimit", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getCurrentBlockTimestamp", - "outputs": [ - { - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "getEthBalance", - "outputs": [ - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getLastBlockHash", - "outputs": [ - { - "internalType": "bytes32", - "name": "blockHash", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - } - ], - "bin": "608060405234801561001057600080fd5b50610abb806100206000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806372425d9d1161005b57806372425d9d1461012a57806386d516e814610148578063a8b0574e14610166578063ee82ac5e1461018457610088565b80630f28c97d1461008d578063252dba42146100ab57806327e86d6e146100dc5780634d2301cc146100fa575b600080fd5b6100956101b4565b6040516100a29190610381565b60405180910390f35b6100c560048036038101906100c091906106b0565b6101bc565b6040516100d3929190610843565b60405180910390f35b6100e461030f565b6040516100f1919061088c565b60405180910390f35b610114600480360381019061010f91906108a7565b610324565b6040516101219190610381565b60405180910390f35b610132610345565b60405161013f9190610381565b60405180910390f35b61015061034d565b60405161015d9190610381565b60405180910390f35b61016e610355565b60405161017b91906108e3565b60405180910390f35b61019e6004803603810190610199919061092a565b61035d565b6040516101ab919061088c565b60405180910390f35b600042905090565b60006060439150825167ffffffffffffffff8111156101de576101dd6103c6565b5b60405190808252806020026020018201604052801561021157816020015b60608152602001906001900390816101fc5790505b50905060005b83518110156103095760008085838151811061023657610235610957565b5b60200260200101516000015173ffffffffffffffffffffffffffffffffffffffff1686848151811061026b5761026a610957565b5b60200260200101516020015160405161028491906109c2565b6000604051808303816000865af19150503d80600081146102c1576040519150601f19603f3d011682016040523d82523d6000602084013e6102c6565b606091505b5091509150816102d557600080fd5b808484815181106102e9576102e8610957565b5b60200260200101819052505050808061030190610a08565b915050610217565b50915091565b600060014361031e9190610a51565b40905090565b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600044905090565b600045905090565b600041905090565b600081409050919050565b6000819050919050565b61037b81610368565b82525050565b60006020820190506103966000830184610372565b92915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6103fe826103b5565b810181811067ffffffffffffffff8211171561041d5761041c6103c6565b5b80604052505050565b600061043061039c565b905061043c82826103f5565b919050565b600067ffffffffffffffff82111561045c5761045b6103c6565b5b602082029050602081019050919050565b600080fd5b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104a78261047c565b9050919050565b6104b78161049c565b81146104c257600080fd5b50565b6000813590506104d4816104ae565b92915050565b600080fd5b600067ffffffffffffffff8211156104fa576104f96103c6565b5b610503826103b5565b9050602081019050919050565b82818337600083830152505050565b600061053261052d846104df565b610426565b90508281526020810184848401111561054e5761054d6104da565b5b610559848285610510565b509392505050565b600082601f830112610576576105756103b0565b5b813561058684826020860161051f565b91505092915050565b6000604082840312156105a5576105a4610472565b5b6105af6040610426565b905060006105bf848285016104c5565b600083015250602082013567ffffffffffffffff8111156105e3576105e2610477565b5b6105ef84828501610561565b60208301525092915050565b600061060e61060984610441565b610426565b905080838252602082019050602084028301858111156106315761063061046d565b5b835b8181101561067857803567ffffffffffffffff811115610656576106556103b0565b5b808601610663898261058f565b85526020850194505050602081019050610633565b5050509392505050565b600082601f830112610697576106966103b0565b5b81356106a78482602086016105fb565b91505092915050565b6000602082840312156106c6576106c56103a6565b5b600082013567ffffffffffffffff8111156106e4576106e36103ab565b5b6106f084828501610682565b91505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561075f578082015181840152602081019050610744565b8381111561076e576000848401525b50505050565b600061077f82610725565b6107898185610730565b9350610799818560208601610741565b6107a2816103b5565b840191505092915050565b60006107b98383610774565b905092915050565b6000602082019050919050565b60006107d9826106f9565b6107e38185610704565b9350836020820285016107f585610715565b8060005b85811015610831578484038952815161081285826107ad565b945061081d836107c1565b925060208a019950506001810190506107f9565b50829750879550505050505092915050565b60006040820190506108586000830185610372565b818103602083015261086a81846107ce565b90509392505050565b6000819050919050565b61088681610873565b82525050565b60006020820190506108a1600083018461087d565b92915050565b6000602082840312156108bd576108bc6103a6565b5b60006108cb848285016104c5565b91505092915050565b6108dd8161049c565b82525050565b60006020820190506108f860008301846108d4565b92915050565b61090781610368565b811461091257600080fd5b50565b600081359050610924816108fe565b92915050565b6000602082840312156109405761093f6103a6565b5b600061094e84828501610915565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600081905092915050565b600061099c82610725565b6109a68185610986565b93506109b6818560208601610741565b80840191505092915050565b60006109ce8284610991565b915081905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610a1382610368565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415610a4657610a456109d9565b5b600182019050919050565b6000610a5c82610368565b9150610a6783610368565b925082821015610a7a57610a796109d9565b5b82820390509291505056fea2646970667358221220e5023d90063e0939116a41565414721ba1350cd3e98b12e7b65983a039644df964736f6c634300080a0033" -} \ No newline at end of file +{"abi":[{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct Multicall.Call[]","name":"calls","type":"tuple[]"}],"name":"aggregate","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"bytes[]","name":"returnData","type":"bytes[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"getBlockHash","outputs":[{"internalType":"bytes32","name":"blockHash","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockCoinbase","outputs":[{"internalType":"address","name":"coinbase","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockDifficulty","outputs":[{"internalType":"uint256","name":"difficulty","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockGasLimit","outputs":[{"internalType":"uint256","name":"gaslimit","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockTimestamp","outputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getEthBalance","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastBlockHash","outputs":[{"internalType":"bytes32","name":"blockHash","type":"bytes32"}],"stateMutability":"view","type":"function"}],"bin":"608060405234801561001057600080fd5b50610abb806100206000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806372425d9d1161005b57806372425d9d1461012a57806386d516e814610148578063a8b0574e14610166578063ee82ac5e1461018457610088565b80630f28c97d1461008d578063252dba42146100ab57806327e86d6e146100dc5780634d2301cc146100fa575b600080fd5b6100956101b4565b6040516100a29190610381565b60405180910390f35b6100c560048036038101906100c091906106b0565b6101bc565b6040516100d3929190610843565b60405180910390f35b6100e461030f565b6040516100f1919061088c565b60405180910390f35b610114600480360381019061010f91906108a7565b610324565b6040516101219190610381565b60405180910390f35b610132610345565b60405161013f9190610381565b60405180910390f35b61015061034d565b60405161015d9190610381565b60405180910390f35b61016e610355565b60405161017b91906108e3565b60405180910390f35b61019e6004803603810190610199919061092a565b61035d565b6040516101ab919061088c565b60405180910390f35b600042905090565b60006060439150825167ffffffffffffffff8111156101de576101dd6103c6565b5b60405190808252806020026020018201604052801561021157816020015b60608152602001906001900390816101fc5790505b50905060005b83518110156103095760008085838151811061023657610235610957565b5b60200260200101516000015173ffffffffffffffffffffffffffffffffffffffff1686848151811061026b5761026a610957565b5b60200260200101516020015160405161028491906109c2565b6000604051808303816000865af19150503d80600081146102c1576040519150601f19603f3d011682016040523d82523d6000602084013e6102c6565b606091505b5091509150816102d557600080fd5b808484815181106102e9576102e8610957565b5b60200260200101819052505050808061030190610a08565b915050610217565b50915091565b600060014361031e9190610a51565b40905090565b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600044905090565b600045905090565b600041905090565b600081409050919050565b6000819050919050565b61037b81610368565b82525050565b60006020820190506103966000830184610372565b92915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6103fe826103b5565b810181811067ffffffffffffffff8211171561041d5761041c6103c6565b5b80604052505050565b600061043061039c565b905061043c82826103f5565b919050565b600067ffffffffffffffff82111561045c5761045b6103c6565b5b602082029050602081019050919050565b600080fd5b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104a78261047c565b9050919050565b6104b78161049c565b81146104c257600080fd5b50565b6000813590506104d4816104ae565b92915050565b600080fd5b600067ffffffffffffffff8211156104fa576104f96103c6565b5b610503826103b5565b9050602081019050919050565b82818337600083830152505050565b600061053261052d846104df565b610426565b90508281526020810184848401111561054e5761054d6104da565b5b610559848285610510565b509392505050565b600082601f830112610576576105756103b0565b5b813561058684826020860161051f565b91505092915050565b6000604082840312156105a5576105a4610472565b5b6105af6040610426565b905060006105bf848285016104c5565b600083015250602082013567ffffffffffffffff8111156105e3576105e2610477565b5b6105ef84828501610561565b60208301525092915050565b600061060e61060984610441565b610426565b905080838252602082019050602084028301858111156106315761063061046d565b5b835b8181101561067857803567ffffffffffffffff811115610656576106556103b0565b5b808601610663898261058f565b85526020850194505050602081019050610633565b5050509392505050565b600082601f830112610697576106966103b0565b5b81356106a78482602086016105fb565b91505092915050565b6000602082840312156106c6576106c56103a6565b5b600082013567ffffffffffffffff8111156106e4576106e36103ab565b5b6106f084828501610682565b91505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561075f578082015181840152602081019050610744565b8381111561076e576000848401525b50505050565b600061077f82610725565b6107898185610730565b9350610799818560208601610741565b6107a2816103b5565b840191505092915050565b60006107b98383610774565b905092915050565b6000602082019050919050565b60006107d9826106f9565b6107e38185610704565b9350836020820285016107f585610715565b8060005b85811015610831578484038952815161081285826107ad565b945061081d836107c1565b925060208a019950506001810190506107f9565b50829750879550505050505092915050565b60006040820190506108586000830185610372565b818103602083015261086a81846107ce565b90509392505050565b6000819050919050565b61088681610873565b82525050565b60006020820190506108a1600083018461087d565b92915050565b6000602082840312156108bd576108bc6103a6565b5b60006108cb848285016104c5565b91505092915050565b6108dd8161049c565b82525050565b60006020820190506108f860008301846108d4565b92915050565b61090781610368565b811461091257600080fd5b50565b600081359050610924816108fe565b92915050565b6000602082840312156109405761093f6103a6565b5b600061094e84828501610915565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600081905092915050565b600061099c82610725565b6109a68185610986565b93506109b6818560208601610741565b80840191505092915050565b60006109ce8284610991565b915081905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610a1382610368565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415610a4657610a456109d9565b5b600182019050919050565b6000610a5c82610368565b9150610a6783610368565b925082821015610a7a57610a796109d9565b5b82820390509291505056fea2646970667358221220e5023d90063e0939116a41565414721ba1350cd3e98b12e7b65983a039644df964736f6c634300080a0033"} \ No newline at end of file diff --git a/crates/anvil/test-data/storage_sample.json b/crates/anvil/test-data/storage_sample.json new file mode 100644 index 0000000000000..5241cb08e8f77 --- /dev/null +++ b/crates/anvil/test-data/storage_sample.json @@ -0,0 +1 @@ +{"0x0000000000000000000000000000000000000000000000000000000000000022":"0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b","0x0000000000000000000000000000000000000000000000000000000000000023":"0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0x0000000000000000000000000000000000000000000000000000000000000024":"0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c","0x0000000000000000000000000000000000000000000000000000000000000025":"0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c","0x0000000000000000000000000000000000000000000000000000000000000026":"0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30","0x0000000000000000000000000000000000000000000000000000000000000027":"0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1","0x0000000000000000000000000000000000000000000000000000000000000028":"0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c","0x0000000000000000000000000000000000000000000000000000000000000029":"0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193","0x000000000000000000000000000000000000000000000000000000000000002a":"0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1","0x000000000000000000000000000000000000000000000000000000000000002b":"0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b","0x000000000000000000000000000000000000000000000000000000000000002c":"0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220","0x000000000000000000000000000000000000000000000000000000000000002d":"0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f","0x000000000000000000000000000000000000000000000000000000000000002e":"0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e","0x000000000000000000000000000000000000000000000000000000000000002f":"0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784","0x0000000000000000000000000000000000000000000000000000000000000030":"0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb","0x0000000000000000000000000000000000000000000000000000000000000031":"0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb","0x0000000000000000000000000000000000000000000000000000000000000032":"0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab","0x0000000000000000000000000000000000000000000000000000000000000033":"0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4","0x0000000000000000000000000000000000000000000000000000000000000034":"0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f","0x0000000000000000000000000000000000000000000000000000000000000035":"0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa","0x0000000000000000000000000000000000000000000000000000000000000036":"0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c","0x0000000000000000000000000000000000000000000000000000000000000037":"0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167","0x0000000000000000000000000000000000000000000000000000000000000038":"0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7","0x0000000000000000000000000000000000000000000000000000000000000039":"0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0","0x000000000000000000000000000000000000000000000000000000000000003a":"0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544","0x000000000000000000000000000000000000000000000000000000000000003b":"0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765","0x000000000000000000000000000000000000000000000000000000000000003c":"0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4","0x000000000000000000000000000000000000000000000000000000000000003d":"0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1","0x000000000000000000000000000000000000000000000000000000000000003e":"0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636","0x000000000000000000000000000000000000000000000000000000000000003f":"0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c","0x0000000000000000000000000000000000000000000000000000000000000040":"0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7"} \ No newline at end of file diff --git a/crates/anvil/tests/it/abi.rs b/crates/anvil/tests/it/abi.rs index c9cb0d7b1d818..fda13dc46327d 100644 --- a/crates/anvil/tests/it/abi.rs +++ b/crates/anvil/tests/it/abi.rs @@ -1,49 +1,52 @@ -//! commonly used abigen generated types +//! commonly used sol generated types +use alloy_sol_types::sol; -use ethers::{ - contract::{abigen, EthEvent}, - types::Address, -}; +sol!( + #[sol(rpc)] + Greeter, + "test-data/greeter.json" +); -#[derive(Clone, Debug, EthEvent)] -pub struct ValueChanged { - #[ethevent(indexed)] - pub old_author: Address, - #[ethevent(indexed)] - pub new_author: Address, - pub old_value: String, - pub new_value: String, -} +sol!( + #[derive(Debug)] + #[sol(rpc)] + SimpleStorage, + "test-data/SimpleStorage.json" +); -abigen!(Greeter, "test-data/greeter.json"); -abigen!(SimpleStorage, "test-data/SimpleStorage.json"); -abigen!(MulticallContract, "test-data/multicall.json"); -abigen!( - Erc721, - r#"[ - balanceOf(address)(uint256) - ownerOf(uint256)(address) - name()(string) - symbol()(string) - tokenURI(uint256)(string) - getApproved(uint256)(address) - setApprovalForAll(address,bool) - isApprovedForAll(address,address) - transferFrom(address,address,uint256) - safeTransferFrom(address,address,uint256,bytes) - _transfer(address,address,uint256) - _approve(address, uint256) - _burn(uint256) - _safeMint(address,uint256,bytes) - _mint(address,uint256) - _exists(uint256)(bool) -]"# +sol!( + #[sol(rpc)] + MulticallContract, + "test-data/multicall.json" +); + +sol!( + #[sol(rpc)] + contract BUSD { + function balanceOf(address) external view returns (uint256); + } ); -abigen!( - BUSD, - r#"[ - balanceOf(address)(uint256) -]"# + +sol!( + #[sol(rpc)] + interface ERC721 { + function balanceOf(address owner) public view virtual returns (uint256); + function ownerOf(uint256 tokenId) public view virtual returns (address); + function name() public view virtual returns (string memory); + function symbol() public view virtual returns (string memory); + function tokenURI(uint256 tokenId) public view virtual returns (string memory); + function getApproved(uint256 tokenId) public view virtual returns (address); + function setApprovalForAll(address operator, bool approved) public virtual; + function isApprovedForAll(address owner, address operator) public view virtual returns (bool); + function transferFrom(address from, address to, uint256 tokenId) public virtual; + function safeTransferFrom(address from, address to, uint256 tokenId) public; + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual; + function _mint(address to, uint256 tokenId) internal; + function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual; + function _burn(uint256 tokenId) internal; + function _transfer(address from, address to, uint256 tokenId) internal; + function _approve(address to, uint256 tokenId, address auth) internal; + } ); // @@ -70,3 +73,12 @@ contract VendingMachine { payable(msg.sender).transfer(address(this).balance); } }"#; + +sol!( + #[sol(rpc)] + contract VendingMachine { + function buyRevert(uint amount) external payable; + function buyRequire(uint amount) external payable; + function withdraw() external; + } +); diff --git a/crates/anvil/tests/it/anvil.rs b/crates/anvil/tests/it/anvil.rs index 07d8e3f967121..7aff5660e6ce9 100644 --- a/crates/anvil/tests/it/anvil.rs +++ b/crates/anvil/tests/it/anvil.rs @@ -1,7 +1,8 @@ //! tests for anvil specific logic +use alloy_primitives::Address; +use alloy_provider::Provider; use anvil::{spawn, NodeConfig}; -use ethers::{prelude::Middleware, types::Address}; #[tokio::test(flavor = "multi_thread")] async fn test_can_change_mining_mode() { @@ -11,25 +12,25 @@ async fn test_can_change_mining_mode() { assert!(api.anvil_get_auto_mine().unwrap()); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0); + assert_eq!(num, 0); api.anvil_set_interval_mining(1).unwrap(); assert!(!api.anvil_get_auto_mine().unwrap()); // changing the mining mode will instantly mine a new block tokio::time::sleep(std::time::Duration::from_millis(500)).await; let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0); + assert_eq!(num, 0); tokio::time::sleep(std::time::Duration::from_millis(700)).await; let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 1); + assert_eq!(num, 1); // assert that no block is mined when the interval is set to 0 api.anvil_set_interval_mining(0).unwrap(); assert!(!api.anvil_get_auto_mine().unwrap()); tokio::time::sleep(std::time::Duration::from_millis(1000)).await; let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 1); + assert_eq!(num, 1); } #[tokio::test(flavor = "multi_thread")] @@ -39,6 +40,7 @@ async fn can_get_default_dev_keys() { let dev_accounts = handle.dev_accounts().collect::>(); let accounts = provider.get_accounts().await.unwrap(); + assert_eq!(dev_accounts, accounts); } @@ -58,7 +60,10 @@ async fn test_can_set_genesis_timestamp() { spawn(NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into())).await; let provider = handle.http_provider(); - assert_eq!(genesis_timestamp, provider.get_block(0).await.unwrap().unwrap().timestamp.as_u64()); + assert_eq!( + genesis_timestamp, + provider.get_block(0.into(), false).await.unwrap().unwrap().header.timestamp + ); } #[tokio::test(flavor = "multi_thread")] @@ -66,5 +71,5 @@ async fn test_can_use_default_genesis_timestamp() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - assert_ne!(0u64, provider.get_block(0).await.unwrap().unwrap().timestamp.as_u64()); + assert_ne!(0u64, provider.get_block(0.into(), false).await.unwrap().unwrap().header.timestamp); } diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index 6591dd25d3d26..4f46d7525384e 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -1,23 +1,22 @@ //! tests for custom anvil endpoints -use crate::{abi::*, fork::fork_config}; + +use crate::{ + abi::{Greeter, MulticallContract, BUSD}, + fork::fork_config, + utils::http_provider_with_signer, +}; +use alloy_network::{EthereumSigner, TransactionBuilder}; +use alloy_primitives::{address, fixed_bytes, Address, U256, U64}; +use alloy_provider::{ext::TxPoolApi, Provider}; +use alloy_rpc_types::{BlockId, BlockNumberOrTag, TransactionRequest, WithOtherFields}; use anvil::{eth::api::CLIENT_VERSION, spawn, Hardfork, NodeConfig}; use anvil_core::{ eth::EthRequest, types::{AnvilMetadata, ForkedNetwork, Forking, NodeEnvironment, NodeForkConfig, NodeInfo}, }; -use ethers::{ - abi::{ethereum_types::BigEndianHash, AbiDecode}, - prelude::{Middleware, SignerMiddleware}, - types::{ - transaction::eip2718::TypedTransaction, Address, BlockNumber, Eip1559TransactionRequest, - TransactionRequest, H256, U256, U64, - }, - utils::hex, -}; use foundry_evm::revm::primitives::SpecId; use std::{ str::FromStr, - sync::Arc, time::{Duration, SystemTime}, }; @@ -26,21 +25,21 @@ async fn can_set_gas_price() { let (api, handle) = spawn(NodeConfig::test().with_hardfork(Some(Hardfork::Berlin))).await; let provider = handle.http_provider(); - let gas_price = 1337u64.into(); + let gas_price = U256::from(1337); api.anvil_set_min_gas_price(gas_price).await.unwrap(); - assert_eq!(gas_price, provider.get_gas_price().await.unwrap()); + assert_eq!(gas_price.to::(), provider.get_gas_price().await.unwrap()); } #[tokio::test(flavor = "multi_thread")] async fn can_set_block_gas_limit() { let (api, _) = spawn(NodeConfig::test().with_hardfork(Some(Hardfork::Berlin))).await; - let block_gas_limit = 1337u64.into(); + let block_gas_limit = U256::from(1337); assert!(api.evm_set_block_gas_limit(block_gas_limit).unwrap()); // Mine a new block, and check the new block gas limit api.mine_one().await; - let latest_block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block_gas_limit, latest_block.gas_limit); + let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + assert_eq!(block_gas_limit.to::(), latest_block.header.gas_limit); } // Ref @@ -58,17 +57,17 @@ async fn can_set_storage() { let storage_value = api.storage_at(addr, slot, None).await.unwrap(); assert_eq!(val, storage_value); - assert_eq!(val, H256::from_uint(&U256::from(12345))); } #[tokio::test(flavor = "multi_thread")] async fn can_impersonate_account() { let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); let impersonate = Address::random(); let to = Address::random(); - let val = 1337u64; + let val = U256::from(1337); let funding = U256::from(1e18 as u64); // fund the impersonated account api.anvil_set_balance(impersonate, funding).await.unwrap(); @@ -76,36 +75,38 @@ async fn can_impersonate_account() { let balance = api.balance(impersonate, None).await.unwrap(); assert_eq!(balance, funding); - let tx = TransactionRequest::new().from(impersonate).to(to).value(val); + let tx = TransactionRequest::default().with_from(impersonate).with_to(to).with_value(val); + let tx = WithOtherFields::new(tx); - let res = provider.send_transaction(tx.clone(), None).await; + let res = provider.send_transaction(tx.clone()).await; res.unwrap_err(); api.anvil_impersonate_account(impersonate).await.unwrap(); assert!(api.accounts().unwrap().contains(&impersonate)); - let res = provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); + let res = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert_eq!(res.from, impersonate); - let nonce = provider.get_transaction_count(impersonate, None).await.unwrap(); - assert_eq!(nonce, 1u64.into()); + let nonce = provider.get_transaction_count(impersonate, BlockId::latest()).await.unwrap(); + assert_eq!(nonce, 1); - let balance = provider.get_balance(to, None).await.unwrap(); - assert_eq!(balance, val.into()); + let balance = provider.get_balance(to, BlockId::latest()).await.unwrap(); + assert_eq!(balance, val); api.anvil_stop_impersonating_account(impersonate).await.unwrap(); - let res = provider.send_transaction(tx, None).await; + let res = provider.send_transaction(tx).await; res.unwrap_err(); } #[tokio::test(flavor = "multi_thread")] async fn can_auto_impersonate_account() { let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); let impersonate = Address::random(); let to = Address::random(); - let val = 1337u64; + let val = U256::from(1337); let funding = U256::from(1e18 as u64); // fund the impersonated account api.anvil_set_balance(impersonate, funding).await.unwrap(); @@ -113,24 +114,25 @@ async fn can_auto_impersonate_account() { let balance = api.balance(impersonate, None).await.unwrap(); assert_eq!(balance, funding); - let tx = TransactionRequest::new().from(impersonate).to(to).value(val); + let tx = TransactionRequest::default().with_from(impersonate).with_to(to).with_value(val); + let tx = WithOtherFields::new(tx); - let res = provider.send_transaction(tx.clone(), None).await; + let res = provider.send_transaction(tx.clone()).await; res.unwrap_err(); api.anvil_auto_impersonate_account(true).await.unwrap(); - let res = provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); + let res = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert_eq!(res.from, impersonate); - let nonce = provider.get_transaction_count(impersonate, None).await.unwrap(); - assert_eq!(nonce, 1u64.into()); + let nonce = provider.get_transaction_count(impersonate, BlockId::latest()).await.unwrap(); + assert_eq!(nonce, 1); - let balance = provider.get_balance(to, None).await.unwrap(); - assert_eq!(balance, val.into()); + let balance = provider.get_balance(to, BlockId::latest()).await.unwrap(); + assert_eq!(balance, val); api.anvil_auto_impersonate_account(false).await.unwrap(); - let res = provider.send_transaction(tx, None).await; + let res = provider.send_transaction(tx).await; res.unwrap_err(); // explicitly impersonated accounts get returned by `eth_accounts` @@ -141,44 +143,40 @@ async fn can_auto_impersonate_account() { #[tokio::test(flavor = "multi_thread")] async fn can_impersonate_contract() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let provider = Arc::new(SignerMiddleware::new(provider, wallet)); + let provider = handle.http_provider(); - let greeter_contract = - Greeter::deploy(provider, "Hello World!".to_string()).unwrap().send().await.unwrap(); - let impersonate = greeter_contract.address(); + let greeter_contract = Greeter::deploy(&provider, "Hello World!".to_string()).await.unwrap(); + let impersonate = greeter_contract.address().to_owned(); let to = Address::random(); - let val = 1337u64; - - let provider = handle.http_provider(); + let val = U256::from(1337); - // fund the impersonated account + // // fund the impersonated account api.anvil_set_balance(impersonate, U256::from(1e18 as u64)).await.unwrap(); - let tx = TransactionRequest::new().from(impersonate).to(to).value(val); + let tx = TransactionRequest::default().with_from(impersonate).to(to).with_value(val); + let tx = WithOtherFields::new(tx); - let res = provider.send_transaction(tx.clone(), None).await; + let res = provider.send_transaction(tx.clone()).await; res.unwrap_err(); - let greeting = greeter_contract.greet().call().await.unwrap(); + let greeting = greeter_contract.greet().call().await.unwrap()._0; assert_eq!("Hello World!", greeting); api.anvil_impersonate_account(impersonate).await.unwrap(); - let res = provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); + let res = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert_eq!(res.from, impersonate); - let balance = provider.get_balance(to, None).await.unwrap(); - assert_eq!(balance, val.into()); + let balance = provider.get_balance(to, BlockId::latest()).await.unwrap(); + assert_eq!(balance, val); api.anvil_stop_impersonating_account(impersonate).await.unwrap(); - let res = provider.send_transaction(tx, None).await; + let res = provider.send_transaction(tx).await; res.unwrap_err(); - let greeting = greeter_contract.greet().call().await.unwrap(); + let greeting = greeter_contract.greet().call().await.unwrap()._0; assert_eq!("Hello World!", greeting); } @@ -187,33 +185,33 @@ async fn can_impersonate_gnosis_safe() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); - // - let safe: Address = "0xA063Cb7CFd8E57c30c788A0572CBbf2129ae56B6".parse().unwrap(); + // + let safe = address!("A063Cb7CFd8E57c30c788A0572CBbf2129ae56B6"); - let code = provider.get_code(safe, None).await.unwrap(); + let code = provider.get_code_at(safe, BlockId::default()).await.unwrap(); assert!(!code.is_empty()); api.anvil_impersonate_account(safe).await.unwrap(); - let code = provider.get_code(safe, None).await.unwrap(); + let code = provider.get_code_at(safe, BlockId::default()).await.unwrap(); assert!(!code.is_empty()); let balance = U256::from(1e18 as u64); // fund the impersonated account api.anvil_set_balance(safe, balance).await.unwrap(); - let on_chain_balance = provider.get_balance(safe, None).await.unwrap(); + let on_chain_balance = provider.get_balance(safe, BlockId::latest()).await.unwrap(); assert_eq!(on_chain_balance, balance); api.anvil_stop_impersonating_account(safe).await.unwrap(); - let code = provider.get_code(safe, None).await.unwrap(); + let code = provider.get_code_at(safe, BlockId::default()).await.unwrap(); // code is added back after stop impersonating assert!(!code.is_empty()); } #[tokio::test(flavor = "multi_thread")] -async fn can_impersonate_multiple_account() { +async fn can_impersonate_multiple_accounts() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); @@ -221,42 +219,44 @@ async fn can_impersonate_multiple_account() { let impersonate1 = Address::random(); let to = Address::random(); - let val = 1337u64; + let val = U256::from(1337); let funding = U256::from(1e18 as u64); // fund the impersonated accounts api.anvil_set_balance(impersonate0, funding).await.unwrap(); api.anvil_set_balance(impersonate1, funding).await.unwrap(); - let tx = TransactionRequest::new().from(impersonate0).to(to).value(val); + let tx = TransactionRequest::default().with_from(impersonate0).to(to).with_value(val); + let tx = WithOtherFields::new(tx); api.anvil_impersonate_account(impersonate0).await.unwrap(); api.anvil_impersonate_account(impersonate1).await.unwrap(); - let res0 = provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); + let res0 = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert_eq!(res0.from, impersonate0); - let nonce = provider.get_transaction_count(impersonate0, None).await.unwrap(); - assert_eq!(nonce, 1u64.into()); + let nonce = provider.get_transaction_count(impersonate0, BlockId::latest()).await.unwrap(); + assert_eq!(nonce, 1); let receipt = provider.get_transaction_receipt(res0.transaction_hash).await.unwrap().unwrap(); - assert_eq!(res0, receipt); + assert_eq!(res0.inner, receipt.inner); let res1 = provider - .send_transaction(tx.from(impersonate1), None) + .send_transaction(tx.with_from(impersonate1)) .await .unwrap() + .get_receipt() .await - .unwrap() .unwrap(); + assert_eq!(res1.from, impersonate1); - let nonce = provider.get_transaction_count(impersonate1, None).await.unwrap(); - assert_eq!(nonce, 1u64.into()); + let nonce = provider.get_transaction_count(impersonate1, BlockId::latest()).await.unwrap(); + assert_eq!(nonce, 1); let receipt = provider.get_transaction_receipt(res1.transaction_hash).await.unwrap().unwrap(); - assert_eq!(res1, receipt); + assert_eq!(res1.inner, receipt.inner); - assert_ne!(res0, res1); + assert_ne!(res0.inner, res1.inner); } #[tokio::test(flavor = "multi_thread")] @@ -269,7 +269,7 @@ async fn can_mine_manually() { for (idx, _) in std::iter::repeat(()).take(10).enumerate() { api.evm_mine(None).await.unwrap(); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num, start_num + idx + 1); + assert_eq!(num, start_num + idx as u64 + 1); } } @@ -287,17 +287,17 @@ async fn test_set_next_timestamp() { api.evm_mine(None).await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false).await.unwrap().unwrap(); - assert_eq!(block.number.unwrap().as_u64(), 1); - assert_eq!(block.timestamp.as_u64(), next_timestamp.as_secs()); + assert_eq!(block.header.number.unwrap(), 1); + assert_eq!(block.header.timestamp, next_timestamp.as_secs()); api.evm_mine(None).await.unwrap(); - let next = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(next.number.unwrap().as_u64(), 2); + let next = provider.get_block(BlockId::default(), false).await.unwrap().unwrap(); + assert_eq!(next.header.number.unwrap(), 2); - assert!(next.timestamp > block.timestamp); + assert!(next.header.timestamp > block.header.timestamp); } #[tokio::test(flavor = "multi_thread")] @@ -314,14 +314,14 @@ async fn test_evm_set_time() { // mine a block api.evm_mine(None).await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false).await.unwrap().unwrap(); - assert!(block.timestamp.as_u64() >= timestamp.as_secs()); + assert!(block.header.timestamp >= timestamp.as_secs()); api.evm_mine(None).await.unwrap(); - let next = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let next = provider.get_block(BlockId::default(), false).await.unwrap().unwrap(); - assert!(next.timestamp > block.timestamp); + assert!(next.header.timestamp > block.header.timestamp); } #[tokio::test(flavor = "multi_thread")] @@ -338,10 +338,10 @@ async fn test_evm_set_time_in_past() { // mine a block api.evm_mine(None).await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false).await.unwrap().unwrap(); - assert!(block.timestamp.as_u64() >= timestamp.as_secs()); - assert!(block.timestamp.as_u64() < now.as_secs()); + assert!(block.header.timestamp >= timestamp.as_secs()); + assert!(block.header.timestamp < now.as_secs()); } #[tokio::test(flavor = "multi_thread")] @@ -353,44 +353,44 @@ async fn test_timestamp_interval() { let interval = 10; for _ in 0..5 { - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false).await.unwrap().unwrap(); // mock timestamp api.evm_set_block_timestamp_interval(interval).unwrap(); api.evm_mine(None).await.unwrap(); - let new_block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let new_block = provider.get_block(BlockId::default(), false).await.unwrap().unwrap(); - assert_eq!(new_block.timestamp, block.timestamp + interval); + assert_eq!(new_block.header.timestamp, block.header.timestamp + interval); } - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false).await.unwrap().unwrap(); - let next_timestamp = block.timestamp + 50; - api.evm_set_next_block_timestamp(next_timestamp.as_u64()).unwrap(); + let next_timestamp = block.header.timestamp + 50; + api.evm_set_next_block_timestamp(next_timestamp).unwrap(); api.evm_mine(None).await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block.timestamp, next_timestamp); + let block = provider.get_block(BlockId::default(), false).await.unwrap().unwrap(); + assert_eq!(block.header.timestamp, next_timestamp); api.evm_mine(None).await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false).await.unwrap().unwrap(); // interval also works after setting the next timestamp manually - assert_eq!(block.timestamp, next_timestamp + interval); + assert_eq!(block.header.timestamp, next_timestamp + interval); assert!(api.evm_remove_block_timestamp_interval().unwrap()); api.evm_mine(None).await.unwrap(); - let new_block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let new_block = provider.get_block(BlockId::default(), false).await.unwrap().unwrap(); // offset is applied correctly after resetting the interval - assert!(new_block.timestamp > block.timestamp); + assert!(new_block.header.timestamp > block.header.timestamp); api.evm_mine(None).await.unwrap(); - let another_block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let another_block = provider.get_block(BlockId::default(), false).await.unwrap().unwrap(); // check interval is disabled - assert!(another_block.timestamp - new_block.timestamp < U256::from(interval)); + assert!(another_block.header.timestamp - new_block.header.timestamp < interval); } // @@ -398,26 +398,25 @@ async fn test_timestamp_interval() { async fn test_can_set_storage_bsc_fork() { let (api, handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some("https://bsc-dataseed.binance.org/"))).await; - let provider = Arc::new(handle.http_provider()); + let provider = handle.http_provider(); - let busd_addr: Address = "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56".parse().unwrap(); - let idx: U256 = - "0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49".parse().unwrap(); - let value: H256 = - "0x0000000000000000000000000000000000000000000000000000000000003039".parse().unwrap(); + let busd_addr = address!("e9e7CEA3DedcA5984780Bafc599bD69ADd087D56"); + let idx = U256::from_str("0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49") + .unwrap(); + let value = fixed_bytes!("0000000000000000000000000000000000000000000000000000000000003039"); api.anvil_set_storage_at(busd_addr, idx, value).await.unwrap(); let storage = api.storage_at(busd_addr, idx, None).await.unwrap(); assert_eq!(storage, value); - let input = - hex::decode("70a082310000000000000000000000000000000000000000000000000000000000000000") - .unwrap(); + let busd_contract = BUSD::new(busd_addr, &provider); - let busd = BUSD::new(busd_addr, provider); - let call = busd::BalanceOfCall::decode(&input).unwrap(); - - let balance = busd.balance_of(call.0).call().await.unwrap(); + let BUSD::balanceOfReturn { _0 } = busd_contract + .balanceOf(address!("0000000000000000000000000000000000000000")) + .call() + .await + .unwrap(); + let balance = _0; assert_eq!(balance, U256::from(12345u64)); } @@ -430,19 +429,19 @@ async fn can_get_node_info() { let provider = handle.http_provider(); let block_number = provider.get_block_number().await.unwrap(); - let block = provider.get_block(block_number).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::from(block_number), false).await.unwrap().unwrap(); let expected_node_info = NodeInfo { - current_block_number: U64([0]), + current_block_number: U64::from(0), current_block_timestamp: 1, - current_block_hash: block.hash.unwrap(), - hard_fork: SpecId::SHANGHAI, + current_block_hash: block.header.hash.unwrap(), + hard_fork: SpecId::CANCUN, transaction_order: "fees".to_owned(), environment: NodeEnvironment { - base_fee: U256::from_str("0x3b9aca00").unwrap(), + base_fee: U256::from_str("0x3b9aca00").unwrap().to(), chain_id: 0x7a69, - gas_limit: U256::from_str("0x1c9c380").unwrap(), - gas_price: U256::from_str("0x77359400").unwrap(), + gas_limit: U256::from_str("0x1c9c380").unwrap().to(), + gas_price: U256::from_str("0x77359400").unwrap().to(), }, fork_config: NodeForkConfig { fork_url: None, @@ -462,12 +461,12 @@ async fn can_get_metadata() { let provider = handle.http_provider(); - let block_number = provider.get_block_number().await.unwrap().as_u64(); - let chain_id = provider.get_chainid().await.unwrap().as_u64(); - let block = provider.get_block(block_number).await.unwrap().unwrap(); + let block_number = provider.get_block_number().await.unwrap(); + let chain_id = provider.get_chain_id().await.unwrap(); + let block = provider.get_block(BlockId::from(block_number), false).await.unwrap().unwrap(); let expected_metadata = AnvilMetadata { - latest_block_hash: block.hash.unwrap(), + latest_block_hash: block.header.hash.unwrap(), latest_block_number: block_number, chain_id, client_version: CLIENT_VERSION, @@ -483,16 +482,16 @@ async fn can_get_metadata() { async fn can_get_metadata_on_fork() { let (api, handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some("https://bsc-dataseed.binance.org/"))).await; - let provider = Arc::new(handle.http_provider()); + let provider = handle.http_provider(); let metadata = api.anvil_metadata().await.unwrap(); - let block_number = provider.get_block_number().await.unwrap().as_u64(); - let chain_id = provider.get_chainid().await.unwrap().as_u64(); - let block = provider.get_block(block_number).await.unwrap().unwrap(); + let block_number = provider.get_block_number().await.unwrap(); + let chain_id = provider.get_chain_id().await.unwrap(); + let block = provider.get_block(BlockId::from(block_number), false).await.unwrap().unwrap(); let expected_metadata = AnvilMetadata { - latest_block_hash: block.hash.unwrap(), + latest_block_hash: block.header.hash.unwrap(), latest_block_number: block_number, chain_id, client_version: CLIENT_VERSION, @@ -500,7 +499,7 @@ async fn can_get_metadata_on_fork() { forked_network: Some(ForkedNetwork { chain_id, fork_block_number: block_number, - fork_block_hash: block.hash.unwrap(), + fork_block_hash: block.header.hash.unwrap(), }), snapshots: Default::default(), }; @@ -530,28 +529,27 @@ async fn test_get_transaction_receipt() { let provider = handle.http_provider(); // set the base fee - let new_base_fee = U256::from(1_000); + let new_base_fee = U256::from(1000); api.anvil_set_next_block_base_fee_per_gas(new_base_fee).await.unwrap(); // send a EIP-1559 transaction - let tx = - TypedTransaction::Eip1559(Eip1559TransactionRequest::new().gas(U256::from(30_000_000))); - let receipt = - provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); + let to = Address::random(); + let val = U256::from(1337); + let tx = TransactionRequest::default().with_to(to).with_value(val); + let tx = WithOtherFields::new(tx); + + let receipt = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); // the block should have the new base fee - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block.base_fee_per_gas.unwrap().as_u64(), new_base_fee.as_u64()); + let block = provider.get_block(BlockId::default(), false).await.unwrap().unwrap(); + assert_eq!(block.header.base_fee_per_gas.unwrap(), new_base_fee.to::()); - // mine block + // mine blocks api.evm_mine(None).await.unwrap(); // the transaction receipt should have the original effective gas price let new_receipt = provider.get_transaction_receipt(receipt.transaction_hash).await.unwrap(); - assert_eq!( - receipt.effective_gas_price.unwrap().as_u64(), - new_receipt.unwrap().effective_gas_price.unwrap().as_u64() - ); + assert_eq!(receipt.effective_gas_price, new_receipt.unwrap().effective_gas_price); } // test can set chain id @@ -559,14 +557,14 @@ async fn test_get_transaction_receipt() { async fn test_set_chain_id() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let chain_id = provider.get_chainid().await.unwrap(); - assert_eq!(chain_id, U256::from(31337)); + let chain_id = provider.get_chain_id().await.unwrap(); + assert_eq!(chain_id, 31337); let chain_id = 1234; api.anvil_set_chain_id(chain_id).await.unwrap(); - let chain_id = provider.get_chainid().await.unwrap(); - assert_eq!(chain_id, U256::from(1234)); + let chain_id = provider.get_chain_id().await.unwrap(); + assert_eq!(chain_id, 1234); } // @@ -576,17 +574,17 @@ async fn test_fork_revert_next_block_timestamp() { // Mine a new block, and check the new block gas limit api.mine_one().await; - let latest_block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); + let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); let snapshot_id = api.evm_snapshot().await.unwrap(); api.mine_one().await; api.evm_revert(snapshot_id).await.unwrap(); - let block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); + let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); assert_eq!(block, latest_block); api.mine_one().await; - let block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); - assert!(block.timestamp > latest_block.timestamp); + let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + assert!(block.header.timestamp > latest_block.header.timestamp); } // test that after a snapshot revert, the env block is reset @@ -598,22 +596,55 @@ async fn test_fork_revert_call_latest_block_timestamp() { // Mine a new block, and check the new block gas limit api.mine_one().await; - let latest_block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); + let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); let snapshot_id = api.evm_snapshot().await.unwrap(); api.mine_one().await; api.evm_revert(snapshot_id).await.unwrap(); - let multicall = MulticallContract::new( - Address::from_str("0xeefba1e63905ef1d7acba5a8513c70307c1ce441").unwrap(), - provider.into(), - ); - - assert_eq!(multicall.get_current_block_timestamp().await.unwrap(), latest_block.timestamp); - assert_eq!(multicall.get_current_block_difficulty().await.unwrap(), latest_block.difficulty); - assert_eq!(multicall.get_current_block_gas_limit().await.unwrap(), latest_block.gas_limit); - assert_eq!( - multicall.get_current_block_coinbase().await.unwrap(), - latest_block.author.unwrap_or_default() - ); + let multicall_contract = + MulticallContract::new(address!("eefba1e63905ef1d7acba5a8513c70307c1ce441"), &provider); + + let MulticallContract::getCurrentBlockTimestampReturn { timestamp } = + multicall_contract.getCurrentBlockTimestamp().call().await.unwrap(); + assert_eq!(timestamp, U256::from(latest_block.header.timestamp)); + + let MulticallContract::getCurrentBlockDifficultyReturn { difficulty } = + multicall_contract.getCurrentBlockDifficulty().call().await.unwrap(); + assert_eq!(difficulty, U256::from(latest_block.header.difficulty)); + + let MulticallContract::getCurrentBlockGasLimitReturn { gaslimit } = + multicall_contract.getCurrentBlockGasLimit().call().await.unwrap(); + assert_eq!(gaslimit, U256::from(latest_block.header.gas_limit)); + + let MulticallContract::getCurrentBlockCoinbaseReturn { coinbase } = + multicall_contract.getCurrentBlockCoinbase().call().await.unwrap(); + assert_eq!(coinbase, latest_block.header.miner); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_remove_pool_transactions() { + let (api, handle) = spawn(NodeConfig::test()).await; + + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); + let from = wallet.address(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let sender = Address::random(); + let to = Address::random(); + let val = U256::from(1337); + let tx = TransactionRequest::default().with_from(sender).with_to(to).with_value(val); + let tx = WithOtherFields::new(tx); + + provider.send_transaction(tx.with_from(from)).await.unwrap().register().await.unwrap(); + + let initial_txs = provider.txpool_inspect().await.unwrap(); + assert_eq!(initial_txs.pending.len(), 1); + + api.anvil_remove_pool_transactions(wallet.address()).await.unwrap(); + + let final_txs = provider.txpool_inspect().await.unwrap(); + assert_eq!(final_txs.pending.len(), 0); } diff --git a/crates/anvil/tests/it/api.rs b/crates/anvil/tests/it/api.rs index 32c6fcf2ba6c5..ec8335562f79a 100644 --- a/crates/anvil/tests/it/api.rs +++ b/crates/anvil/tests/it/api.rs @@ -1,31 +1,30 @@ //! general eth api tests -use crate::abi::{MulticallContract, SimpleStorage}; -use anvil::{ - eth::{api::CLIENT_VERSION, EthApi}, - spawn, NodeConfig, CHAIN_ID, +use crate::{ + abi::{MulticallContract, SimpleStorage}, + utils::{connect_pubsub_with_signer, http_provider_with_signer}, }; -use anvil_core::eth::{state::AccountOverride, transaction::EthTransactionRequest}; -use ethers::{ - abi::{Address, Tokenizable}, - prelude::{builders::ContractCall, decode_function_data, Middleware, SignerMiddleware}, - signers::Signer, - types::{Block, BlockNumber, Chain, Transaction, TransactionRequest, H256, U256}, - utils::get_contract_address, +use alloy_network::{EthereumSigner, TransactionBuilder}; +use alloy_primitives::{Address, ChainId, B256, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{ + request::TransactionRequest, state::AccountOverride, BlockId, BlockNumberOrTag, + BlockTransactions, WithOtherFields, }; -use std::{collections::HashMap, sync::Arc, time::Duration}; +use anvil::{eth::api::CLIENT_VERSION, spawn, NodeConfig, CHAIN_ID}; +use std::{collections::HashMap, time::Duration}; #[tokio::test(flavor = "multi_thread")] async fn can_get_block_number() { let (api, handle) = spawn(NodeConfig::test()).await; let block_num = api.block_number().unwrap(); - assert_eq!(block_num, U256::zero()); + assert_eq!(block_num, U256::from(0)); let provider = handle.http_provider(); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num, block_num.as_u64().into()); + assert_eq!(num, block_num.to::()); } #[tokio::test(flavor = "multi_thread")] @@ -35,7 +34,7 @@ async fn can_dev_get_balance() { let genesis_balance = handle.genesis_balance(); for acc in handle.genesis_accounts() { - let balance = provider.get_balance(acc, None).await.unwrap(); + let balance = provider.get_balance(acc, BlockId::latest()).await.unwrap(); assert_eq!(balance, genesis_balance); } } @@ -61,7 +60,7 @@ async fn can_get_client_version() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let version = provider.client_version().await.unwrap(); + let version = provider.get_client_version().await.unwrap(); assert_eq!(CLIENT_VERSION, version); } @@ -70,20 +69,21 @@ async fn can_get_chain_id() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let chain_id = provider.get_chainid().await.unwrap(); - assert_eq!(chain_id, CHAIN_ID.into()); + let chain_id = provider.get_chain_id().await.unwrap(); + assert_eq!(chain_id, CHAIN_ID); } #[tokio::test(flavor = "multi_thread")] async fn can_modify_chain_id() { - let (_api, handle) = spawn(NodeConfig::test().with_chain_id(Some(Chain::Goerli))).await; + let (_api, handle) = + spawn(NodeConfig::test().with_chain_id(Some(ChainId::from(777_u64)))).await; let provider = handle.http_provider(); - let chain_id = provider.get_chainid().await.unwrap(); - assert_eq!(chain_id, Chain::Goerli.into()); + let chain_id = provider.get_chain_id().await.unwrap(); + assert_eq!(chain_id, 777); let chain_id = provider.get_net_version().await.unwrap(); - assert_eq!(chain_id, (Chain::Goerli as u64).to_string()); + assert_eq!(chain_id, 777); } #[tokio::test(flavor = "multi_thread")] @@ -97,238 +97,218 @@ async fn can_get_network_id() { #[tokio::test(flavor = "multi_thread")] async fn can_get_block_by_number() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); + let accounts: Vec<_> = handle.dev_wallets().collect(); + let signer: EthereumSigner = accounts[0].clone().into(); let from = accounts[0].address(); let to = accounts[1].address(); - let amount = handle.genesis_balance().checked_div(2u64.into()).unwrap(); - // send a dummy transactions - let tx = TransactionRequest::new().to(to).value(amount).from(from); - let _ = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - let block: Block = provider.get_block_with_txs(1u64).await.unwrap().unwrap(); - assert_eq!(block.transactions.len(), 1); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let val = handle.genesis_balance().checked_div(U256::from(2)).unwrap(); + + // send a dummy transaction + let tx = TransactionRequest::default().with_from(from).with_to(to).with_value(val); + let tx = WithOtherFields::new(tx); + + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); - let block = provider.get_block(1u64).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::number(1), true).await.unwrap().unwrap(); assert_eq!(block.transactions.len(), 1); - let block = provider.get_block(block.hash.unwrap()).await.unwrap().unwrap(); + let block = + provider.get_block(BlockId::hash(block.header.hash.unwrap()), true).await.unwrap().unwrap(); assert_eq!(block.transactions.len(), 1); } #[tokio::test(flavor = "multi_thread")] async fn can_get_pending_block() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); + let accounts: Vec<_> = handle.dev_wallets().collect(); + let signer: EthereumSigner = accounts[0].clone().into(); + let from = accounts[0].address(); + let to = accounts[1].address(); - let block = provider.get_block(BlockNumber::Pending).await.unwrap().unwrap(); + let provider = connect_pubsub_with_signer(&handle.http_endpoint(), signer).await; - assert_eq!(block.number.unwrap().as_u64(), 1u64); + let block = provider.get_block(BlockId::pending(), false).await.unwrap().unwrap(); + assert_eq!(block.header.number.unwrap(), 1); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0u64); + assert_eq!(num, 0); api.anvil_set_auto_mine(false).await.unwrap(); - let from = accounts[0].address(); - let to = accounts[1].address(); - let tx = TransactionRequest::new().to(to).value(100u64).from(from); + let tx = TransactionRequest::default().with_from(from).with_to(to).with_value(U256::from(100)); - let tx = provider.send_transaction(tx, None).await.unwrap(); + let pending = provider.send_transaction(tx.clone()).await.unwrap().register().await.unwrap(); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0u64); + assert_eq!(num, 0); - let block = provider.get_block(BlockNumber::Pending).await.unwrap().unwrap(); - assert_eq!(block.number.unwrap().as_u64(), 1u64); + let block = provider.get_block(BlockId::pending(), false).await.unwrap().unwrap(); + assert_eq!(block.header.number.unwrap(), 1); assert_eq!(block.transactions.len(), 1); - assert_eq!(block.transactions, vec![tx.tx_hash()]); + assert_eq!(block.transactions, BlockTransactions::Hashes(vec![*pending.tx_hash()])); - let block = provider.get_block_with_txs(BlockNumber::Pending).await.unwrap().unwrap(); - assert_eq!(block.number.unwrap().as_u64(), 1u64); + let block = provider.get_block(BlockId::pending(), true).await.unwrap().unwrap(); + assert_eq!(block.header.number.unwrap(), 1); assert_eq!(block.transactions.len(), 1); } #[tokio::test(flavor = "multi_thread")] async fn can_call_on_pending_block() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - - let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0u64); - - api.anvil_set_auto_mine(false).await.unwrap(); let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let mut deploy_tx = MulticallContract::deploy(Arc::clone(&client), ()).unwrap().deployer.tx; - deploy_tx.set_nonce(0); - let pending_contract_address = get_contract_address(sender, deploy_tx.nonce().unwrap()); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let num = provider.get_block_number().await.unwrap(); + assert_eq!(num, 0); - client.send_transaction(deploy_tx, None).await.unwrap(); + api.anvil_set_auto_mine(false).await.unwrap(); - let pending_contract = MulticallContract::new(pending_contract_address, client.clone()); + let _contract_pending = MulticallContract::deploy_builder(&provider) + .from(wallet.address()) + .send() + .await + .unwrap() + .register() + .await + .unwrap(); + let contract_address = sender.create(0); + let contract = MulticallContract::new(contract_address, &provider); - let num = client.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0u64); + let num = provider.get_block_number().await.unwrap(); + assert_eq!(num, 0); // Ensure that we can get the block_number from the pending contract - let (ret_block_number, _) = - pending_contract.aggregate(vec![]).block(BlockNumber::Pending).call().await.unwrap(); - assert_eq!(ret_block_number.as_u64(), 1u64); + let MulticallContract::aggregateReturn { blockNumber: ret_block_number, .. } = + contract.aggregate(vec![]).block(BlockId::pending()).call().await.unwrap(); + assert_eq!(ret_block_number, U256::from(1)); let accounts: Vec

= handle.dev_wallets().map(|w| w.address()).collect(); + for i in 1..10 { api.anvil_set_coinbase(accounts[i % accounts.len()]).await.unwrap(); - api.evm_set_block_gas_limit((30_000_000 + i).into()).unwrap(); + api.evm_set_block_gas_limit(U256::from(30_000_000 + i)).unwrap(); - api.anvil_mine(Some(1.into()), None).await.unwrap(); + api.anvil_mine(Some(U256::from(1)), None).await.unwrap(); tokio::time::sleep(Duration::from_secs(1)).await; } + // Ensure that the right header values are set when calling a past block - for block_number in 1..(api.block_number().unwrap().as_usize() + 1) { - let block_number = BlockNumber::Number(block_number.into()); + for anvil_block_number in 1..(api.block_number().unwrap().to::() + 1) { + let block_number = BlockNumberOrTag::Number(anvil_block_number as u64); let block = api.block_by_number(block_number).await.unwrap().unwrap(); - let block_timestamp = pending_contract - .get_current_block_timestamp() - .block(block_number) - .call() - .await - .unwrap(); - assert_eq!(block.timestamp, block_timestamp); - - let block_gas_limit = pending_contract - .get_current_block_gas_limit() - .block(block_number) - .call() - .await - .unwrap(); - assert_eq!(block.gas_limit, block_gas_limit); - - let block_coinbase = - pending_contract.get_current_block_coinbase().block(block_number).call().await.unwrap(); - assert_eq!(block.author.unwrap(), block_coinbase); + let MulticallContract::getCurrentBlockTimestampReturn { timestamp: ret_timestamp, .. } = + contract + .getCurrentBlockTimestamp() + .block(BlockId::number(anvil_block_number as u64)) + .call() + .await + .unwrap(); + assert_eq!(block.header.timestamp, ret_timestamp.to::()); + + let MulticallContract::getCurrentBlockGasLimitReturn { gaslimit: ret_gas_limit, .. } = + contract + .getCurrentBlockGasLimit() + .block(BlockId::number(anvil_block_number as u64)) + .call() + .await + .unwrap(); + assert_eq!(block.header.gas_limit, ret_gas_limit.to::()); + + let MulticallContract::getCurrentBlockCoinbaseReturn { coinbase: ret_coinbase, .. } = + contract + .getCurrentBlockCoinbase() + .block(BlockId::number(anvil_block_number as u64)) + .call() + .await + .unwrap(); + assert_eq!(block.header.miner, ret_coinbase); } } -async fn call_with_override( - api: &EthApi, - call: ContractCall, - to: Address, - overrides: HashMap, -) -> D -where - D: Tokenizable, -{ - let result = api - .call( - EthTransactionRequest { - data: call.tx.data().cloned(), - to: Some(to), - ..Default::default() - }, - None, - Some(overrides), - ) - .await - .unwrap(); - decode_function_data(&call.function, result.as_ref(), false).unwrap() -} - #[tokio::test(flavor = "multi_thread")] async fn can_call_with_state_override() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); + let account = wallet.address(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); api.anvil_set_auto_mine(true).await.unwrap(); - let wallet = handle.dev_wallets().next().unwrap(); - let account = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let multicall_contract = MulticallContract::deploy(&provider).await.unwrap(); let init_value = "toto".to_string(); - let multicall = - MulticallContract::deploy(Arc::clone(&client), ()).unwrap().send().await.unwrap(); - let simple_storage = SimpleStorage::deploy(Arc::clone(&client), init_value.clone()) - .unwrap() - .send() - .await - .unwrap(); + + let simple_storage_contract = + SimpleStorage::deploy(&provider, init_value.clone()).await.unwrap(); // Test the `balance` account override - let balance = 42u64.into(); - let result = call_with_override( - &api, - multicall.get_eth_balance(account), - multicall.address(), - HashMap::from([( - account, - AccountOverride { balance: Some(balance), ..Default::default() }, - )]), - ) - .await; + let balance = U256::from(42u64); + let overrides = HashMap::from([( + account, + AccountOverride { balance: Some(balance), ..Default::default() }, + )]); + let result = + multicall_contract.getEthBalance(account).state(overrides).call().await.unwrap().balance; assert_eq!(result, balance); // Test the `state_diff` account override let overrides = HashMap::from([( - simple_storage.address(), + *simple_storage_contract.address(), AccountOverride { // The `lastSender` is in the first storage slot - state_diff: Some(HashMap::from([(H256::from_low_u64_be(0), account.into())])), + state_diff: Some(HashMap::from([( + B256::ZERO, + U256::from_be_slice(B256::from(account.into_word()).as_slice()), + )])), ..Default::default() }, )]); - let last_sender = call_with_override( - &api, - simple_storage.last_sender(), - simple_storage.address(), - Default::default(), - ) - .await; + let last_sender = + simple_storage_contract.lastSender().state(HashMap::default()).call().await.unwrap()._0; // No `sender` set without override - assert_eq!(last_sender, Address::zero()); - let last_sender = call_with_override( - &api, - simple_storage.last_sender(), - simple_storage.address(), - overrides.clone(), - ) - .await; + assert_eq!(last_sender, Address::ZERO); + + let last_sender = + simple_storage_contract.lastSender().state(overrides.clone()).call().await.unwrap()._0; // `sender` *is* set with override assert_eq!(last_sender, account); - let value = - call_with_override(&api, simple_storage.get_value(), simple_storage.address(), overrides) - .await; + + let value = simple_storage_contract.getValue().state(overrides).call().await.unwrap()._0; // `value` *is not* changed with state-diff assert_eq!(value, init_value); // Test the `state` account override let overrides = HashMap::from([( - simple_storage.address(), + *simple_storage_contract.address(), AccountOverride { // The `lastSender` is in the first storage slot - state: Some(HashMap::from([(H256::from_low_u64_be(0), account.into())])), + state: Some(HashMap::from([( + B256::ZERO, + U256::from_be_slice(B256::from(account.into_word()).as_slice()), + )])), ..Default::default() }, )]); - let last_sender = call_with_override( - &api, - simple_storage.last_sender(), - simple_storage.address(), - overrides.clone(), - ) - .await; + let last_sender = + simple_storage_contract.lastSender().state(overrides.clone()).call().await.unwrap()._0; // `sender` *is* set with override assert_eq!(last_sender, account); - let value = - call_with_override(&api, simple_storage.get_value(), simple_storage.address(), overrides) - .await; + + let value = simple_storage_contract.getValue().state(overrides).call().await.unwrap()._0; // `value` *is* changed with state assert_eq!(value, ""); } diff --git a/crates/anvil/tests/it/eip4844.rs b/crates/anvil/tests/it/eip4844.rs new file mode 100644 index 0000000000000..cb3bf5aa983ca --- /dev/null +++ b/crates/anvil/tests/it/eip4844.rs @@ -0,0 +1,192 @@ +use crate::utils::http_provider; +use alloy_consensus::{SidecarBuilder, SimpleCoder}; +use alloy_eips::eip4844::{DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK}; +use alloy_network::TransactionBuilder; +use alloy_primitives::U256; +use alloy_provider::Provider; +use alloy_rpc_types::{TransactionRequest, WithOtherFields}; +use anvil::{spawn, Hardfork, NodeConfig}; + +#[tokio::test(flavor = "multi_thread")] +async fn can_send_eip4844_transaction() { + let node_config = NodeConfig::default().with_hardfork(Some(Hardfork::Cancun)); + let (_api, handle) = spawn(node_config).await; + + let wallets = handle.dev_wallets().collect::>(); + let from = wallets[0].address(); + let to = wallets[1].address(); + let provider = http_provider(&handle.http_endpoint()); + + let eip1559_est = provider.estimate_eip1559_fees(None).await.unwrap(); + let gas_price = provider.get_gas_price().await.unwrap(); + + let sidecar: SidecarBuilder = SidecarBuilder::from_slice("Hello World".as_bytes()); + + let sidecar = sidecar.build().unwrap(); + let tx = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_nonce(0) + .with_max_fee_per_blob_gas(gas_price + 1) + .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) + .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) + .with_blob_sidecar(sidecar) + .value(U256::from(5)); + + let mut tx = WithOtherFields::new(tx); + + tx.populate_blob_hashes(); + + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert_eq!(receipt.blob_gas_used, Some(131072)); + assert_eq!(receipt.blob_gas_price, Some(0x1)); // 1 wei +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_send_multiple_blobs_in_one_tx() { + let node_config = NodeConfig::default().with_hardfork(Some(Hardfork::Cancun)); + let (_api, handle) = spawn(node_config).await; + + let wallets = handle.dev_wallets().collect::>(); + + let from = wallets[0].address(); + let to = wallets[1].address(); + + let provider = http_provider(&handle.http_endpoint()); + + let eip1559_est = provider.estimate_eip1559_fees(None).await.unwrap(); + let gas_price = provider.get_gas_price().await.unwrap(); + + let large_data = vec![1u8; DATA_GAS_PER_BLOB as usize * 5]; // 131072 is DATA_GAS_PER_BLOB and also BYTE_PER_BLOB + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(&large_data); + + let sidecar = sidecar.build().unwrap(); + + let tx = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_nonce(0) + .with_max_fee_per_blob_gas(gas_price + 1) + .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) + .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) + .with_blob_sidecar(sidecar); + let mut tx = WithOtherFields::new(tx); + + tx.populate_blob_hashes(); + + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert_eq!(receipt.blob_gas_used, Some(MAX_DATA_GAS_PER_BLOCK as u128)); + assert_eq!(receipt.blob_gas_price, Some(0x1)); // 1 wei +} + +#[tokio::test(flavor = "multi_thread")] +async fn cannot_exceed_six_blobs() { + let node_config = NodeConfig::default().with_hardfork(Some(Hardfork::Cancun)); + let (_api, handle) = spawn(node_config).await; + + let wallets = handle.dev_wallets().collect::>(); + + let from = wallets[0].address(); + let to = wallets[1].address(); + + let provider = http_provider(&handle.http_endpoint()); + + let eip1559_est = provider.estimate_eip1559_fees(None).await.unwrap(); + let gas_price = provider.get_gas_price().await.unwrap(); + + let large_data = vec![1u8; DATA_GAS_PER_BLOB as usize * 6]; // 131072 is DATA_GAS_PER_BLOB and also BYTE_PER_BLOB + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(&large_data); + + let sidecar = sidecar.build().unwrap(); + + let tx = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_nonce(0) + .with_max_fee_per_blob_gas(gas_price + 1) + .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) + .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) + .with_blob_sidecar(sidecar); + let mut tx = WithOtherFields::new(tx); + + tx.populate_blob_hashes(); + + let err = provider.send_transaction(tx).await.unwrap_err(); + + assert!(err.to_string().contains("too many blobs")); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_mine_blobs_when_exceeds_max_blobs() { + let node_config = NodeConfig::default().with_hardfork(Some(Hardfork::Cancun)); + let (api, handle) = spawn(node_config).await; + api.anvil_set_auto_mine(false).await.unwrap(); + + let wallets = handle.dev_wallets().collect::>(); + + let from = wallets[0].address(); + let to = wallets[1].address(); + + let provider = http_provider(&handle.http_endpoint()); + + let eip1559_est = provider.estimate_eip1559_fees(None).await.unwrap(); + let gas_price = provider.get_gas_price().await.unwrap(); + + let first_batch = vec![1u8; DATA_GAS_PER_BLOB as usize * 3]; + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(&first_batch); + + let num_blobs_first = sidecar.clone().take().len(); + + let sidecar = sidecar.build().unwrap(); + + let tx = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_nonce(0) + .with_max_fee_per_blob_gas(gas_price + 1) + .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) + .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) + .with_blob_sidecar(sidecar); + let mut tx = WithOtherFields::new(tx); + + tx.populate_blob_hashes(); + + let first_tx = provider.send_transaction(tx.clone()).await.unwrap(); + + let second_batch = vec![1u8; DATA_GAS_PER_BLOB as usize * 2]; + + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(&second_batch); + + let num_blobs_second = sidecar.clone().take().len(); + + let sidecar = sidecar.build().unwrap(); + tx.set_blob_sidecar(sidecar); + tx.set_nonce(1); + tx.populate_blob_hashes(); + let second_tx = provider.send_transaction(tx).await.unwrap(); + + api.mine_one().await; + + let first_receipt = first_tx.get_receipt().await.unwrap(); + + api.mine_one().await; + let second_receipt = second_tx.get_receipt().await.unwrap(); + + let (first_block, second_block) = tokio::join!( + provider.get_block_by_number(first_receipt.block_number.unwrap().into(), false), + provider.get_block_by_number(second_receipt.block_number.unwrap().into(), false) + ); + assert_eq!( + first_block.unwrap().unwrap().header.blob_gas_used, + Some(DATA_GAS_PER_BLOB as u128 * num_blobs_first as u128) + ); + + assert_eq!( + second_block.unwrap().unwrap().header.blob_gas_used, + Some(DATA_GAS_PER_BLOB as u128 * num_blobs_second as u128) + ); + assert_eq!(first_receipt.block_number.unwrap() + 1, second_receipt.block_number.unwrap()); // Mined in two + // different blocks +} diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index 444b234831511..2ef1e70788196 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -1,24 +1,22 @@ //! various fork related test -use crate::{abi::*, utils}; -use anvil::{eth::EthApi, spawn, NodeConfig, NodeHandle}; -use anvil_core::{eth::transaction::EthTransactionRequest, types::Forking}; -use ethers::{ - core::rand, - prelude::{Bytes, LocalWallet, Middleware, SignerMiddleware}, - providers::{Http, Provider}, - signers::Signer, - types::{ - transaction::eip2718::TypedTransaction, Address, BlockNumber, Chain, TransactionRequest, - U256, - }, +use crate::{ + abi::{Greeter, ERC721}, + utils::{http_provider, http_provider_with_signer}, }; -use foundry_common::{ - get_http_provider, rpc, - rpc::next_http_rpc_endpoint, - types::{ToAlloy, ToEthers}, +use alloy_network::{EthereumSigner, TransactionBuilder}; +use alloy_primitives::{address, Address, Bytes, TxKind, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{ + request::{TransactionInput, TransactionRequest}, + BlockId, BlockNumberOrTag, WithOtherFields, }; +use alloy_signer_wallet::LocalWallet; +use anvil::{eth::EthApi, spawn, NodeConfig, NodeHandle}; +use anvil_core::types::Forking; +use foundry_common::provider::get_http_provider; use foundry_config::Config; +use foundry_test_utils::rpc::{self, next_http_rpc_endpoint}; use futures::StreamExt; use std::{sync::Arc, time::Duration}; @@ -67,7 +65,7 @@ async fn test_spawn_fork() { assert!(api.is_fork()); let head = api.block_number().unwrap(); - assert_eq!(head, BLOCK_NUMBER.into()) + assert_eq!(head, U256::from(BLOCK_NUMBER)) } #[tokio::test(flavor = "multi_thread")] @@ -77,7 +75,7 @@ async fn test_fork_eth_get_balance() { for _ in 0..10 { let addr = Address::random(); let balance = api.balance(addr, None).await.unwrap(); - let provider_balance = provider.get_balance(addr, None).await.unwrap(); + let provider_balance = provider.get_balance(addr, BlockId::latest()).await.unwrap(); assert_eq!(balance, provider_balance) } } @@ -93,17 +91,11 @@ async fn test_fork_eth_get_balance_after_mine() { let address = Address::random(); - let _balance = provider - .get_balance(address, Some(BlockNumber::Number(number.into()).into())) - .await - .unwrap(); + let _balance = provider.get_balance(address, BlockId::Number(number.into())).await.unwrap(); api.evm_mine(None).await.unwrap(); - let _balance = provider - .get_balance(address, Some(BlockNumber::Number(number.into()).into())) - .await - .unwrap(); + let _balance = provider.get_balance(address, BlockId::Number(number.into())).await.unwrap(); } // @@ -117,13 +109,11 @@ async fn test_fork_eth_get_code_after_mine() { let address = Address::random(); - let _code = - provider.get_code(address, Some(BlockNumber::Number(number.into()).into())).await.unwrap(); + let _code = provider.get_code_at(address, BlockId::Number(1.into())).await.unwrap(); api.evm_mine(None).await.unwrap(); - let _code = - provider.get_code(address, Some(BlockNumber::Number(number.into()).into())).await.unwrap(); + let _code = provider.get_code_at(address, BlockId::Number(1.into())).await.unwrap(); } #[tokio::test(flavor = "multi_thread")] @@ -133,17 +123,24 @@ async fn test_fork_eth_get_code() { for _ in 0..10 { let addr = Address::random(); let code = api.get_code(addr, None).await.unwrap(); - let provider_code = provider.get_code(addr, None).await.unwrap(); + let provider_code = provider.get_code_at(addr, BlockId::latest()).await.unwrap(); assert_eq!(code, provider_code) } - for address in utils::contract_addresses(Chain::Mainnet) { + let addresses: Vec
= vec![ + "0x6b175474e89094c44da98b954eedeac495271d0f".parse().unwrap(), + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48".parse().unwrap(), + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".parse().unwrap(), + "0x1F98431c8aD98523631AE4a59f267346ea31F984".parse().unwrap(), + "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45".parse().unwrap(), + ]; + for address in addresses { let prev_code = api - .get_code(address, Some(BlockNumber::Number((BLOCK_NUMBER - 10).into()).into())) + .get_code(address, Some(BlockNumberOrTag::Number(BLOCK_NUMBER - 10).into())) .await .unwrap(); let code = api.get_code(address, None).await.unwrap(); - let provider_code = provider.get_code(address, None).await.unwrap(); + let provider_code = provider.get_code_at(address, BlockId::latest()).await.unwrap(); assert_eq!(code, prev_code); assert_eq!(code, provider_code); assert!(!code.as_ref().is_empty()); @@ -157,14 +154,14 @@ async fn test_fork_eth_get_nonce() { for _ in 0..10 { let addr = Address::random(); - let api_nonce = api.transaction_count(addr, None).await.unwrap(); - let provider_nonce = provider.get_transaction_count(addr, None).await.unwrap(); + let api_nonce = api.transaction_count(addr, None).await.unwrap().to::(); + let provider_nonce = provider.get_transaction_count(addr, BlockId::latest()).await.unwrap(); assert_eq!(api_nonce, provider_nonce); } let addr = Config::DEFAULT_SENDER; - let api_nonce = api.transaction_count(addr.to_ethers(), None).await.unwrap(); - let provider_nonce = provider.get_transaction_count(addr.to_ethers(), None).await.unwrap(); + let api_nonce = api.transaction_count(addr, None).await.unwrap().to::(); + let provider_nonce = provider.get_transaction_count(addr, BlockId::latest()).await.unwrap(); assert_eq!(api_nonce, provider_nonce); } @@ -174,8 +171,10 @@ async fn test_fork_eth_fee_history() { let provider = handle.http_provider(); let count = 10u64; - let _history = api.fee_history(count.into(), BlockNumber::Latest, vec![]).await.unwrap(); - let _provider_history = provider.fee_history(count, BlockNumber::Latest, &[]).await.unwrap(); + let _history = + api.fee_history(U256::from(count), BlockNumberOrTag::Latest, vec![]).await.unwrap(); + let _provider_history = + provider.get_fee_history(count, BlockNumberOrTag::Latest, &[]).await.unwrap(); } #[tokio::test(flavor = "multi_thread")] @@ -187,36 +186,33 @@ async fn test_fork_reset() { let from = accounts[0].address(); let to = accounts[1].address(); let block_number = provider.get_block_number().await.unwrap(); - let balance_before = provider.get_balance(to, None).await.unwrap(); - let amount = handle.genesis_balance().checked_div(2u64.into()).unwrap(); - - let initial_nonce = provider.get_transaction_count(from, None).await.unwrap(); + let balance_before = provider.get_balance(to, BlockId::latest()).await.unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); - let tx = TransactionRequest::new().to(to).value(amount).from(from); + let initial_nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.transaction_index, 0u64.into()); + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + assert_eq!(tx.transaction_index, Some(0)); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); assert_eq!(nonce, initial_nonce + 1); - let to_balance = provider.get_balance(to, None).await.unwrap(); + let to_balance = provider.get_balance(to, BlockId::latest()).await.unwrap(); assert_eq!(balance_before.saturating_add(amount), to_balance); - api.anvil_reset(Some(Forking { - json_rpc_url: None, - block_number: Some(block_number.as_u64()), - })) - .await - .unwrap(); + api.anvil_reset(Some(Forking { json_rpc_url: None, block_number: Some(block_number) })) + .await + .unwrap(); // reset block number assert_eq!(block_number, provider.get_block_number().await.unwrap()); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); assert_eq!(nonce, initial_nonce); - let balance = provider.get_balance(from, None).await.unwrap(); + let balance = provider.get_balance(from, BlockId::latest()).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); - let balance = provider.get_balance(to, None).await.unwrap(); + let balance = provider.get_balance(to, BlockId::latest()).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); // reset to latest @@ -234,10 +230,10 @@ async fn test_fork_reset_setup() { let dead_addr: Address = "000000000000000000000000000000000000dEaD".parse().unwrap(); let block_number = provider.get_block_number().await.unwrap(); - assert_eq!(block_number, 0.into()); + assert_eq!(block_number, 0); - let local_balance = provider.get_balance(dead_addr, None).await.unwrap(); - assert_eq!(local_balance, 0.into()); + let local_balance = provider.get_balance(dead_addr, BlockId::latest()).await.unwrap(); + assert_eq!(local_balance, U256::ZERO); api.anvil_reset(Some(Forking { json_rpc_url: Some(rpc::next_http_archive_rpc_endpoint()), @@ -247,17 +243,16 @@ async fn test_fork_reset_setup() { .unwrap(); let block_number = provider.get_block_number().await.unwrap(); - assert_eq!(block_number, BLOCK_NUMBER.into()); + assert_eq!(block_number, BLOCK_NUMBER); - let remote_balance = provider.get_balance(dead_addr, None).await.unwrap(); - assert_eq!(remote_balance, DEAD_BALANCE_AT_BLOCK_NUMBER.into()); + let remote_balance = provider.get_balance(dead_addr, BlockId::latest()).await.unwrap(); + assert_eq!(remote_balance, U256::from(DEAD_BALANCE_AT_BLOCK_NUMBER)); } #[tokio::test(flavor = "multi_thread")] async fn test_fork_snapshotting() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); - let snapshot = api.evm_snapshot().await.unwrap(); let accounts: Vec<_> = handle.dev_wallets().collect(); @@ -265,26 +260,30 @@ async fn test_fork_snapshotting() { let to = accounts[1].address(); let block_number = provider.get_block_number().await.unwrap(); - let initial_nonce = provider.get_transaction_count(from, None).await.unwrap(); - let balance_before = provider.get_balance(to, None).await.unwrap(); - let amount = handle.genesis_balance().checked_div(2u64.into()).unwrap(); + let initial_nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); + let balance_before = provider.get_balance(to, BlockId::latest()).await.unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); - let tx = TransactionRequest::new().to(to).value(amount).from(from); + let provider = handle.http_provider(); + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); + + let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); - let _ = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let provider = handle.http_provider(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); assert_eq!(nonce, initial_nonce + 1); - let to_balance = provider.get_balance(to, None).await.unwrap(); + let to_balance = provider.get_balance(to, BlockId::latest()).await.unwrap(); assert_eq!(balance_before.saturating_add(amount), to_balance); assert!(api.evm_revert(snapshot).await.unwrap()); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); assert_eq!(nonce, initial_nonce); - let balance = provider.get_balance(from, None).await.unwrap(); + let balance = provider.get_balance(from, BlockId::latest()).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); - let balance = provider.get_balance(to, None).await.unwrap(); + let balance = provider.get_balance(to, BlockId::latest()).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); assert_eq!(block_number, provider.get_block_number().await.unwrap()); } @@ -301,28 +300,29 @@ async fn test_fork_snapshotting_repeated() { let to = accounts[1].address(); let block_number = provider.get_block_number().await.unwrap(); - let initial_nonce = provider.get_transaction_count(from, None).await.unwrap(); - let balance_before = provider.get_balance(to, None).await.unwrap(); - let amount = handle.genesis_balance().checked_div(2u64.into()).unwrap(); - - let tx = TransactionRequest::new().to(to).value(amount).from(from); + let initial_nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); + let balance_before = provider.get_balance(to, BlockId::latest()).await.unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(92u64)).unwrap(); - let _ = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); + let tx_provider = handle.http_provider(); + let _ = tx_provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); assert_eq!(nonce, initial_nonce + 1); - let to_balance = provider.get_balance(to, None).await.unwrap(); + let to_balance = provider.get_balance(to, BlockId::latest()).await.unwrap(); assert_eq!(balance_before.saturating_add(amount), to_balance); let _second_snapshot = api.evm_snapshot().await.unwrap(); assert!(api.evm_revert(snapshot).await.unwrap()); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); assert_eq!(nonce, initial_nonce); - let balance = provider.get_balance(from, None).await.unwrap(); + let balance = provider.get_balance(from, BlockId::latest()).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); - let balance = provider.get_balance(to, None).await.unwrap(); + let balance = provider.get_balance(to, BlockId::latest()).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); assert_eq!(block_number, provider.get_block_number().await.unwrap()); @@ -334,6 +334,60 @@ async fn test_fork_snapshotting_repeated() { assert!(!api.evm_revert(snapshot).await.unwrap()); } +// +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_snapshotting_blocks() { + let (api, handle) = spawn(fork_config()).await; + let provider = handle.http_provider(); + + // create a snapshot + let snapshot = api.evm_snapshot().await.unwrap(); + + let accounts: Vec<_> = handle.dev_wallets().collect(); + let from = accounts[0].address(); + let to = accounts[1].address(); + let block_number = provider.get_block_number().await.unwrap(); + + let initial_nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); + let balance_before = provider.get_balance(to, BlockId::latest()).await.unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); + + // send the transaction + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); + let _ = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let block_number_after = provider.get_block_number().await.unwrap(); + assert_eq!(block_number_after, block_number + 1); + + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); + assert_eq!(nonce, initial_nonce + 1); + let to_balance = provider.get_balance(to, BlockId::latest()).await.unwrap(); + assert_eq!(balance_before.saturating_add(amount), to_balance); + + // revert snapshot + assert!(api.evm_revert(snapshot).await.unwrap()); + + assert_eq!( + initial_nonce, + provider.get_transaction_count(from, BlockId::latest()).await.unwrap() + ); + let block_number_after = provider.get_block_number().await.unwrap(); + assert_eq!(block_number_after, block_number); + + // repeat transaction + let _ = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); + assert_eq!(nonce, initial_nonce + 1); + + // revert again: nothing to revert since snapshot gone + assert!(!api.evm_revert(snapshot).await.unwrap()); + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); + assert_eq!(nonce, initial_nonce + 1); + let block_number_after = provider.get_block_number().await.unwrap(); + assert_eq!(block_number_after, block_number + 1); +} + /// tests that the remote state and local state are kept separate. /// changes don't make into the read only Database that holds the remote state, which is flushed to /// a cache file. @@ -344,12 +398,12 @@ async fn test_separate_states() { let addr: Address = "000000000000000000000000000000000000dEaD".parse().unwrap(); - let remote_balance = provider.get_balance(addr, None).await.unwrap(); - assert_eq!(remote_balance, 12556104082473169733500u128.into()); + let remote_balance = provider.get_balance(addr, BlockId::latest()).await.unwrap(); + assert_eq!(remote_balance, U256::from(12556104082473169733500u128)); - api.anvil_set_balance(addr, 1337u64.into()).await.unwrap(); - let balance = provider.get_balance(addr, None).await.unwrap(); - assert_eq!(balance, 1337u64.into()); + api.anvil_set_balance(addr, U256::from(1337u64)).await.unwrap(); + let balance = provider.get_balance(addr, BlockId::latest()).await.unwrap(); + assert_eq!(balance, U256::from(1337u64)); let fork = api.get_fork().unwrap(); let fork_db = fork.database.read().await; @@ -359,35 +413,31 @@ async fn test_separate_states() { .db() .accounts .read() - .get(&addr.to_alloy()) + .get(&addr) .cloned() .unwrap(); - assert_eq!(acc.balance, remote_balance.to_alloy()) + assert_eq!(acc.balance, remote_balance); } #[tokio::test(flavor = "multi_thread")] async fn can_deploy_greeter_on_fork() { let (_api, handle) = spawn(fork_config().with_fork_block_number(Some(14723772u64))).await; - let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let signer: EthereumSigner = wallet.into(); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .send() - .await - .unwrap(); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let greeter_contract = Greeter::deploy(&provider, "Hello World!".to_string()).await.unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); - let greeter_contract = - Greeter::deploy(client, "Hello World!".to_string()).unwrap().send().await.unwrap(); + let greeter_contract = Greeter::deploy(&provider, "Hello World!".to_string()).await.unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); } #[tokio::test(flavor = "multi_thread")] @@ -395,36 +445,50 @@ async fn can_reset_properly() { let (origin_api, origin_handle) = spawn(NodeConfig::test()).await; let account = origin_handle.dev_accounts().next().unwrap(); let origin_provider = origin_handle.http_provider(); - let origin_nonce = 1u64.into(); - origin_api.anvil_set_nonce(account, origin_nonce).await.unwrap(); + let origin_nonce = 1u64; + origin_api.anvil_set_nonce(account, U256::from(origin_nonce)).await.unwrap(); - assert_eq!(origin_nonce, origin_provider.get_transaction_count(account, None).await.unwrap()); + assert_eq!( + origin_nonce, + origin_provider.get_transaction_count(account, BlockId::latest()).await.unwrap() + ); let (fork_api, fork_handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some(origin_handle.http_endpoint()))).await; let fork_provider = fork_handle.http_provider(); - assert_eq!(origin_nonce, fork_provider.get_transaction_count(account, None).await.unwrap()); + let fork_tx_provider = http_provider(&fork_handle.http_endpoint()); + assert_eq!( + origin_nonce, + fork_provider.get_transaction_count(account, BlockId::latest()).await.unwrap() + ); let to = Address::random(); - let to_balance = fork_provider.get_balance(to, None).await.unwrap(); - let tx = TransactionRequest::new().from(account).to(to).value(1337u64); - let tx = fork_provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let to_balance = fork_provider.get_balance(to, BlockId::latest()).await.unwrap(); + let tx = TransactionRequest::default().from(account).to(to).value(U256::from(1337u64)); + let tx = WithOtherFields::new(tx); + let tx = fork_tx_provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); // nonce incremented by 1 - assert_eq!(origin_nonce + 1, fork_provider.get_transaction_count(account, None).await.unwrap()); + assert_eq!( + origin_nonce + 1, + fork_provider.get_transaction_count(account, BlockId::latest()).await.unwrap() + ); // resetting to origin state fork_api.anvil_reset(Some(Forking::default())).await.unwrap(); // nonce reset to origin - assert_eq!(origin_nonce, fork_provider.get_transaction_count(account, None).await.unwrap()); + assert_eq!( + origin_nonce, + fork_provider.get_transaction_count(account, BlockId::latest()).await.unwrap() + ); // balance is reset - assert_eq!(to_balance, fork_provider.get_balance(to, None).await.unwrap()); + assert_eq!(to_balance, fork_provider.get_balance(to, BlockId::latest()).await.unwrap()); // tx does not exist anymore - assert!(fork_provider.get_transaction(tx.transaction_hash).await.unwrap().is_none()) + assert!(fork_tx_provider.get_transaction_by_hash(tx.transaction_hash).await.is_err()) } #[tokio::test(flavor = "multi_thread")] @@ -434,39 +498,46 @@ async fn test_fork_timestamp() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); - let block = provider.get_block(BLOCK_NUMBER).await.unwrap().unwrap(); - assert_eq!(block.timestamp.as_u64(), BLOCK_TIMESTAMP); + let block = + provider.get_block(BlockId::Number(BLOCK_NUMBER.into()), false).await.unwrap().unwrap(); + assert_eq!(block.header.timestamp, BLOCK_TIMESTAMP); let accounts: Vec<_> = handle.dev_wallets().collect(); let from = accounts[0].address(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64).from(from); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + let tx = + TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let status = tx.inner.inner.inner.receipt.status; + assert!(status); - let elapsed = start.elapsed().as_secs(); + let block = provider.get_block(BlockId::latest(), false).await.unwrap().unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let elapsed = start.elapsed().as_secs() + 1; // ensure the diff between the new mined block and the original block is within the elapsed time - let diff = block.timestamp - BLOCK_TIMESTAMP; - assert!(diff <= elapsed.into(), "diff={diff}, elapsed={elapsed}"); + let diff = block.header.timestamp - BLOCK_TIMESTAMP; + assert!(diff <= elapsed, "diff={diff}, elapsed={elapsed}"); let start = std::time::Instant::now(); // reset to check timestamp works after resetting api.anvil_reset(Some(Forking { json_rpc_url: None, block_number: Some(BLOCK_NUMBER) })) .await .unwrap(); - let block = provider.get_block(BLOCK_NUMBER).await.unwrap().unwrap(); - assert_eq!(block.timestamp.as_u64(), BLOCK_TIMESTAMP); + let block = + provider.get_block(BlockId::Number(BLOCK_NUMBER.into()), false).await.unwrap().unwrap(); + assert_eq!(block.header.timestamp, BLOCK_TIMESTAMP); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64).from(from); - let _tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = + TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); + let tx = WithOtherFields::new(tx); + let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); // FIXME: Awaits endlessly here. - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::latest(), false).await.unwrap().unwrap(); let elapsed = start.elapsed().as_secs() + 1; - let diff = block.timestamp - BLOCK_TIMESTAMP; - assert!(diff <= elapsed.into()); + let diff = block.header.timestamp - BLOCK_TIMESTAMP; + assert!(diff <= elapsed); // ensure that after setting a timestamp manually, then next block time is correct let start = std::time::Instant::now(); @@ -474,19 +545,23 @@ async fn test_fork_timestamp() { .await .unwrap(); api.evm_set_next_block_timestamp(BLOCK_TIMESTAMP + 1).unwrap(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64).from(from); - let _tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = + TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); + let tx = WithOtherFields::new(tx); + let _tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block.timestamp.as_u64(), BLOCK_TIMESTAMP + 1); + let block = provider.get_block(BlockId::latest(), false).await.unwrap().unwrap(); + assert_eq!(block.header.timestamp, BLOCK_TIMESTAMP + 1); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64).from(from); - let _tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = + TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); + let tx = WithOtherFields::new(tx); + let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::latest(), false).await.unwrap().unwrap(); let elapsed = start.elapsed().as_secs() + 1; - let diff = block.timestamp - (BLOCK_TIMESTAMP + 1); - assert!(diff <= elapsed.into()); + let diff = block.header.timestamp - (BLOCK_TIMESTAMP + 1); + assert!(diff <= elapsed); } #[tokio::test(flavor = "multi_thread")] @@ -505,21 +580,25 @@ async fn test_fork_can_send_tx() { let (api, handle) = spawn(fork_config().with_blocktime(Some(std::time::Duration::from_millis(800)))).await; - let wallet = LocalWallet::new(&mut rand::thread_rng()); - - api.anvil_set_balance(wallet.address(), U256::from(1e18 as u64)).await.unwrap(); + let wallet = LocalWallet::random(); + let signer = wallet.address(); + let provider = handle.http_provider(); + // let provider = SignerMiddleware::new(provider, wallet); - let provider = SignerMiddleware::new(handle.http_provider(), wallet); + api.anvil_set_balance(signer, U256::MAX).await.unwrap(); + api.anvil_impersonate_account(signer).await.unwrap(); // Added until SignerFiller for alloy-provider is fixed. + let balance = provider.get_balance(signer, BlockId::latest()).await.unwrap(); + assert_eq!(balance, U256::MAX); let addr = Address::random(); - let val = 1337u64; - let tx = TransactionRequest::new().to(addr).value(val); - + let val = U256::from(1337u64); + let tx = TransactionRequest::default().to(addr).value(val).from(signer); + let tx = WithOtherFields::new(tx); // broadcast it via the eth_sendTransaction API - let _ = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); - let balance = provider.get_balance(addr, None).await.unwrap(); - assert_eq!(balance, val.into()); + let balance = provider.get_balance(addr, BlockId::latest()).await.unwrap(); + assert_eq!(balance, val); } // @@ -534,39 +613,50 @@ async fn test_fork_nft_set_approve_all() { .await; // create and fund a random wallet - let wallet = LocalWallet::new(&mut rand::thread_rng()); - api.anvil_set_balance(wallet.address(), U256::from(1000e18 as u64)).await.unwrap(); + let wallet = LocalWallet::random(); + let signer = wallet.address(); + api.anvil_set_balance(signer, U256::from(1000e18)).await.unwrap(); - let provider = Arc::new(SignerMiddleware::new(handle.http_provider(), wallet.clone())); + let provider = handle.http_provider(); // pick a random nft let nouns_addr: Address = "0x9c8ff314c9bc7f6e59a9d9225fb22946427edc03".parse().unwrap(); let owner: Address = "0x052564eb0fd8b340803df55def89c25c432f43f4".parse().unwrap(); - let token_id: U256 = 154u64.into(); - - let nouns = Erc721::new(nouns_addr, Arc::clone(&provider)); - - let real_owner = nouns.owner_of(token_id).call().await.unwrap(); - assert_eq!(real_owner, owner); - let approval = nouns.set_approval_for_all(nouns_addr, true); - let tx = approval.send().await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + let token_id: U256 = U256::from(154u64); + + let nouns = ERC721::new(nouns_addr, provider.clone()); + + let real_owner = nouns.ownerOf(token_id).call().await.unwrap(); + assert_eq!(real_owner._0, owner); + let approval = nouns.setApprovalForAll(nouns_addr, true); + let tx = TransactionRequest::default() + .from(owner) + .to(nouns_addr) + .with_input(approval.calldata().to_owned()); + let tx = WithOtherFields::new(tx); + api.anvil_impersonate_account(owner).await.unwrap(); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let status = tx.inner.inner.inner.receipt.status; + assert!(status); // transfer: impersonate real owner and transfer nft - api.anvil_impersonate_account(real_owner).await.unwrap(); - - api.anvil_set_balance(real_owner, U256::from(10000e18 as u64)).await.unwrap(); - - let call = nouns.transfer_from(real_owner, wallet.address(), token_id); - let mut tx: TypedTransaction = call.tx; - tx.set_from(real_owner); - provider.fill_transaction(&mut tx, None).await.unwrap(); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); - - let real_owner = nouns.owner_of(token_id).call().await.unwrap(); - assert_eq!(real_owner, wallet.address()); + api.anvil_impersonate_account(real_owner._0).await.unwrap(); + + api.anvil_set_balance(real_owner._0, U256::from(10000e18 as u64)).await.unwrap(); + + let call = nouns.transferFrom(real_owner._0, signer, token_id); + let tx = TransactionRequest::default() + .from(real_owner._0) + .to(nouns_addr) + .with_input(call.calldata().to_owned()); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let status = tx.inner.inner.inner.receipt.status; + assert!(status); + + let real_owner = nouns.ownerOf(token_id).call().await.unwrap(); + assert_eq!(real_owner._0, wallet.address()); } // @@ -589,7 +679,7 @@ async fn test_fork_with_custom_chain_id() { let config_chain_id = handle.config().chain_id; // check that the chainIds are the same - assert_eq!(eth_chain_id.unwrap().unwrap().as_u64(), 3145u64); + assert_eq!(eth_chain_id.unwrap().unwrap().to::(), 3145u64); assert_eq!(txn_chain_id, 3145u64); assert_eq!(config_chain_id, Some(3145u64)); } @@ -613,16 +703,18 @@ async fn test_fork_can_send_opensea_tx() { let input: Bytes = "0xfb0f3ee1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ff2e795f5000000000000000000000000000023f28ae3e9756ba982a6290f9081b6a84900b758000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c0000000000000000000000000003235b597a78eabcb08ffcb4d97411073211dbcb0000000000000000000000000000000000000000000000000000000000000e72000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000062ad47c20000000000000000000000000000000000000000000000000000000062d43104000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000df44e65d2a2cf40000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000001c6bf526340000000000000000000000000008de9c5a032463c561423387a9648c5c7bcc5bc900000000000000000000000000000000000000000000000000005543df729c0000000000000000000000000006eb234847a9e3a546539aac57a071c01dc3f398600000000000000000000000000000000000000000000000000000000000000416d39b5352353a22cf2d44faa696c2089b03137a13b5acfee0366306f2678fede043bc8c7e422f6f13a3453295a4a063dac7ee6216ab7bade299690afc77397a51c00000000000000000000000000000000000000000000000000000000000000".parse().unwrap(); let to: Address = "0x00000000006c3852cbef3e08e8df289169ede581".parse().unwrap(); - let tx = TransactionRequest::new() + let tx = TransactionRequest::default() .from(sender) .to(to) - .value(20000000000000000u64) - .data(input) - .gas_price(22180711707u64) - .gas(150_000u64); - - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + .value(U256::from(20000000000000000u64)) + .with_input(input) + .with_gas_price(22180711707u128) + .with_gas_limit(150_000u128); + let tx = WithOtherFields::new(tx); + + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let status = tx.inner.inner.inner.receipt.status; + assert!(status); } #[tokio::test(flavor = "multi_thread")] @@ -634,13 +726,13 @@ async fn test_fork_base_fee() { let provider = handle.http_provider(); - api.anvil_set_next_block_base_fee_per_gas(U256::zero()).await.unwrap(); + api.anvil_set_next_block_base_fee_per_gas(U256::ZERO).await.unwrap(); let addr = Address::random(); - let val = 1337u64; - let tx = TransactionRequest::new().from(from).to(addr).value(val); - - let _res = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let val = U256::from(1337u64); + let tx = TransactionRequest::default().from(from).to(addr).value(val); + let tx = WithOtherFields::new(tx); + let _res = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); } #[tokio::test(flavor = "multi_thread")] @@ -649,17 +741,17 @@ async fn test_fork_init_base_fee() { let provider = handle.http_provider(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::latest(), false).await.unwrap().unwrap(); // - assert_eq!(block.number.unwrap().as_u64(), 13184859u64); - let init_base_fee = block.base_fee_per_gas.unwrap(); - assert_eq!(init_base_fee, 63739886069u64.into()); + assert_eq!(block.header.number.unwrap(), 13184859u64); + let init_base_fee = block.header.base_fee_per_gas.unwrap(); + assert_eq!(init_base_fee, 63739886069u128); api.mine_one().await; - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::latest(), false).await.unwrap().unwrap(); - let next_base_fee = block.base_fee_per_gas.unwrap(); + let next_base_fee = block.header.base_fee_per_gas.unwrap(); assert!(next_base_fee < init_base_fee); } @@ -671,18 +763,23 @@ async fn test_reset_fork_on_new_blocks() { .await; let anvil_provider = handle.http_provider(); - let endpoint = next_http_rpc_endpoint(); - let provider = Arc::new(get_http_provider(&endpoint).interval(Duration::from_secs(2))); + let provider = Arc::new(get_http_provider(&endpoint)); let current_block = anvil_provider.get_block_number().await.unwrap(); handle.task_manager().spawn_reset_on_new_polled_blocks(provider.clone(), api); - let mut stream = provider.watch_blocks().await.unwrap(); + let mut stream = provider + .watch_blocks() + .await + .unwrap() + .with_poll_interval(Duration::from_secs(2)) + .into_stream() + .flat_map(futures::stream::iter); // the http watcher may fetch multiple blocks at once, so we set a timeout here to offset edge // cases where the stream immediately returns a block - tokio::time::sleep(Chain::Mainnet.average_blocktime_hint().unwrap()).await; + tokio::time::sleep(Duration::from_secs(12)).await; stream.next().await.unwrap(); stream.next().await.unwrap(); @@ -697,17 +794,20 @@ async fn test_fork_call() { let to: Address = "0x99d1Fa417f94dcD62BfE781a1213c092a47041Bc".parse().unwrap(); let block_number = 14746300u64; - let provider = Provider::::try_from(rpc::next_http_archive_rpc_endpoint()).unwrap(); - let mut tx = TypedTransaction::default(); - tx.set_to(to).set_data(input.clone()); - let res0 = - provider.call(&tx, Some(BlockNumber::Number(block_number.into()).into())).await.unwrap(); + let provider = http_provider(rpc::next_http_archive_rpc_endpoint().as_str()); + let tx = TransactionRequest::default().to(to).with_input(input.clone()); + let tx = WithOtherFields::new(tx); + let res0 = provider.call(&tx, BlockId::Number(block_number.into())).await.unwrap(); let (api, _) = spawn(fork_config().with_fork_block_number(Some(block_number))).await; let res1 = api .call( - EthTransactionRequest { to: Some(to), data: Some(input), ..Default::default() }, + WithOtherFields::new(TransactionRequest { + to: Some(TxKind::from(to)), + input: input.into(), + ..Default::default() + }), None, None, ) @@ -721,11 +821,11 @@ async fn test_fork_call() { async fn test_fork_block_timestamp() { let (api, _) = spawn(fork_config()).await; - let initial_block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); - api.anvil_mine(Some(1.into()), None).await.unwrap(); - let latest_block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); + let initial_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + api.anvil_mine(Some(U256::from(1)), None).await.unwrap(); + let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); - assert!(initial_block.timestamp.as_u64() < latest_block.timestamp.as_u64()); + assert!(initial_block.header.timestamp < latest_block.header.timestamp); } #[tokio::test(flavor = "multi_thread")] @@ -733,14 +833,14 @@ async fn test_fork_snapshot_block_timestamp() { let (api, _) = spawn(fork_config()).await; let snapshot_id = api.evm_snapshot().await.unwrap(); - api.anvil_mine(Some(1.into()), None).await.unwrap(); - let initial_block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); + api.anvil_mine(Some(U256::from(1)), None).await.unwrap(); + let initial_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); api.evm_revert(snapshot_id).await.unwrap(); - api.evm_set_next_block_timestamp(initial_block.timestamp.as_u64()).unwrap(); - api.anvil_mine(Some(1.into()), None).await.unwrap(); - let latest_block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); + api.evm_set_next_block_timestamp(initial_block.header.timestamp).unwrap(); + api.anvil_mine(Some(U256::from(1)), None).await.unwrap(); + let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); - assert_eq!(initial_block.timestamp.as_u64(), latest_block.timestamp.as_u64()); + assert_eq!(initial_block.header.timestamp, latest_block.header.timestamp); } #[tokio::test(flavor = "multi_thread")] @@ -752,32 +852,33 @@ async fn test_fork_uncles_fetch() { let block_with_uncles = 190u64; let block = - api.block_by_number(BlockNumber::Number(block_with_uncles.into())).await.unwrap().unwrap(); + api.block_by_number(BlockNumberOrTag::Number(block_with_uncles)).await.unwrap().unwrap(); assert_eq!(block.uncles.len(), 2); - let count = provider.get_uncle_count(block_with_uncles).await.unwrap(); - assert_eq!(count.as_usize(), block.uncles.len()); + let count = provider.get_uncle_count(block_with_uncles.into()).await.unwrap(); + assert_eq!(count as usize, block.uncles.len()); - let count = provider.get_uncle_count(block.hash.unwrap()).await.unwrap(); - assert_eq!(count.as_usize(), block.uncles.len()); + let hash = BlockId::hash(block.header.hash.unwrap()); + let count = provider.get_uncle_count(hash).await.unwrap(); + assert_eq!(count as usize, block.uncles.len()); for (uncle_idx, uncle_hash) in block.uncles.iter().enumerate() { // Try with block number let uncle = provider - .get_uncle(block_with_uncles, (uncle_idx as u64).into()) + .get_uncle(BlockId::number(block_with_uncles), uncle_idx as u64) .await .unwrap() .unwrap(); - assert_eq!(*uncle_hash, uncle.hash.unwrap()); + assert_eq!(*uncle_hash, uncle.header.hash.unwrap()); // Try with block hash let uncle = provider - .get_uncle(block.hash.unwrap(), (uncle_idx as u64).into()) + .get_uncle(BlockId::hash(block.header.hash.unwrap()), uncle_idx as u64) .await .unwrap() .unwrap(); - assert_eq!(*uncle_hash, uncle.hash.unwrap()); + assert_eq!(*uncle_hash, uncle.header.hash.unwrap()); } } @@ -794,34 +895,39 @@ async fn test_fork_block_transaction_count() { // transfer: impersonate real sender api.anvil_impersonate_account(sender).await.unwrap(); - let tx = TransactionRequest::new().from(sender).value(42u64).gas(100_000); - provider.send_transaction(tx, None).await.unwrap(); + let tx = + TransactionRequest::default().from(sender).value(U256::from(42u64)).with_gas_limit(100_000); + let tx = WithOtherFields::new(tx); + let _ = provider.send_transaction(tx).await.unwrap(); let pending_txs = - api.block_transaction_count_by_number(BlockNumber::Pending).await.unwrap().unwrap(); - assert_eq!(pending_txs.as_usize(), 1); + api.block_transaction_count_by_number(BlockNumberOrTag::Pending).await.unwrap().unwrap(); + assert_eq!(pending_txs.to::(), 1); // mine a new block api.anvil_mine(None, None).await.unwrap(); let pending_txs = - api.block_transaction_count_by_number(BlockNumber::Pending).await.unwrap().unwrap(); - assert_eq!(pending_txs.as_usize(), 0); + api.block_transaction_count_by_number(BlockNumberOrTag::Pending).await.unwrap().unwrap(); + assert_eq!(pending_txs.to::(), 0); let latest_txs = - api.block_transaction_count_by_number(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(latest_txs.as_usize(), 1); - let latest_block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); - let latest_txs = - api.block_transaction_count_by_hash(latest_block.hash.unwrap()).await.unwrap().unwrap(); - assert_eq!(latest_txs.as_usize(), 1); + api.block_transaction_count_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + assert_eq!(latest_txs.to::(), 1); + let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + let latest_txs = api + .block_transaction_count_by_hash(latest_block.header.hash.unwrap()) + .await + .unwrap() + .unwrap(); + assert_eq!(latest_txs.to::(), 1); // check txs count on an older block: 420000 has 3 txs on mainnet let count_txs = api - .block_transaction_count_by_number(BlockNumber::Number(420000.into())) + .block_transaction_count_by_number(BlockNumberOrTag::Number(420000)) .await .unwrap() .unwrap(); - assert_eq!(count_txs.as_usize(), 3); + assert_eq!(count_txs.to::(), 3); let count_txs = api .block_transaction_count_by_hash( "0xb3b0e3e0c64e23fb7f1ccfd29245ae423d2f6f1b269b63b70ff882a983ce317c".parse().unwrap(), @@ -829,7 +935,7 @@ async fn test_fork_block_transaction_count() { .await .unwrap() .unwrap(); - assert_eq!(count_txs.as_usize(), 3); + assert_eq!(count_txs.to::(), 3); } // @@ -840,27 +946,28 @@ async fn can_impersonate_in_fork() { let token_holder: Address = "0x2f0b23f53734252bda2277357e97e1517d6b042a".parse().unwrap(); let to = Address::random(); - let val = 1337u64; + let val = U256::from(1337u64); // fund the impersonated account - api.anvil_set_balance(token_holder, U256::from(1e18 as u64)).await.unwrap(); - - let tx = TransactionRequest::new().from(token_holder).to(to).value(val); + api.anvil_set_balance(token_holder, U256::from(1e18)).await.unwrap(); - let res = provider.send_transaction(tx.clone(), None).await; + let tx = TransactionRequest::default().from(token_holder).to(to).value(val); + let tx = WithOtherFields::new(tx); + let res = provider.send_transaction(tx.clone()).await; res.unwrap_err(); api.anvil_impersonate_account(token_holder).await.unwrap(); - let res = provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); + let res = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert_eq!(res.from, token_holder); - assert_eq!(res.status, Some(1u64.into())); + let status = res.inner.inner.inner.receipt.status; + assert!(status); - let balance = provider.get_balance(to, None).await.unwrap(); - assert_eq!(balance, val.into()); + let balance = provider.get_balance(to, BlockId::latest()).await.unwrap(); + assert_eq!(balance, val); api.anvil_stop_impersonating_account(token_holder).await.unwrap(); - let res = provider.send_transaction(tx, None).await; + let res = provider.send_transaction(tx).await; res.unwrap_err(); } @@ -869,22 +976,22 @@ async fn can_impersonate_in_fork() { async fn test_total_difficulty_fork() { let (api, handle) = spawn(fork_config()).await; - let total_difficulty: U256 = 46_673_965_560_973_856_260_636u128.into(); - let difficulty: U256 = 13_680_435_288_526_144u128.into(); + let total_difficulty = U256::from(46_673_965_560_973_856_260_636u128); + let difficulty = U256::from(13_680_435_288_526_144u128); let provider = handle.http_provider(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block.total_difficulty, Some(total_difficulty)); - assert_eq!(block.difficulty, difficulty); + let block = provider.get_block(BlockId::latest(), false).await.unwrap().unwrap(); + assert_eq!(block.header.total_difficulty, Some(total_difficulty)); + assert_eq!(block.header.difficulty, difficulty); api.mine_one().await; api.mine_one().await; let next_total_difficulty = total_difficulty + difficulty; - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block.total_difficulty, Some(next_total_difficulty)); - assert_eq!(block.difficulty, U256::zero()); + let block = provider.get_block(BlockId::latest(), false).await.unwrap().unwrap(); + assert_eq!(block.header.total_difficulty, Some(next_total_difficulty)); + assert_eq!(block.header.difficulty, U256::ZERO); } // @@ -911,6 +1018,20 @@ async fn test_transaction_receipt() { assert!(receipt.is_none()); } +// +#[tokio::test(flavor = "multi_thread")] +async fn test_block_receipts() { + let (api, _) = spawn(fork_config()).await; + + // Receipts from the forked block (14608400) + let receipts = api.block_receipts(BlockNumberOrTag::Number(BLOCK_NUMBER)).await.unwrap(); + assert!(receipts.is_some()); + + // Receipts from a block in the future (14608401) + let receipts = api.block_receipts(BlockNumberOrTag::Number(BLOCK_NUMBER + 1)).await.unwrap(); + assert!(receipts.is_none()); +} + #[tokio::test(flavor = "multi_thread")] async fn can_override_fork_chain_id() { let chain_id_override = 5u64; @@ -920,29 +1041,24 @@ async fn can_override_fork_chain_id() { .with_chain_id(Some(chain_id_override)), ) .await; - let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .send() - .await - .unwrap(); + let signer: EthereumSigner = wallet.into(); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + let greeter_contract = + Greeter::deploy(provider.clone(), "Hello World!".to_string()).await.unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); let greeter_contract = - Greeter::deploy(client, "Hello World!".to_string()).unwrap().send().await.unwrap(); - + Greeter::deploy(provider.clone(), "Hello World!".to_string()).await.unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); let provider = handle.http_provider(); - let chain_id = provider.get_chainid().await.unwrap(); - assert_eq!(chain_id.as_u64(), chain_id_override); + let chain_id = provider.get_chain_id().await.unwrap(); + assert_eq!(chain_id, chain_id_override); } // @@ -960,9 +1076,13 @@ async fn test_fork_reset_moonbeam() { let accounts: Vec<_> = handle.dev_wallets().collect(); let from = accounts[0].address(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64).from(from); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + let tx = + TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); + let tx = WithOtherFields::new(tx); + api.anvil_impersonate_account(from).await.unwrap(); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let status = tx.inner.inner.inner.receipt.status; + assert!(status); // reset to check timestamp works after resetting api.anvil_reset(Some(Forking { @@ -972,7 +1092,123 @@ async fn test_fork_reset_moonbeam() { .await .unwrap(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64).from(from); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + let tx = + TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let status = tx.inner.inner.inner.receipt.status; + assert!(status); +} + +// + let (api, _handle) = spawn(fork_config().with_fork_block_number(Some(18835000u64))).await; + + api.mine_one().await; + let latest = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + + // basefee of +1 block: + assert_eq!(latest.header.base_fee_per_gas.unwrap(), 59455969592u128); + + // now reset to block 18835000 -1 + api.anvil_reset(Some(Forking { json_rpc_url: None, block_number: Some(18835000u64 - 1) })) + .await + .unwrap(); + + api.mine_one().await; + let latest = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + + // basefee of the forked block: + assert_eq!(latest.header.base_fee_per_gas.unwrap(), 59017001138u128); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_arbitrum_fork_dev_balance() { + let (api, handle) = spawn( + fork_config() + .with_fork_block_number(None::) + .with_eth_rpc_url(Some("https://arb1.arbitrum.io/rpc".to_string())), + ) + .await; + + let accounts: Vec<_> = handle.dev_wallets().collect(); + for acc in accounts { + let balance = api.balance(acc.address(), Some(Default::default())).await.unwrap(); + assert_eq!(balance, U256::from(100000000000000000000u128)); + } +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_arbitrum_fork_block_number() { + // fork to get initial block for test + let (_, handle) = spawn( + fork_config() + .with_fork_block_number(None::) + .with_eth_rpc_url(Some("https://arb1.arbitrum.io/rpc".to_string())), + ) + .await; + let provider = handle.http_provider(); + let initial_block_number = provider.get_block_number().await.unwrap(); + + // fork again at block number returned by `eth_blockNumber` + // if wrong block number returned (e.g. L1) then fork will fail with error code -32000: missing + // trie node + let (api, _) = spawn( + fork_config() + .with_fork_block_number(Some(initial_block_number)) + .with_eth_rpc_url(Some("https://arb1.arbitrum.io/rpc".to_string())), + ) + .await; + let block_number = api.block_number().unwrap().to::(); + assert_eq!(block_number, initial_block_number); + + // take snapshot at initial block number + let snapshot = api.evm_snapshot().await.unwrap(); + + // mine new block and check block number returned by `eth_blockNumber` + api.mine_one().await; + let block_number = api.block_number().unwrap().to::(); + assert_eq!(block_number, initial_block_number + 1); + + // revert to recorded snapshot and check block number + assert!(api.evm_revert(snapshot).await.unwrap()); + let block_number = api.block_number().unwrap().to::(); + assert_eq!(block_number, initial_block_number); + + // reset fork to different block number and compare with block returned by `eth_blockNumber` + api.anvil_reset(Some(Forking { + json_rpc_url: Some("https://arb1.arbitrum.io/rpc".to_string()), + block_number: Some(initial_block_number - 2), + })) + .await + .unwrap(); + let block_number = api.block_number().unwrap().to::(); + assert_eq!(block_number, initial_block_number - 2); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_execution_reverted() { + let target = 16681681u64; + let (api, _handle) = spawn(fork_config().with_fork_block_number(Some(target + 1))).await; + + let resp = api + .call( + WithOtherFields::new(TransactionRequest { + to: Some(TxKind::from(address!("Fd6CC4F251eaE6d02f9F7B41D1e80464D3d2F377"))), + input: TransactionInput::new("0x8f283b3c".as_bytes().into()), + ..Default::default() + }), + Some(target.into()), + None, + ) + .await; + + assert!(resp.is_err()); + let err = resp.unwrap_err(); + assert!(err.to_string().contains("execution reverted")); } diff --git a/crates/anvil/tests/it/ganache.rs b/crates/anvil/tests/it/ganache.rs index 3ea0e84f508fc..ca8589e210bdf 100644 --- a/crates/anvil/tests/it/ganache.rs +++ b/crates/anvil/tests/it/ganache.rs @@ -1,98 +1,93 @@ //! tests against local ganache for local debug purposes #![allow(unused)] -use crate::init_tracing; -use ethers::{ - abi::Address, - contract::{Contract, ContractFactory, ContractInstance}, - core::k256::SecretKey, - prelude::{abigen, Middleware, Signer, SignerMiddleware, TransactionRequest, Ws}, - providers::{Http, Provider}, - signers::LocalWallet, - types::{BlockNumber, U256}, - utils::hex, +use crate::{ + abi::Greeter, + init_tracing, + utils::{http_provider, http_provider_with_signer, ws_provider_with_signer}, }; -use ethers_solc::{project_util::TempProject, Artifact}; -use std::sync::Arc; +use alloy_contract::ContractInstance; +use alloy_network::EthereumSigner; +use alloy_primitives::{address, Address, TxKind}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockId, TransactionRequest, WithOtherFields}; +use alloy_signer_wallet::{LocalWallet, MnemonicBuilder}; +use alloy_sol_types::{sol, Revert}; +use foundry_compilers::{project_util::TempProject, Artifact}; +use std::{str::FromStr, sync::Arc}; // the mnemonic used to start the local ganache instance const MNEMONIC: &str = "amazing discover palace once resource choice flush horn wink shift planet relief"; fn ganache_wallet() -> LocalWallet { - wallet("552dd2534c4984f892191997d6b1dd9e6a23c7e07b908a6cebfad1d3f2af4c4c") + LocalWallet::from_str("552dd2534c4984f892191997d6b1dd9e6a23c7e07b908a6cebfad1d3f2af4c4c") + .unwrap() } fn ganache_wallet2() -> LocalWallet { - wallet("305b526d493844b63466be6d48a424ab83f5216011eef860acc6db4c1821adc9") + LocalWallet::from_str("305b526d493844b63466be6d48a424ab83f5216011eef860acc6db4c1821adc9") + .unwrap() } fn wallet(key_str: &str) -> LocalWallet { - let key_hex = hex::decode(key_str).expect("could not parse as hex"); - let key = SecretKey::from_bytes(key_hex.as_slice().into()).expect("did not get private key"); - key.into() -} - -fn http_client() -> Arc, LocalWallet>> { - let provider = Provider::::try_from("http://127.0.0.1:8545").unwrap(); - Arc::new(SignerMiddleware::new(provider, ganache_wallet())) -} - -async fn ws_client() -> Arc, LocalWallet>> { - let provider = Provider::::connect("ws://127.0.0.1:8545").await.unwrap(); - Arc::new(SignerMiddleware::new(provider, ganache_wallet())) + LocalWallet::from_str(key_str).unwrap() } #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_ganache_block_number() { - let client = http_client(); - let balance = client - .get_balance(Address::random(), Some(BlockNumber::Number(100u64.into()).into())) - .await; + let provider = http_provider("http://127.0.0.1:8545"); + + let balance = provider.get_balance(Address::random(), BlockId::latest()).await; } #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_ganache_deploy() { - abigen!(Greeter, "test-data/greeter.json"); - let client = http_client(); + let signer: EthereumSigner = ganache_wallet().into(); + let provider = http_provider_with_signer("http://127.0.0.1:8545", signer); - let greeter_contract = - Greeter::deploy(client, "Hello World!".to_string()).unwrap().legacy().send().await.unwrap(); + let greeter_contract_builder = Greeter::deploy_builder(&provider, "Hello World!".to_string()); + let greeter_contract_address = greeter_contract_builder.deploy().await.unwrap(); + let greeter_contract = Greeter::new(greeter_contract_address, &provider); - let greeting = greeter_contract.greet().call().await.unwrap(); + let Greeter::greetReturn { _0 } = greeter_contract.greet().call().await.unwrap(); + let greeting = _0; assert_eq!("Hello World!", greeting); } #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_ganache_emit_logs() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let client = ws_client().await; + sol!( + #[sol(rpc)] + EmitLogs, + "test-data/emit_logs.json" + ); - let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().legacy().send().await.unwrap(); + let signer: EthereumSigner = ganache_wallet().into(); + let provider = ws_provider_with_signer("ws://127.0.0.1:8545", signer); - let val = contract.get_value().call().await.unwrap(); - assert_eq!(val, msg); + let first_msg = "First Message".to_string(); + let next_msg = "Next Message".to_string(); + let emit_logs_contract_builder = EmitLogs::deploy_builder(&provider, first_msg.clone()); + let emit_logs_contract_address = emit_logs_contract_builder.deploy().await.unwrap(); + let emit_logs_contract = EmitLogs::new(emit_logs_contract_address, &provider); - let val = contract - .set_value("Next Message".to_string()) - .legacy() - .send() - .await - .unwrap() - .await - .unwrap() - .unwrap(); + let EmitLogs::getValueReturn { _0 } = emit_logs_contract.getValue().call().await.unwrap(); + let val = _0; + assert_eq!(val, first_msg); + + emit_logs_contract.setValue(next_msg.clone()).send().await.unwrap(); + + let EmitLogs::getValueReturn { _0 } = emit_logs_contract.getValue().call().await.unwrap(); + let val = _0; + assert_eq!(val, next_msg); } #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_ganache_deploy_reverting() { - let client = http_client(); - let prj = TempProject::dapptools().unwrap(); prj.add_source( "Contract", @@ -107,17 +102,33 @@ contract Contract { ) .unwrap(); + sol!( + #[sol(rpc)] + contract Contract {} + ); + let mut compiled = prj.compile().unwrap(); - println!("{compiled}"); assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); - - let factory = ContractFactory::new(abi.unwrap(), bytecode.unwrap(), Arc::clone(&client)); - let contract = factory.deploy(()).unwrap().legacy().send().await; - contract.unwrap_err(); + let bytecode = contract.into_bytecode_bytes().unwrap(); + + let wallet = ganache_wallet(); + let signer: EthereumSigner = wallet.clone().into(); + let sender = wallet.address(); + + let provider = http_provider_with_signer("http://127.0.0.1:8545", signer); + + // should catch the revert during estimation which results in an err + let err = provider + .send_transaction(WithOtherFields::new(TransactionRequest { + from: Some(sender), + to: Some(TxKind::Create), + input: bytecode.into(), + ..Default::default() + })) + .await + .unwrap_err(); + assert!(err.to_string().contains("execution reverted")); } #[tokio::test(flavor = "multi_thread")] @@ -145,41 +156,63 @@ contract Contract { ) .unwrap(); + sol!( + #[sol(rpc)] + contract Contract { + function getSecret() public view returns (uint256); + } + ); + let mut compiled = prj.compile().unwrap(); assert!(!compiled.has_compiler_errors()); let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + let bytecode = contract.into_bytecode_bytes().unwrap(); + + let wallet = ganache_wallet(); + let signer: EthereumSigner = wallet.clone().into(); + let sender = wallet.address(); - let client = Arc::new(http_client()); + let provider = http_provider_with_signer("http://127.0.0.1:8545", signer); // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().legacy().send().await.unwrap(); - let provider = SignerMiddleware::new( - Provider::::try_from("http://127.0.0.1:8545").unwrap(), - ganache_wallet2(), - ); - let contract = ContractInstance::new(contract.address(), abi.unwrap(), provider); - let resp = contract.method::<_, U256>("getSecret", ()).unwrap().legacy().call().await; - resp.unwrap_err(); - - /* Ganache rpc errors look like: - < { - < "id": 1627277502538, - < "jsonrpc": "2.0", - < "error": { - < "message": "VM Exception while processing transaction: revert !authorized", - < "code": -32000, - < "data": { - < "0x90264de254689f1d4e7f8670cd97f60d9bc803874fdecb34d249bd1cc3ca823a": { - < "error": "revert", - < "program_counter": 223, - < "return": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b21617574686f72697a6564000000000000000000000000000000000000000000", - < "reason": "!authorized" - < }, - < "stack": "c: VM Exception while processing transaction: revert !authorized\n at Function.c.fromResults (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:4:192416)\n at /usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:42:50402", - < "name": "c" - < } - < } - */ + provider + .send_transaction(WithOtherFields::new(TransactionRequest { + from: Some(sender), + to: Some(TxKind::Create), + input: bytecode.into(), + ..Default::default() + })) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let contract_address = sender.create(0); + let contract = Contract::new(contract_address, &provider); + + // should catch the revert during the call which results in an err + contract.getSecret().send().await.unwrap_err(); + + // /* Ganache rpc errors look like: + // < { + // < "id": 1627277502538, + // < "jsonrpc": "2.0", + // < "error": { + // < "message": "VM Exception while processing transaction: revert !authorized", + // < "code": -32000, + // < "data": { + // < "0x90264de254689f1d4e7f8670cd97f60d9bc803874fdecb34d249bd1cc3ca823a": { + // < "error": "revert", + // < "program_counter": 223, + // < "return": + // "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b21617574686f72697a6564000000000000000000000000000000000000000000" + // , < "reason": "!authorized" + // < }, + // < "stack": "c: VM Exception while processing transaction: revert !authorized\n at + // Function.c.fromResults + // (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:4:192416)\n at + // /usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:42:50402", < + // "name": "c" < } + // < } + // */ } diff --git a/crates/anvil/tests/it/gas.rs b/crates/anvil/tests/it/gas.rs index 27c6055be3ee3..bca7102cbf6b9 100644 --- a/crates/anvil/tests/it/gas.rs +++ b/crates/anvil/tests/it/gas.rs @@ -1,15 +1,13 @@ //! Gas related tests +use crate::utils::http_provider_with_signer; +use alloy_network::{EthereumSigner, TransactionBuilder}; +use alloy_primitives::{Address, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockId, TransactionRequest, WithOtherFields}; use anvil::{eth::fees::INITIAL_BASE_FEE, spawn, NodeConfig}; -use ethers::{ - prelude::Middleware, - types::{ - transaction::eip2718::TypedTransaction, Address, BlockNumber, Eip1559TransactionRequest, - TransactionRequest, - }, -}; -const GAS_TRANSFER: u64 = 21_000u64; +const GAS_TRANSFER: u128 = 21_000; #[tokio::test(flavor = "multi_thread")] async fn test_basefee_full_block() { @@ -17,19 +15,41 @@ async fn test_basefee_full_block() { NodeConfig::test().with_base_fee(Some(INITIAL_BASE_FEE)).with_gas_limit(Some(GAS_TRANSFER)), ) .await; - let provider = handle.http_provider(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); - provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); - let base_fee = - provider.get_block(BlockNumber::Latest).await.unwrap().unwrap().base_fee_per_gas.unwrap(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); - provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); - let next_base_fee = - provider.get_block(BlockNumber::Latest).await.unwrap().unwrap().base_fee_per_gas.unwrap(); + + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let tx = TransactionRequest::default().to(Address::random()).with_value(U256::from(1337)); + let tx = WithOtherFields::new(tx); + + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let base_fee = provider + .get_block(BlockId::latest(), false) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); + + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let next_base_fee = provider + .get_block(BlockId::latest(), false) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); assert!(next_base_fee > base_fee); + // max increase, full block - assert_eq!(next_base_fee.as_u64(), INITIAL_BASE_FEE + 125_000_000); + assert_eq!(next_base_fee, INITIAL_BASE_FEE + 125_000_000); } #[tokio::test(flavor = "multi_thread")] @@ -40,32 +60,69 @@ async fn test_basefee_half_block() { .with_gas_limit(Some(GAS_TRANSFER * 2)), ) .await; - let provider = handle.http_provider(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); - provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); - provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); - let next_base_fee = - provider.get_block(BlockNumber::Latest).await.unwrap().unwrap().base_fee_per_gas.unwrap(); + + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let tx = TransactionRequest::default().to(Address::random()).with_value(U256::from(1337)); + let tx = WithOtherFields::new(tx); + + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let tx = TransactionRequest::default().to(Address::random()).with_value(U256::from(1337)); + let tx = WithOtherFields::new(tx); + + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let next_base_fee = provider + .get_block(BlockId::latest(), false) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); // unchanged, half block - assert_eq!(next_base_fee.as_u64(), INITIAL_BASE_FEE); + assert_eq!(next_base_fee, INITIAL_BASE_FEE); } + #[tokio::test(flavor = "multi_thread")] async fn test_basefee_empty_block() { let (api, handle) = spawn(NodeConfig::test().with_base_fee(Some(INITIAL_BASE_FEE))).await; - let provider = handle.http_provider(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); - provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - let base_fee = - provider.get_block(BlockNumber::Latest).await.unwrap().unwrap().base_fee_per_gas.unwrap(); + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let tx = TransactionRequest::default().with_to(Address::random()).with_value(U256::from(1337)); + let tx = WithOtherFields::new(tx); + + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let base_fee = provider + .get_block(BlockId::latest(), false) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); // mine empty block api.mine_one().await; - let next_base_fee = - provider.get_block(BlockNumber::Latest).await.unwrap().unwrap().base_fee_per_gas.unwrap(); + let next_base_fee = provider + .get_block(BlockId::latest(), false) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); // empty block, decreased base fee assert!(next_base_fee < base_fee); @@ -73,40 +130,65 @@ async fn test_basefee_empty_block() { #[tokio::test(flavor = "multi_thread")] async fn test_respect_base_fee() { - let base_fee = 50u64; + let base_fee = 50u128; let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee))).await; + let provider = handle.http_provider(); - let mut tx = TypedTransaction::default(); - tx.set_value(100u64); - tx.set_to(Address::random()); + + let tx = TransactionRequest::default().with_to(Address::random()).with_value(U256::from(100)); + let mut tx = WithOtherFields::new(tx); let mut underpriced = tx.clone(); underpriced.set_gas_price(base_fee - 1); - let res = provider.send_transaction(underpriced, None).await; + + let res = provider.send_transaction(underpriced).await; assert!(res.is_err()); assert!(res.unwrap_err().to_string().contains("max fee per gas less than block base fee")); tx.set_gas_price(base_fee); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); } #[tokio::test(flavor = "multi_thread")] async fn test_tip_above_fee_cap() { - let base_fee = 50u64; + let base_fee = 50u128; let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee))).await; + let provider = handle.http_provider(); - let tx = TypedTransaction::Eip1559( - Eip1559TransactionRequest::new() - .max_fee_per_gas(base_fee) - .max_priority_fee_per_gas(base_fee + 1) - .to(Address::random()) - .value(100u64), - ); - let res = provider.send_transaction(tx, None).await; + + let tx = TransactionRequest::default() + .max_fee_per_gas(base_fee) + .max_priority_fee_per_gas(base_fee + 1) + .with_to(Address::random()) + .with_value(U256::from(100)); + let tx = WithOtherFields::new(tx); + + let res = provider.send_transaction(tx.clone()).await; assert!(res.is_err()); assert!(res .unwrap_err() .to_string() .contains("max priority fee per gas higher than max fee per gas")); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_can_use_fee_history() { + let base_fee = 50u128; + let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee))).await; + let provider = handle.http_provider(); + + for _ in 0..10 { + let fee_history = provider.get_fee_history(1, Default::default(), &[]).await.unwrap(); + let next_base_fee = fee_history.base_fee_per_gas.last().unwrap(); + + let tx = TransactionRequest::default() + .with_to(Address::random()) + .with_value(U256::from(100)) + .with_gas_price(*next_base_fee); + let tx = WithOtherFields::new(tx); + + let receipt = + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + assert!(receipt.inner.inner.is_success()); + } +} diff --git a/crates/anvil/tests/it/genesis.rs b/crates/anvil/tests/it/genesis.rs index 6c028599fc146..bb4e2d24bd5b9 100644 --- a/crates/anvil/tests/it/genesis.rs +++ b/crates/anvil/tests/it/genesis.rs @@ -1,7 +1,11 @@ //! genesis.json tests -use anvil::{genesis::Genesis, spawn, NodeConfig}; -use ethers::{abi::Address, prelude::Middleware, types::U256}; +use alloy_genesis::Genesis; +use alloy_primitives::{Address, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::BlockId; +use anvil::{spawn, NodeConfig}; +use std::str::FromStr; #[tokio::test(flavor = "multi_thread")] async fn can_apply_genesis() { @@ -37,11 +41,11 @@ async fn can_apply_genesis() { let provider = handle.http_provider(); - assert_eq!(provider.get_chainid().await.unwrap(), 19763u64.into()); + assert_eq!(provider.get_chain_id().await.unwrap(), 19763u64); - let addr: Address = "71562b71999873db5b286df957af199ec94617f7".parse().unwrap(); - let balance = provider.get_balance(addr, None).await.unwrap(); + let addr: Address = Address::from_str("71562b71999873db5b286df957af199ec94617f7").unwrap(); + let balance = provider.get_balance(addr, BlockId::latest()).await.unwrap(); - let expected: U256 = "ffffffffffffffffffffffffff".parse().unwrap(); + let expected: U256 = U256::from_str_radix("ffffffffffffffffffffffffff", 16).unwrap(); assert_eq!(balance, expected); } diff --git a/crates/anvil/tests/it/geth.rs b/crates/anvil/tests/it/geth.rs index 31429d0bd4167..7879f4911f676 100644 --- a/crates/anvil/tests/it/geth.rs +++ b/crates/anvil/tests/it/geth.rs @@ -1,34 +1,35 @@ //! tests against local geth for local debug purposes -use crate::abi::VENDING_MACHINE_CONTRACT; -use ethers::{ - abi::Address, - contract::{Contract, ContractFactory}, - prelude::{Middleware, TransactionRequest}, - providers::Provider, - types::U256, - utils::WEI_IN_ETHER, +use crate::{ + abi::{VendingMachine, VENDING_MACHINE_CONTRACT}, + utils::{http_provider, http_provider_with_signer}, }; -use ethers_solc::{project_util::TempProject, Artifact}; +use alloy_network::{EthereumSigner, TransactionBuilder}; +use alloy_primitives::{Address, TxKind, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{TransactionRequest, WithOtherFields}; +use anvil::{spawn, NodeConfig}; +use foundry_compilers::{project_util::TempProject, Artifact}; use futures::StreamExt; -use std::sync::Arc; use tokio::time::timeout; #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_geth_pending_transaction() { - let client = Provider::try_from("http://127.0.0.1:8545").unwrap(); - let accounts = client.get_accounts().await.unwrap(); - let tx = TransactionRequest::new() - .from(accounts[0]) - .to(Address::random()) - .value(1337u64) - .nonce(2u64); + let provider = http_provider("http://127.0.0.1:8545"); + + let account = provider.get_accounts().await.unwrap().remove(0); - let mut watch_tx_stream = - client.watch_pending_transactions().await.unwrap().transactions_unordered(1).fuse(); + let tx = TransactionRequest::default() + .with_from(account) + .with_to(Address::random()) + .with_value(U256::from(1337u64)) + .with_nonce(2u64); + let tx = WithOtherFields::new(tx); - let _res = client.send_transaction(tx, None).await.unwrap(); + let mut watch_tx_stream = provider.watch_pending_transactions().await.unwrap().into_stream(); + + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); let pending = timeout(std::time::Duration::from_secs(3), watch_tx_stream.next()).await; pending.unwrap_err(); @@ -44,48 +45,59 @@ async fn test_geth_revert_transaction() { let mut compiled = prj.compile().unwrap(); assert!(!compiled.has_compiler_errors()); let contract = compiled.remove_first("VendingMachine").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + let bytecode = contract.into_bytecode_bytes().unwrap(); - let client = Arc::new(Provider::try_from("http://127.0.0.1:8545").unwrap()); + let (_api, handle) = spawn(NodeConfig::test()).await; + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); + let sender = wallet.address(); - let account = client.get_accounts().await.unwrap().remove(0); + let provider = http_provider_with_signer("http://127.0.0.1:8545", signer); // deploy successfully - let factory = - ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), Arc::clone(&client)); - - let mut tx = factory.deploy(()).unwrap().tx; - tx.set_from(account); - - let resp = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - - let contract = - Contract::>::new(resp.contract_address.unwrap(), abi.unwrap(), client); - - let ten = WEI_IN_ETHER.saturating_mul(10u64.into()); - let call = contract.method::<_, ()>("buyRevert", ten).unwrap().value(ten).from(account); - let resp = call.call().await; - let err = resp.unwrap_err().to_string(); - assert!(err.contains("execution reverted: Not enough Ether provided.")); - assert!(err.contains("code: 3")); + provider + .send_transaction(WithOtherFields::new(TransactionRequest { + from: Some(sender), + to: Some(TxKind::Create), + input: bytecode.into(), + ..Default::default() + })) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let contract_address = sender.create(0); + let contract = VendingMachine::new(contract_address, &provider); + + let res = contract + .buyRevert(U256::from(100)) + .value(U256::from(1)) + .from(sender) + .send() + .await + .unwrap_err(); + let msg = res.to_string(); + assert!(msg.contains("execution reverted: revert: Not enough Ether provided.")); + assert!(msg.contains("code: 3")); } #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_geth_low_gas_limit() { - let provider = Arc::new(Provider::try_from("http://127.0.0.1:8545").unwrap()); + let provider = http_provider("http://127.0.0.1:8545"); let account = provider.get_accounts().await.unwrap().remove(0); - let gas = 21_000u64 - 1; - let tx = TransactionRequest::new() + let gas_limit = 21_000u128 - 1; + let tx = TransactionRequest::default() + .from(account) .to(Address::random()) .value(U256::from(1337u64)) - .from(account) - .gas(gas); - - let resp = provider.send_transaction(tx, None).await; + .gas_limit(gas_limit); + let tx = WithOtherFields::new(tx); + let resp = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await; let err = resp.unwrap_err().to_string(); assert!(err.contains("intrinsic gas too low")); } diff --git a/crates/anvil/tests/it/ipc.rs b/crates/anvil/tests/it/ipc.rs index b25bbba5249fd..676a0f61b5171 100644 --- a/crates/anvil/tests/it/ipc.rs +++ b/crates/anvil/tests/it/ipc.rs @@ -1,7 +1,9 @@ //! IPC tests +use crate::utils::connect_pubsub; +use alloy_primitives::U256; +use alloy_provider::Provider; use anvil::{spawn, NodeConfig}; -use ethers::{core::rand, prelude::Middleware, types::U256}; use futures::StreamExt; pub fn rand_ipc_endpoint() -> String { @@ -18,31 +20,33 @@ fn ipc_config() -> NodeConfig { } #[tokio::test(flavor = "multi_thread")] +#[cfg_attr(target_os = "windows", ignore)] async fn can_get_block_number_ipc() { let (api, handle) = spawn(ipc_config()).await; let block_num = api.block_number().unwrap(); - assert_eq!(block_num, U256::zero()); + assert_eq!(block_num, U256::ZERO); let provider = handle.ipc_provider().unwrap(); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num, block_num.as_u64().into()); + assert_eq!(num, block_num.to::()); } #[tokio::test(flavor = "multi_thread")] +#[cfg_attr(target_os = "windows", ignore)] async fn test_sub_new_heads_ipc() { let (api, handle) = spawn(ipc_config()).await; - let provider = handle.ipc_provider().unwrap(); + let provider = connect_pubsub(handle.ipc_path().unwrap().as_str()).await; - let blocks = provider.subscribe_blocks().await.unwrap(); + let blocks = provider.subscribe_blocks().await.unwrap().into_stream(); // mine a block every 1 seconds api.anvil_set_interval_mining(1).unwrap(); let blocks = blocks.take(3).collect::>().await; - let block_numbers = blocks.into_iter().map(|b| b.number.unwrap().as_u64()).collect::>(); + let block_numbers = blocks.into_iter().map(|b| b.header.number.unwrap()).collect::>(); assert_eq!(block_numbers, vec![1, 2, 3]); } diff --git a/crates/anvil/tests/it/logs.rs b/crates/anvil/tests/it/logs.rs index 913015de5edf0..afcb340197fd3 100644 --- a/crates/anvil/tests/it/logs.rs +++ b/crates/anvil/tests/it/logs.rs @@ -1,175 +1,204 @@ //! log/event related tests -use crate::abi::*; -use anvil::{spawn, NodeConfig}; -use ethers::{ - middleware::SignerMiddleware, - prelude::{BlockNumber, Filter, FilterKind, Middleware, Signer, H256}, - types::Log, +use crate::{ + abi::SimpleStorage::{self}, + utils::{http_provider_with_signer, ws_provider_with_signer}, }; +use alloy_network::EthereumSigner; +use alloy_primitives::B256; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockNumberOrTag, Filter}; +use anvil::{spawn, NodeConfig}; use futures::StreamExt; -use std::sync::Arc; #[tokio::test(flavor = "multi_thread")] async fn get_past_events() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let address = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let account = wallet.address(); + let signer: EthereumSigner = wallet.into(); - let contract = SimpleStorage::deploy(Arc::clone(&client), "initial value".to_string()) - .unwrap() + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let contract = + SimpleStorage::deploy(provider.clone(), "initial value".to_string()).await.unwrap(); + let _ = contract + .setValue("hi".to_string()) + .from(account) .send() .await + .unwrap() + .get_receipt() + .await .unwrap(); + let simple_storage_address = *contract.address(); - let func = contract.method::<_, H256>("setValue", "hi".to_owned()).unwrap(); - let tx = func.send().await.unwrap(); - let _receipt = tx.await.unwrap(); + let filter = Filter::new() + .address(simple_storage_address) + .topic1(B256::from(account.into_word())) + .from_block(BlockNumberOrTag::from(0)); - // and we can fetch the events - let logs: Vec = - contract.event().from_block(0u64).topic1(address).query().await.unwrap(); + let logs = provider + .get_logs(&filter) + .await + .unwrap() + .into_iter() + .map(|log| log.log_decode::().unwrap()) + .collect::>(); // 2 events, 1 in constructor, 1 in call - assert_eq!(logs[0].new_value, "initial value"); - assert_eq!(logs[1].new_value, "hi"); + assert_eq!(logs[0].inner.newValue, "initial value"); + assert_eq!(logs[1].inner.newValue, "hi"); assert_eq!(logs.len(), 2); // and we can fetch the events at a block hash - let hash = client.get_block(1).await.unwrap().unwrap().hash.unwrap(); + // let hash = provider.get_block(1).await.unwrap().unwrap().hash.unwrap(); + let hash = provider + .get_block_by_number(BlockNumberOrTag::from(1), false) + .await + .unwrap() + .unwrap() + .header + .hash + .unwrap(); + + let filter = Filter::new() + .address(simple_storage_address) + .topic1(B256::from(account.into_word())) + .at_block_hash(hash); + + let logs = provider + .get_logs(&filter) + .await + .unwrap() + .into_iter() + .map(|log| log.log_decode::().unwrap()) + .collect::>(); - let logs: Vec = - contract.event().at_block_hash(hash).topic1(address).query().await.unwrap(); - assert_eq!(logs[0].new_value, "initial value"); + assert_eq!(logs[0].inner.newValue, "initial value"); assert_eq!(logs.len(), 1); } #[tokio::test(flavor = "multi_thread")] async fn get_all_events() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let account = wallet.address(); + let signer: EthereumSigner = wallet.into(); - let contract = SimpleStorage::deploy(Arc::clone(&client), "initial value".to_string()) - .unwrap() - .send() - .await - .unwrap(); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let contract = + SimpleStorage::deploy(provider.clone(), "initial value".to_string()).await.unwrap(); api.anvil_set_auto_mine(false).await.unwrap(); - let pre_logs = client.get_logs(&Filter::new().from_block(BlockNumber::Earliest)).await.unwrap(); + let pre_logs = + provider.get_logs(&Filter::new().from_block(BlockNumberOrTag::Earliest)).await.unwrap(); assert_eq!(pre_logs.len(), 1); let pre_logs = - client.get_logs(&Filter::new().from_block(BlockNumber::Number(0u64.into()))).await.unwrap(); + provider.get_logs(&Filter::new().from_block(BlockNumberOrTag::Number(0))).await.unwrap(); assert_eq!(pre_logs.len(), 1); // spread logs across several blocks let num_tx = 10; + let tx = contract.setValue("hi".to_string()).from(account); for _ in 0..num_tx { - let func = contract.method::<_, H256>("setValue", "hi".to_owned()).unwrap(); - let tx = func.send().await.unwrap(); + let tx = tx.send().await.unwrap(); api.mine_one().await; - let _receipt = tx.await.unwrap(); + tx.get_receipt().await.unwrap(); } - let logs = client.get_logs(&Filter::new().from_block(BlockNumber::Earliest)).await.unwrap(); + + let logs = + provider.get_logs(&Filter::new().from_block(BlockNumberOrTag::Earliest)).await.unwrap(); let num_logs = num_tx + pre_logs.len(); assert_eq!(logs.len(), num_logs); -} -#[tokio::test(flavor = "multi_thread")] -async fn can_install_filter() { - let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + // test that logs returned from get_logs and get_transaction_receipt have + // the same log_index, block_number, and transaction_hash + let mut tasks = vec![]; + let mut seen_tx_hashes = std::collections::HashSet::new(); + for log in &logs { + if seen_tx_hashes.contains(&log.transaction_hash.unwrap()) { + continue; + } + tasks.push(provider.get_transaction_receipt(log.transaction_hash.unwrap())); + seen_tx_hashes.insert(log.transaction_hash.unwrap()); + } - let contract = SimpleStorage::deploy(Arc::clone(&client), "initial value".to_string()) - .unwrap() - .send() + let receipt_logs = futures::future::join_all(tasks) .await - .unwrap(); - - let filter = Filter::new().from_block(BlockNumber::Number(0u64.into())); - - let filter = client.new_filter(FilterKind::Logs(&filter)).await.unwrap(); - - let logs = client.get_filter_changes::<_, Log>(filter).await.unwrap(); - assert_eq!(logs.len(), 1); - - let logs = client.get_filter_changes::<_, Log>(filter).await.unwrap(); - assert!(logs.is_empty()); - api.anvil_set_auto_mine(false).await.unwrap(); - // create some logs - let num_logs = 10; - for _ in 0..num_logs { - let func = contract.method::<_, H256>("setValue", "hi".to_owned()).unwrap(); - let tx = func.send().await.unwrap(); - api.mine_one().await; - let _receipt = tx.await.unwrap(); - let logs = client.get_filter_changes::<_, Log>(filter).await.unwrap(); - assert_eq!(logs.len(), 1); + .into_iter() + .collect::, _>>() + .unwrap() + .into_iter() + .flat_map(|receipt| receipt.unwrap().inner.inner.inner.receipt.logs) + .collect::>(); + + assert_eq!(receipt_logs.len(), logs.len()); + for (receipt_log, log) in receipt_logs.iter().zip(logs.iter()) { + assert_eq!(receipt_log.transaction_hash, log.transaction_hash); + assert_eq!(receipt_log.block_number, log.block_number); + assert_eq!(receipt_log.log_index, log.log_index); } - let all_logs = api - .get_filter_logs(&serde_json::to_string(&filter).unwrap().replace('\"', "")) - .await - .unwrap(); - - assert_eq!(all_logs.len(), num_logs + 1); } #[tokio::test(flavor = "multi_thread")] async fn watch_events() { let (_api, handle) = spawn(NodeConfig::test()).await; - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(handle.http_provider(), wallet)); - - let contract = SimpleStorage::deploy(Arc::clone(&client), "initial value".to_string()) - .unwrap() - .send() - .await - .unwrap(); - // We spawn the event listener: - let event = contract.event::(); - let mut stream = event.stream().await.unwrap(); - - // Also set up a subscription for the same thing - let ws = Arc::new(handle.ws_provider()); - let contract2 = SimpleStorage::new(contract.address(), ws); - let event2 = contract2.event::(); - let mut subscription = event2.subscribe().await.unwrap(); - - let mut subscription_meta = event2.subscribe().await.unwrap().with_meta(); - - let num_calls = 3u64; - - // and we make a few calls - let num = client.get_block_number().await.unwrap(); - for i in 0..num_calls { - let call = contract.method::<_, H256>("setValue", i.to_string()).unwrap().legacy(); - let pending_tx = call.send().await.unwrap(); - let _receipt = pending_tx.await.unwrap(); - } - - for i in 0..num_calls { - // unwrap the option of the stream, then unwrap the decoding result - let log = stream.next().await.unwrap().unwrap(); - let log2 = subscription.next().await.unwrap().unwrap(); - let (log3, meta) = subscription_meta.next().await.unwrap().unwrap(); - assert_eq!(log.new_value, log3.new_value); - assert_eq!(log.new_value, log2.new_value); - assert_eq!(log.new_value, i.to_string()); - assert_eq!(meta.block_number, num + i + 1); - let hash = client.get_block(num + i + 1).await.unwrap().unwrap().hash.unwrap(); - assert_eq!(meta.block_hash, hash); + let wallet = handle.dev_wallets().next().unwrap(); + let account = wallet.address(); + let signer: EthereumSigner = wallet.into(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer.clone()); + + let contract1 = + SimpleStorage::deploy(provider.clone(), "initial value".to_string()).await.unwrap(); + + // Spawn the event listener. + let event1 = contract1.event_filter::(); + let mut stream1 = event1.watch().await.unwrap().into_stream(); + + // Also set up a subscription for the same thing. + let ws = ws_provider_with_signer(&handle.ws_endpoint(), signer.clone()); + let contract2 = SimpleStorage::new(*contract1.address(), ws); + let event2 = contract2.event_filter::(); + let mut stream2 = event2.watch().await.unwrap().into_stream(); + + let num_tx = 3; + + let starting_block_number = provider.get_block_number().await.unwrap(); + for i in 0..num_tx { + contract1 + .setValue(i.to_string()) + .from(account) + .send() + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + + let log = stream1.next().await.unwrap().unwrap(); + let log2 = stream2.next().await.unwrap().unwrap(); + + assert_eq!(log.0.newValue, log2.0.newValue); + assert_eq!(log.0.newValue, i.to_string()); + assert_eq!(log.1.block_number.unwrap(), starting_block_number + i + 1); + + let hash = provider + .get_block_by_number(BlockNumberOrTag::from(starting_block_number + i + 1), false) + .await + .unwrap() + .unwrap() + .header + .hash + .unwrap(); + assert_eq!(log.1.block_hash.unwrap(), hash); } } diff --git a/crates/anvil/tests/it/main.rs b/crates/anvil/tests/it/main.rs index 057a9fdea596d..94b1bc492c317 100644 --- a/crates/anvil/tests/it/main.rs +++ b/crates/anvil/tests/it/main.rs @@ -2,6 +2,7 @@ mod abi; mod anvil; mod anvil_api; mod api; +mod eip4844; mod fork; mod ganache; mod gas; @@ -10,11 +11,12 @@ mod geth; mod ipc; mod logs; mod optimism; +mod otterscan; mod proof; mod pubsub; -// mod revert; // TODO uncomment -mod otterscan; +mod revert; mod sign; +mod state; mod traces; mod transaction; mod txpool; diff --git a/crates/anvil/tests/it/optimism.rs b/crates/anvil/tests/it/optimism.rs index e7ee430bb257f..0a7801c4ed6dd 100644 --- a/crates/anvil/tests/it/optimism.rs +++ b/crates/anvil/tests/it/optimism.rs @@ -1,46 +1,44 @@ //! Tests for OP chain support. +use crate::utils::http_provider_with_signer; +use alloy_eips::{eip2718::Encodable2718, BlockId}; +use alloy_network::{EthereumSigner, TransactionBuilder}; +use alloy_primitives::{b256, U128, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{optimism::OptimismTransactionFields, TransactionRequest, WithOtherFields}; use anvil::{spawn, Hardfork, NodeConfig}; -use ethers::{ - abi::Address, - providers::Middleware, - types::{ - transaction::{eip2718::TypedTransaction, optimism::DepositTransaction}, - TransactionRequest, U256, - }, -}; -use ethers_core::types::{Bytes, H256}; -use std::str::FromStr; +// TODO: transaction is expected to fail, it does not, remove ignore once fixed #[tokio::test(flavor = "multi_thread")] +#[ignore] async fn test_deposits_not_supported_if_optimism_disabled() { - // optimism disabled by default - let (_, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - - let from_addr: Address = "cf7f9e66af820a19257a2108375b180b0ec49167".parse().unwrap(); - let to_addr: Address = "71562b71999873db5b286df957af199ec94617f7".parse().unwrap(); - let deposit_tx: TypedTransaction = TypedTransaction::DepositTransaction(DepositTransaction { - tx: TransactionRequest { - chain_id: None, - from: Some(from_addr), - to: Some(ethers::types::NameOrAddress::Address(to_addr)), - value: Some("1234".parse().unwrap()), - gas: Some(U256::from(21000)), - gas_price: None, - data: Some(Bytes::default()), - nonce: None, - }, - source_hash: H256::from_str( - "0000000000000000000000000000000000000000000000000000000000000000", - ) - .unwrap(), - mint: Some(U256::zero()), - is_system_tx: true, - }); - - // sending the deposit transaction should fail with error saying not supported - let res = provider.send_transaction(deposit_tx.clone(), None).await; + let (_api, handle) = spawn(NodeConfig::test()).await; + + let accounts: Vec<_> = handle.dev_wallets().collect(); + let signer: EthereumSigner = accounts[0].clone().into(); + let from = accounts[0].address(); + let to = accounts[1].address(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let tx = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_value(U256::from(1234)) + .with_gas_limit(21000); + let tx = WithOtherFields { + inner: tx, + other: OptimismTransactionFields { + source_hash: Some(b256!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + mint: Some(U128::from(0)), + is_system_tx: Some(true), + } + .into(), + }; + + let res = provider.send_transaction(tx).await.unwrap().register().await; assert!(res .unwrap_err() .to_string() @@ -52,45 +50,47 @@ async fn test_send_value_deposit_transaction() { // enable the Optimism flag let (api, handle) = spawn(NodeConfig::test().with_optimism(true).with_hardfork(Some(Hardfork::Paris))).await; - let provider = handle.http_provider(); + + let accounts: Vec<_> = handle.dev_wallets().collect(); + let signer: EthereumSigner = accounts[0].clone().into(); + let from = accounts[0].address(); + let to = accounts[1].address(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); let send_value = U256::from(1234); - let from_addr: Address = "cf7f9e66af820a19257a2108375b180b0ec49167".parse().unwrap(); - let to_addr: Address = "71562b71999873db5b286df957af199ec94617f7".parse().unwrap(); - - // fund the sender - api.anvil_set_balance(from_addr, send_value).await.unwrap(); - - let deposit_tx: TypedTransaction = TypedTransaction::DepositTransaction(DepositTransaction { - tx: TransactionRequest { - chain_id: None, - from: Some(from_addr), - to: Some(ethers::types::NameOrAddress::Address(to_addr)), - value: Some(send_value), - gas: Some(U256::from(21000)), - gas_price: None, - data: Some(Bytes::default()), - nonce: None, - }, - source_hash: H256::from_str( - "0000000000000000000000000000000000000000000000000000000000000000", - ) - .unwrap(), - mint: Some(U256::zero()), - is_system_tx: true, - }); - - let pending = provider.send_transaction(deposit_tx.clone(), None).await.unwrap(); - let receipt = pending.await.unwrap().expect("dropped"); - assert_eq!(receipt.from, from_addr); - assert_eq!(receipt.to, Some(to_addr)); + let before_balance_to = provider.get_balance(to, BlockId::latest()).await.unwrap(); + + let tx = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_value(send_value) + .with_gas_limit(21000); + let tx: WithOtherFields = WithOtherFields { + inner: tx, + other: OptimismTransactionFields { + source_hash: Some(b256!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + mint: Some(U128::from(0)), + is_system_tx: Some(true), + } + .into(), + }; + + let pending = provider.send_transaction(tx).await.unwrap().register().await.unwrap(); // mine block api.evm_mine(None).await.unwrap(); + let receipt = + provider.get_transaction_receipt(pending.tx_hash().to_owned()).await.unwrap().unwrap(); + assert_eq!(receipt.from, from); + assert_eq!(receipt.to, Some(to)); + // the recipient should have received the value - let balance = provider.get_balance(to_addr, None).await.unwrap(); - assert_eq!(balance, send_value); + let after_balance_to = provider.get_balance(to, BlockId::latest()).await.unwrap(); + assert_eq!(after_balance_to, before_balance_to + send_value); } #[tokio::test(flavor = "multi_thread")] @@ -98,44 +98,54 @@ async fn test_send_value_raw_deposit_transaction() { // enable the Optimism flag let (api, handle) = spawn(NodeConfig::test().with_optimism(true).with_hardfork(Some(Hardfork::Paris))).await; - let provider = handle.http_provider(); + + let accounts: Vec<_> = handle.dev_wallets().collect(); + let signer: EthereumSigner = accounts[0].clone().into(); + let from = accounts[0].address(); + let to = accounts[1].address(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer.clone()); let send_value = U256::from(1234); - let from_addr: Address = "cf7f9e66af820a19257a2108375b180b0ec49167".parse().unwrap(); - let to_addr: Address = "71562b71999873db5b286df957af199ec94617f7".parse().unwrap(); - - // fund the sender - api.anvil_set_balance(from_addr, send_value).await.unwrap(); - - let deposit_tx: TypedTransaction = TypedTransaction::DepositTransaction(DepositTransaction { - tx: TransactionRequest { - chain_id: None, - from: Some(from_addr), - to: Some(ethers::types::NameOrAddress::Address(to_addr)), - value: Some(send_value), - gas: Some(U256::from(21000)), - gas_price: None, - data: Some(Bytes::default()), - nonce: None, - }, - source_hash: H256::from_str( - "0000000000000000000000000000000000000000000000000000000000000000", - ) - .unwrap(), - mint: Some(U256::zero()), - is_system_tx: true, - }); - - let rlpbytes = deposit_tx.rlp(); - let pending = provider.send_raw_transaction(rlpbytes).await.unwrap(); - let receipt = pending.await.unwrap().expect("dropped"); - assert_eq!(receipt.from, from_addr); - assert_eq!(receipt.to, Some(to_addr)); + let before_balance_to = provider.get_balance(to, BlockId::latest()).await.unwrap(); + + let tx = TransactionRequest::default() + .with_chain_id(31337) + .with_nonce(0) + .with_from(from) + .with_to(to) + .with_value(send_value) + .with_gas_limit(21_000) + .with_max_fee_per_gas(20_000_000_000) + .with_max_priority_fee_per_gas(1_000_000_000); + let tx = WithOtherFields { + inner: tx, + other: OptimismTransactionFields { + source_hash: Some(b256!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + mint: Some(U128::from(0)), + is_system_tx: Some(true), + } + .into(), + }; + let tx_envelope = tx.build(&signer).await.unwrap(); + let mut tx_buffer = Vec::with_capacity(tx_envelope.encode_2718_len()); + tx_envelope.encode_2718(&mut tx_buffer); + let tx_encoded = tx_buffer.as_slice(); + + let pending = + provider.send_raw_transaction(tx_encoded).await.unwrap().register().await.unwrap(); // mine block api.evm_mine(None).await.unwrap(); + let receipt = + provider.get_transaction_receipt(pending.tx_hash().to_owned()).await.unwrap().unwrap(); + assert_eq!(receipt.from, from); + assert_eq!(receipt.to, Some(to)); + // the recipient should have received the value - let balance = provider.get_balance(to_addr, None).await.unwrap(); - assert_eq!(balance, send_value); + let after_balance_to = provider.get_balance(to, BlockId::latest()).await.unwrap(); + assert_eq!(after_balance_to, before_balance_to + send_value); } diff --git a/crates/anvil/tests/it/otterscan.rs b/crates/anvil/tests/it/otterscan.rs index becbcb852e534..07af1b8ead13e 100644 --- a/crates/anvil/tests/it/otterscan.rs +++ b/crates/anvil/tests/it/otterscan.rs @@ -1,20 +1,21 @@ //! tests for otterscan endpoints -use crate::abi::MulticallContract; +use crate::{ + abi::MulticallContract, + utils::{http_provider_with_signer, ws_provider_with_signer}, +}; +use alloy_network::EthereumSigner; +use alloy_primitives::{Address, Bytes, TxKind, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockNumberOrTag, BlockTransactions, TransactionRequest, WithOtherFields}; +use alloy_sol_types::sol; use anvil::{ eth::otterscan::types::{ OtsInternalOperation, OtsInternalOperationType, OtsTrace, OtsTraceType, }, spawn, NodeConfig, }; -use ethers::{ - abi::Address, - prelude::{ContractFactory, ContractInstance, Middleware, SignerMiddleware}, - signers::Signer, - types::{BlockNumber, Bytes, TransactionRequest, U256}, - utils::get_contract_address, -}; -use ethers_solc::{project_util::TempProject, Artifact}; -use std::{collections::VecDeque, str::FromStr, sync::Arc}; +use foundry_compilers::{project_util::TempProject, Artifact}; +use std::{collections::VecDeque, str::FromStr}; #[tokio::test(flavor = "multi_thread")] async fn can_call_erigon_get_header_by_number() { @@ -24,8 +25,8 @@ async fn can_call_erigon_get_header_by_number() { let res0 = api.erigon_get_header_by_number(0.into()).await.unwrap().unwrap(); let res1 = api.erigon_get_header_by_number(1.into()).await.unwrap().unwrap(); - assert_eq!(res0.number, Some(0.into())); - assert_eq!(res1.number, Some(1.into())); + assert_eq!(res0.header.number, Some(0)); + assert_eq!(res1.header.number, Some(1)); } #[tokio::test(flavor = "multi_thread")] @@ -38,19 +39,24 @@ async fn can_call_ots_get_api_level() { #[tokio::test(flavor = "multi_thread")] async fn can_call_ots_get_internal_operations_contract_deploy() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let mut deploy_tx = MulticallContract::deploy(Arc::clone(&client), ()).unwrap().deployer.tx; - deploy_tx.set_nonce(0); - let contract_address = get_contract_address(sender, deploy_tx.nonce().unwrap()); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); - let receipt = client.send_transaction(deploy_tx, None).await.unwrap().await.unwrap().unwrap(); + let contract_receipt = MulticallContract::deploy_builder(provider.clone()) + .from(sender) + .send() + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let contract_address = sender.create(0); - let res = api.ots_get_internal_operations(receipt.transaction_hash).await.unwrap(); + let res = api.ots_get_internal_operations(contract_receipt.transaction_hash).await.unwrap(); assert_eq!(res.len(), 1); assert_eq!( @@ -59,7 +65,7 @@ async fn can_call_ots_get_internal_operations_contract_deploy() { r#type: OtsInternalOperationType::Create, from: sender, to: contract_address, - value: 0.into() + value: U256::from(0) } ); } @@ -67,18 +73,19 @@ async fn can_call_ots_get_internal_operations_contract_deploy() { #[tokio::test(flavor = "multi_thread")] async fn can_call_ots_get_internal_operations_contract_transfer() { let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); let accounts: Vec<_> = handle.dev_wallets().collect(); let from = accounts[0].address(); let to = accounts[1].address(); - //let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let amount = handle.genesis_balance().checked_div(2u64.into()).unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); - let tx = TransactionRequest::new().to(to).value(amount).from(from); + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); - let receipt = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let res = api.ots_get_internal_operations(receipt.transaction_hash).await.unwrap(); @@ -104,7 +111,7 @@ pragma solidity 0.8.13; contract Contract { address constant CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; constructor() {} - function deploy() public { + function deployContract() public { uint256 salt = 0; uint256 code = 0; bytes memory creationCode = abi.encodePacked(code); @@ -116,28 +123,42 @@ contract Contract { ) .unwrap(); + sol!( + #[sol(rpc)] + contract Contract { + function deployContract() external; + } + ); + let mut compiled = prj.compile().unwrap(); assert!(!compiled.has_compiler_errors()); let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + let bytecode = contract.into_bytecode_bytes().unwrap(); let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); - let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); + let sender = wallet.address(); - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); + let provider = ws_provider_with_signer(&handle.ws_endpoint(), signer); - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); - let call = contract.method::<_, ()>("deploy", ()).unwrap(); + // deploy successfully + provider + .send_transaction(WithOtherFields::new(TransactionRequest { + from: Some(sender), + to: Some(TxKind::Create), + input: bytecode.into(), + ..Default::default() + })) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let contract_address = sender.create(0); + let contract = Contract::new(contract_address, &provider); + let receipt = contract.deployContract().send().await.unwrap().get_receipt().await.unwrap(); - let receipt = call.send().await.unwrap().await.unwrap().unwrap(); let res = api.ots_get_internal_operations(receipt.transaction_hash).await.unwrap(); assert_eq!(res.len(), 1); @@ -147,7 +168,7 @@ contract Contract { r#type: OtsInternalOperationType::Create2, from: Address::from_str("0x4e59b44847b379578588920cA78FbF26c0B4956C").unwrap(), to: Address::from_str("0x347bcdad821abc09b8c275881b368de36476b62c").unwrap(), - value: 0.into() + value: U256::from(0) } ); } @@ -172,28 +193,42 @@ contract Contract { ) .unwrap(); + sol!( + #[sol(rpc)] + contract Contract { + function goodbye() external; + } + ); + let mut compiled = prj.compile().unwrap(); assert!(!compiled.has_compiler_errors()); let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + let bytecode = contract.into_bytecode_bytes().unwrap(); let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); - let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); + let sender = wallet.address(); - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); + let provider = ws_provider_with_signer(&handle.ws_endpoint(), signer); - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); - let call = contract.method::<_, ()>("goodbye", ()).unwrap(); + // deploy successfully + provider + .send_transaction(WithOtherFields::new(TransactionRequest { + from: Some(sender), + to: Some(TxKind::Create), + input: bytecode.into(), + ..Default::default() + })) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let contract_address = sender.create(0); + let contract = Contract::new(contract_address, &provider); - let receipt = call.send().await.unwrap().await.unwrap().unwrap(); + let receipt = contract.goodbye().send().await.unwrap().get_receipt().await.unwrap(); let res = api.ots_get_internal_operations(receipt.transaction_hash).await.unwrap(); @@ -202,9 +237,9 @@ contract Contract { res[0], OtsInternalOperation { r#type: OtsInternalOperationType::SelfDestruct, - from: contract.address(), + from: *contract.address(), to: Default::default(), - value: 0.into() + value: U256::from(0) } ); } @@ -212,38 +247,30 @@ contract Contract { #[tokio::test(flavor = "multi_thread")] async fn can_call_ots_has_code() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - api.mine_one().await; - let mut deploy_tx = MulticallContract::deploy(Arc::clone(&client), ()).unwrap().deployer.tx; - deploy_tx.set_nonce(0); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + api.mine_one().await; - let pending_contract_address = get_contract_address(sender, deploy_tx.nonce().unwrap()); + let contract_address = sender.create(0); // no code in the address before deploying - assert!(!api - .ots_has_code(pending_contract_address, BlockNumber::Number(1.into())) - .await - .unwrap()); + assert!(!api.ots_has_code(contract_address, BlockNumberOrTag::Number(1)).await.unwrap()); - let pending = client.send_transaction(deploy_tx, None).await.unwrap(); - let receipt = pending.await.unwrap().unwrap(); + let contract_builder = MulticallContract::deploy_builder(provider.clone()); + let contract_receipt = contract_builder.send().await.unwrap().get_receipt().await.unwrap(); - let num = client.get_block_number().await.unwrap(); - assert_eq!(num, receipt.block_number.unwrap()); + let num = provider.get_block_number().await.unwrap(); + assert_eq!(num, contract_receipt.block_number.unwrap()); // code is detected after deploying - assert!(api.ots_has_code(pending_contract_address, BlockNumber::Number(num)).await.unwrap()); + assert!(api.ots_has_code(contract_address, BlockNumberOrTag::Number(num)).await.unwrap()); // code is not detected for the previous block - assert!(!api - .ots_has_code(pending_contract_address, BlockNumber::Number(num - 1)) - .await - .unwrap()); + assert!(!api.ots_has_code(contract_address, BlockNumberOrTag::Number(num - 1)).await.unwrap()); } #[tokio::test(flavor = "multi_thread")] @@ -279,27 +306,54 @@ contract Contract { ) .unwrap(); + sol!( + #[sol(rpc)] + contract Contract { + function run() external payable; + function do_staticcall() external view returns (bool); + function do_call() external; + function do_delegatecall() external; + } + ); + let mut compiled = prj.compile().unwrap(); assert!(!compiled.has_compiler_errors()); let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + let bytecode = contract.into_bytecode_bytes().unwrap(); let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); + let signer: EthereumSigner = wallets[0].clone().into(); + let sender = wallets[0].address(); - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); + let provider = ws_provider_with_signer(&handle.ws_endpoint(), signer); - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); - let call = contract.method::<_, ()>("run", ()).unwrap().value(1337); - let receipt = call.send().await.unwrap().await.unwrap().unwrap(); + // deploy successfully + provider + .send_transaction(WithOtherFields::new(TransactionRequest { + from: Some(sender), + to: Some(TxKind::Create), + input: bytecode.into(), + ..Default::default() + })) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let contract_address = sender.create(0); + let contract = Contract::new(contract_address, &provider); + + let receipt = contract + .run() + .from(sender) + .value(U256::from(1337)) + .send() + .await + .unwrap() + .get_receipt() + .await + .unwrap(); let res = api.ots_trace_transaction(receipt.transaction_hash).await.unwrap(); @@ -309,42 +363,42 @@ contract Contract { OtsTrace { r#type: OtsTraceType::Call, depth: 0, - from: wallets[1].address(), - to: contract.address(), - value: 1337.into(), - input: Bytes::from_str("0xc0406226").unwrap() + from: sender, + to: contract_address, + value: U256::from(1337), + input: Bytes::from_str("0xc0406226").unwrap().0.into() }, OtsTrace { r#type: OtsTraceType::StaticCall, depth: 1, - from: contract.address(), - to: contract.address(), - value: U256::zero(), - input: Bytes::from_str("0x6a6758fe").unwrap() + from: contract_address, + to: contract_address, + value: U256::ZERO, + input: Bytes::from_str("0x6a6758fe").unwrap().0.into() }, OtsTrace { r#type: OtsTraceType::Call, depth: 1, - from: contract.address(), - to: contract.address(), - value: U256::zero(), - input: Bytes::from_str("0x96385e39").unwrap() + from: contract_address, + to: contract_address, + value: U256::ZERO, + input: Bytes::from_str("0x96385e39").unwrap().0.into() }, OtsTrace { r#type: OtsTraceType::Call, depth: 2, - from: contract.address(), - to: wallets[0].address(), - value: 1337.into(), - input: Bytes::from_str("0x").unwrap() + from: contract_address, + to: sender, + value: U256::from(1337), + input: Bytes::from_str("0x").unwrap().0.into() }, OtsTrace { r#type: OtsTraceType::DelegateCall, depth: 2, - from: contract.address(), - to: contract.address(), - value: U256::zero(), - input: Bytes::from_str("0xa1325397").unwrap() + from: contract_address, + to: contract_address, + value: U256::ZERO, + input: Bytes::from_str("0xa1325397").unwrap().0.into() }, ] ); @@ -368,79 +422,143 @@ contract Contract { ) .unwrap(); + sol!( + #[sol(rpc)] + contract Contract { + function trigger_revert() external; + } + ); + let mut compiled = prj.compile().unwrap(); assert!(!compiled.has_compiler_errors()); let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + let bytecode = contract.into_bytecode_bytes().unwrap(); - let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); + let (_api, handle) = spawn(NodeConfig::test()).await; + let wallets = handle.dev_wallets().collect::>(); + let signer: EthereumSigner = wallets[0].clone().into(); + let sender = wallets[0].address(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let provider = ws_provider_with_signer(&handle.ws_endpoint(), signer); // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); + provider + .send_transaction(WithOtherFields::new(TransactionRequest { + from: Some(sender), + to: Some(TxKind::Create), + input: bytecode.into(), + ..Default::default() + })) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let contract_address = sender.create(0); + let _contract = Contract::new(contract_address, &provider); - let call = contract.method::<_, ()>("trigger_revert", ()).unwrap().gas(150_000u64); - let receipt = call.send().await.unwrap().await.unwrap().unwrap(); + // TODO: currently not possible to capture the receipt + // let receipt = contract.trigger_revert().send().await.unwrap().get_receipt().await.unwrap(); - let res = api.ots_get_transaction_error(receipt.transaction_hash).await.unwrap().unwrap(); - assert_eq!(res, Bytes::from_str("0x8d6ea8be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012526576657274537472696e67466f6f4261720000000000000000000000000000").unwrap()); + // let res = api.ots_get_transaction_error(receipt.transaction_hash).await; + // assert!(res.is_err()); + // assert!(res.unwrap_err().to_string().contains("0x8d6ea8be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012526576657274537472696e67466f6f4261720000000000000000000000000000")); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_block_details() { +async fn ots_get_transaction_error_no_error() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); + let wallets = handle.dev_wallets().collect::>(); + let signer: EthereumSigner = wallets[0].clone().into(); + let sender = wallets[0].address(); + + let provider = ws_provider_with_signer(&handle.ws_endpoint(), signer); + + // Send a successful transaction + let tx = + TransactionRequest::default().from(sender).to(Address::random()).value(U256::from(100)); + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let res = api.ots_get_transaction_error(receipt.transaction_hash).await; + assert!(res.is_ok()); + assert_eq!(res.unwrap().to_string(), "0x"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_call_ots_get_block_details() { + let (api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let signer: EthereumSigner = wallet.clone().into(); + let sender = wallet.address(); - let tx = TransactionRequest::new().to(Address::random()).value(100u64); - let receipt = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let tx = + TransactionRequest::default().from(sender).to(Address::random()).value(U256::from(100)); + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let result = api.ots_get_block_details(1.into()).await.unwrap(); assert_eq!(result.block.transaction_count, 1); - assert_eq!(result.block.block.transactions[0], receipt.transaction_hash); + let hash = match result.block.block.transactions { + BlockTransactions::Full(txs) => txs[0].hash, + BlockTransactions::Hashes(hashes) => hashes[0], + BlockTransactions::Uncle => unreachable!(), + }; + assert_eq!(hash, receipt.transaction_hash); } #[tokio::test(flavor = "multi_thread")] async fn can_call_ots_get_block_details_by_hash() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let signer: EthereumSigner = wallet.clone().into(); + let sender = wallet.address(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); - let tx = TransactionRequest::new().to(Address::random()).value(100u64); - let receipt = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = + TransactionRequest::default().from(sender).to(Address::random()).value(U256::from(100)); + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let block_hash = receipt.block_hash.unwrap(); let result = api.ots_get_block_details_by_hash(block_hash).await.unwrap(); assert_eq!(result.block.transaction_count, 1); - assert_eq!(result.block.block.transactions[0], receipt.transaction_hash); + let hash = match result.block.block.transactions { + BlockTransactions::Full(txs) => txs[0].hash, + BlockTransactions::Hashes(hashes) => hashes[0], + BlockTransactions::Uncle => unreachable!(), + }; + assert_eq!(hash, receipt.transaction_hash); } #[tokio::test(flavor = "multi_thread")] async fn can_call_ots_get_block_transactions() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let signer: EthereumSigner = wallet.clone().into(); + let sender = wallet.address(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); // disable automine api.anvil_set_auto_mine(false).await.unwrap(); let mut hashes = VecDeque::new(); for i in 0..10 { - let tx = TransactionRequest::new().to(Address::random()).value(100u64).nonce(i); - let receipt = client.send_transaction(tx, None).await.unwrap(); - hashes.push_back(receipt.tx_hash()); + let tx = TransactionRequest::default() + .from(sender) + .to(Address::random()) + .value(U256::from(100)) + .nonce(i); + let tx = WithOtherFields::new(tx); + let pending_receipt = + provider.send_transaction(tx).await.unwrap().register().await.unwrap(); + hashes.push_back(*pending_receipt.tx_hash()); } api.mine_one().await; @@ -450,13 +568,14 @@ async fn can_call_ots_get_block_transactions() { let result = api.ots_get_block_transactions(1, page, page_size).await.unwrap(); assert!(result.receipts.len() <= page_size); - assert!(result.fullblock.block.transactions.len() <= page_size); + let len = result.receipts.len(); + assert!(len <= page_size); assert!(result.fullblock.transaction_count == result.receipts.len()); result.receipts.iter().enumerate().for_each(|(i, receipt)| { let expected = hashes.pop_front(); assert_eq!(expected, Some(receipt.transaction_hash)); - assert_eq!(expected, Some(result.fullblock.block.transactions[i].hash)); + assert_eq!(expected, result.fullblock.block.transactions.hashes().nth(i).copied()); }); } @@ -466,33 +585,39 @@ async fn can_call_ots_get_block_transactions() { #[tokio::test(flavor = "multi_thread")] async fn can_call_ots_search_transactions_before() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); let mut hashes = vec![]; for i in 0..7 { - let tx = TransactionRequest::new().to(Address::random()).value(100u64).nonce(i); - let receipt = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = TransactionRequest::default() + .from(sender) + .to(Address::random()) + .value(U256::from(100)) + .nonce(i); + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); hashes.push(receipt.transaction_hash); } let page_size = 2; let mut block = 0; - for _ in 0..4 { + for i in 0..4 { let result = api.ots_search_transactions_before(sender, block, page_size).await.unwrap(); - assert!(result.txs.len() <= page_size); + assert_eq!(result.first_page, i == 0); + assert_eq!(result.last_page, i == 3); // check each individual hash result.txs.iter().for_each(|tx| { assert_eq!(hashes.pop(), Some(tx.hash)); }); - block = result.txs.last().unwrap().block_number.unwrap().as_u64() - 1; + block = result.txs.last().unwrap().block_number.unwrap(); } assert!(hashes.is_empty()); @@ -501,33 +626,39 @@ async fn can_call_ots_search_transactions_before() { #[tokio::test(flavor = "multi_thread")] async fn can_call_ots_search_transactions_after() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); let mut hashes = VecDeque::new(); for i in 0..7 { - let tx = TransactionRequest::new().to(Address::random()).value(100u64).nonce(i); - let receipt = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = TransactionRequest::default() + .from(sender) + .to(Address::random()) + .value(U256::from(100)) + .nonce(i); + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); hashes.push_front(receipt.transaction_hash); } let page_size = 2; let mut block = 0; - for _ in 0..4 { + for i in 0..4 { let result = api.ots_search_transactions_after(sender, block, page_size).await.unwrap(); - assert!(result.txs.len() <= page_size); + assert_eq!(result.first_page, i == 3); + assert_eq!(result.last_page, i == 0); // check each individual hash - result.txs.iter().for_each(|tx| { + result.txs.iter().rev().for_each(|tx| { assert_eq!(hashes.pop_back(), Some(tx.hash)); }); - block = result.txs.last().unwrap().block_number.unwrap().as_u64() + 1; + block = result.txs.first().unwrap().block_number.unwrap(); } assert!(hashes.is_empty()); @@ -536,45 +667,58 @@ async fn can_call_ots_search_transactions_after() { #[tokio::test(flavor = "multi_thread")] async fn can_call_ots_get_transaction_by_sender_and_nonce() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - api.mine_one().await; - let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let tx1 = TransactionRequest::new().to(Address::random()).value(100u64); - let tx2 = TransactionRequest::new().to(Address::random()).value(100u64); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + api.mine_one().await; - let receipt1 = client.send_transaction(tx1, None).await.unwrap().await.unwrap().unwrap(); - let receipt2 = client.send_transaction(tx2, None).await.unwrap().await.unwrap().unwrap(); + let tx1 = WithOtherFields::new( + TransactionRequest::default() + .from(sender) + .to(Address::random()) + .value(U256::from(100)) + .nonce(0), + ); + let tx2 = WithOtherFields::new( + TransactionRequest::default() + .from(sender) + .to(Address::random()) + .value(U256::from(100)) + .nonce(1), + ); - let result1 = api.ots_get_transaction_by_sender_and_nonce(sender, 0.into()).await.unwrap(); - let result2 = api.ots_get_transaction_by_sender_and_nonce(sender, 1.into()).await.unwrap(); + let receipt1 = provider.send_transaction(tx1).await.unwrap().get_receipt().await.unwrap(); + let receipt2 = provider.send_transaction(tx2).await.unwrap().get_receipt().await.unwrap(); - assert_eq!(result1.unwrap().hash, receipt1.transaction_hash); - assert_eq!(result2.unwrap().hash, receipt2.transaction_hash); + let result1 = + api.ots_get_transaction_by_sender_and_nonce(sender, U256::from(0)).await.unwrap().unwrap(); + let result2 = + api.ots_get_transaction_by_sender_and_nonce(sender, U256::from(1)).await.unwrap().unwrap(); + + assert_eq!(result1, receipt1.transaction_hash); + assert_eq!(result2, receipt2.transaction_hash); } #[tokio::test(flavor = "multi_thread")] async fn can_call_ots_get_contract_creator() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - api.mine_one().await; - let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let mut deploy_tx = MulticallContract::deploy(Arc::clone(&client), ()).unwrap().deployer.tx; - deploy_tx.set_nonce(0); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); - let pending_contract_address = get_contract_address(sender, deploy_tx.nonce().unwrap()); + api.mine_one().await; - let receipt = client.send_transaction(deploy_tx, None).await.unwrap().await.unwrap().unwrap(); + let contract_builder = MulticallContract::deploy_builder(provider.clone()); + let contract_receipt = contract_builder.send().await.unwrap().get_receipt().await.unwrap(); + let contract_address = sender.create(0); - let creator = api.ots_get_contract_creator(pending_contract_address).await.unwrap().unwrap(); + let creator = api.ots_get_contract_creator(contract_address).await.unwrap().unwrap(); assert_eq!(creator.creator, sender); - assert_eq!(creator.hash, receipt.transaction_hash); + assert_eq!(creator.hash, contract_receipt.transaction_hash); } diff --git a/crates/anvil/tests/it/proof.rs b/crates/anvil/tests/it/proof.rs new file mode 100644 index 0000000000000..757d36082696d --- /dev/null +++ b/crates/anvil/tests/it/proof.rs @@ -0,0 +1,133 @@ +//! tests for `eth_getProof` + +use alloy_primitives::{address, fixed_bytes, Address, Bytes, B256, U256}; +use anvil::{eth::EthApi, spawn, NodeConfig}; +use std::{collections::BTreeMap, str::FromStr}; + +async fn verify_account_proof( + api: &EthApi, + address: Address, + proof: impl IntoIterator, +) { + let expected_proof = + proof.into_iter().map(Bytes::from_str).collect::, _>>().unwrap(); + let proof = api.get_proof(address, Vec::new(), None).await.unwrap(); + + assert_eq!(proof.account_proof, expected_proof); +} + +async fn verify_storage_proof( + api: &EthApi, + address: Address, + slot: B256, + proof: impl IntoIterator, +) { + let expected_proof = + proof.into_iter().map(Bytes::from_str).collect::, _>>().unwrap(); + let proof = api.get_proof(address, vec![slot], None).await.unwrap(); + + assert_eq!(proof.storage_proof[0].proof, expected_proof); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_account_proof() { + let (api, _handle) = spawn(NodeConfig::empty_state()).await; + + api.anvil_set_balance( + address!("2031f89b3ea8014eb51a78c316e42af3e0d7695f"), + U256::from(45000000000000000000_u128), + ) + .await + .unwrap(); + api.anvil_set_balance(address!("33f0fc440b8477fcfbe9d0bf8649e7dea9baedb2"), U256::from(1)) + .await + .unwrap(); + api.anvil_set_balance( + address!("62b0dd4aab2b1a0a04e279e2b828791a10755528"), + U256::from(1100000000000000000_u128), + ) + .await + .unwrap(); + api.anvil_set_balance( + address!("1ed9b1dd266b607ee278726d324b855a093394a6"), + U256::from(120000000000000000_u128), + ) + .await + .unwrap(); + + verify_account_proof(&api, address!("2031f89b3ea8014eb51a78c316e42af3e0d7695f"), [ + "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", + "0xf8719f31355ec1c8f7e26bb3ccbcb0b75d870d15846c0b98e5cc452db46c37faea40b84ff84d80890270801d946c940000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + ]).await; + + verify_account_proof(&api, address!("33f0fc440b8477fcfbe9d0bf8649e7dea9baedb2"), [ + "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", + "0xe48200d3a0ef957210bca5b9b402d614eb8408c88cfbf4913eb6ab83ca233c8b8f0e626b54", + "0xf851808080a02743a5addaf4cf9b8c0c073e1eaa555deaaf8c41cb2b41958e88624fa45c2d908080808080a0bfbf6937911dfb88113fecdaa6bde822e4e99dae62489fcf61a91cb2f36793d680808080808080", + "0xf8679e207781e762f3577784bab7491fcc43e291ce5a356b9bc517ac52eed3a37ab846f8448001a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + ]).await; + + verify_account_proof(&api, address!("62b0dd4aab2b1a0a04e279e2b828791a10755528"), [ + "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", + "0xf8709f3936599f93b769acf90c7178fd2ddcac1b5b4bc9949ee5a04b7e0823c2446eb84ef84c80880f43fc2c04ee0000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + ]).await; + + verify_account_proof(&api, address!("1ed9b1dd266b607ee278726d324b855a093394a6"), [ + "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", + "0xe48200d3a0ef957210bca5b9b402d614eb8408c88cfbf4913eb6ab83ca233c8b8f0e626b54", + "0xf851808080a02743a5addaf4cf9b8c0c073e1eaa555deaaf8c41cb2b41958e88624fa45c2d908080808080a0bfbf6937911dfb88113fecdaa6bde822e4e99dae62489fcf61a91cb2f36793d680808080808080", + "0xf86f9e207a32b8ab5eb4b043c65b1f00c93f517bc8883c5cd31baf8e8a279475e3b84ef84c808801aa535d3d0c0000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + ]).await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_storage_proof() { + let target = address!("1ed9b1dd266b607ee278726d324b855a093394a6"); + + let (api, _handle) = spawn(NodeConfig::empty_state()).await; + let storage: BTreeMap = + serde_json::from_str(include_str!("../../test-data/storage_sample.json")).unwrap(); + + for (key, value) in storage { + api.anvil_set_storage_at(target, key, value).await.unwrap(); + } + + verify_storage_proof(&api, target, fixed_bytes!("0000000000000000000000000000000000000000000000000000000000000022"), [ + "0xf9019180a0aafd5b14a6edacd149e110ba6776a654f2dbffca340902be933d011113f2750380a0a502c93b1918c4c6534d4593ae03a5a23fa10ebc30ffb7080b297bff2446e42da02eb2bf45fd443bd1df8b6f9c09726a4c6252a0f7896a131a081e39a7f644b38980a0a9cf7f673a0bce76fd40332afe8601542910b48dea44e93933a3e5e930da5d19a0ddf79db0a36d0c8134ba143bcb541cd4795a9a2bae8aca0ba24b8d8963c2a77da0b973ec0f48f710bf79f63688485755cbe87f9d4c68326bb83c26af620802a80ea0f0855349af6bf84afc8bca2eda31c8ef8c5139be1929eeb3da4ba6b68a818cb0a0c271e189aeeb1db5d59d7fe87d7d6327bbe7cfa389619016459196497de3ccdea0e7503ba5799e77aa31bbe1310c312ca17b2c5bcc8fa38f266675e8f154c2516ba09278b846696d37213ab9d20a5eb42b03db3173ce490a2ef3b2f3b3600579fc63a0e9041059114f9c910adeca12dbba1fef79b2e2c8899f2d7213cd22dfe4310561a047c59da56bb2bf348c9dd2a2e8f5538a92b904b661cfe54a4298b85868bbe4858080", + "0xf85180a0776aa456ba9c5008e03b82b841a9cf2fc1e8578cfacd5c9015804eae315f17fb80808080808080808080808080a072e3e284d47badbb0a5ca1421e1179d3ea90cc10785b26b74fb8a81f0f9e841880", + "0xf843a020035b26e3e9eee00e0d72fd1ee8ddca6894550dca6916ea2ac6baa90d11e510a1a0f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b" + ]).await; + + verify_storage_proof(&api, target, fixed_bytes!("0000000000000000000000000000000000000000000000000000000000000023"), [ + "0xf9019180a0aafd5b14a6edacd149e110ba6776a654f2dbffca340902be933d011113f2750380a0a502c93b1918c4c6534d4593ae03a5a23fa10ebc30ffb7080b297bff2446e42da02eb2bf45fd443bd1df8b6f9c09726a4c6252a0f7896a131a081e39a7f644b38980a0a9cf7f673a0bce76fd40332afe8601542910b48dea44e93933a3e5e930da5d19a0ddf79db0a36d0c8134ba143bcb541cd4795a9a2bae8aca0ba24b8d8963c2a77da0b973ec0f48f710bf79f63688485755cbe87f9d4c68326bb83c26af620802a80ea0f0855349af6bf84afc8bca2eda31c8ef8c5139be1929eeb3da4ba6b68a818cb0a0c271e189aeeb1db5d59d7fe87d7d6327bbe7cfa389619016459196497de3ccdea0e7503ba5799e77aa31bbe1310c312ca17b2c5bcc8fa38f266675e8f154c2516ba09278b846696d37213ab9d20a5eb42b03db3173ce490a2ef3b2f3b3600579fc63a0e9041059114f9c910adeca12dbba1fef79b2e2c8899f2d7213cd22dfe4310561a047c59da56bb2bf348c9dd2a2e8f5538a92b904b661cfe54a4298b85868bbe4858080", + "0xf8518080808080a0d546c4ca227a267d29796643032422374624ed109b3d94848c5dc06baceaee76808080808080a027c48e210ccc6e01686be2d4a199d35f0e1e8df624a8d3a17c163be8861acd6680808080", + "0xf843a0207b2b5166478fd4318d2acc6cc2c704584312bdd8781b32d5d06abda57f4230a1a0db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71" + ]).await; + + verify_storage_proof(&api, target, fixed_bytes!("0000000000000000000000000000000000000000000000000000000000000024"), [ + "0xf9019180a0aafd5b14a6edacd149e110ba6776a654f2dbffca340902be933d011113f2750380a0a502c93b1918c4c6534d4593ae03a5a23fa10ebc30ffb7080b297bff2446e42da02eb2bf45fd443bd1df8b6f9c09726a4c6252a0f7896a131a081e39a7f644b38980a0a9cf7f673a0bce76fd40332afe8601542910b48dea44e93933a3e5e930da5d19a0ddf79db0a36d0c8134ba143bcb541cd4795a9a2bae8aca0ba24b8d8963c2a77da0b973ec0f48f710bf79f63688485755cbe87f9d4c68326bb83c26af620802a80ea0f0855349af6bf84afc8bca2eda31c8ef8c5139be1929eeb3da4ba6b68a818cb0a0c271e189aeeb1db5d59d7fe87d7d6327bbe7cfa389619016459196497de3ccdea0e7503ba5799e77aa31bbe1310c312ca17b2c5bcc8fa38f266675e8f154c2516ba09278b846696d37213ab9d20a5eb42b03db3173ce490a2ef3b2f3b3600579fc63a0e9041059114f9c910adeca12dbba1fef79b2e2c8899f2d7213cd22dfe4310561a047c59da56bb2bf348c9dd2a2e8f5538a92b904b661cfe54a4298b85868bbe4858080", + "0xf85180808080a030263404acfee103d0b1019053ff3240fce433c69b709831673285fa5887ce4c80808080808080a0f8f1fbb1f7b482d9860480feebb83ff54a8b6ec1ead61cc7d2f25d7c01659f9c80808080", + "0xf843a020d332d19b93bcabe3cce7ca0c18a052f57e5fd03b4758a09f30f5ddc4b22ec4a1a0c78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + ]).await; + + verify_storage_proof(&api, target, fixed_bytes!("0000000000000000000000000000000000000000000000000000000000000100"), [ + "0xf9019180a0aafd5b14a6edacd149e110ba6776a654f2dbffca340902be933d011113f2750380a0a502c93b1918c4c6534d4593ae03a5a23fa10ebc30ffb7080b297bff2446e42da02eb2bf45fd443bd1df8b6f9c09726a4c6252a0f7896a131a081e39a7f644b38980a0a9cf7f673a0bce76fd40332afe8601542910b48dea44e93933a3e5e930da5d19a0ddf79db0a36d0c8134ba143bcb541cd4795a9a2bae8aca0ba24b8d8963c2a77da0b973ec0f48f710bf79f63688485755cbe87f9d4c68326bb83c26af620802a80ea0f0855349af6bf84afc8bca2eda31c8ef8c5139be1929eeb3da4ba6b68a818cb0a0c271e189aeeb1db5d59d7fe87d7d6327bbe7cfa389619016459196497de3ccdea0e7503ba5799e77aa31bbe1310c312ca17b2c5bcc8fa38f266675e8f154c2516ba09278b846696d37213ab9d20a5eb42b03db3173ce490a2ef3b2f3b3600579fc63a0e9041059114f9c910adeca12dbba1fef79b2e2c8899f2d7213cd22dfe4310561a047c59da56bb2bf348c9dd2a2e8f5538a92b904b661cfe54a4298b85868bbe4858080", + "0xf891a090bacef44b189ddffdc5f22edc70fe298c58e5e523e6e1dfdf7dbc6d657f7d1b80a026eed68746028bc369eb456b7d3ee475aa16f34e5eaa0c98fdedb9c59ebc53b0808080a09ce86197173e14e0633db84ce8eea32c5454eebe954779255644b45b717e8841808080a0328c7afb2c58ef3f8c4117a8ebd336f1a61d24591067ed9c5aae94796cac987d808080808080", + ]).await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_get_random_account_proofs() { + let (api, _handle) = spawn(NodeConfig::test()).await; + + for acc in std::iter::repeat_with(Address::random).take(10) { + let _ = api + .get_proof(acc, Vec::new(), None) + .await + .unwrap_or_else(|_| panic!("Failed to get proof for {acc:?}")); + } +} diff --git a/crates/anvil/tests/it/proof/eip1186.rs b/crates/anvil/tests/it/proof/eip1186.rs deleted file mode 100644 index 2c6875ecaa4da..0000000000000 --- a/crates/anvil/tests/it/proof/eip1186.rs +++ /dev/null @@ -1,297 +0,0 @@ -/// Taken from https://github.com/paritytech/trie/blob/aa3168d6de01793e71ebd906d3a82ae4b363db59/trie-eip1186/src/eip1186.rs -use hash_db::Hasher; -use trie_db::{ - node::{decode_hash, Node, NodeHandle, Value}, - CError, NibbleSlice, NodeCodec, TrieHash, TrieLayout, -}; - -/// Errors that may occur during proof verification. Most of the errors types simply indicate that -/// the proof is invalid with respect to the statement being verified, and the exact error type can -/// be used for debugging. -#[derive(PartialEq, Eq, Debug)] -pub enum VerifyError<'a, HO, CE> { - /// The proof does not contain any value for the given key - /// the error carries the nibbles left after traversing the trie - NonExistingValue(NibbleSlice<'a>), - /// The proof contains a value for the given key - /// while we were expecting to find a non-existence proof - ExistingValue(Vec), - /// The proof indicates that the trie contains a different value. - /// the error carries the value contained in the trie - ValueMismatch(Vec), - /// The proof is missing trie nodes required to verify. - IncompleteProof, - /// The node hash computed from the proof is not matching. - HashMismatch(HO), - /// One of the proof nodes could not be decoded. - DecodeError(CE), - /// Error in converting a plain hash into a HO - HashDecodeError(&'a [u8]), -} - -#[cfg(feature = "std")] -impl<'a, HO: std::fmt::Debug, CE: std::error::Error> std::fmt::Display for VerifyError<'a, HO, CE> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - match self { - VerifyError::NonExistingValue(key) => { - write!(f, "Key does not exist in trie: reaming key={:?}", key) - } - VerifyError::ExistingValue(value) => { - write!(f, "trie contains a value for given key value={:?}", value) - } - VerifyError::ValueMismatch(key) => { - write!(f, "Expected value was not found in the trie: key={:?}", key) - } - VerifyError::IncompleteProof => write!(f, "Proof is incomplete -- expected more nodes"), - VerifyError::HashMismatch(hash) => write!(f, "hash mismatch found: hash={:?}", hash), - VerifyError::DecodeError(err) => write!(f, "Unable to decode proof node: {}", err), - VerifyError::HashDecodeError(plain_hash) => { - write!(f, "Unable to decode hash value plain_hash: {:?}", plain_hash) - } - } - } -} - -#[cfg(feature = "std")] -impl<'a, HO: std::fmt::Debug, CE: std::error::Error + 'static> std::error::Error - for VerifyError<'a, HO, CE> -{ - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - VerifyError::DecodeError(err) => Some(err), - _ => None, - } - } -} - -/// Verify a compact proof for key-value pairs in a trie given a root hash. -pub fn verify_proof<'a, L>( - root: &::Out, - proof: &'a [Vec], - raw_key: &'a [u8], - expected_value: Option<&[u8]>, -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if proof.is_empty() { - return Err(VerifyError::IncompleteProof) - } - let key = NibbleSlice::new(raw_key); - process_node::(Some(root), &proof[0], key, expected_value, &proof[1..]) -} - -fn process_node<'a, L>( - expected_node_hash: Option<&::Out>, - encoded_node: &'a [u8], - key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if let Some(value) = expected_value { - if encoded_node == value { - return Ok(()) - } - } - if let Some(expected) = expected_node_hash { - let calculated_node_hash = ::hash(encoded_node); - if calculated_node_hash != *expected { - return Err(VerifyError::HashMismatch(calculated_node_hash)) - } - } - let node = ::decode(encoded_node).map_err(VerifyError::DecodeError)?; - match node { - Node::Empty => process_empty::(key, expected_value, proof), - Node::Leaf(nib, data) => process_leaf::(nib, data, key, expected_value, proof), - Node::Extension(nib, handle) => { - process_extension::(&nib, handle, key, expected_value, proof) - } - Node::Branch(children, maybe_data) => { - process_branch::(children, maybe_data, key, expected_value, proof) - } - Node::NibbledBranch(nib, children, maybe_data) => { - process_nibbledbranch::(nib, children, maybe_data, key, expected_value, proof) - } - } -} - -fn process_empty<'a, L>( - key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - _: &[Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if expected_value.is_none() { - Ok(()) - } else { - Err(VerifyError::NonExistingValue(key)) - } -} - -fn process_leaf<'a, L>( - nib: NibbleSlice, - data: Value<'a>, - key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if key != nib && expected_value.is_none() { - return Ok(()) - } else if key != nib { - return Err(VerifyError::NonExistingValue(key)) - } - match_value::(Some(data), key, expected_value, proof) -} -fn process_extension<'a, L>( - nib: &NibbleSlice, - handle: NodeHandle<'a>, - mut key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if !key.starts_with(nib) && expected_value.is_none() { - return Ok(()) - } else if !key.starts_with(nib) { - return Err(VerifyError::NonExistingValue(key)) - } - key.advance(nib.len()); - - match handle { - NodeHandle::Inline(encoded_node) => { - process_node::(None, encoded_node, key, expected_value, proof) - } - NodeHandle::Hash(plain_hash) => { - let new_root = decode_hash::(plain_hash) - .ok_or_else(|| VerifyError::HashDecodeError(plain_hash))?; - process_node::(Some(&new_root), &proof[0], key, expected_value, &proof[1..]) - } - } -} - -fn process_nibbledbranch<'a, L>( - nib: NibbleSlice, - children: [Option>; 16], - maybe_data: Option>, - mut key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if !key.starts_with(&nib) && expected_value.is_none() { - return Ok(()) - } else if !key.starts_with(&nib) && expected_value.is_some() { - return Err(VerifyError::NonExistingValue(key)) - } - key.advance(nib.len()); - - if key.is_empty() { - match_value::(maybe_data, key, expected_value, proof) - } else { - match_children::(children, key, expected_value, proof) - } -} - -fn process_branch<'a, L>( - children: [Option>; 16], - maybe_data: Option>, - key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if key.is_empty() { - match_value::(maybe_data, key, expected_value, proof) - } else { - match_children::(children, key, expected_value, proof) - } -} -fn match_children<'a, L>( - children: [Option>; 16], - mut key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - match children.get(key.at(0) as usize) { - Some(Some(NodeHandle::Hash(hash))) => { - if proof.is_empty() { - Err(VerifyError::IncompleteProof) - } else { - key.advance(1); - let new_root = decode_hash::(hash) - .ok_or_else(|| VerifyError::HashDecodeError(hash))?; - process_node::(Some(&new_root), &proof[0], key, expected_value, &proof[1..]) - } - } - Some(Some(NodeHandle::Inline(encoded_node))) => { - key.advance(1); - process_node::(None, encoded_node, key, expected_value, proof) - } - Some(None) => { - if expected_value.is_none() { - Ok(()) - } else { - Err(VerifyError::NonExistingValue(key)) - } - } - None => panic!("key index is out of range in children array"), - } -} - -fn match_value<'a, L>( - maybe_data: Option>, - key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - match (maybe_data, proof.first(), expected_value) { - (None, _, None) => Ok(()), - (None, _, Some(_)) => Err(VerifyError::NonExistingValue(key)), - (Some(Value::Inline(inline_data)), _, Some(value)) => { - if inline_data == value { - Ok(()) - } else { - Err(VerifyError::ValueMismatch(inline_data.to_vec())) - } - } - (Some(Value::Inline(inline_data)), _, None) => { - Err(VerifyError::ExistingValue(inline_data.to_vec())) - } - (Some(Value::Node(plain_hash, _)), Some(next_proof_item), Some(value)) => { - let value_hash = L::Hash::hash(value); - let node_hash = decode_hash::(plain_hash) - .ok_or_else(|| VerifyError::HashDecodeError(plain_hash))?; - if node_hash != value_hash { - Err(VerifyError::HashMismatch(node_hash)) - } else if next_proof_item != value { - Err(VerifyError::ValueMismatch(next_proof_item.to_vec())) - } else { - Ok(()) - } - } - (Some(Value::Node(_, _)), None, _) => Err(VerifyError::IncompleteProof), - (Some(Value::Node(_, _)), Some(proof_item), None) => { - Err(VerifyError::ExistingValue(proof_item.to_vec())) - } - } -} diff --git a/crates/anvil/tests/it/proof/mod.rs b/crates/anvil/tests/it/proof/mod.rs deleted file mode 100644 index 2e6278bb18266..0000000000000 --- a/crates/anvil/tests/it/proof/mod.rs +++ /dev/null @@ -1,81 +0,0 @@ -//! tests for `eth_getProof` - -use crate::proof::eip1186::verify_proof; -use anvil::{spawn, NodeConfig}; -use anvil_core::eth::{ - proof::{AccountProof, BasicAccount}, - trie::ExtensionLayout, -}; -use ethers::{ - abi::ethereum_types::BigEndianHash, - types::{Address, H256, U256}, - utils::{keccak256, rlp}, -}; -use foundry_common::types::ToEthers; -use foundry_evm::revm::primitives::KECCAK_EMPTY; - -mod eip1186; - -#[tokio::test(flavor = "multi_thread")] -async fn can_get_proof() { - let (api, _handle) = spawn(NodeConfig::test()).await; - - let acc: Address = "0xaaaf5374fce5edbc8e2a8697c15331677e6ebaaa".parse().unwrap(); - - let key = U256::zero(); - let value = U256::one(); - - api.anvil_set_storage_at(acc, key, H256::from_uint(&value)).await.unwrap(); - - let proof: AccountProof = api.get_proof(acc, vec![H256::from_uint(&key)], None).await.unwrap(); - - let account = BasicAccount { - nonce: 0.into(), - balance: 0.into(), - storage_root: proof.storage_hash, - code_hash: KECCAK_EMPTY.to_ethers(), - }; - - let rlp_account = rlp::encode(&account); - - let root: H256 = api.state_root().await.unwrap(); - let acc_proof: Vec> = proof - .account_proof - .into_iter() - .map(|node| rlp::decode::>(&node).unwrap()) - .collect(); - - verify_proof::( - &root.0, - &acc_proof, - &keccak256(acc.as_bytes())[..], - Some(rlp_account.as_ref()), - ) - .unwrap(); - - assert_eq!(proof.storage_proof.len(), 1); - let expected_value = rlp::encode(&value); - let proof = proof.storage_proof[0].clone(); - let storage_proof: Vec> = - proof.proof.into_iter().map(|node| rlp::decode::>(&node).unwrap()).collect(); - let key = H256::from(keccak256(proof.key.as_bytes())); - verify_proof::( - &account.storage_root.0, - &storage_proof, - key.as_bytes(), - Some(expected_value.as_ref()), - ) - .unwrap(); -} - -#[tokio::test(flavor = "multi_thread")] -async fn can_get_random_account_proofs() { - let (api, _handle) = spawn(NodeConfig::test()).await; - - for acc in std::iter::repeat_with(Address::random).take(10) { - let _ = api - .get_proof(acc, Vec::new(), None) - .await - .unwrap_or_else(|_| panic!("Failed to get proof for {acc:?}")); - } -} diff --git a/crates/anvil/tests/it/pubsub.rs b/crates/anvil/tests/it/pubsub.rs index 22df53c968259..f29dbb81f2117 100644 --- a/crates/anvil/tests/it/pubsub.rs +++ b/crates/anvil/tests/it/pubsub.rs @@ -1,117 +1,124 @@ //! tests for subscriptions +use crate::utils::{connect_pubsub, connect_pubsub_with_signer}; +use alloy_network::{EthereumSigner, TransactionBuilder}; +use alloy_primitives::{Address, U256}; +use alloy_provider::Provider; +use alloy_pubsub::Subscription; +use alloy_rpc_types::{Block as AlloyBlock, Filter, TransactionRequest, WithOtherFields}; +use alloy_sol_types::sol; use anvil::{spawn, NodeConfig}; -use ethers::{ - contract::abigen, - middleware::SignerMiddleware, - prelude::{Middleware, Ws}, - providers::{JsonRpcClient, PubsubClient}, - signers::Signer, - types::{Address, Block, Filter, TransactionRequest, TxHash, ValueOrArray, U256}, -}; use futures::StreamExt; -use std::sync::Arc; #[tokio::test(flavor = "multi_thread")] async fn test_sub_new_heads() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); + let provider = connect_pubsub(&handle.ws_endpoint()).await; let blocks = provider.subscribe_blocks().await.unwrap(); // mine a block every 1 seconds api.anvil_set_interval_mining(1).unwrap(); - let blocks = blocks.take(3).collect::>().await; - let block_numbers = blocks.into_iter().map(|b| b.number.unwrap().as_u64()).collect::>(); + let blocks = blocks.into_stream().take(3).collect::>().await; + let block_numbers = blocks.into_iter().map(|b| b.header.number.unwrap()).collect::>(); assert_eq!(block_numbers, vec![1, 2, 3]); } +sol!( + #[sol(rpc)] + EmitLogs, + "test-data/emit_logs.json" +); +// FIXME: Use .legacy() in tx when implemented in alloy #[tokio::test(flavor = "multi_thread")] async fn test_sub_logs_legacy() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let provider = connect_pubsub(&handle.ws_endpoint()).await; let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().legacy().send().await.unwrap(); + let contract_addr = EmitLogs::deploy_builder(provider.clone(), msg.clone()) + .from(wallet.address()) + .deploy() + .await + .unwrap(); + let contract = EmitLogs::new(contract_addr, provider.clone()); - let val = contract.get_value().call().await.unwrap(); - assert_eq!(val, msg); + let val = contract.getValue().call().await.unwrap(); + assert_eq!(val._0, msg); // subscribe to events from the contract - let filter = Filter::new().address(ValueOrArray::Value(contract.address())); - let mut logs_sub = client.subscribe_logs(&filter).await.unwrap(); + let filter = Filter::new().address(contract.address().to_owned()); + let logs_sub = provider.subscribe_logs(&filter).await.unwrap(); // send a tx triggering an event + // FIXME: Use .legacy() in tx let receipt = contract - .set_value("Next Message".to_string()) - .legacy() + .setValue("Next Message".to_string()) .send() .await .unwrap() + .get_receipt() .await - .unwrap() .unwrap(); + let mut logs_sub = logs_sub.into_stream(); // get the emitted event let log = logs_sub.next().await.unwrap(); // ensure the log in the receipt is the same as received via subscription stream - assert_eq!(receipt.logs[0], log); + assert_eq!(receipt.inner.logs()[0], log); } #[tokio::test(flavor = "multi_thread")] async fn test_sub_logs() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let provider = connect_pubsub(&handle.ws_endpoint()).await; let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().send().await.unwrap(); + let contract_addr = EmitLogs::deploy_builder(provider.clone(), msg.clone()) + .from(wallet.address()) + .deploy() + .await + .unwrap(); + let contract = EmitLogs::new(contract_addr, provider.clone()); - let val = contract.get_value().call().await.unwrap(); - assert_eq!(val, msg); + let val = contract.getValue().call().await.unwrap(); + assert_eq!(val._0, msg); // subscribe to events from the contract - let filter = Filter::new().address(ValueOrArray::Value(contract.address())); - let mut logs_sub = client.subscribe_logs(&filter).await.unwrap(); + let filter = Filter::new().address(contract.address().to_owned()); + let logs_sub = provider.subscribe_logs(&filter).await.unwrap(); // send a tx triggering an event let receipt = contract - .set_value("Next Message".to_string()) + .setValue("Next Message".to_string()) .send() .await .unwrap() + .get_receipt() .await - .unwrap() .unwrap(); + let mut logs_sub = logs_sub.into_stream(); // get the emitted event let log = logs_sub.next().await.unwrap(); // ensure the log in the receipt is the same as received via subscription stream - assert_eq!(receipt.logs[0], log); + assert_eq!(receipt.inner.logs()[0], log); } #[tokio::test(flavor = "multi_thread")] async fn test_sub_logs_impersonated() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); + let wallet = handle.dev_wallets().next().unwrap(); + let provider = + connect_pubsub_with_signer(&handle.ws_endpoint(), EthereumSigner::from(wallet.clone())) + .await; // impersonate account let impersonate = Address::random(); @@ -119,155 +126,147 @@ async fn test_sub_logs_impersonated() { api.anvil_set_balance(impersonate, funding).await.unwrap(); api.anvil_impersonate_account(impersonate).await.unwrap(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().send().await.unwrap(); + let contract = EmitLogs::deploy(provider.clone(), msg.clone()).await.unwrap(); - let _val = contract.get_value().call().await.unwrap(); + let _val = contract.getValue().call().await.unwrap(); // subscribe to events from the impersonated account - let filter = Filter::new().address(ValueOrArray::Value(contract.address())); - let mut logs_sub = client.subscribe_logs(&filter).await.unwrap(); + let filter = Filter::new().address(contract.address().to_owned()); + let logs_sub = provider.subscribe_logs(&filter).await.unwrap(); // send a tx triggering an event - let data = contract.set_value("Next Message".to_string()).tx.data().cloned().unwrap(); + let data = contract.setValue("Next Message".to_string()); + let data = data.calldata().clone(); - let tx = TransactionRequest::new().from(impersonate).to(contract.address()).data(data); + let tx = + TransactionRequest::default().from(impersonate).to(*contract.address()).with_input(data); + let tx = WithOtherFields::new(tx); let provider = handle.http_provider(); - let receipt = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let mut logs_sub = logs_sub.into_stream(); // get the emitted event let log = logs_sub.next().await.unwrap(); // ensure the log in the receipt is the same as received via subscription stream - assert_eq!(receipt.logs[0], log); + assert_eq!(receipt.inner.inner.logs()[0], log); } +// FIXME: Use legacy() in tx when implemented in alloy #[tokio::test(flavor = "multi_thread")] async fn test_filters_legacy() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); + let provider = + connect_pubsub_with_signer(&handle.ws_endpoint(), EthereumSigner::from(wallet.clone())) + .await; + let from = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().legacy().send().await.unwrap(); - let filter = contract.value_changed_filter(); - let mut stream = filter.stream().await.unwrap(); + // FIXME: Use legacy() in tx when implemented in alloy + let contract = EmitLogs::deploy(provider.clone(), msg.clone()).await.unwrap(); + + let stream = contract.ValueChanged_filter().subscribe().await.unwrap(); // send a tx triggering an event + // FIXME: Use legacy() in tx when implemented in alloy let _receipt = contract - .set_value("Next Message".to_string()) - .legacy() + .setValue("Next Message".to_string()) .send() .await .unwrap() + .get_receipt() .await - .unwrap() .unwrap(); + let mut log = stream.into_stream(); // get the emitted event - let log = stream.next().await.unwrap().unwrap(); - assert_eq!( - log, - ValueChangedFilter { - author: from, - old_value: "First Message".to_string(), - new_value: "Next Message".to_string(), - }, - ); + let (value_changed, _log) = log.next().await.unwrap().unwrap(); + + assert_eq!(value_changed.author, from); + assert_eq!(value_changed.oldValue, "First Message".to_string()); + assert_eq!(value_changed.newValue, "Next Message".to_string()); } #[tokio::test(flavor = "multi_thread")] async fn test_filters() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); + let provider = + connect_pubsub_with_signer(&handle.ws_endpoint(), EthereumSigner::from(wallet.clone())) + .await; + let from = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().send().await.unwrap(); - let filter = contract.value_changed_filter(); - let mut stream = filter.stream().await.unwrap(); + let contract = EmitLogs::deploy(provider.clone(), msg.clone()).await.unwrap(); + + let stream = contract.ValueChanged_filter().subscribe().await.unwrap(); // send a tx triggering an event let _receipt = contract - .set_value("Next Message".to_string()) + .setValue("Next Message".to_string()) .send() .await .unwrap() + .get_receipt() .await - .unwrap() .unwrap(); + let mut log = stream.into_stream(); // get the emitted event - let log = stream.next().await.unwrap().unwrap(); - assert_eq!( - log, - ValueChangedFilter { - author: from, - old_value: "First Message".to_string(), - new_value: "Next Message".to_string(), - }, - ); + let (value_changed, _log) = log.next().await.unwrap().unwrap(); + + assert_eq!(value_changed.author, from); + assert_eq!(value_changed.oldValue, "First Message".to_string()); + assert_eq!(value_changed.newValue, "Next Message".to_string()); } #[tokio::test(flavor = "multi_thread")] async fn test_subscriptions() { let (_api, handle) = spawn(NodeConfig::test().with_blocktime(Some(std::time::Duration::from_secs(1)))).await; - let ws = Ws::connect(handle.ws_endpoint()).await.unwrap(); - - // Subscribing requires sending the sub request and then subscribing to - // the returned sub_id - let sub_id: U256 = ws.request("eth_subscribe", ["newHeads"]).await.unwrap(); - let mut stream = ws.subscribe(sub_id).unwrap(); - - let mut blocks = Vec::new(); - for _ in 0..3 { - let item = stream.next().await.unwrap(); - let block: Block = serde_json::from_str(item.get()).unwrap(); - blocks.push(block.number.unwrap_or_default().as_u64()); - } + let provider = connect_pubsub(&handle.ws_endpoint()).await; + let sub_id: U256 = provider.raw_request("eth_subscribe".into(), ["newHeads"]).await.unwrap(); + let stream: Subscription = provider.get_subscription(sub_id).await.unwrap(); + let blocks = stream + .into_stream() + .take(3) + .collect::>() + .await + .into_iter() + .map(|b| b.header.number.unwrap()) + .collect::>(); assert_eq!(blocks, vec![1, 2, 3]) } +// TODO: Fix this, num > 17 breaks the test due to poller channel_size defaults to 16. Recv channel +// lags behind. #[tokio::test(flavor = "multi_thread")] async fn test_sub_new_heads_fast() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); + let provider = connect_pubsub(&handle.ws_endpoint()).await; let blocks = provider.subscribe_blocks().await.unwrap(); + let mut blocks = blocks.into_stream(); + + let num = 1000u64; + + let mut block_numbers = Vec::new(); + for _ in 0..num { + api.mine_one().await; + let block_number = blocks.next().await.unwrap().header.number.unwrap(); + block_numbers.push(block_number); + } - let num = 1_000u64; - let mine_api = api.clone(); - tokio::task::spawn(async move { - for _ in 0..num { - mine_api.mine_one().await; - } - }); - - // collect all the blocks - let blocks = blocks.take(num as usize).collect::>().await; - let block_numbers = blocks.into_iter().map(|b| b.number.unwrap().as_u64()).collect::>(); + println!("Collected {} blocks", block_numbers.len()); let numbers = (1..=num).collect::>(); assert_eq!(block_numbers, numbers); diff --git a/crates/anvil/tests/it/revert.rs b/crates/anvil/tests/it/revert.rs index 45da5eb2ef63e..5cb9b2fedc81a 100644 --- a/crates/anvil/tests/it/revert.rs +++ b/crates/anvil/tests/it/revert.rs @@ -1,13 +1,14 @@ -use crate::abi::VENDING_MACHINE_CONTRACT; -use anvil::{spawn, NodeConfig}; -use ethers::{ - contract::{ContractFactory, ContractInstance}, - middleware::SignerMiddleware, - types::U256, - utils::WEI_IN_ETHER, +use crate::{ + abi::{VendingMachine, VENDING_MACHINE_CONTRACT}, + utils::ws_provider_with_signer, }; -use ethers_solc::{project_util::TempProject, Artifact}; -use std::sync::Arc; +use alloy_network::EthereumSigner; +use alloy_primitives::{TxKind, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{TransactionRequest, WithOtherFields}; +use alloy_sol_types::sol; +use anvil::{spawn, NodeConfig}; +use foundry_compilers::{project_util::TempProject, Artifact}; #[tokio::test(flavor = "multi_thread")] async fn test_deploy_reverting() { @@ -28,20 +29,25 @@ contract Contract { let mut compiled = prj.compile().unwrap(); assert!(!compiled.has_compiler_errors()); let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + let bytecode = contract.into_bytecode_bytes().unwrap(); let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let signer: EthereumSigner = wallet.clone().into(); + let sender = wallet.address(); - let factory = ContractFactory::new(abi.unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await; - assert!(contract.is_err()); + let provider = ws_provider_with_signer(&handle.ws_endpoint(), signer); // should catch the revert during estimation which results in an err - let err = contract.unwrap_err(); + let err = provider + .send_transaction(WithOtherFields::new(TransactionRequest { + from: Some(sender), + to: Some(TxKind::Create), + input: bytecode.into(), + ..Default::default() + })) + .await + .unwrap_err(); assert!(err.to_string().contains("execution reverted")); } @@ -55,7 +61,7 @@ pragma solidity 0.8.13; contract Contract { address owner; constructor() public { - owner = msg.sender; + owner = address(1); } modifier onlyOwner() { require(msg.sender == owner, "!authorized"); @@ -69,31 +75,45 @@ contract Contract { ) .unwrap(); + sol!( + #[sol(rpc)] + contract Contract { + function getSecret() external; + } + ); + let mut compiled = prj.compile().unwrap(); assert!(!compiled.has_compiler_errors()); let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + let bytecode = contract.into_bytecode_bytes().unwrap(); let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); - let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); + let sender = wallet.address(); - let resp = contract.method::<_, U256>("getSecret", ()).unwrap().call().await; + let provider = ws_provider_with_signer(&handle.ws_endpoint(), signer); - let err = resp.unwrap_err(); - let msg = err.to_string(); - assert!(msg.contains("execution reverted: !authorized")); + // deploy successfully + provider + .send_transaction(WithOtherFields::new(TransactionRequest { + from: Some(sender), + to: Some(TxKind::Create), + input: bytecode.into(), + ..Default::default() + })) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let contract_address = sender.create(0); + let contract = Contract::new(contract_address, &provider); + + let res = contract.getSecret().send().await.unwrap_err(); + + let msg = res.to_string(); + assert!(msg.contains("execution reverted: revert: !authorized")); } #[tokio::test(flavor = "multi_thread")] @@ -104,35 +124,50 @@ async fn test_solc_revert_example() { let mut compiled = prj.compile().unwrap(); assert!(!compiled.has_compiler_errors()); let contract = compiled.remove_first("VendingMachine").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + let bytecode = contract.into_bytecode_bytes().unwrap(); let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); - let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); - - for fun in ["buyRevert", "buyRequire"] { - let resp = contract.method::<_, ()>(fun, U256::zero()).unwrap().call().await; - resp.unwrap(); + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); + let sender = wallet.address(); - let ten = WEI_IN_ETHER.saturating_mul(10u64.into()); - let call = contract.method::<_, ()>(fun, ten).unwrap().value(ten); + let provider = ws_provider_with_signer(&handle.ws_endpoint(), signer); - let resp = call.clone().call().await; - let err = resp.unwrap_err().to_string(); - assert!(err.contains("execution reverted: Not enough Ether provided.")); - assert!(err.contains("code: 3")); - } + // deploy successfully + provider + .send_transaction(WithOtherFields::new(TransactionRequest { + from: Some(sender), + to: Some(TxKind::Create), + input: bytecode.into(), + ..Default::default() + })) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let contract_address = sender.create(0); + let contract = VendingMachine::new(contract_address, &provider); + + let res = contract + .buyRevert(U256::from(100)) + .value(U256::from(1)) + .from(sender) + .send() + .await + .unwrap_err(); + let msg = res.to_string(); + assert!(msg.contains("execution reverted: revert: Not enough Ether provided.")); + + let res = contract + .buyRequire(U256::from(100)) + .value(U256::from(1)) + .from(sender) + .send() + .await + .unwrap_err(); + let msg = res.to_string(); + assert!(msg.contains("execution reverted: revert: Not enough Ether provided.")); } // @@ -155,31 +190,45 @@ contract Contract { ) .unwrap(); + sol!( + #[sol(rpc)] + contract Contract { + function setNumber(uint256 num) external; + } + ); + let mut compiled = prj.compile().unwrap(); assert!(!compiled.has_compiler_errors()); let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + let bytecode = contract.into_bytecode_bytes().unwrap(); let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); - let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); + let sender = wallet.address(); - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); - let call = contract.method::<_, ()>("setNumber", U256::zero()).unwrap(); - let resp = call.send().await; + let provider = ws_provider_with_signer(&handle.ws_endpoint(), signer); - let err = resp.unwrap_err(); - let msg = err.to_string(); - assert!(msg.contains("execution reverted: RevertStringFooBar")); + // deploy successfully + provider + .send_transaction(WithOtherFields::new(TransactionRequest { + from: Some(sender), + to: Some(TxKind::Create), + input: bytecode.into(), + ..Default::default() + })) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let contract_address = sender.create(0); + let contract = Contract::new(contract_address, &provider); + + let res = contract.setNumber(U256::from(0)).send().await.unwrap_err(); + + let msg = res.to_string(); + assert!(msg.contains("execution reverted: revert: RevertStringFooBar")); } #[tokio::test(flavor = "multi_thread")] @@ -201,25 +250,41 @@ contract Contract { ) .unwrap(); + sol!( + #[sol(rpc)] + contract Contract { + function revertAddress() external; + } + ); + let mut compiled = prj.compile().unwrap(); assert!(!compiled.has_compiler_errors()); let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + let bytecode = contract.into_bytecode_bytes().unwrap(); let (_api, handle) = spawn(NodeConfig::test()).await; + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumSigner = wallet.clone().into(); + let sender = wallet.address(); - let provider = handle.ws_provider(); - let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); + let provider = ws_provider_with_signer(&handle.ws_endpoint(), signer); // deploy successfully - let factory = - ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), Arc::clone(&client)); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let call = contract.method::<_, ()>("revertAddress", ()).unwrap().gas(150000); - - let resp = call.call().await; - - let _ = resp.unwrap_err(); + provider + .send_transaction(WithOtherFields::new(TransactionRequest { + from: Some(sender), + to: Some(TxKind::Create), + input: bytecode.into(), + ..Default::default() + })) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let contract_address = sender.create(0); + let contract = Contract::new(contract_address, &provider); + + let res = contract.revertAddress().send().await.unwrap_err(); + assert!(res.to_string().contains("execution reverted")); } diff --git a/crates/anvil/tests/it/sign.rs b/crates/anvil/tests/it/sign.rs index 368a34da17633..1417fec52d4db 100644 --- a/crates/anvil/tests/it/sign.rs +++ b/crates/anvil/tests/it/sign.rs @@ -1,9 +1,11 @@ +use crate::utils::http_provider_with_signer; +use alloy_dyn_abi::TypedData; +use alloy_network::EthereumSigner; +use alloy_primitives::{Address, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{TransactionRequest, WithOtherFields}; +use alloy_signer::Signer; use anvil::{spawn, NodeConfig}; -use ethers::{ - prelude::{Middleware, SignerMiddleware}, - signers::Signer, - types::{transaction::eip712::TypedData, Address, Chain, TransactionRequest}, -}; #[tokio::test(flavor = "multi_thread")] async fn can_sign_typed_data() { @@ -281,28 +283,51 @@ async fn can_sign_typed_data_os() { } #[tokio::test(flavor = "multi_thread")] -async fn rejects_different_chain_id() { - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); +async fn can_sign_transaction() { + let (api, handle) = spawn(NodeConfig::test()).await; - let wallet = handle.dev_wallets().next().unwrap(); - let client = SignerMiddleware::new(provider, wallet.with_chain_id(Chain::Mainnet)); + let accounts = handle.dev_wallets().collect::>(); + let from = accounts[0].address(); + let to = accounts[1].address(); + + // craft the tx + // specify the `from` field so that the client knows which account to use + let tx = TransactionRequest::default() + .nonce(10) + .max_fee_per_gas(100) + .max_priority_fee_per_gas(101) + .to(to) + .value(U256::from(1001u64)) + .from(from); + let tx = WithOtherFields::new(tx); + // sign it via the eth_signTransaction API + let signed_tx = api.sign_transaction(tx).await.unwrap(); + + assert_eq!(signed_tx, "0x02f868827a690a65648252089470997970c51812dc3a010c7d01b50e0d17dc79c88203e980c082f4f6a0e4de88aefcf87ccb04466e60de66a83192e46aa26177d5ea35efbfd43fd0ecdca00e3148e0e8e0b9a6f9b329efd6e30c4a461920f3a27497be3dbefaba996601da"); +} - let tx = TransactionRequest::new().to(Address::random()).value(100u64); +#[tokio::test(flavor = "multi_thread")] +async fn rejects_different_chain_id() { + let (_api, handle) = spawn(NodeConfig::test()).await; + let wallet = handle.dev_wallets().next().unwrap().with_chain_id(Some(1)); + let provider = http_provider_with_signer(&handle.http_endpoint(), EthereumSigner::from(wallet)); - let res = client.send_transaction(tx, None).await; + let tx = TransactionRequest::default().to(Address::random()).value(U256::from(100)); + let tx = WithOtherFields::new(tx); + let res = provider.send_transaction(tx).await; let err = res.unwrap_err(); - assert!(err.to_string().contains("signed for another chain")); + assert!(err.to_string().contains("does not match the signer's"), "{}", err.to_string()); } #[tokio::test(flavor = "multi_thread")] async fn rejects_invalid_chain_id() { let (_api, handle) = spawn(NodeConfig::test()).await; - let wallet = handle.dev_wallets().next().unwrap().with_chain_id(99u64); - let provider = handle.http_provider(); - let client = SignerMiddleware::new(provider, wallet); - let tx = TransactionRequest::new().to(Address::random()).value(100u64); - let res = client.send_transaction(tx, None).await; + let wallet = handle.dev_wallets().next().unwrap(); + let wallet = wallet.with_chain_id(Some(99u64)); + let provider = http_provider_with_signer(&handle.http_endpoint(), EthereumSigner::from(wallet)); + let tx = TransactionRequest::default().to(Address::random()).value(U256::from(100u64)); + let tx = WithOtherFields::new(tx); + let res = provider.send_transaction(tx).await; let _err = res.unwrap_err(); } diff --git a/crates/anvil/tests/it/state.rs b/crates/anvil/tests/it/state.rs new file mode 100644 index 0000000000000..7e822ee23b5ac --- /dev/null +++ b/crates/anvil/tests/it/state.rs @@ -0,0 +1,23 @@ +//! general eth api tests + +use anvil::{spawn, NodeConfig}; + +#[tokio::test(flavor = "multi_thread")] +async fn can_load_state() { + let tmp = tempfile::tempdir().unwrap(); + let state_file = tmp.path().join("state.json"); + + let (api, _handle) = spawn(NodeConfig::test()).await; + + api.mine_one().await; + + let num = api.block_number().unwrap(); + + let state = api.serialized_state().await.unwrap(); + foundry_common::fs::write_json_file(&state_file, &state).unwrap(); + + let (api, _handle) = spawn(NodeConfig::test().with_init_state_path(state_file)).await; + + let num2 = api.block_number().unwrap(); + assert_eq!(num, num2); +} diff --git a/crates/anvil/tests/it/traces.rs b/crates/anvil/tests/it/traces.rs index 8a7d1a66ba812..2774f7e144528 100644 --- a/crates/anvil/tests/it/traces.rs +++ b/crates/anvil/tests/it/traces.rs @@ -1,36 +1,35 @@ -use crate::fork::fork_config; -use anvil::{spawn, NodeConfig}; -use ethers::{ - contract::ContractInstance, - prelude::{ - Action, ContractFactory, GethTrace, GethTraceFrame, Middleware, Signer, SignerMiddleware, - TransactionRequest, - }, - types::{ActionType, Address, GethDebugTracingCallOptions, Trace}, - utils::hex, +use crate::{fork::fork_config, utils::http_provider_with_signer}; +use alloy_network::{EthereumSigner, TransactionBuilder}; +use alloy_primitives::{hex, Address, Bytes, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockNumberOrTag, TransactionRequest, WithOtherFields}; +use alloy_rpc_types_trace::{ + geth::{GethDebugTracingCallOptions, GethTrace}, + parity::{Action, LocalizedTransactionTrace}, }; -use ethers_solc::{project_util::TempProject, Artifact}; -use std::sync::Arc; +use alloy_sol_types::sol; +use anvil::{spawn, Hardfork, NodeConfig}; #[tokio::test(flavor = "multi_thread")] async fn test_get_transfer_parity_traces() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); + let provider = handle.ws_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let amount = handle.genesis_balance().checked_div(2u64.into()).unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); // specify the `from` field so that the client knows which account to use - let tx = TransactionRequest::new().to(to).value(amount).from(from); + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); // broadcast it via the eth_sendTransaction API - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let traces = provider.trace_transaction(tx.transaction_hash).await.unwrap(); assert!(!traces.is_empty()); - match traces[0].action { + match traces[0].trace.action { Action::Call(ref call) => { assert_eq!(call.from, from); assert_eq!(call.to, to); @@ -46,109 +45,89 @@ async fn test_get_transfer_parity_traces() { assert_eq!(traces, block_traces); } -#[tokio::test(flavor = "multi_thread")] -async fn test_parity_suicide_trace() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r" -pragma solidity 0.8.13; -contract Contract { - address payable private owner; - constructor() public { - owner = payable(msg.sender); - } - function goodbye() public { - selfdestruct(owner); +sol!( + #[sol(rpc, bytecode = "0x6080604052348015600f57600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060a48061005e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806375fc8e3c14602d575b600080fd5b60336035565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212205006867290df97c54f2df1cb94fc081197ab670e2adf5353071d2ecce1d694b864736f6c634300080d0033")] + contract SuicideContract { + address payable private owner; + constructor() public { + owner = payable(msg.sender); + } + function goodbye() public { + selfdestruct(owner); + } } -} -", - ) - .unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); +); - let (_api, handle) = spawn(NodeConfig::test()).await; +#[tokio::test(flavor = "multi_thread")] +async fn test_parity_suicide_trace() { + let (_api, handle) = spawn(NodeConfig::test().with_hardfork(Some(Hardfork::Shanghai))).await; let provider = handle.ws_provider(); let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); + let owner = wallets[0].address(); + let destructor = wallets[1].address(); // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); - let call = contract.method::<_, ()>("goodbye", ()).unwrap(); - let tx = call.send().await.unwrap().await.unwrap().unwrap(); + let contract_addr = + SuicideContract::deploy_builder(provider.clone()).from(owner).deploy().await.unwrap(); + let contract = SuicideContract::new(contract_addr, provider.clone()); + let call = contract.goodbye().from(destructor); + let call = call.send().await.unwrap(); + let tx = call.get_receipt().await.unwrap(); let traces = handle.http_provider().trace_transaction(tx.transaction_hash).await.unwrap(); assert!(!traces.is_empty()); - assert_eq!(traces[0].action_type, ActionType::Suicide); + assert!(traces[1].trace.action.is_selfdestruct()); } -#[tokio::test(flavor = "multi_thread")] -async fn test_transfer_debug_trace_call() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r" -pragma solidity 0.8.13; -contract Contract { - address payable private owner; - constructor() public { - owner = payable(msg.sender); - } - function goodbye() public { - selfdestruct(owner); +sol!( + #[sol(rpc, bytecode = "0x6080604052348015600f57600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060a48061005e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806375fc8e3c14602d575b600080fd5b60336035565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212205006867290df97c54f2df1cb94fc081197ab670e2adf5353071d2ecce1d694b864736f6c634300080d0033")] + contract DebugTraceContract { + address payable private owner; + constructor() public { + owner = payable(msg.sender); + } + function goodbye() public { + selfdestruct(owner); + } } -} -", - ) - .unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); +); +#[tokio::test(flavor = "multi_thread")] +async fn test_transfer_debug_trace_call() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); + let deployer: EthereumSigner = wallets[0].clone().into(); + let provider = http_provider_with_signer(&handle.http_endpoint(), deployer); - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); + let contract_addr = DebugTraceContract::deploy_builder(provider.clone()) + .from(wallets[0].clone().address()) + .deploy() + .await + .unwrap(); + + let caller: EthereumSigner = wallets[1].clone().into(); + let caller_provider = http_provider_with_signer(&handle.http_endpoint(), caller); + let contract = DebugTraceContract::new(contract_addr, caller_provider); - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); - let call = contract.method::<_, ()>("goodbye", ()).unwrap(); + let call = contract.goodbye().from(wallets[1].address()); + let calldata = call.calldata().to_owned(); + + let tx = TransactionRequest::default() + .from(wallets[1].address()) + .to(*contract.address()) + .with_input(calldata); let traces = handle .http_provider() - .debug_trace_call(call.tx, None, GethDebugTracingCallOptions::default()) + .debug_trace_call(tx, BlockNumberOrTag::Latest, GethDebugTracingCallOptions::default()) .await .unwrap(); + match traces { - GethTrace::Known(traces) => match traces { - GethTraceFrame::Default(traces) => { - assert!(!traces.failed); - } - _ => { - unreachable!() - } - }, - GethTrace::Unknown(_) => { + GethTrace::Default(default_frame) => { + assert!(!default_frame.failed); + } + _ => { unreachable!() } } @@ -164,15 +143,20 @@ async fn test_trace_address_fork() { let from: Address = "0x2e4777139254ff76db957e284b186a4507ff8c67".parse().unwrap(); let to: Address = "0xe2f2a5c287993345a840db3b0845fbc70f5935a5".parse().unwrap(); - let tx = TransactionRequest::new().to(to).from(from).data(input).gas(300_000); + let tx = TransactionRequest::default() + .to(to) + .from(from) + .with_input::(input.into()) + .with_gas_limit(300_000); + let tx = WithOtherFields::new(tx); api.anvil_impersonate_account(from).await.unwrap(); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let traces = provider.trace_transaction(tx.transaction_hash).await.unwrap(); assert!(!traces.is_empty()); - match traces[0].action { + match traces[0].trace.action { Action::Call(ref call) => { assert_eq!(call.from, from); assert_eq!(call.to, to); @@ -330,13 +314,13 @@ async fn test_trace_address_fork() { } ]); - let expected_traces: Vec = serde_json::from_value(json).unwrap(); + let expected_traces: Vec = serde_json::from_value(json).unwrap(); // test matching traceAddress traces.into_iter().zip(expected_traces).for_each(|(a, b)| { - assert_eq!(a.trace_address, b.trace_address); - assert_eq!(a.subtraces, b.subtraces); - match (a.action, b.action) { + assert_eq!(a.trace.trace_address, b.trace.trace_address); + assert_eq!(a.trace.subtraces, b.trace.subtraces); + match (a.trace.action, b.trace.action) { (Action::Call(a), Action::Call(b)) => { assert_eq!(a.from, b.from); assert_eq!(a.to, b.to); @@ -357,17 +341,23 @@ async fn test_trace_address_fork2() { let from: Address = "0xa009fa1ac416ec02f6f902a3a4a584b092ae6123".parse().unwrap(); let to: Address = "0x99999999d116ffa7d76590de2f427d8e15aeb0b8".parse().unwrap(); - let tx = TransactionRequest::new().to(to).from(from).data(input).gas(350_000); + let tx = TransactionRequest::default() + .to(to) + .from(from) + .with_input::(input.into()) + .with_gas_limit(350_000); + let tx = WithOtherFields::new(tx); api.anvil_impersonate_account(from).await.unwrap(); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let status = tx.inner.inner.inner.receipt.status; + assert!(status); let traces = provider.trace_transaction(tx.transaction_hash).await.unwrap(); assert!(!traces.is_empty()); - match traces[0].action { + match traces[0].trace.action { Action::Call(ref call) => { assert_eq!(call.from, from); assert_eq!(call.to, to); @@ -588,13 +578,13 @@ async fn test_trace_address_fork2() { } ]); - let expected_traces: Vec = serde_json::from_value(json).unwrap(); + let expected_traces: Vec = serde_json::from_value(json).unwrap(); // test matching traceAddress traces.into_iter().zip(expected_traces).for_each(|(a, b)| { - assert_eq!(a.trace_address, b.trace_address); - assert_eq!(a.subtraces, b.subtraces); - match (a.action, b.action) { + assert_eq!(a.trace.trace_address, b.trace.trace_address); + assert_eq!(a.trace.subtraces, b.trace.subtraces); + match (a.trace.action, b.trace.action) { (Action::Call(a), Action::Call(b)) => { assert_eq!(a.from, b.from); assert_eq!(a.to, b.to); diff --git a/crates/anvil/tests/it/transaction.rs b/crates/anvil/tests/it/transaction.rs index 7cb5c4bc6e5a8..7e088e0b5cc79 100644 --- a/crates/anvil/tests/it/transaction.rs +++ b/crates/anvil/tests/it/transaction.rs @@ -1,18 +1,19 @@ -use crate::abi::*; -use anvil::{spawn, Hardfork, NodeConfig}; -use ethers::{ - abi::ethereum_types::BigEndianHash, - prelude::{ - signer::SignerMiddlewareError, BlockId, Middleware, Signer, SignerMiddleware, - TransactionRequest, - }, - types::{ - transaction::eip2930::{AccessList, AccessListItem}, - Address, BlockNumber, Transaction, TransactionReceipt, H256, U256, - }, +use crate::{ + abi::{Greeter, MulticallContract, SimpleStorage}, + utils::http_provider_with_signer, +}; +use alloy_network::{EthereumSigner, TransactionBuilder}; +use alloy_primitives::{Address, Bytes, FixedBytes, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{ + state::{AccountOverride, StateOverride}, + AccessList, AccessListItem, BlockId, BlockNumberOrTag, BlockTransactions, TransactionRequest, + WithOtherFields, }; +use anvil::{spawn, Hardfork, NodeConfig}; +use eyre::Ok; use futures::{future::join_all, FutureExt, StreamExt}; -use std::{collections::HashSet, sync::Arc, time::Duration}; +use std::{collections::HashSet, str::FromStr, time::Duration}; use tokio::time::timeout; #[tokio::test(flavor = "multi_thread")] @@ -20,32 +21,34 @@ async fn can_transfer_eth() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); - assert!(nonce.is_zero()); + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); + assert!(nonce == 0); - let balance_before = provider.get_balance(to, None).await.unwrap(); + let balance_before = provider.get_balance(to, BlockId::latest()).await.unwrap(); - let amount = handle.genesis_balance().checked_div(2u64.into()).unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); // craft the tx // specify the `from` field so that the client knows which account to use - let tx = TransactionRequest::new().to(to).value(amount).from(from); - + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); // broadcast it via the eth_sendTransaction API - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = provider.send_transaction(tx).await.unwrap(); + + let tx = tx.get_receipt().await.unwrap(); - assert_eq!(tx.block_number, Some(1u64.into())); - assert_eq!(tx.transaction_index, 0u64.into()); + assert_eq!(tx.block_number, Some(1)); + assert_eq!(tx.transaction_index, Some(0)); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); - assert_eq!(nonce, 1u64.into()); + assert_eq!(nonce, 1); - let to_balance = provider.get_balance(to, None).await.unwrap(); + let to_balance = provider.get_balance(to, BlockId::latest()).await.unwrap(); assert_eq!(balance_before.saturating_add(amount), to_balance); } @@ -58,30 +61,38 @@ async fn can_order_transactions() { // disable automine api.anvil_set_auto_mine(false).await.unwrap(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let amount = handle.genesis_balance().checked_div(2u64.into()).unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); // craft the tx with lower price - let tx = TransactionRequest::new().to(to).from(from).value(amount).gas_price(gas_price); - let tx_lower = provider.send_transaction(tx, None).await.unwrap(); + let mut tx = TransactionRequest::default().to(to).from(from).value(amount); + + tx.set_gas_price(gas_price); + let tx = WithOtherFields::new(tx); + let tx_lower = provider.send_transaction(tx).await.unwrap(); // craft the tx with higher price - let tx = TransactionRequest::new().to(from).from(to).value(amount).gas_price(gas_price + 1); - let tx_higher = provider.send_transaction(tx, None).await.unwrap(); + let mut tx = TransactionRequest::default().to(from).from(to).value(amount); + + tx.set_gas_price(gas_price + 1); + let tx = WithOtherFields::new(tx); + let tx_higher = provider.send_transaction(tx).await.unwrap(); // manually mine the block with the transactions api.mine_one().await; + let higher_price = tx_higher.get_receipt().await.unwrap().transaction_hash; + let lower_price = tx_lower.get_receipt().await.unwrap().transaction_hash; + // get the block, await receipts - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); - let lower_price = tx_lower.await.unwrap().unwrap().transaction_hash; - let higher_price = tx_higher.await.unwrap().unwrap().transaction_hash; - assert_eq!(block.transactions, vec![higher_price, lower_price]) + let block = provider.get_block(BlockId::latest(), false).await.unwrap().unwrap(); + + assert_eq!(block.transactions, BlockTransactions::Hashes(vec![higher_price, lower_price])) } #[tokio::test(flavor = "multi_thread")] @@ -89,34 +100,41 @@ async fn can_respect_nonces() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); - let amount = handle.genesis_balance().checked_div(3u64.into()).unwrap(); + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); - let tx = TransactionRequest::new().to(to).value(amount).from(from); + let tx = TransactionRequest::default().to(to).value(amount).from(from).nonce(nonce + 1); + + let tx = WithOtherFields::new(tx); // send the transaction with higher nonce than on chain - let higher_pending_tx = - provider.send_transaction(tx.clone().nonce(nonce + 1u64), None).await.unwrap(); + let higher_pending_tx = provider.send_transaction(tx).await.unwrap(); // ensure the listener for ready transactions times out let mut listener = api.new_ready_transactions(); let res = timeout(Duration::from_millis(1500), listener.next()).await; res.unwrap_err(); + let tx = TransactionRequest::default().to(to).value(amount).from(from).nonce(nonce); + + let tx = WithOtherFields::new(tx); // send with the actual nonce which is mined immediately - let tx = - provider.send_transaction(tx.nonce(nonce), None).await.unwrap().await.unwrap().unwrap(); + let tx = provider.send_transaction(tx).await.unwrap(); + let tx = tx.get_receipt().await.unwrap(); // this will unblock the currently pending tx - let higher_tx = higher_pending_tx.await.unwrap().unwrap(); + let higher_tx = higher_pending_tx.get_receipt().await.unwrap(); // Awaits endlessly here due to alloy/#389 - let block = provider.get_block(1u64).await.unwrap().unwrap(); + let block = provider.get_block(1.into(), false).await.unwrap().unwrap(); assert_eq!(2, block.transactions.len()); - assert_eq!(vec![tx.transaction_hash, higher_tx.transaction_hash], block.transactions); + assert_eq!( + BlockTransactions::Hashes(vec![tx.transaction_hash, higher_tx.transaction_hash]), + block.transactions + ); } #[tokio::test(flavor = "multi_thread")] @@ -128,37 +146,46 @@ async fn can_replace_transaction() { let provider = handle.http_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); - let amount = handle.genesis_balance().checked_div(3u64.into()).unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); + + let tx = TransactionRequest::default().to(to).value(amount).from(from).nonce(nonce); - let tx = TransactionRequest::new().to(to).value(amount).from(from).nonce(nonce); + let mut tx = WithOtherFields::new(tx); + tx.set_gas_price(gas_price); // send transaction with lower gas price - let lower_priced_pending_tx = - provider.send_transaction(tx.clone().gas_price(gas_price), None).await.unwrap(); + let _lower_priced_pending_tx = provider.send_transaction(tx.clone()).await.unwrap(); + tx.set_gas_price(gas_price + 1); // send the same transaction with higher gas price - let higher_priced_pending_tx = - provider.send_transaction(tx.gas_price(gas_price + 1u64), None).await.unwrap(); + let higher_priced_pending_tx = provider.send_transaction(tx).await.unwrap(); + let higher_tx_hash = *higher_priced_pending_tx.tx_hash(); // mine exactly one block api.mine_one().await; - // lower priced transaction was replaced - let lower_priced_receipt = lower_priced_pending_tx.await.unwrap(); - assert!(lower_priced_receipt.is_none()); + let block = provider.get_block(1.into(), false).await.unwrap().unwrap(); - let higher_priced_receipt = higher_priced_pending_tx.await.unwrap().unwrap(); + assert_eq!(block.transactions.len(), 1); + assert_eq!(BlockTransactions::Hashes(vec![higher_tx_hash]), block.transactions); - // ensure that only the replacement tx was mined - let block = provider.get_block(1u64).await.unwrap().unwrap(); - assert_eq!(1, block.transactions.len()); - assert_eq!(vec![higher_priced_receipt.transaction_hash], block.transactions); + // FIXME: Unable to get receipt despite hotfix in https://github.com/alloy-rs/alloy/pull/614 + + // lower priced transaction was replaced + // let _lower_priced_receipt = lower_priced_pending_tx.get_receipt().await.unwrap(); + // let higher_priced_receipt = higher_priced_pending_tx.get_receipt().await.unwrap(); + + // assert_eq!(1, block.transactions.len()); + // assert_eq!( + // BlockTransactions::Hashes(vec![higher_priced_receipt.transaction_hash]), + // block.transactions + // ); } #[tokio::test(flavor = "multi_thread")] @@ -166,22 +193,28 @@ async fn can_reject_too_high_gas_limits() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let gas_limit = api.gas_limit(); - let amount = handle.genesis_balance().checked_div(3u64.into()).unwrap(); + let gas_limit = api.gas_limit().to::(); + let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); - let tx = TransactionRequest::new().to(to).value(amount).from(from); + let tx = + TransactionRequest::default().to(to).value(amount).from(from).with_gas_limit(gas_limit); + + let mut tx = WithOtherFields::new(tx); // send transaction with the exact gas limit - let pending = provider.send_transaction(tx.clone().gas(gas_limit), None).await; + let pending = provider.send_transaction(tx.clone()).await.unwrap(); + + let pending_receipt = pending.get_receipt().await; + assert!(pending_receipt.is_ok()); - pending.unwrap(); + tx.set_gas_limit(gas_limit + 1); // send transaction with higher gas limit - let pending = provider.send_transaction(tx.clone().gas(gas_limit + 1u64), None).await; + let pending = provider.send_transaction(tx.clone()).await; assert!(pending.is_err()); let err = pending.unwrap_err(); @@ -189,8 +222,9 @@ async fn can_reject_too_high_gas_limits() { api.anvil_set_balance(from, U256::MAX).await.unwrap(); - let pending = provider.send_transaction(tx.gas(gas_limit), None).await; - pending.unwrap(); + tx.set_gas_limit(gas_limit); + let pending = provider.send_transaction(tx).await; + let _ = pending.unwrap(); } #[tokio::test(flavor = "multi_thread")] @@ -202,59 +236,63 @@ async fn can_reject_underpriced_replacement() { let provider = handle.http_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); - let amount = handle.genesis_balance().checked_div(3u64.into()).unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); - let tx = TransactionRequest::new().to(to).value(amount).from(from).nonce(nonce); + let tx = TransactionRequest::default().to(to).value(amount).from(from).nonce(nonce); + let mut tx = WithOtherFields::new(tx); + + tx.set_gas_price(gas_price + 1); // send transaction with higher gas price - let higher_priced_pending_tx = - provider.send_transaction(tx.clone().gas_price(gas_price + 1u64), None).await.unwrap(); + let higher_priced_pending_tx = provider.send_transaction(tx.clone()).await.unwrap(); + tx.set_gas_price(gas_price); // send the same transaction with lower gas price - let lower_priced_pending_tx = provider.send_transaction(tx.gas_price(gas_price), None).await; + let lower_priced_pending_tx = provider.send_transaction(tx).await; let replacement_err = lower_priced_pending_tx.unwrap_err(); assert!(replacement_err.to_string().contains("replacement transaction underpriced")); // mine exactly one block api.mine_one().await; - let higher_priced_receipt = higher_priced_pending_tx.await.unwrap().unwrap(); + let higher_priced_receipt = higher_priced_pending_tx.get_receipt().await.unwrap(); // ensure that only the higher priced tx was mined - let block = provider.get_block(1u64).await.unwrap().unwrap(); + let block = provider.get_block(1.into(), false).await.unwrap().unwrap(); assert_eq!(1, block.transactions.len()); - assert_eq!(vec![higher_priced_receipt.transaction_hash], block.transactions); + assert_eq!( + BlockTransactions::Hashes(vec![higher_priced_receipt.transaction_hash]), + block.transactions + ); } #[tokio::test(flavor = "multi_thread")] async fn can_deploy_greeter_http() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .legacy() - .send() - .await - .unwrap(); + let signer: EthereumSigner = wallet.clone().into(); - let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + let alloy_provider = http_provider_with_signer(&handle.http_endpoint(), signer); - let greeter_contract = - Greeter::deploy(client, "Hello World!".to_string()).unwrap().send().await.unwrap(); + let alloy_greeter_addr = + Greeter::deploy_builder(alloy_provider.clone(), "Hello World!".to_string()) + // .legacy() unimplemented! in alloy + .deploy() + .await + .unwrap(); - let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + let alloy_greeter = Greeter::new(alloy_greeter_addr, alloy_provider); + + let greeting = alloy_greeter.greet().call().await.unwrap(); + + assert_eq!("Hello World!", greeting._0); } #[tokio::test(flavor = "multi_thread")] @@ -271,31 +309,37 @@ async fn can_deploy_and_mine_manually() { let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let from = wallet.address(); - let tx = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()).unwrap().deployer.tx; + let greeter_builder = + Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()).from(from); + let greeter_calldata = greeter_builder.calldata(); - let tx = client.send_transaction(tx, None).await.unwrap(); + let tx = TransactionRequest::default().from(from).with_input(greeter_calldata.to_owned()); + + let tx = WithOtherFields::new(tx); + + let tx = provider.send_transaction(tx).await.unwrap(); // mine block with tx manually api.evm_mine(None).await.unwrap(); - let receipt = tx.await.unwrap().unwrap(); + let receipt = tx.get_receipt().await.unwrap(); let address = receipt.contract_address.unwrap(); - let greeter_contract = Greeter::new(address, Arc::clone(&client)); + let greeter_contract = Greeter::new(address, provider); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); - let set_greeting = greeter_contract.set_greeting("Another Message".to_string()); + let set_greeting = greeter_contract.setGreeting("Another Message".to_string()); let tx = set_greeting.send().await.unwrap(); // mine block manually api.evm_mine(None).await.unwrap(); - let _tx = tx.await.unwrap(); + let _tx = tx.get_receipt().await.unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Another Message", greeting); + assert_eq!("Another Message", greeting._0); } #[tokio::test(flavor = "multi_thread")] @@ -307,51 +351,59 @@ async fn can_mine_automatically() { api.anvil_set_auto_mine(false).await.unwrap(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let tx = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()).unwrap().deployer.tx; - let sent_tx = client.send_transaction(tx, None).await.unwrap(); + let greeter_builder = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) + .from(wallet.address()); + + let greeter_calldata = greeter_builder.calldata(); + + let tx = TransactionRequest::default() + .from(wallet.address()) + .with_input(greeter_calldata.to_owned()); + + let tx = WithOtherFields::new(tx); + + let sent_tx = provider.send_transaction(tx).await.unwrap(); // re-enable auto mine api.anvil_set_auto_mine(true).await.unwrap(); - let receipt = sent_tx.await.unwrap().unwrap(); - assert_eq!(receipt.status.unwrap().as_u64(), 1u64); + let receipt = sent_tx.get_receipt().await.unwrap(); + assert_eq!(receipt.block_number, Some(1)); } #[tokio::test(flavor = "multi_thread")] async fn can_call_greeter_historic() { - let (_api, handle) = spawn(NodeConfig::test()).await; + let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .send() + let greeter_addr = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) + .from(wallet.address()) + .deploy() .await .unwrap(); + let greeter_contract = Greeter::new(greeter_addr, provider.clone()); + let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); - let block = client.get_block_number().await.unwrap(); + let block_number = provider.get_block_number().await.unwrap(); + + let _ = greeter_contract.setGreeting("Another Message".to_string()).send().await.unwrap(); - greeter_contract - .set_greeting("Another Message".to_string()) - .send() - .await - .unwrap() - .await - .unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Another Message", greeting); + assert_eq!("Another Message", greeting._0); + + // min + api.mine_one().await; // returns previous state let greeting = - greeter_contract.greet().block(BlockId::Number(block.into())).call().await.unwrap(); - assert_eq!("Hello World!", greeting); + greeter_contract.greet().block(BlockId::Number(block_number.into())).call().await.unwrap(); + assert_eq!("Hello World!", greeting._0); } #[tokio::test(flavor = "multi_thread")] @@ -360,23 +412,18 @@ async fn can_deploy_greeter_ws() { let provider = handle.ws_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .legacy() - .send() + let greeter_addr = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) + .from(wallet.address()) + // .legacy() unimplemented! in alloy + .deploy() .await .unwrap(); - let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); - - let greeter_contract = - Greeter::deploy(client, "Hello World!".to_string()).unwrap().send().await.unwrap(); + let greeter_contract = Greeter::new(greeter_addr, provider.clone()); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); } #[tokio::test(flavor = "multi_thread")] @@ -385,16 +432,14 @@ async fn can_deploy_get_code() { let provider = handle.ws_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .legacy() - .send() + let greeter_addr = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) + .from(wallet.address()) + .deploy() .await .unwrap(); - let code = client.get_code(greeter_contract.address(), None).await.unwrap(); + let code = provider.get_code_at(greeter_addr, BlockId::latest()).await.unwrap(); assert!(!code.as_ref().is_empty()); } @@ -403,93 +448,90 @@ async fn get_blocktimestamp_works() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - - let contract = - MulticallContract::deploy(Arc::clone(&client), ()).unwrap().send().await.unwrap(); + let contract = MulticallContract::deploy(provider.clone()).await.unwrap(); - let timestamp = contract.get_current_block_timestamp().call().await.unwrap(); + let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap().timestamp; - assert!(timestamp > U256::one()); + assert!(timestamp > U256::from(1)); - let latest_block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); + let latest_block = + api.block_by_number(alloy_rpc_types::BlockNumberOrTag::Latest).await.unwrap().unwrap(); - let timestamp = contract.get_current_block_timestamp().call().await.unwrap(); - assert_eq!(timestamp, latest_block.timestamp); + let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap().timestamp; + assert_eq!(timestamp.to::(), latest_block.header.timestamp); // repeat call same result - let timestamp = contract.get_current_block_timestamp().call().await.unwrap(); - assert_eq!(timestamp, latest_block.timestamp); + let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap().timestamp; + assert_eq!(timestamp.to::(), latest_block.header.timestamp); // mock timestamp - let next_timestamp = timestamp.as_u64() + 1337; + let next_timestamp = timestamp.to::() + 1337; api.evm_set_next_block_timestamp(next_timestamp).unwrap(); - let timestamp = - contract.get_current_block_timestamp().block(BlockNumber::Pending).call().await.unwrap(); - assert_eq!(timestamp, next_timestamp.into()); + let timestamp = contract + .getCurrentBlockTimestamp() + .block(BlockId::pending()) + .call() + .await + .unwrap() + .timestamp; + assert_eq!(timestamp, U256::from(next_timestamp)); // repeat call same result - let timestamp = - contract.get_current_block_timestamp().block(BlockNumber::Pending).call().await.unwrap(); - assert_eq!(timestamp, next_timestamp.into()); + let timestamp = contract + .getCurrentBlockTimestamp() + .block(BlockId::pending()) + .call() + .await + .unwrap() + .timestamp; + assert_eq!(timestamp, U256::from(next_timestamp)); } #[tokio::test(flavor = "multi_thread")] async fn call_past_state() { - let (_api, handle) = spawn(NodeConfig::test()).await; + let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let contract_addr = + SimpleStorage::deploy_builder(provider.clone(), "initial value".to_string()) + .from(wallet.address()) + .deploy() + .await + .unwrap(); - let contract = SimpleStorage::deploy(Arc::clone(&client), "initial value".to_string()) - .unwrap() - .send() - .await - .unwrap(); + let contract = SimpleStorage::new(contract_addr, provider.clone()); - let deployed_block = client.get_block_number().await.unwrap(); + let deployed_block = provider.get_block_number().await.unwrap(); - // assert initial state - let value = contract.method::<_, String>("getValue", ()).unwrap().call().await.unwrap(); - assert_eq!(value, "initial value"); + let value = contract.getValue().call().await.unwrap(); + assert_eq!(value._0, "initial value"); - // make a call with `client` - let _tx_hash = contract - .method::<_, H256>("setValue", "hi".to_owned()) - .unwrap() - .send() - .await - .unwrap() - .await - .unwrap() - .unwrap(); + let gas_price = api.gas_price().unwrap().to::(); + let set_tx = contract.setValue("hi".to_string()).gas_price(gas_price + 1); + + let _set_tx = set_tx.send().await.unwrap().get_receipt().await.unwrap(); // assert new value - let value = contract.method::<_, String>("getValue", ()).unwrap().call().await.unwrap(); - assert_eq!(value, "hi"); + let value = contract.getValue().call().await.unwrap(); + assert_eq!(value._0, "hi"); // assert previous value - let value = contract - .method::<_, String>("getValue", ()) - .unwrap() - .block(BlockId::Number(deployed_block.into())) - .call() - .await - .unwrap(); - assert_eq!(value, "initial value"); + let value = + contract.getValue().block(BlockId::Number(deployed_block.into())).call().await.unwrap(); + assert_eq!(value._0, "initial value"); - let hash = client.get_block(1).await.unwrap().unwrap().hash.unwrap(); - let value = contract - .method::<_, String>("getValue", ()) - .unwrap() - .block(BlockId::Hash(hash)) - .call() + let hash = provider + .get_block(BlockId::Number(1.into()), false) .await + .unwrap() + .unwrap() + .header + .hash .unwrap(); - assert_eq!(value, "initial value"); + let value = contract.getValue().block(BlockId::Hash(hash.into())).call().await.unwrap(); + assert_eq!(value._0, "initial value"); } #[tokio::test(flavor = "multi_thread")] @@ -498,29 +540,38 @@ async fn can_handle_multiple_concurrent_transfers_with_same_nonce() { let provider = handle.ws_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); // explicitly set the nonce - let tx = TransactionRequest::new().to(to).value(100u64).from(from).nonce(nonce).gas(21_000u64); + let tx = TransactionRequest::default() + .to(to) + .value(U256::from(100)) + .from(from) + .nonce(nonce) + .with_gas_limit(21000u128); + + let tx = WithOtherFields::new(tx); + let mut tasks = Vec::new(); for _ in 0..10 { - let provider = provider.clone(); let tx = tx.clone(); - let task = - tokio::task::spawn(async move { provider.send_transaction(tx, None).await?.await }); + let provider = provider.clone(); + let task = tokio::task::spawn(async move { + provider.send_transaction(tx).await.unwrap().get_receipt().await + }); tasks.push(task); } // only one succeeded let successful_tx = - join_all(tasks).await.into_iter().filter(|res| res.as_ref().unwrap().is_ok()).count(); + join_all(tasks).await.into_iter().filter(|res| res.as_ref().is_ok()).count(); assert_eq!(successful_tx, 1); - assert_eq!(provider.get_transaction_count(from, None).await.unwrap(), 1u64.into()); + assert_eq!(provider.get_transaction_count(from, BlockId::latest()).await.unwrap(), 1u64); } #[tokio::test(flavor = "multi_thread")] @@ -530,22 +581,27 @@ async fn can_handle_multiple_concurrent_deploys_with_same_nonce() { let wallet = handle.dev_wallets().next().unwrap(); let from = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let nonce = client.get_transaction_count(from, None).await.unwrap(); - // explicitly set the nonce + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); + let mut tasks = Vec::new(); - let mut tx = - Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()).unwrap().deployer.tx; - tx.set_nonce(nonce); - tx.set_gas(300_000u64); + + let greeter = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()); + + let greeter_calldata = greeter.calldata(); + + let tx = TransactionRequest::default() + .from(from) + .with_input(greeter_calldata.to_owned()) + .nonce(nonce) + .with_gas_limit(300_000u128); + + let tx = WithOtherFields::new(tx); for _ in 0..10 { - let client = Arc::clone(&client); + let provider = provider.clone(); let tx = tx.clone(); let task = tokio::task::spawn(async move { - Ok::<_, SignerMiddlewareError<_, _>>( - client.send_transaction(tx, None).await?.await.unwrap(), - ) + Ok(provider.send_transaction(tx).await?.get_receipt().await.unwrap()) }); tasks.push(task); } @@ -554,7 +610,7 @@ async fn can_handle_multiple_concurrent_deploys_with_same_nonce() { let successful_tx = join_all(tasks).await.into_iter().filter(|res| res.as_ref().unwrap().is_ok()).count(); assert_eq!(successful_tx, 1); - assert_eq!(client.get_transaction_count(from, None).await.unwrap(), 1u64.into()); + assert_eq!(provider.get_transaction_count(from, BlockId::latest()).await.unwrap(), 1u64); } #[tokio::test(flavor = "multi_thread")] @@ -564,41 +620,44 @@ async fn can_handle_multiple_concurrent_transactions_with_same_nonce() { let wallet = handle.dev_wallets().next().unwrap(); let from = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .send() - .await - .unwrap(); + let greeter_contract = + Greeter::deploy(provider.clone(), "Hello World!".to_string()).await.unwrap(); + + let nonce = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); - let nonce = client.get_transaction_count(from, None).await.unwrap(); - // explicitly set the nonce let mut tasks = Vec::new(); - let mut deploy_tx = - Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()).unwrap().deployer.tx; - deploy_tx.set_nonce(nonce); - deploy_tx.set_gas(300_000u64); - let mut set_greeting_tx = greeter_contract.set_greeting("Hello".to_string()).tx; - set_greeting_tx.set_nonce(nonce); - set_greeting_tx.set_gas(300_000u64); + let deploy = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()); + let deploy_calldata = deploy.calldata(); + let deploy_tx = TransactionRequest::default() + .from(from) + .with_input(deploy_calldata.to_owned()) + .nonce(nonce) + .with_gas_limit(300_000u128); + let deploy_tx = WithOtherFields::new(deploy_tx); + + let set_greeting = greeter_contract.setGreeting("Hello".to_string()); + let set_greeting_calldata = set_greeting.calldata(); + + let set_greeting_tx = TransactionRequest::default() + .from(from) + .with_input(set_greeting_calldata.to_owned()) + .nonce(nonce) + .with_gas_limit(300_000u128); + let set_greeting_tx = WithOtherFields::new(set_greeting_tx); for idx in 0..10 { - let client = Arc::clone(&client); + let provider = provider.clone(); let task = if idx % 2 == 0 { let tx = deploy_tx.clone(); tokio::task::spawn(async move { - Ok::<_, SignerMiddlewareError<_, _>>( - client.send_transaction(tx, None).await?.await.unwrap(), - ) + Ok(provider.send_transaction(tx).await?.get_receipt().await.unwrap()) }) } else { let tx = set_greeting_tx.clone(); tokio::task::spawn(async move { - Ok::<_, SignerMiddlewareError<_, _>>( - client.send_transaction(tx, None).await?.await.unwrap(), - ) + Ok(provider.send_transaction(tx).await?.get_receipt().await.unwrap()) }) }; @@ -609,9 +668,8 @@ async fn can_handle_multiple_concurrent_transactions_with_same_nonce() { let successful_tx = join_all(tasks).await.into_iter().filter(|res| res.as_ref().unwrap().is_ok()).count(); assert_eq!(successful_tx, 1); - assert_eq!(client.get_transaction_count(from, None).await.unwrap(), nonce + 1); + assert_eq!(provider.get_transaction_count(from, BlockId::latest()).await.unwrap(), nonce + 1); } - #[tokio::test(flavor = "multi_thread")] async fn can_get_pending_transaction() { let (api, handle) = spawn(NodeConfig::test()).await; @@ -622,14 +680,15 @@ async fn can_get_pending_transaction() { let provider = handle.http_provider(); let from = handle.dev_wallets().next().unwrap().address(); - let tx = TransactionRequest::new().from(from).value(1337u64).to(Address::random()); - let tx = provider.send_transaction(tx, None).await.unwrap(); + let tx = TransactionRequest::default().from(from).value(U256::from(1337)).to(Address::random()); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap(); - let pending = provider.get_transaction(tx.tx_hash()).await.unwrap(); - assert!(pending.is_some()); + let pending = provider.get_transaction_by_hash(*tx.tx_hash()).await; + assert!(pending.is_ok()); api.mine_one().await; - let mined = provider.get_transaction(tx.tx_hash()).await.unwrap().unwrap(); + let mined = provider.get_transaction_by_hash(*tx.tx_hash()).await.unwrap(); assert_eq!(mined.hash, pending.unwrap().hash); } @@ -643,12 +702,9 @@ async fn test_first_noce_is_zero() { let provider = handle.http_provider(); let from = handle.dev_wallets().next().unwrap().address(); - let nonce = provider - .get_transaction_count(from, Some(BlockId::Number(BlockNumber::Pending))) - .await - .unwrap(); + let nonce = provider.get_transaction_count(from, BlockId::pending()).await.unwrap(); - assert_eq!(nonce, U256::zero()); + assert_eq!(nonce, 0); } #[tokio::test(flavor = "multi_thread")] @@ -658,7 +714,7 @@ async fn can_handle_different_sender_nonce_calculation() { api.anvil_set_auto_mine(false).await.unwrap(); let provider = handle.http_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from_first = accounts[0].address(); let from_second = accounts[1].address(); @@ -666,23 +722,25 @@ async fn can_handle_different_sender_nonce_calculation() { // send a bunch of tx to the mempool and check nonce is returned correctly for idx in 1..=tx_count { - let tx_from_first = - TransactionRequest::new().from(from_first).value(1337u64).to(Address::random()); - let _tx = provider.send_transaction(tx_from_first, None).await.unwrap(); - let nonce_from_first = provider - .get_transaction_count(from_first, Some(BlockId::Number(BlockNumber::Pending))) - .await - .unwrap(); - assert_eq!(nonce_from_first, idx.into()); - - let tx_from_second = - TransactionRequest::new().from(from_second).value(1337u64).to(Address::random()); - let _tx = provider.send_transaction(tx_from_second, None).await.unwrap(); - let nonce_from_second = provider - .get_transaction_count(from_second, Some(BlockId::Number(BlockNumber::Pending))) - .await - .unwrap(); - assert_eq!(nonce_from_second, idx.into()); + let tx_from_first = TransactionRequest::default() + .from(from_first) + .value(U256::from(1337u64)) + .to(Address::random()); + let tx_from_first = WithOtherFields::new(tx_from_first); + let _tx = provider.send_transaction(tx_from_first).await.unwrap(); + let nonce_from_first = + provider.get_transaction_count(from_first, BlockId::pending()).await.unwrap(); + assert_eq!(nonce_from_first, idx); + + let tx_from_second = TransactionRequest::default() + .from(from_second) + .value(U256::from(1337u64)) + .to(Address::random()); + let tx_from_second = WithOtherFields::new(tx_from_second); + let _tx = provider.send_transaction(tx_from_second).await.unwrap(); + let nonce_from_second = + provider.get_transaction_count(from_second, BlockId::pending()).await.unwrap(); + assert_eq!(nonce_from_second, idx); } } @@ -699,21 +757,17 @@ async fn includes_pending_tx_for_transaction_count() { // send a bunch of tx to the mempool and check nonce is returned correctly for idx in 1..=tx_count { - let tx = TransactionRequest::new().from(from).value(1337u64).to(Address::random()); - let _tx = provider.send_transaction(tx, None).await.unwrap(); - let nonce = provider - .get_transaction_count(from, Some(BlockId::Number(BlockNumber::Pending))) - .await - .unwrap(); - assert_eq!(nonce, idx.into()); + let tx = + TransactionRequest::default().from(from).value(U256::from(1337)).to(Address::random()); + let tx = WithOtherFields::new(tx); + let _tx = provider.send_transaction(tx).await.unwrap(); + let nonce = provider.get_transaction_count(from, BlockId::pending()).await.unwrap(); + assert_eq!(nonce, idx); } api.mine_one().await; - let nonce = provider - .get_transaction_count(from, Some(BlockId::Number(BlockNumber::Pending))) - .await - .unwrap(); - assert_eq!(nonce, tx_count.into()); + let nonce = provider.get_transaction_count(from, BlockId::pending()).await.unwrap(); + assert_eq!(nonce, tx_count); } #[tokio::test(flavor = "multi_thread")] @@ -721,32 +775,29 @@ async fn can_get_historic_info() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let amount = handle.genesis_balance().checked_div(2u64.into()).unwrap(); - let tx = TransactionRequest::new().to(to).value(amount).from(from); - let _tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap(); + let _ = tx.get_receipt().await.unwrap(); - let nonce_pre = provider - .get_transaction_count(from, Some(BlockNumber::Number(0.into()).into())) - .await - .unwrap(); + let nonce_pre = provider.get_transaction_count(from, BlockId::Number(0.into())).await.unwrap(); - let nonce_post = - provider.get_transaction_count(from, Some(BlockNumber::Latest.into())).await.unwrap(); + let nonce_post = provider.get_transaction_count(from, BlockId::latest()).await.unwrap(); assert!(nonce_pre < nonce_post); - let balance_pre = - provider.get_balance(from, Some(BlockNumber::Number(0.into()).into())).await.unwrap(); + let balance_pre = provider.get_balance(from, BlockId::Number(0.into())).await.unwrap(); - let balance_post = provider.get_balance(from, Some(BlockNumber::Latest.into())).await.unwrap(); + let balance_post = provider.get_balance(from, BlockId::latest()).await.unwrap(); assert!(balance_post < balance_pre); - let to_balance = provider.get_balance(to, None).await.unwrap(); + let to_balance = provider.get_balance(to, BlockId::latest()).await.unwrap(); assert_eq!(balance_pre.saturating_add(amount), to_balance); } @@ -756,53 +807,77 @@ async fn test_tx_receipt() { let (_api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(handle.http_provider(), wallet)); + let provider = handle.http_provider(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); + let tx = TransactionRequest::default().to(Address::random()).value(U256::from(1337)); - let tx = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); assert!(tx.to.is_some()); - let tx = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()).unwrap().deployer.tx; + let greeter_deploy = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()); + let greeter_calldata = greeter_deploy.calldata(); + + let tx = TransactionRequest::default() + .from(wallet.address()) + .with_input(greeter_calldata.to_owned()); - let tx = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); // `to` field is none if it's a contract creation transaction: https://eth.wiki/json-rpc/API#eth_getTransactionReceipt assert!(tx.to.is_none()); assert!(tx.contract_address.is_some()); } +// TODO: Fix error: ErrorPayload { code: -32602, message: "invalid type: boolean `true`, expected +// unit", data: None } originating from watch_full_pending_transactions, remove ignore +#[ignore] #[tokio::test(flavor = "multi_thread")] async fn can_stream_pending_transactions() { let (_api, handle) = spawn(NodeConfig::test().with_blocktime(Some(Duration::from_secs(2)))).await; let num_txs = 5; + let provider = handle.http_provider(); let ws_provider = handle.ws_provider(); let accounts = provider.get_accounts().await.unwrap(); - let tx = TransactionRequest::new().from(accounts[0]).to(accounts[0]).value(1e18 as u64); + let tx = + TransactionRequest::default().from(accounts[0]).to(accounts[0]).value(U256::from(1e18)); let mut sending = futures::future::join_all( std::iter::repeat(tx.clone()) .take(num_txs) .enumerate() - .map(|(nonce, tx)| tx.nonce(nonce)) + .map(|(nonce, tx)| tx.nonce(nonce as u64)) .map(|tx| async { - provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap() + let tx = WithOtherFields::new(tx); + provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap() }), ) .fuse(); - let mut watch_tx_stream = - provider.watch_pending_transactions().await.unwrap().transactions_unordered(num_txs).fuse(); - - let mut sub_tx_stream = - ws_provider.subscribe_pending_txs().await.unwrap().transactions_unordered(2).fuse(); + let mut watch_tx_stream = provider + .watch_full_pending_transactions() + .await + .unwrap() // TODO: Fix error here + .into_stream() + .flat_map(futures::stream::iter) + .take(num_txs) + .fuse(); + + let mut sub_tx_stream = ws_provider + .subscribe_full_pending_transactions() + .await + .unwrap() + .into_stream() + .take(2) + .fuse(); - let mut sent: Option> = None; - let mut watch_received: Vec = Vec::with_capacity(num_txs); - let mut sub_received: Vec = Vec::with_capacity(num_txs); + let mut sent = None; + let mut watch_received = Vec::with_capacity(num_txs); + let mut sub_received = Vec::with_capacity(num_txs); loop { futures::select! { @@ -810,12 +885,17 @@ async fn can_stream_pending_transactions() { sent = Some(txs) }, tx = watch_tx_stream.next() => { - watch_received.push(tx.unwrap().unwrap()); + if let Some(tx) = tx { + watch_received.push(tx); + } }, tx = sub_tx_stream.next() => { - sub_received.push(tx.unwrap().unwrap()); + if let Some(tx) = tx { + sub_received.push(tx); + } }, }; + if watch_received.len() == num_txs && sub_received.len() == num_txs { if let Some(ref sent) = sent { assert_eq!(sent.len(), watch_received.len()); @@ -860,38 +940,51 @@ async fn test_tx_access_list() { // - The sender shouldn't be in the AL let (_api, handle) = spawn(NodeConfig::test()).await; - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(handle.http_provider(), wallet)); + let provider = handle.http_provider(); let sender = Address::random(); let other_acc = Address::random(); - let multicall = MulticallContract::deploy(client.clone(), ()).unwrap().send().await.unwrap(); - let simple_storage = - SimpleStorage::deploy(client.clone(), "foo".to_string()).unwrap().send().await.unwrap(); + let multicall = MulticallContract::deploy(provider.clone()).await.unwrap(); + let simple_storage = SimpleStorage::deploy(provider.clone(), "foo".to_string()).await.unwrap(); // when calling `setValue` on SimpleStorage, both the `lastSender` and `_value` storages are // modified The `_value` is a `string`, so the storage slots here (small string) are `0x1` // and `keccak(0x1)` - let set_value_tx = simple_storage.set_value("bar".to_string()).from(sender).tx; - let access_list = client.create_access_list(&set_value_tx, None).await.unwrap(); + let set_value = simple_storage.setValue("bar".to_string()); + let set_value_calldata = set_value.calldata(); + let set_value_tx = TransactionRequest::default() + .from(sender) + .to(*simple_storage.address()) + .with_input(set_value_calldata.to_owned()); + let set_value_tx = WithOtherFields::new(set_value_tx); + let access_list = provider.create_access_list(&set_value_tx, BlockId::latest()).await.unwrap(); + // let set_value_tx = simple_storage.set_value("bar".to_string()).from(sender).tx; + // let access_list = client.create_access_list(&set_value_tx, None).await.unwrap(); assert_access_list_eq( access_list.access_list, AccessList::from(vec![AccessListItem { - address: simple_storage.address(), + address: *simple_storage.address(), storage_keys: vec![ - H256::zero(), - H256::from_uint(&(1u64.into())), - "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6" - .parse() - .unwrap(), + FixedBytes::ZERO, + FixedBytes::with_last_byte(1), + FixedBytes::from_str( + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", + ) + .unwrap(), ], }]), ); // With a subcall that fetches the balances of an account (`other_acc`), only the address // of this account should be in the Access List - let call_tx = multicall.get_eth_balance(other_acc).from(sender).tx; - let access_list = client.create_access_list(&call_tx, None).await.unwrap(); + let call_tx = multicall.getEthBalance(other_acc); + let call_tx_data = call_tx.calldata(); + let call_tx = TransactionRequest::default() + .from(sender) + .to(*multicall.address()) + .with_input(call_tx_data.to_owned()); + let call_tx = WithOtherFields::new(call_tx); + let access_list = provider.create_access_list(&call_tx, BlockId::latest()).await.unwrap(); assert_access_list_eq( access_list.access_list, AccessList::from(vec![AccessListItem { address: other_acc, storage_keys: vec![] }]), @@ -899,24 +992,31 @@ async fn test_tx_access_list() { // With a subcall to another contract, the AccessList should be the same as when calling the // subcontract directly (given that the proxy contract doesn't read/write any state) - let subcall_tx = multicall - .aggregate(vec![Call { - target: simple_storage.address(), - call_data: set_value_tx.data().unwrap().clone(), - }]) + let subcall_tx = multicall.aggregate(vec![MulticallContract::Call { + target: *simple_storage.address(), + callData: set_value_calldata.to_owned(), + }]); + + let subcall_tx_calldata = subcall_tx.calldata(); + + let subcall_tx = TransactionRequest::default() .from(sender) - .tx; - let access_list = client.create_access_list(&subcall_tx, None).await.unwrap(); + .to(*multicall.address()) + .with_input(subcall_tx_calldata.to_owned()); + let subcall_tx = WithOtherFields::new(subcall_tx); + let access_list = provider.create_access_list(&subcall_tx, BlockId::latest()).await.unwrap(); assert_access_list_eq( access_list.access_list, + // H256::from_uint(&(1u64.into())), AccessList::from(vec![AccessListItem { - address: simple_storage.address(), + address: *simple_storage.address(), storage_keys: vec![ - H256::zero(), - H256::from_uint(&(1u64.into())), - "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6" - .parse() - .unwrap(), + FixedBytes::ZERO, + FixedBytes::with_last_byte(1), + FixedBytes::from_str( + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", + ) + .unwrap(), ], }]), ); @@ -936,14 +1036,58 @@ async fn estimates_gas_on_pending_by_default() { let sender = wallet.address(); let recipient = Address::random(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let tx = TransactionRequest::default().from(sender).to(recipient).value(U256::from(1e18)); + let tx = WithOtherFields::new(tx); - let tx = TransactionRequest::new().from(sender).to(recipient).value(1e18 as u64); - client.send_transaction(tx, None).await.unwrap(); + let _pending = provider.send_transaction(tx).await.unwrap(); - let tx = - TransactionRequest::new().from(recipient).to(sender).value(1e10 as u64).data(vec![0x42]); - api.estimate_gas(tx.into(), None).await.unwrap(); + let tx = TransactionRequest::default() + .from(recipient) + .to(sender) + .value(U256::from(1e10)) + .input(Bytes::from(vec![0x42]).into()); + api.estimate_gas(WithOtherFields::new(tx), None, None).await.unwrap(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_estimate_gas() { + let (api, handle) = spawn(NodeConfig::test()).await; + + let wallet = handle.dev_wallets().next().unwrap(); + let sender = wallet.address(); + let recipient = Address::random(); + + let tx = TransactionRequest::default() + .from(recipient) + .to(sender) + .value(U256::from(1e10)) + .input(Bytes::from(vec![0x42]).into()); + // Expect the gas estimation to fail due to insufficient funds. + let error_result = api.estimate_gas(WithOtherFields::new(tx.clone()), None, None).await; + + assert!(error_result.is_err(), "Expected an error due to insufficient funds"); + let error_message = error_result.unwrap_err().to_string(); + assert!( + error_message.contains("Insufficient funds for gas * price + value"), + "Error message did not match expected: {}", + error_message + ); + + // Setup state override to simulate sufficient funds for the recipient. + let addr = recipient; + let account_override = + AccountOverride { balance: Some(alloy_primitives::U256::from(1e18)), ..Default::default() }; + let mut state_override = StateOverride::new(); + state_override.insert(addr, account_override); + + // Estimate gas with state override implying sufficient funds. + let gas_estimate = api + .estimate_gas(WithOtherFields::new(tx), None, Some(state_override)) + .await + .expect("Failed to estimate gas with state override"); + + // Assert the gas estimate meets the expected minimum. + assert!(gas_estimate >= U256::from(21000), "Gas estimate is lower than expected minimum"); } #[tokio::test(flavor = "multi_thread")] @@ -954,13 +1098,14 @@ async fn test_reject_gas_too_low() { let account = handle.dev_accounts().next().unwrap(); let gas = 21_000u64 - 1; - let tx = TransactionRequest::new() + let tx = TransactionRequest::default() .to(Address::random()) .value(U256::from(1337u64)) .from(account) - .gas(gas); + .with_gas_limit(gas as u128); + let tx = WithOtherFields::new(tx); - let resp = provider.send_transaction(tx, None).await; + let resp = provider.send_transaction(tx).await; let err = resp.unwrap_err().to_string(); assert!(err.contains("intrinsic gas too low")); @@ -969,21 +1114,13 @@ async fn test_reject_gas_too_low() { // #[tokio::test(flavor = "multi_thread")] async fn can_call_with_high_gas_limit() { - let (_api, handle) = - spawn(NodeConfig::test().with_gas_limit(Some(U256::from(100_000_000)))).await; + let (_api, handle) = spawn(NodeConfig::test().with_gas_limit(Some(100_000_000))).await; let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let greeter_contract = Greeter::deploy(provider, "Hello World!".to_string()).await.unwrap(); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .send() - .await - .unwrap(); - - let greeting = greeter_contract.greet().gas(60_000_000u64).call().await.unwrap(); - assert_eq!("Hello World!", greeting); + let greeting = greeter_contract.greet().gas(60_000_000u128).call().await.unwrap(); + assert_eq!("Hello World!", greeting._0); } #[tokio::test(flavor = "multi_thread")] @@ -991,28 +1128,60 @@ async fn test_reject_eip1559_pre_london() { let (api, handle) = spawn(NodeConfig::test().with_hardfork(Some(Hardfork::Berlin))).await; let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let gas_limit = api.gas_limit().to::(); + let gas_price = api.gas_price().unwrap().to::(); - let gas_limit = api.gas_limit(); - let gas_price = api.gas_price().unwrap(); - let unsupported = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .gas(gas_limit) - .gas_price(gas_price) - .send() - .await - .unwrap_err() - .to_string(); + let unsupported_call_builder = + Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()); + let unsupported_calldata = unsupported_call_builder.calldata(); + + let unsup_tx = TransactionRequest::default() + .from(handle.dev_accounts().next().unwrap()) + .with_input(unsupported_calldata.to_owned()) + .with_gas_limit(gas_limit) + .with_max_fee_per_gas(gas_price) + .with_max_priority_fee_per_gas(gas_price); + + let unsup_tx = WithOtherFields::new(unsup_tx); + + let unsupported = provider.send_transaction(unsup_tx).await.unwrap_err().to_string(); assert!(unsupported.contains("not supported by the current hardfork"), "{unsupported}"); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .legacy() - .send() - .await - .unwrap(); + let greeter_contract_addr = + Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) + .gas(gas_limit) + .gas_price(gas_price) + .deploy() + .await + .unwrap(); + + let greeter_contract = Greeter::new(greeter_contract_addr, provider); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); +} + +// https://github.com/foundry-rs/foundry/issues/6931 +#[tokio::test(flavor = "multi_thread")] +async fn can_mine_multiple_in_block() { + let (api, _handle) = spawn(NodeConfig::test()).await; + + // disable auto mine + api.anvil_set_auto_mine(false).await.unwrap(); + + let tx = TransactionRequest { + from: Some("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap()), + ..Default::default() + }; + + // broadcast it via the eth_sendTransaction API + let first = api.send_transaction(WithOtherFields::new(tx.clone())).await.unwrap(); + let second = api.send_transaction(WithOtherFields::new(tx.clone())).await.unwrap(); + + api.anvil_mine(Some(U256::from(1)), Some(U256::ZERO)).await.unwrap(); + + let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + + let txs = block.transactions.hashes().copied().collect::>(); + assert_eq!(txs, vec![first, second]); } diff --git a/crates/anvil/tests/it/txpool.rs b/crates/anvil/tests/it/txpool.rs index ea0f6b4d246af..ed30e5acd0565 100644 --- a/crates/anvil/tests/it/txpool.rs +++ b/crates/anvil/tests/it/txpool.rs @@ -1,33 +1,40 @@ //! txpool related tests +use alloy_network::TransactionBuilder; +use alloy_primitives::U256; +use alloy_provider::Provider; +use alloy_rpc_types::{TransactionRequest, WithOtherFields}; use anvil::{spawn, NodeConfig}; -use ethers::{ - prelude::Middleware, - types::{TransactionRequest, U256}, -}; #[tokio::test(flavor = "multi_thread")] async fn geth_txpool() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); + api.anvil_set_auto_mine(false).await.unwrap(); - let account = provider.get_accounts().await.unwrap()[0]; - let value: u64 = 42; - let gas_price: U256 = 221435145689u64.into(); - let tx = TransactionRequest::new().to(account).from(account).value(value).gas_price(gas_price); + let account = provider.get_accounts().await.unwrap().remove(0); + let value = U256::from(42); + let gas_price = 221435145689u128; + + let tx = TransactionRequest::default() + .with_to(account) + .with_from(account) + .with_value(value) + .with_gas_price(gas_price); + let tx = WithOtherFields::new(tx); // send a few transactions let mut txs = Vec::new(); for _ in 0..10 { - let tx_hash = provider.send_transaction(tx.clone(), None).await.unwrap(); + let tx_hash = provider.send_transaction(tx.clone()).await.unwrap(); txs.push(tx_hash); } // we gave a 20s block time, should be plenty for us to get the txpool's content let status = provider.txpool_status().await.unwrap(); - assert_eq!(status.pending.as_u64(), 10); - assert_eq!(status.queued.as_u64(), 0); + assert_eq!(status.pending, 10); + assert_eq!(status.queued, 0); let inspect = provider.txpool_inspect().await.unwrap(); assert!(inspect.queued.is_empty()); @@ -35,8 +42,8 @@ async fn geth_txpool() { for i in 0..10 { let tx_summary = summary.get(&i.to_string()).unwrap(); assert_eq!(tx_summary.gas_price, gas_price); - assert_eq!(tx_summary.value, value.into()); - assert_eq!(tx_summary.gas, 21000.into()); + assert_eq!(tx_summary.value, value); + assert_eq!(tx_summary.gas, 21000); assert_eq!(tx_summary.to.unwrap(), account); } diff --git a/crates/anvil/tests/it/utils.rs b/crates/anvil/tests/it/utils.rs index 0a566609300fe..066ec807c96e9 100644 --- a/crates/anvil/tests/it/utils.rs +++ b/crates/anvil/tests/it/utils.rs @@ -1,15 +1,62 @@ -use ethers::{ - addressbook::contract, - types::{Address, Chain}, +use alloy_network::{Ethereum, EthereumSigner}; +use foundry_common::provider::{ + get_http_provider, ProviderBuilder, RetryProvider, RetryProviderWithSigner, }; -/// Returns a set of various contract addresses -pub fn contract_addresses(chain: Chain) -> Vec
{ - vec![ - contract("dai").unwrap().address(chain).unwrap(), - contract("usdc").unwrap().address(chain).unwrap(), - contract("weth").unwrap().address(chain).unwrap(), - contract("uniswapV3Factory").unwrap().address(chain).unwrap(), - contract("uniswapV3SwapRouter02").unwrap().address(chain).unwrap(), - ] +pub fn http_provider(http_endpoint: &str) -> RetryProvider { + get_http_provider(http_endpoint) +} + +pub fn http_provider_with_signer( + http_endpoint: &str, + signer: EthereumSigner, +) -> RetryProviderWithSigner { + ProviderBuilder::new(http_endpoint) + .build_with_signer(signer) + .expect("failed to build Alloy HTTP provider with signer") +} + +pub fn ws_provider_with_signer( + ws_endpoint: &str, + signer: EthereumSigner, +) -> RetryProviderWithSigner { + ProviderBuilder::new(ws_endpoint) + .build_with_signer(signer) + .expect("failed to build Alloy WS provider with signer") +} + +pub async fn connect_pubsub(conn_str: &str) -> RootProvider { + alloy_provider::ProviderBuilder::new().on_builtin(conn_str).await.unwrap() +} + +use alloy_provider::{ + fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, SignerFiller}, + Identity, RootProvider, +}; +use alloy_transport::BoxTransport; +type PubsubSigner = FillProvider< + JoinFill< + JoinFill, NonceFiller>, ChainIdFiller>, + SignerFiller, + >, + RootProvider, + BoxTransport, + Ethereum, +>; +pub async fn connect_pubsub_with_signer(conn_str: &str, signer: EthereumSigner) -> PubsubSigner { + alloy_provider::ProviderBuilder::new() + .with_recommended_fillers() + .signer(signer) + .on_builtin(conn_str) + .await + .unwrap() +} + +pub async fn ipc_provider_with_signer( + ipc_endpoint: &str, + signer: EthereumSigner, +) -> RetryProviderWithSigner { + ProviderBuilder::new(ipc_endpoint) + .build_with_signer(signer) + .expect("failed to build Alloy IPC provider with signer") } diff --git a/crates/anvil/tests/it/wsapi.rs b/crates/anvil/tests/it/wsapi.rs index f185c6b8ee241..f68b15312a673 100644 --- a/crates/anvil/tests/it/wsapi.rs +++ b/crates/anvil/tests/it/wsapi.rs @@ -1,18 +1,20 @@ //! general eth api tests with websocket provider +use alloy_primitives::U256; +use alloy_provider::Provider; +use alloy_rpc_types::BlockId; use anvil::{spawn, NodeConfig}; -use ethers::{prelude::Middleware, types::U256}; #[tokio::test(flavor = "multi_thread")] async fn can_get_block_number_ws() { let (api, handle) = spawn(NodeConfig::test()).await; let block_num = api.block_number().unwrap(); - assert_eq!(block_num, U256::zero()); + assert_eq!(block_num, U256::ZERO); let provider = handle.ws_provider(); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num, block_num.as_u64().into()); + assert_eq!(num, block_num.to::()); } #[tokio::test(flavor = "multi_thread")] @@ -22,7 +24,7 @@ async fn can_dev_get_balance_ws() { let genesis_balance = handle.genesis_balance(); for acc in handle.genesis_accounts() { - let balance = provider.get_balance(acc, None).await.unwrap(); + let balance = provider.get_balance(acc, BlockId::latest()).await.unwrap(); assert_eq!(balance, genesis_balance); } } diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index c822ffb75336a..ff7457e20c973 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -15,7 +15,11 @@ name = "cast" path = "bin/main.rs" [build-dependencies] -vergen = { version = "8", default-features = false, features = ["build", "git", "git2"] } +vergen = { workspace = true, default-features = false, features = [ + "build", + "git", + "gitcl", +] } [dependencies] # lib @@ -24,17 +28,29 @@ foundry-common.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true foundry-evm.workspace = true +foundry-wallets.workspace = true alloy-dyn-abi.workspace = true alloy-json-abi.workspace = true alloy-primitives.workspace = true alloy-rlp.workspace = true +alloy-provider = { workspace = true, features = ["reqwest", "ws", "ipc"] } +alloy-transport.workspace = true +alloy-rpc-types.workspace = true +alloy-json-rpc.workspace = true +alloy-signer.workspace = true +alloy-signer-wallet = { workspace = true, features = ["mnemonic", "keystore"] } +alloy-contract.workspace = true +alloy-consensus = { workspace = true, features = ["serde"] } +alloy-network.workspace = true +alloy-sol-types.workspace = true +alloy-chains.workspace = true ethers-core.workspace = true -ethers-providers.workspace = true +ethers-contract = { workspace = true, features = ["abigen"] } chrono.workspace = true -evm-disassembler = "0.3" +evm-disassembler.workspace = true eyre.workspace = true futures = "0.3" hex.workspace = true @@ -44,17 +60,11 @@ serde_json.workspace = true serde.workspace = true # aws -rusoto_core = { version = "0.48", default-features = false } -rusoto_kms = { version = "0.48", default-features = false } +aws-sdk-kms = { version = "1", default-features = false } # bin foundry-cli.workspace = true -ethers-contract.workspace = true -ethers-middleware.workspace = true -ethers-signers.workspace = true -eth-keystore = "0.5" - clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } clap_complete = "4" clap_complete_fig = "4" @@ -65,10 +75,14 @@ itertools.workspace = true regex = { version = "1", default-features = false } rpassword = "7" semver = "1" -tempfile = "3" +tempfile.workspace = true tokio = { version = "1", features = ["macros", "signal"] } tracing.workspace = true -yansi = "0.5" +yansi.workspace = true +evmole = "0.3.1" + +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = { workspace = true, optional = true } [dev-dependencies] foundry-test-utils.workspace = true @@ -77,8 +91,10 @@ criterion = "0.5" [features] default = ["rustls"] -rustls = ["foundry-cli/rustls"] +rustls = ["foundry-cli/rustls", "foundry-wallets/rustls"] openssl = ["foundry-cli/openssl"] +asm-keccak = ["alloy-primitives/asm-keccak"] +jemalloc = ["dep:tikv-jemallocator"] [[bench]] name = "vanity" diff --git a/crates/cast/bin/cmd/access_list.rs b/crates/cast/bin/cmd/access_list.rs index 28be54ee04fa7..d344017f629df 100644 --- a/crates/cast/bin/cmd/access_list.rs +++ b/crates/cast/bin/cmd/access_list.rs @@ -1,13 +1,16 @@ -use cast::{Cast, TxBuilder}; +use alloy_network::{AnyNetwork, TransactionBuilder}; +use alloy_primitives::{Address, Bytes}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockId, TransactionRequest, WithOtherFields}; +use alloy_transport::Transport; +use cast::Cast; use clap::Parser; -use ethers_core::types::{BlockId, NameOrAddress}; -use ethers_providers::Middleware; -use eyre::{Result, WrapErr}; +use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, - utils, + utils::{self, parse_function_args}, }; -use foundry_common::types::ToEthers; +use foundry_common::ens::NameOrAddress; use foundry_config::{Chain, Config}; use std::str::FromStr; @@ -15,25 +18,24 @@ use std::str::FromStr; #[derive(Debug, Parser)] pub struct AccessListArgs { /// The destination of the transaction. - #[clap( + #[arg( value_name = "TO", value_parser = NameOrAddress::from_str )] to: Option, /// The signature of the function to call. - #[clap(value_name = "SIG")] + #[arg(value_name = "SIG")] sig: Option, /// The arguments of the function to call. - #[clap(value_name = "ARGS")] + #[arg(value_name = "ARGS")] args: Vec, /// The data for the transaction. - #[clap( + #[arg( long, value_name = "DATA", - value_parser = foundry_common::clap_helpers::strip_0x_prefix, conflicts_with_all = &["sig", "args"] )] data: Option, @@ -41,17 +43,17 @@ pub struct AccessListArgs { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, /// Print the access list as JSON. - #[clap(long, short, help_heading = "Display options")] + #[arg(long, short, help_heading = "Display options")] json: bool, - #[clap(flatten)] + #[command(flatten)] tx: TransactionOpts, - #[clap(flatten)] + #[command(flatten)] eth: EthereumOpts, } @@ -63,18 +65,37 @@ impl AccessListArgs { let provider = utils::get_provider(&config)?; let chain = utils::get_chain(config.chain, &provider).await?; let sender = eth.wallet.sender().await; - - access_list(&provider, sender.to_ethers(), to, sig, args, data, tx, chain, block, to_json) - .await?; + let etherscan_api_key = config.get_etherscan_api_key(Some(chain)); + + let to = match to { + Some(to) => Some(to.resolve(&provider).await?), + None => None, + }; + + access_list( + &provider, + etherscan_api_key.as_deref(), + sender, + to, + sig, + args, + data, + tx, + chain, + block, + to_json, + ) + .await?; Ok(()) } } #[allow(clippy::too_many_arguments)] -async fn access_list, T: Into>( - provider: M, - from: F, - to: Option, +async fn access_list, T: Transport + Clone>( + provider: P, + etherscan_api_key: Option<&str>, + from: Address, + to: Option
, sig: Option, args: Vec, data: Option, @@ -82,32 +103,35 @@ async fn access_list, T: Into, to_json: bool, -) -> Result<()> -where - M::Error: 'static, -{ - let mut builder = TxBuilder::new(&provider, from, to, chain, tx.legacy).await?; - builder - .gas(tx.gas_limit) - .gas_price(tx.gas_price) - .priority_gas_price(tx.priority_gas_price) - .nonce(tx.nonce); - - builder.value(tx.value); - - if let Some(sig) = sig { - builder.set_args(sig.as_str(), args).await?; +) -> Result<()> { + let mut req = WithOtherFields::::default() + .with_to(to.unwrap_or_default()) + .with_from(from) + .with_value(tx.value.unwrap_or_default()) + .with_chain_id(chain.id()); + + if let Some(gas_limit) = tx.gas_limit { + req.set_gas_limit(gas_limit.to()); } - if let Some(data) = data { - // Note: `sig+args` and `data` are mutually exclusive - builder.set_data(hex::decode(data).wrap_err("Expected hex encoded function data")?); + + if let Some(nonce) = tx.nonce { + req.set_nonce(nonce.to()); } - let builder_output = builder.peek(); + let data = if let Some(sig) = sig { + parse_function_args(&sig, args, to, chain, &provider, etherscan_api_key).await?.0 + } else if let Some(data) = data { + // Note: `sig+args` and `data` are mutually exclusive + hex::decode(data)? + } else { + Vec::new() + }; + + req.set_input::(data.into()); let cast = Cast::new(&provider); - let access_list: String = cast.access_list(builder_output, block, to_json).await?; + let access_list: String = cast.access_list(&req, block, to_json).await?; println!("{}", access_list); diff --git a/crates/cast/bin/cmd/bind.rs b/crates/cast/bin/cmd/bind.rs index 3496302917392..73a62825ae7b4 100644 --- a/crates/cast/bin/cmd/bind.rs +++ b/crates/cast/bin/cmd/bind.rs @@ -10,7 +10,7 @@ static DEFAULT_CRATE_NAME: &str = "foundry-contracts"; static DEFAULT_CRATE_VERSION: &str = "0.0.1"; /// CLI arguments for `cast bind`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct BindArgs { /// The contract address, or the path to an ABI Directory /// @@ -18,7 +18,7 @@ pub struct BindArgs { path_or_address: String, /// Path to where bindings will be stored - #[clap( + #[arg( short, long, value_hint = ValueHint::DirPath, @@ -30,7 +30,7 @@ pub struct BindArgs { /// /// This should be a valid crates.io crate name. However, this is currently not validated by /// this command. - #[clap( + #[arg( long, default_value = DEFAULT_CRATE_NAME, value_name = "NAME" @@ -41,7 +41,7 @@ pub struct BindArgs { /// /// This should be a standard semver version string. However, it is not currently validated by /// this command. - #[clap( + #[arg( long, default_value = DEFAULT_CRATE_VERSION, value_name = "VERSION" @@ -49,10 +49,10 @@ pub struct BindArgs { crate_version: String, /// Generate bindings as separate files. - #[clap(long)] + #[arg(long)] separate_files: bool, - #[clap(flatten)] + #[command(flatten)] etherscan: EtherscanOpts, } diff --git a/crates/cast/bin/cmd/call.rs b/crates/cast/bin/cmd/call.rs index b5cc9e2b8a4b2..2caa54d2abc30 100644 --- a/crates/cast/bin/cmd/call.rs +++ b/crates/cast/bin/cmd/call.rs @@ -1,28 +1,24 @@ -use alloy_primitives::U256; -use cast::{Cast, TxBuilder}; +use alloy_network::TransactionBuilder; +use alloy_primitives::{TxKind, U256}; +use alloy_rpc_types::{BlockId, TransactionRequest, WithOtherFields}; +use cast::Cast; use clap::Parser; -use ethers_core::types::{BlockId, NameOrAddress}; -use eyre::{Result, WrapErr}; +use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, - utils::{self, handle_traces, parse_ether_value, TraceResult}, -}; -use foundry_common::{ - runtime_client::RuntimeClient, - types::{ToAlloy, ToEthers}, + utils::{self, handle_traces, parse_ether_value, parse_function_args, TraceResult}, }; +use foundry_common::ens::NameOrAddress; use foundry_compilers::EvmVersion; use foundry_config::{find_project_root_path, Config}; use foundry_evm::{executors::TracingExecutor, opts::EvmOpts}; use std::str::FromStr; -type Provider = ethers_providers::Provider; - /// CLI arguments for `cast call`. #[derive(Debug, Parser)] pub struct CallArgs { /// The destination of the transaction. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] to: Option, /// The signature of the function to call. @@ -32,62 +28,51 @@ pub struct CallArgs { args: Vec, /// Data for the transaction. - #[clap( + #[arg( long, - value_parser = foundry_common::clap_helpers::strip_0x_prefix, conflicts_with_all = &["sig", "args"] )] data: Option, /// Forks the remote rpc, executes the transaction locally and prints a trace - #[clap(long, default_value_t = false)] + #[arg(long, default_value_t = false)] trace: bool, - /// Can only be used with "--trace" - /// - /// opens an interactive debugger - #[clap(long, requires = "trace")] + /// Opens an interactive debugger. + /// Can only be used with `--trace`. + #[arg(long, requires = "trace")] debug: bool, - /// Can only be used with "--trace" - /// - /// prints a more verbose trace - #[clap(long, requires = "trace")] - verbose: bool, - - /// Can only be used with "--trace" - /// Labels to apply to the traces. - /// - /// Format: `address:label` - #[clap(long, requires = "trace")] + /// Labels to apply to the traces; format: `address:label`. + /// Can only be used with `--trace`. + #[arg(long, requires = "trace")] labels: Vec, - /// Can only be used with "--trace" - /// /// The EVM Version to use. - #[clap(long, requires = "trace")] + /// Can only be used with `--trace`. + #[arg(long, requires = "trace")] evm_version: Option, /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short)] + #[arg(long, short)] block: Option, - #[clap(subcommand)] + #[command(subcommand)] command: Option, - #[clap(flatten)] + #[command(flatten)] tx: TransactionOpts, - #[clap(flatten)] + #[command(flatten)] eth: EthereumOpts, } #[derive(Debug, Parser)] pub enum CallSubcommands { /// ignores the address field and simulates creating a contract - #[clap(name = "--create")] + #[command(name = "--create")] Create { /// Bytecode of contract. code: String, @@ -103,7 +88,7 @@ pub enum CallSubcommands { /// Either specified in wei, or as a string with a unit type. /// /// Examples: 1ether, 10gwei, 0.01ether - #[clap(long, value_parser = parse_ether_value)] + #[arg(long, value_parser = parse_ether_value)] value: Option, }, } @@ -122,7 +107,6 @@ impl CallArgs { trace, evm_version, debug, - verbose, labels, } = self; @@ -130,19 +114,43 @@ impl CallArgs { let provider = utils::get_provider(&config)?; let chain = utils::get_chain(config.chain, &provider).await?; let sender = eth.wallet.sender().await; + let etherscan_api_key = config.get_etherscan_api_key(Some(chain)); - let mut builder: TxBuilder<'_, Provider> = - TxBuilder::new(&provider, sender.to_ethers(), to, chain, tx.legacy).await?; + let to = match to { + Some(to) => Some(to.resolve(&provider).await?), + None => None, + }; - builder - .gas(tx.gas_limit) - .etherscan_api_key(config.get_etherscan_api_key(Some(chain))) - .gas_price(tx.gas_price) - .priority_gas_price(tx.priority_gas_price) - .nonce(tx.nonce); + let mut req = WithOtherFields::::default() + .with_to(to.unwrap_or_default()) + .with_from(sender) + .with_value(tx.value.unwrap_or_default()); - match command { + if let Some(nonce) = tx.nonce { + req.set_nonce(nonce.to()); + } + + let (data, func) = match command { Some(CallSubcommands::Create { code, sig, args, value }) => { + if let Some(value) = value { + req.set_value(value); + } + + let mut data = hex::decode(code)?; + + if let Some(s) = sig { + let (mut constructor_args, _) = parse_function_args( + &s, + args, + None, + chain, + &provider, + etherscan_api_key.as_deref(), + ) + .await?; + data.append(&mut constructor_args); + } + if trace { let figment = Config::figment_with_root(find_project_root_path(None).unwrap()) .merge(eth.rpc); @@ -152,31 +160,43 @@ impl CallArgs { let (env, fork, chain) = TracingExecutor::get_fork_material(&config, evm_opts).await?; - let mut executor = - foundry_evm::executors::TracingExecutor::new(env, fork, evm_version, debug) - .await; + let mut executor = TracingExecutor::new(env, fork, evm_version, debug); let trace = match executor.deploy( sender, - code.into_bytes().into(), - value.unwrap_or(U256::ZERO), + data.into(), + req.value.unwrap_or_default(), None, ) { Ok(deploy_result) => TraceResult::from(deploy_result), Err(evm_err) => TraceResult::try_from(evm_err)?, }; - handle_traces(trace, &config, chain, labels, verbose, debug).await?; + handle_traces(trace, &config, chain, labels, debug).await?; - return Ok(()) + return Ok(()); } - // fill the builder after the conditional so we dont move values - fill_create(&mut builder, value, code, sig, args).await?; + (data, None) } _ => { // fill first here because we need to use the builder in the conditional - fill_tx(&mut builder, tx.value, sig, args, data).await?; + let (data, func) = if let Some(sig) = sig { + parse_function_args( + &sig, + args, + to, + chain, + &provider, + etherscan_api_key.as_deref(), + ) + .await? + } else if let Some(data) = data { + // Note: `sig+args` and `data` are mutually exclusive + (hex::decode(data)?, None) + } else { + (Vec::new(), None) + }; if trace { let figment = Config::figment_with_root(find_project_root_path(None).unwrap()) @@ -187,75 +207,31 @@ impl CallArgs { let (env, fork, chain) = TracingExecutor::get_fork_material(&config, evm_opts).await?; - let mut executor = - foundry_evm::executors::TracingExecutor::new(env, fork, evm_version, debug) - .await; - - let (tx, _) = builder.build(); + let mut executor = TracingExecutor::new(env, fork, evm_version, debug); + let to = if let Some(TxKind::Call(to)) = req.to { Some(to) } else { None }; let trace = TraceResult::from(executor.call_raw_committing( sender, - tx.to_addr().copied().expect("an address to be here").to_alloy(), - tx.data().cloned().unwrap_or_default().to_vec().into(), - tx.value().copied().unwrap_or_default().to_alloy(), + to.expect("an address to be here"), + data.into(), + req.value.unwrap_or_default(), )?); - handle_traces(trace, &config, chain, labels, verbose, debug).await?; + handle_traces(trace, &config, chain, labels, debug).await?; - return Ok(()) + return Ok(()); } + + (data, func) } }; - let builder_output = builder.build(); - println!("{}", Cast::new(provider).call(builder_output, block).await?); - - Ok(()) - } -} - -/// fills the builder from create arg -async fn fill_create( - builder: &mut TxBuilder<'_, Provider>, - value: Option, - code: String, - sig: Option, - args: Vec, -) -> Result<()> { - builder.value(value); - - let mut data = hex::decode(code)?; - - if let Some(s) = sig { - let (mut sigdata, _func) = builder.create_args(&s, args).await?; - data.append(&mut sigdata); - } + req.set_input(data); - builder.set_data(data); - - Ok(()) -} + println!("{}", Cast::new(provider).call(&req, func.as_ref(), block).await?); -/// fills the builder from args -async fn fill_tx( - builder: &mut TxBuilder<'_, Provider>, - value: Option, - sig: Option, - args: Vec, - data: Option, -) -> Result<()> { - builder.value(value); - - if let Some(sig) = sig { - builder.set_args(sig.as_str(), args).await?; - } - - if let Some(data) = data { - // Note: `sig+args` and `data` are mutually exclusive - builder.set_data(hex::decode(data).wrap_err("Expected hex encoded function data")?); + Ok(()) } - - Ok(()) } #[cfg(test)] @@ -266,11 +242,11 @@ mod tests { #[test] fn can_parse_call_data() { let data = hex::encode("hello"); - let args: CallArgs = - CallArgs::parse_from(["foundry-cli", "--data", format!("0x{data}").as_str()]); - assert_eq!(args.data, Some(data.clone())); + let args = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]); + assert_eq!(args.data, Some(data)); - let args: CallArgs = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]); + let data = hex::encode_prefixed("hello"); + let args = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]); assert_eq!(args.data, Some(data)); } diff --git a/crates/cast/bin/cmd/create2.rs b/crates/cast/bin/cmd/create2.rs index 44f9bef1bf143..c841d171089e3 100644 --- a/crates/cast/bin/cmd/create2.rs +++ b/crates/cast/bin/cmd/create2.rs @@ -16,10 +16,10 @@ use std::{ const DEPLOYER: &str = "0x4e59b44847b379578588920ca78fbf26c0b4956c"; /// CLI arguments for `cast create2`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct Create2Args { /// Prefix for the contract address. - #[clap( + #[arg( long, short, required_unless_present_any = &["ends_with", "matching"], @@ -28,19 +28,19 @@ pub struct Create2Args { starts_with: Option, /// Suffix for the contract address. - #[clap(long, short, value_name = "HEX")] + #[arg(long, short, value_name = "HEX")] ends_with: Option, /// Sequence that the address has to match. - #[clap(long, short, value_name = "HEX")] + #[arg(long, short, value_name = "HEX")] matching: Option, /// Case sensitive matching. - #[clap(short, long)] + #[arg(short, long)] case_sensitive: bool, /// Address of the contract deployer. - #[clap( + #[arg( short, long, default_value = DEPLOYER, @@ -49,27 +49,27 @@ pub struct Create2Args { deployer: Address, /// Init code of the contract to be deployed. - #[clap(short, long, value_name = "HEX")] + #[arg(short, long, value_name = "HEX")] init_code: Option, /// Init code hash of the contract to be deployed. - #[clap(alias = "ch", long, value_name = "HASH", required_unless_present = "init_code")] + #[arg(alias = "ch", long, value_name = "HASH", required_unless_present = "init_code")] init_code_hash: Option, /// Number of threads to use. Defaults to and caps at the number of logical cores. - #[clap(short, long)] + #[arg(short, long)] jobs: Option, /// Address of the caller. Used for the first 20 bytes of the salt. - #[clap(long, value_name = "ADDRESS")] + #[arg(long, value_name = "ADDRESS")] caller: Option
, /// The random number generator's seed, used to initialize the salt. - #[clap(long, value_name = "HEX")] + #[arg(long, value_name = "HEX")] seed: Option, /// Don't initialize the salt with a random value, and instead use the default value of 0. - #[clap(long, conflicts_with = "seed")] + #[arg(long, conflicts_with = "seed")] no_random: bool, } @@ -135,14 +135,12 @@ impl Create2Args { let regex = RegexSetBuilder::new(regexs).case_insensitive(!case_sensitive).build()?; let init_code_hash = if let Some(init_code_hash) = init_code_hash { - let mut hash: [u8; 32] = [0; 32]; - hex::decode_to_slice(init_code_hash, &mut hash)?; - hash.into() + hex::FromHex::from_hex(init_code_hash) } else if let Some(init_code) = init_code { - keccak256(hex::decode(init_code)?) + hex::decode(init_code).map(keccak256) } else { unreachable!(); - }; + }?; let mut n_threads = std::thread::available_parallelism().map_or(1, |n| n.get()); if let Some(jobs) = jobs { @@ -168,7 +166,11 @@ impl Create2Args { rng.fill_bytes(remaining); } - println!("Starting to generate deterministic contract address..."); + println!("Configuration:"); + println!("Init code hash: {init_code_hash}"); + println!("Regex patterns: {:?}", regex.patterns()); + println!(); + println!("Starting to generate deterministic contract address with {n_threads} threads..."); let mut handles = Vec::with_capacity(n_threads); let found = Arc::new(AtomicBool::new(false)); let timer = Instant::now(); @@ -201,9 +203,9 @@ impl Create2Args { // Calculate the `CREATE2` address. #[allow(clippy::needless_borrows_for_generic_args)] - let addr = deployer.create2(&salt.0, init_code_hash); + let addr = deployer.create2(&salt.0, &init_code_hash); - // Check if the the regex matches the calculated address' checksum. + // Check if the regex matches the calculated address' checksum. let _ = addr.to_checksum_raw(&mut checksum, None); // SAFETY: stripping 2 ASCII bytes ("0x") off of an already valid UTF-8 string // is safe. @@ -211,7 +213,7 @@ impl Create2Args { if regex.matches(s).into_iter().count() == regex_len { // Notify other threads that we found a result. found.store(true, Ordering::Relaxed); - break Some((salt.0, addr)) + break Some((addr, salt.0)); } // Increment the salt for the next iteration. @@ -221,15 +223,11 @@ impl Create2Args { } let results = handles.into_iter().filter_map(|h| h.join().unwrap()).collect::>(); - println!("Successfully found contract address(es) in {:?}", timer.elapsed()); - for (i, (salt, address)) in results.iter().enumerate() { - if i > 0 { - println!("---"); - } - println!("Address: {address}\nSalt: {salt} ({})", U256::from_be_bytes(salt.0)); - } + let (address, salt) = results.into_iter().next().unwrap(); + println!("Successfully found contract address in {:?}", timer.elapsed()); + println!("Address: {address}"); + println!("Salt: {salt} ({})", U256::from_be_bytes(salt.0)); - let (salt, address) = results.into_iter().next().unwrap(); Ok(Create2Output { address, salt }) } } diff --git a/crates/cast/bin/cmd/estimate.rs b/crates/cast/bin/cmd/estimate.rs index c3352cab35b85..b0f0d6fcf27df 100644 --- a/crates/cast/bin/cmd/estimate.rs +++ b/crates/cast/bin/cmd/estimate.rs @@ -1,12 +1,14 @@ +use alloy_network::TransactionBuilder; use alloy_primitives::U256; -use cast::{Cast, TxBuilder}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockId, TransactionRequest, WithOtherFields}; use clap::Parser; -use ethers_core::types::NameOrAddress; use eyre::Result; use foundry_cli::{ opts::{EtherscanOpts, RpcOpts}, - utils::{self, parse_ether_value}, + utils::{self, parse_ether_value, parse_function_args}, }; +use foundry_common::ens::NameOrAddress; use foundry_config::{figment::Figment, Config}; use std::str::FromStr; @@ -14,7 +16,7 @@ use std::str::FromStr; #[derive(Debug, Parser)] pub struct EstimateArgs { /// The destination of the transaction. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] to: Option, /// The signature of the function to call. @@ -24,7 +26,7 @@ pub struct EstimateArgs { args: Vec, /// The sender account. - #[clap( + #[arg( short, long, value_parser = NameOrAddress::from_str, @@ -38,23 +40,23 @@ pub struct EstimateArgs { /// Either specified in wei, or as a string with a unit type: /// /// Examples: 1ether, 10gwei, 0.01ether - #[clap(long, value_parser = parse_ether_value)] + #[arg(long, value_parser = parse_ether_value)] value: Option, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, - #[clap(flatten)] + #[command(flatten)] etherscan: EtherscanOpts, - #[clap(subcommand)] + #[command(subcommand)] command: Option, } #[derive(Debug, Parser)] pub enum EstimateSubcommands { /// Estimate gas cost to deploy a smart contract - #[clap(name = "--create")] + #[command(name = "--create")] Create { /// The bytecode of contract code: String, @@ -70,7 +72,7 @@ pub enum EstimateSubcommands { /// Either specified in wei, or as a string with a unit type: /// /// Examples: 1ether, 10gwei, 0.01ether - #[clap(long, value_parser = parse_ether_value)] + #[arg(long, value_parser = parse_ether_value)] value: Option, }, } @@ -80,36 +82,48 @@ impl EstimateArgs { let EstimateArgs { from, to, sig, args, value, rpc, etherscan, command } = self; let figment = Figment::from(Config::figment()).merge(etherscan).merge(rpc); - let config = Config::from_provider(figment); - + let config = Config::try_from(figment)?; let provider = utils::get_provider(&config)?; let chain = utils::get_chain(config.chain, &provider).await?; let api_key = config.get_etherscan_api_key(Some(chain)); - let mut builder = TxBuilder::new(&provider, from, to, chain, false).await?; - builder.etherscan_api_key(api_key); + let from = from.resolve(&provider).await?; + let to = match to { + Some(to) => Some(to.resolve(&provider).await?), + None => None, + }; + + let mut req = WithOtherFields::::default() + .with_to(to.unwrap_or_default()) + .with_from(from) + .with_value(value.unwrap_or_default()); - match command { + let data = match command { Some(EstimateSubcommands::Create { code, sig, args, value }) => { - builder.value(value); + if let Some(value) = value { + req.set_value(value); + } let mut data = hex::decode(code)?; if let Some(s) = sig { - let (mut sigdata, _func) = builder.create_args(&s, args).await?; - data.append(&mut sigdata); + let (mut constructor_args, _) = + parse_function_args(&s, args, to, chain, &provider, api_key.as_deref()) + .await?; + data.append(&mut constructor_args); } - builder.set_data(data); + data } _ => { let sig = sig.ok_or_else(|| eyre::eyre!("Function signature must be provided."))?; - builder.value(value).set_args(sig.as_str(), args).await?; + parse_function_args(&sig, args, to, chain, &provider, api_key.as_deref()).await?.0 } }; - let builder_output = builder.peek(); - let gas = Cast::new(&provider).estimate(builder_output).await?; + req.set_input(data); + + let gas = provider.estimate_gas(&req, BlockId::latest()).await?; println!("{gas}"); Ok(()) } diff --git a/crates/cast/bin/cmd/find_block.rs b/crates/cast/bin/cmd/find_block.rs index 6186e47a6741b..f75f2c82f2634 100644 --- a/crates/cast/bin/cmd/find_block.rs +++ b/crates/cast/bin/cmd/find_block.rs @@ -1,20 +1,18 @@ -use alloy_primitives::{U256, U64}; +use alloy_provider::Provider; use cast::Cast; use clap::Parser; -use ethers_providers::Middleware; use eyre::Result; use foundry_cli::{opts::RpcOpts, utils}; -use foundry_common::types::{ToAlloy, ToEthers}; use foundry_config::Config; use futures::join; /// CLI arguments for `cast find-block`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct FindBlockArgs { /// The UNIX timestamp to search for, in seconds. timestamp: u64, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, } @@ -22,7 +20,7 @@ impl FindBlockArgs { pub async fn run(self) -> Result<()> { let FindBlockArgs { timestamp, rpc } = self; - let ts_target = U256::from(timestamp); + let ts_target = timestamp; let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; @@ -30,43 +28,43 @@ impl FindBlockArgs { let cast_provider = Cast::new(provider); let res = join!(cast_provider.timestamp(last_block_num), cast_provider.timestamp(1)); - let ts_block_latest = res.0?; - let ts_block_1 = res.1?; + let ts_block_latest: u64 = res.0?.to(); + let ts_block_1: u64 = res.1?.to(); let block_num = if ts_block_latest < ts_target { // If the most recent block's timestamp is below the target, return it - last_block_num.to_alloy() + last_block_num } else if ts_block_1 > ts_target { // If the target timestamp is below block 1's timestamp, return that - U64::from(1_u64) + 1 } else { // Otherwise, find the block that is closest to the timestamp - let mut low_block = U64::from(1_u64); // block 0 has a timestamp of 0: https://github.com/ethereum/go-ethereum/issues/17042#issuecomment-559414137 - let mut high_block = last_block_num.to_alloy(); - let mut matching_block: Option = None; + let mut low_block = 1_u64; // block 0 has a timestamp of 0: https://github.com/ethereum/go-ethereum/issues/17042#issuecomment-559414137 + let mut high_block = last_block_num; + let mut matching_block = None; while high_block > low_block && matching_block.is_none() { // Get timestamp of middle block (this approach approach to avoids overflow) let high_minus_low_over_2 = high_block .checked_sub(low_block) .ok_or_else(|| eyre::eyre!("unexpected underflow")) .unwrap() - .checked_div(U64::from(2_u64)) + .checked_div(2_u64) .unwrap(); let mid_block = high_block.checked_sub(high_minus_low_over_2).unwrap(); - let ts_mid_block = cast_provider.timestamp(mid_block.to_ethers()).await?; + let ts_mid_block = cast_provider.timestamp(mid_block).await?.to::(); // Check if we've found a match or should keep searching if ts_mid_block == ts_target { matching_block = Some(mid_block) - } else if high_block.checked_sub(low_block).unwrap() == U64::from(1_u64) { + } else if high_block.checked_sub(low_block).unwrap() == 1_u64 { // The target timestamp is in between these blocks. This rounds to the // highest block if timestamp is equidistant between blocks let res = join!( - cast_provider.timestamp(high_block.to_ethers()), - cast_provider.timestamp(low_block.to_ethers()) + cast_provider.timestamp(high_block), + cast_provider.timestamp(low_block) ); - let ts_high = res.0.unwrap(); - let ts_low = res.1.unwrap(); + let ts_high: u64 = res.0.unwrap().to(); + let ts_low: u64 = res.1.unwrap().to(); let high_diff = ts_high.checked_sub(ts_target).unwrap(); let low_diff = ts_target.checked_sub(ts_low).unwrap(); let is_low = low_diff < high_diff; diff --git a/crates/cast/bin/cmd/interface.rs b/crates/cast/bin/cmd/interface.rs index cc2b7290cc97a..14de351f202b7 100644 --- a/crates/cast/bin/cmd/interface.rs +++ b/crates/cast/bin/cmd/interface.rs @@ -8,7 +8,7 @@ use itertools::Itertools; use std::path::{Path, PathBuf}; /// CLI arguments for `cast interface`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct InterfaceArgs { /// The contract address, or the path to an ABI file. /// @@ -16,17 +16,17 @@ pub struct InterfaceArgs { path_or_address: String, /// The name to use for the generated interface. - #[clap(long, short)] + #[arg(long, short)] name: Option, /// Solidity pragma version. - #[clap(long, short, default_value = "^0.8.4", value_name = "VERSION")] + #[arg(long, short, default_value = "^0.8.4", value_name = "VERSION")] pragma: String, /// The path to the output file. /// /// If not specified, the interface will be output to stdout. - #[clap( + #[arg( short, long, value_hint = clap::ValueHint::FilePath, @@ -35,10 +35,10 @@ pub struct InterfaceArgs { output: Option, /// If specified, the interface will be output as JSON rather than Solidity. - #[clap(long, short)] + #[arg(long, short)] json: bool, - #[clap(flatten)] + #[command(flatten)] etherscan: EtherscanOpts, } diff --git a/crates/cast/bin/cmd/logs.rs b/crates/cast/bin/cmd/logs.rs index e7e6aed5d9b7b..85f0c8414d67d 100644 --- a/crates/cast/bin/cmd/logs.rs +++ b/crates/cast/bin/cmd/logs.rs @@ -1,18 +1,15 @@ +use alloy_dyn_abi::{DynSolType, DynSolValue, Specifier}; +use alloy_json_abi::Event; +use alloy_network::AnyNetwork; +use alloy_primitives::{Address, B256}; +use alloy_rpc_types::{BlockId, BlockNumberOrTag, Filter, FilterBlockOption, FilterSet, Topic}; use cast::Cast; use clap::Parser; -use ethers_core::{ - abi::{ - token::{LenientTokenizer, StrictTokenizer, Tokenizer}, - Address, Event, HumanReadableParser, ParamType, RawTopicFilter, Token, Topic, TopicFilter, - }, - types::{ - BlockId, BlockNumber, Filter, FilterBlockOption, NameOrAddress, ValueOrArray, H256, U256, - }, -}; -use ethers_providers::Middleware; -use eyre::{Result, WrapErr}; +use eyre::Result; use foundry_cli::{opts::EthereumOpts, utils}; +use foundry_common::ens::NameOrAddress; use foundry_config::Config; +use hex::FromHex; use itertools::Itertools; use std::{io, str::FromStr}; @@ -22,17 +19,17 @@ pub struct LogsArgs { /// The block height to start query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long)] + #[arg(long)] from_block: Option, /// The block height to stop query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long)] + #[arg(long)] to_block: Option, /// The contract address to filter on. - #[clap( + #[arg( long, value_parser = NameOrAddress::from_str )] @@ -40,24 +37,24 @@ pub struct LogsArgs { /// The signature of the event to filter logs by which will be converted to the first topic or /// a topic to filter on. - #[clap(value_name = "SIG_OR_TOPIC")] + #[arg(value_name = "SIG_OR_TOPIC")] sig_or_topic: Option, /// If used with a signature, the indexed fields of the event to filter by. Otherwise, the /// remaining topics of the filter. - #[clap(value_name = "TOPICS_OR_ARGS")] + #[arg(value_name = "TOPICS_OR_ARGS")] topics_or_args: Vec, /// If the RPC type and endpoints supports `eth_subscribe` stream logs instead of printing and /// exiting. Will continue until interrupted or TO_BLOCK is reached. - #[clap(long)] + #[arg(long)] subscribe: bool, /// Print the logs as JSON.s - #[clap(long, short, help_heading = "Display options")] + #[arg(long, short, help_heading = "Display options")] json: bool, - #[clap(flatten)] + #[command(flatten)] eth: EthereumOpts, } @@ -80,18 +77,14 @@ impl LogsArgs { let cast = Cast::new(&provider); let address = match address { - Some(address) => { - let address = match address { - NameOrAddress::Name(name) => provider.resolve_name(&name).await?, - NameOrAddress::Address(address) => address, - }; - Some(address) - } + Some(address) => Some(address.resolve(&provider).await?), None => None, }; - let from_block = cast.convert_block_number(from_block).await?; - let to_block = cast.convert_block_number(to_block).await?; + let from_block = + cast.convert_block_number(Some(from_block.unwrap_or_else(BlockId::earliest))).await?; + let to_block = + cast.convert_block_number(Some(to_block.unwrap_or_else(BlockId::latest))).await?; let filter = build_filter(from_block, to_block, address, sig_or_topic, topics_or_args)?; @@ -103,6 +96,14 @@ impl LogsArgs { return Ok(()) } + // FIXME: this is a hotfix for + // currently the alloy `eth_subscribe` impl does not work with all transports, so we use + // the builtin transport here for now + let url = config.get_rpc_url_or_localhost_http()?; + let provider = alloy_provider::ProviderBuilder::<_, _, AnyNetwork>::default() + .on_builtin(url.as_ref()) + .await?; + let cast = Cast::new(&provider); let mut stdout = io::stdout(); cast.subscribe(filter, &mut stdout, json).await?; @@ -114,47 +115,36 @@ impl LogsArgs { /// successful, `topics_or_args` is parsed as indexed inputs and converted to topics. Otherwise, /// `sig_or_topic` is prepended to `topics_or_args` and used as raw topics. fn build_filter( - from_block: Option, - to_block: Option, + from_block: Option, + to_block: Option, address: Option
, sig_or_topic: Option, topics_or_args: Vec, ) -> Result { let block_option = FilterBlockOption::Range { from_block, to_block }; - let topic_filter = match sig_or_topic { + let filter = match sig_or_topic { // Try and parse the signature as an event signature - Some(sig_or_topic) => match HumanReadableParser::parse_event(sig_or_topic.as_str()) { + Some(sig_or_topic) => match foundry_common::abi::get_event(sig_or_topic.as_str()) { Ok(event) => build_filter_event_sig(event, topics_or_args)?, Err(_) => { let topics = [vec![sig_or_topic], topics_or_args].concat(); build_filter_topics(topics)? } }, - None => TopicFilter::default(), + None => Filter::default(), }; - // Convert from TopicFilter to Filter - let topics = - vec![topic_filter.topic0, topic_filter.topic1, topic_filter.topic2, topic_filter.topic3] - .into_iter() - .map(|topic| match topic { - Topic::Any => None, - Topic::This(topic) => Some(ValueOrArray::Value(Some(topic))), - _ => unreachable!(), - }) - .collect::>(); - - let filter = Filter { - block_option, - address: address.map(ValueOrArray::Value), - topics: [topics[0].clone(), topics[1].clone(), topics[2].clone(), topics[3].clone()], - }; + let mut filter = filter.select(block_option); + + if let Some(address) = address { + filter = filter.address(address) + } Ok(filter) } -/// Creates a TopicFilter from the given event signature and arguments. -fn build_filter_event_sig(event: Event, args: Vec) -> Result { +/// Creates a [Filter] from the given event signature and arguments. +fn build_filter_event_sig(event: Event, args: Vec) -> Result { let args = args.iter().map(|arg| arg.as_str()).collect::>(); // Match the args to indexed inputs. Enumerate so that the ordering can be restored @@ -164,129 +154,75 @@ fn build_filter_event_sig(event: Event, args: Vec) -> Result>>()? + .into_iter() .enumerate() .partition(|(_, (_, arg))| !arg.is_empty()); // Only parse the inputs with arguments - let indexed_tokens = parse_params(with_args.iter().map(|(_, p)| *p), true)?; + let indexed_tokens = with_args + .iter() + .map(|(_, (kind, arg))| kind.coerce_str(arg)) + .collect::, _>>()?; // Merge the inputs restoring the original ordering - let mut tokens = with_args + let mut topics = with_args .into_iter() .zip(indexed_tokens) .map(|((i, _), t)| (i, Some(t))) .chain(without_args.into_iter().map(|(i, _)| (i, None))) .sorted_by(|(i1, _), (i2, _)| i1.cmp(i2)) - .map(|(_, token)| token) - .collect::>(); + .map(|(_, token)| { + token + .map(|token| Topic::from(B256::from_slice(token.abi_encode().as_slice()))) + .unwrap_or(Topic::default()) + }) + .collect::>(); - tokens.resize(3, None); + topics.resize(3, Topic::default()); - let raw = RawTopicFilter { - topic0: tokens[0].clone().map_or(Topic::Any, Topic::This), - topic1: tokens[1].clone().map_or(Topic::Any, Topic::This), - topic2: tokens[2].clone().map_or(Topic::Any, Topic::This), - }; + let filter = Filter::new() + .event_signature(event.selector()) + .topic1(topics[0].clone()) + .topic2(topics[1].clone()) + .topic3(topics[2].clone()); - // Let filter do the hardwork of converting arguments to topics - Ok(event.filter(raw)?) + Ok(filter) } -/// Creates a TopicFilter from raw topic hashes. -fn build_filter_topics(topics: Vec) -> Result { +/// Creates a [Filter] from raw topic hashes. +fn build_filter_topics(topics: Vec) -> Result { let mut topics = topics .into_iter() - .map(|topic| if topic.is_empty() { Ok(None) } else { H256::from_str(&topic).map(Some) }) - .collect::, _>>()?; - - topics.resize(4, None); - - Ok(TopicFilter { - topic0: topics[0].map_or(Topic::Any, Topic::This), - topic1: topics[1].map_or(Topic::Any, Topic::This), - topic2: topics[2].map_or(Topic::Any, Topic::This), - topic3: topics[3].map_or(Topic::Any, Topic::This), - }) -} - -fn parse_params<'a, I: IntoIterator>( - params: I, - lenient: bool, -) -> eyre::Result> { - let mut tokens = Vec::new(); - - for (param, value) in params { - let mut token = if lenient { - LenientTokenizer::tokenize(param, value) - } else { - StrictTokenizer::tokenize(param, value) - }; - if token.is_err() && value.starts_with("0x") { - match param { - ParamType::FixedBytes(32) => { - if value.len() < 66 { - let padded_value = [value, &"0".repeat(66 - value.len())].concat(); - token = if lenient { - LenientTokenizer::tokenize(param, &padded_value) - } else { - StrictTokenizer::tokenize(param, &padded_value) - }; - } - } - ParamType::Uint(_) => { - // try again if value is hex - if let Ok(value) = U256::from_str(value).map(|v| v.to_string()) { - token = if lenient { - LenientTokenizer::tokenize(param, &value) - } else { - StrictTokenizer::tokenize(param, &value) - }; - } - } - // TODO: Not sure what to do here. Put the no effect in for now, but that is not - // ideal. We could attempt massage for every value type? - _ => {} + .map(|topic| { + if topic.is_empty() { + Ok(Topic::default()) + } else { + Ok(Topic::from(B256::from_hex(topic.as_str())?)) } - } + }) + .collect::>>>()?; - let token = token.map(sanitize_token).wrap_err_with(|| { - format!("Failed to parse `{value}`, expected value of type: {param}") - })?; - tokens.push(token); - } - Ok(tokens) -} + topics.resize(4, Topic::default()); -pub fn sanitize_token(token: Token) -> Token { - match token { - Token::Array(tokens) => { - let mut sanitized = Vec::with_capacity(tokens.len()); - for token in tokens { - let token = match token { - Token::String(val) => { - let val = match val.as_str() { - // this is supposed to be an empty string - "\"\"" | "''" => String::new(), - _ => val, - }; - Token::String(val) - } - _ => sanitize_token(token), - }; - sanitized.push(token) - } - Token::Array(sanitized) - } - _ => token, - } + let filter = Filter::new() + .event_signature(topics[0].clone()) + .topic1(topics[1].clone()) + .topic2(topics[2].clone()) + .topic3(topics[3].clone()); + + Ok(filter) } #[cfg(test)] mod tests { use super::*; - use ethers_core::types::{H160, H256}; - use std::str::FromStr; + use alloy_primitives::{U160, U256 as rU256}; + use alloy_rpc_types::ValueOrArray; const ADDRESS: &str = "0x4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38"; const TRANSFER_SIG: &str = "Transfer(address indexed,address indexed,uint256)"; @@ -295,13 +231,13 @@ mod tests { #[test] fn test_build_filter_basic() { - let from_block = Some(BlockNumber::from(1337)); - let to_block = Some(BlockNumber::Latest); + let from_block = Some(BlockNumberOrTag::from(1337)); + let to_block = Some(BlockNumberOrTag::Latest); let address = Address::from_str(ADDRESS).ok(); let expected = Filter { block_option: FilterBlockOption::Range { from_block, to_block }, - address: Some(ValueOrArray::Value(address.unwrap())), - topics: [None, None, None, None], + address: ValueOrArray::Value(address.unwrap()).into(), + topics: [vec![].into(), vec![].into(), vec![].into(), vec![].into()], }; let filter = build_filter(from_block, to_block, address, None, vec![]).unwrap(); assert_eq!(filter, expected) @@ -311,8 +247,13 @@ mod tests { fn test_build_filter_sig() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, - address: None, - topics: [Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), None, None, None], + address: vec![].into(), + topics: [ + B256::from_str(TRANSFER_TOPIC).unwrap().into(), + vec![].into(), + vec![].into(), + vec![].into(), + ], }; let filter = build_filter(None, None, None, Some(TRANSFER_SIG.to_string()), vec![]).unwrap(); @@ -323,8 +264,13 @@ mod tests { fn test_build_filter_mismatch() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, - address: None, - topics: [Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), None, None, None], + address: vec![].into(), + topics: [ + B256::from_str(TRANSFER_TOPIC).unwrap().into(), + vec![].into(), + vec![].into(), + vec![].into(), + ], }; let filter = build_filter( None, @@ -339,14 +285,16 @@ mod tests { #[test] fn test_build_filter_sig_with_arguments() { + let addr = Address::from_str(ADDRESS).unwrap(); + let addr = rU256::from(U160::from_be_bytes(addr.0 .0)); let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, - address: None, + address: vec![].into(), topics: [ - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - Some(H160::from_str(ADDRESS).unwrap().into()), - None, - None, + B256::from_str(TRANSFER_TOPIC).unwrap().into(), + addr.into(), + vec![].into(), + vec![].into(), ], }; let filter = build_filter( @@ -362,14 +310,16 @@ mod tests { #[test] fn test_build_filter_sig_with_skipped_arguments() { + let addr = Address::from_str(ADDRESS).unwrap(); + let addr = rU256::from(U160::from_be_bytes(addr.0 .0)); let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, - address: None, + address: vec![].into(), topics: [ - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - None, - Some(H160::from_str(ADDRESS).unwrap().into()), - None, + vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), + vec![].into(), + addr.into(), + vec![].into(), ], }; let filter = build_filter( @@ -387,12 +337,12 @@ mod tests { fn test_build_filter_with_topics() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, - address: None, + address: vec![].into(), topics: [ - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - None, - None, + vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), + vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), + vec![].into(), + vec![].into(), ], }; let filter = build_filter( @@ -411,12 +361,12 @@ mod tests { fn test_build_filter_with_skipped_topic() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, - address: None, + address: vec![].into(), topics: [ - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - None, - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - None, + vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), + vec![].into(), + vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), + vec![].into(), ], }; let filter = build_filter( @@ -444,7 +394,7 @@ mod tests { .unwrap() .to_string(); - assert_eq!(err, "Failed to parse `1234`, expected value of type: address"); + assert_eq!(err, "parser error:\n1234\n^\nInvalid string length"); } #[test] @@ -454,7 +404,7 @@ mod tests { .unwrap() .to_string(); - assert_eq!(err, "Invalid character 's' at position 1"); + assert_eq!(err, "Odd number of digits"); } #[test] @@ -464,7 +414,7 @@ mod tests { .unwrap() .to_string(); - assert_eq!(err, "Invalid input length"); + assert_eq!(err, "Invalid string length"); } #[test] @@ -480,6 +430,6 @@ mod tests { .unwrap() .to_string(); - assert_eq!(err, "Invalid input length"); + assert_eq!(err, "Invalid string length"); } } diff --git a/crates/cast/bin/cmd/mktx.rs b/crates/cast/bin/cmd/mktx.rs new file mode 100644 index 0000000000000..e26115da32ab5 --- /dev/null +++ b/crates/cast/bin/cmd/mktx.rs @@ -0,0 +1,109 @@ +use crate::tx; +use alloy_network::{eip2718::Encodable2718, EthereumSigner, TransactionBuilder}; +use alloy_primitives::U64; +use alloy_provider::Provider; +use alloy_rpc_types::BlockId; +use alloy_signer::Signer; +use clap::Parser; +use eyre::Result; +use foundry_cli::{ + opts::{EthereumOpts, TransactionOpts}, + utils::{self, get_provider}, +}; +use foundry_common::ens::NameOrAddress; +use foundry_config::Config; +use std::str::FromStr; + +/// CLI arguments for `cast mktx`. +#[derive(Debug, Parser)] +pub struct MakeTxArgs { + /// The destination of the transaction. + /// + /// If not provided, you must use `cast mktx --create`. + #[arg(value_parser = NameOrAddress::from_str)] + to: Option, + + /// The signature of the function to call. + sig: Option, + + /// The arguments of the function to call. + args: Vec, + + /// Reuse the latest nonce for the sender account. + #[arg(long, conflicts_with = "nonce")] + resend: bool, + + #[command(subcommand)] + command: Option, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + eth: EthereumOpts, +} + +#[derive(Debug, Parser)] +pub enum MakeTxSubcommands { + /// Use to deploy raw contract bytecode. + #[command(name = "--create")] + Create { + /// The initialization bytecode of the contract to deploy. + code: String, + + /// The signature of the constructor. + sig: Option, + + /// The constructor arguments. + args: Vec, + }, +} + +impl MakeTxArgs { + pub async fn run(self) -> Result<()> { + let MakeTxArgs { to, mut sig, mut args, resend, command, mut tx, eth } = self; + + let code = if let Some(MakeTxSubcommands::Create { + code, + sig: constructor_sig, + args: constructor_args, + }) = command + { + sig = constructor_sig; + args = constructor_args; + Some(code) + } else { + None + }; + + tx::validate_to_address(&code, &to)?; + + let config = Config::from(ð); + let provider = utils::get_provider(&config)?; + let chain = utils::get_chain(config.chain, &provider).await?; + let api_key = config.get_etherscan_api_key(Some(chain)); + + // Retrieve the signer, and bail if it can't be constructed. + let signer = eth.wallet.signer().await?; + let from = signer.address(); + + tx::validate_from_address(eth.wallet.from, from)?; + + if resend { + tx.nonce = + Some(U64::from(provider.get_transaction_count(from, BlockId::latest()).await?)); + } + + let provider = get_provider(&config)?; + + let (tx, _) = + tx::build_tx(&provider, from, to, code, sig, args, tx, chain, api_key).await?; + + let tx = tx.build(&EthereumSigner::new(signer)).await?; + + let signed_tx = hex::encode(tx.encoded_2718()); + println!("0x{signed_tx}"); + + Ok(()) + } +} diff --git a/crates/cast/bin/cmd/mod.rs b/crates/cast/bin/cmd/mod.rs index bd1c8ddf6a079..6c904417407c2 100644 --- a/crates/cast/bin/cmd/mod.rs +++ b/crates/cast/bin/cmd/mod.rs @@ -13,6 +13,7 @@ pub mod estimate; pub mod find_block; pub mod interface; pub mod logs; +pub mod mktx; pub mod rpc; pub mod run; pub mod send; diff --git a/crates/cast/bin/cmd/rpc.rs b/crates/cast/bin/cmd/rpc.rs index 78c0105de6c96..9dffcfd18de53 100644 --- a/crates/cast/bin/cmd/rpc.rs +++ b/crates/cast/bin/cmd/rpc.rs @@ -6,7 +6,7 @@ use foundry_config::Config; use itertools::Itertools; /// CLI arguments for `cast rpc`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct RpcArgs { /// RPC method name method: String, @@ -26,10 +26,10 @@ pub struct RpcArgs { /// /// cast rpc eth_getBlockByNumber '["0x123", false]' --raw /// => {"method": "eth_getBlockByNumber", "params": ["0x123", false] ... } - #[clap(long, short = 'w')] + #[arg(long, short = 'w')] raw: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, } diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index 12237a6d6d952..599d73247f73b 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -1,14 +1,16 @@ use alloy_primitives::U256; +use alloy_provider::Provider; +use alloy_rpc_types::BlockTransactions; +use cast::revm::primitives::EnvWithHandlerCfg; use clap::Parser; -use ethers_providers::Middleware; use eyre::{Result, WrapErr}; use foundry_cli::{ init_progress, opts::RpcOpts, - update_progress, utils, + update_progress, utils::{handle_traces, TraceResult}, }; -use foundry_common::{is_known_system_sender, types::ToAlloy, SYSTEM_TRANSACTION_TYPE}; +use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE}; use foundry_compilers::EvmVersion; use foundry_config::{find_project_root_path, Config}; use foundry_evm::{ @@ -18,49 +20,50 @@ use foundry_evm::{ }; /// CLI arguments for `cast run`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct RunArgs { /// The transaction hash. tx_hash: String, /// Opens the transaction in the debugger. - #[clap(long, short)] + #[arg(long, short)] debug: bool, /// Print out opcode traces. - #[clap(long, short)] + #[arg(long, short)] trace_printer: bool, /// Executes the transaction only with the state from the previous block. /// /// May result in different results than the live execution! - #[clap(long, short)] + #[arg(long, short)] quick: bool, /// Prints the full address of the contract. - #[clap(long, short)] + #[arg(long, short)] verbose: bool, /// Label addresses in the trace. /// /// Example: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045:vitalik.eth - #[clap(long, short)] + #[arg(long, short)] label: Vec, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, - /// The evm version to use. + /// The EVM version to use. /// /// Overrides the version specified in the config. - #[clap(long, short)] + #[arg(long, short)] evm_version: Option, + /// Sets the number of assumed available compute units per second for this provider /// /// default value: 330 /// /// See also, https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second - #[clap(long, alias = "cups", value_name = "CUPS")] + #[arg(long, alias = "cups", value_name = "CUPS")] pub compute_units_per_second: Option, /// Disables rate limiting for this node's provider. @@ -68,7 +71,7 @@ pub struct RunArgs { /// default value: false /// /// See also, https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second - #[clap(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rpc-rate-limit")] + #[arg(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rpc-rate-limit")] pub no_rate_limit: bool, } @@ -82,56 +85,68 @@ impl RunArgs { let figment = Config::figment_with_root(find_project_root_path(None).unwrap()).merge(self.rpc); let evm_opts = figment.extract::()?; - let mut config = Config::from_provider(figment).sanitized(); + let mut config = Config::try_from(figment)?.sanitized(); let compute_units_per_second = if self.no_rate_limit { Some(u64::MAX) } else { self.compute_units_per_second }; - let provider = utils::get_provider_builder(&config)? - .compute_units_per_second_opt(compute_units_per_second) - .build()?; + let provider = foundry_common::provider::ProviderBuilder::new( + &config.get_rpc_url_or_localhost_http()?, + ) + .compute_units_per_second_opt(compute_units_per_second) + .build()?; let tx_hash = self.tx_hash.parse().wrap_err("invalid tx hash")?; let tx = provider - .get_transaction(tx_hash) - .await? - .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))?; + .get_transaction_by_hash(tx_hash) + .await + .wrap_err_with(|| format!("tx not found: {:?}", tx_hash))?; // check if the tx is a system transaction - if is_known_system_sender(tx.from.to_alloy()) || - tx.transaction_type.map(|ty| ty.as_u64()) == Some(SYSTEM_TRANSACTION_TYPE) - { + if is_known_system_sender(tx.from) || tx.transaction_type == Some(SYSTEM_TRANSACTION_TYPE) { return Err(eyre::eyre!( "{:?} is a system transaction.\nReplaying system transactions is currently not supported.", tx.hash - )) + )); } - let tx_block_number = tx - .block_number - .ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))? - .as_u64(); + let tx_block_number = + tx.block_number.ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))?; + + // fetch the block the transaction was mined in + let block = provider.get_block(tx_block_number.into(), true).await?; // we need to fork off the parent block config.fork_block_number = Some(tx_block_number - 1); let (mut env, fork, chain) = TracingExecutor::get_fork_material(&config, evm_opts).await?; - let mut executor = - TracingExecutor::new(env.clone(), fork, self.evm_version, self.debug).await; + let mut evm_version = self.evm_version; env.block.number = U256::from(tx_block_number); - let block = provider.get_block_with_txs(tx_block_number).await?; - if let Some(ref block) = block { - env.block.timestamp = block.timestamp.to_alloy(); - env.block.coinbase = block.author.unwrap_or_default().to_alloy(); - env.block.difficulty = block.difficulty.to_alloy(); - env.block.prevrandao = Some(block.mix_hash.map(|h| h.to_alloy()).unwrap_or_default()); - env.block.basefee = block.base_fee_per_gas.unwrap_or_default().to_alloy(); - env.block.gas_limit = block.gas_limit.to_alloy(); + if let Some(block) = &block { + env.block.timestamp = U256::from(block.header.timestamp); + env.block.coinbase = block.header.miner; + env.block.difficulty = block.header.difficulty; + env.block.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); + env.block.basefee = U256::from(block.header.base_fee_per_gas.unwrap_or_default()); + env.block.gas_limit = U256::from(block.header.gas_limit); + + // TODO: we need a smarter way to map the block to the corresponding evm_version for + // commonly used chains + if evm_version.is_none() { + // if the block has the excess_blob_gas field, we assume it's a Cancun block + if block.header.excess_blob_gas.is_some() { + evm_version = Some(EvmVersion::Cancun); + } + } } + let mut executor = TracingExecutor::new(env.clone(), fork, evm_version, self.debug); + let mut env = + EnvWithHandlerCfg::new_with_spec_id(Box::new(env.clone()), executor.spec_id()); + // Set the state to the moment right before the transaction if !self.quick { println!("Executing previous transactions from the block."); @@ -140,18 +155,22 @@ impl RunArgs { let pb = init_progress!(block.transactions, "tx"); pb.set_position(0); - for (index, tx) in block.transactions.into_iter().enumerate() { - // System transactions such as on L2s don't contain any pricing info so we skip - // them otherwise this would cause reverts - if is_known_system_sender(tx.from.to_alloy()) || - tx.transaction_type.map(|ty| ty.as_u64()) == - Some(SYSTEM_TRANSACTION_TYPE) + let BlockTransactions::Full(txs) = block.transactions else { + return Err(eyre::eyre!("Could not get block txs")) + }; + + for (index, tx) in txs.into_iter().enumerate() { + // System transactions such as on L2s don't contain any pricing info so + // we skip them otherwise this would cause + // reverts + if is_known_system_sender(tx.from) || + tx.transaction_type == Some(SYSTEM_TRANSACTION_TYPE) { update_progress!(pb, index); - continue + continue; } if tx.hash == tx_hash { - break + break; } configure_tx_env(&mut env, &tx); @@ -205,7 +224,7 @@ impl RunArgs { } }; - handle_traces(result, &config, chain, self.label, self.verbose, self.debug).await?; + handle_traces(result, &config, chain, self.label, self.debug).await?; Ok(()) } diff --git a/crates/cast/bin/cmd/send.rs b/crates/cast/bin/cmd/send.rs index b79bf1d7cd9bd..7799f522691d8 100644 --- a/crates/cast/bin/cmd/send.rs +++ b/crates/cast/bin/cmd/send.rs @@ -1,18 +1,18 @@ -use cast::{Cast, TxBuilder}; +use crate::tx; +use alloy_network::{AnyNetwork, EthereumSigner}; +use alloy_primitives::{Address, U64}; +use alloy_provider::{Provider, ProviderBuilder}; +use alloy_rpc_types::BlockId; +use alloy_signer::Signer; +use alloy_transport::Transport; +use cast::Cast; use clap::Parser; -use ethers_core::types::NameOrAddress; -use ethers_middleware::MiddlewareBuilder; -use ethers_providers::Middleware; -use ethers_signers::Signer; use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, utils, }; -use foundry_common::{ - cli_warn, - types::{ToAlloy, ToEthers}, -}; +use foundry_common::{cli_warn, ens::NameOrAddress}; use foundry_config::{Chain, Config}; use std::str::FromStr; @@ -22,7 +22,7 @@ pub struct SendTxArgs { /// The destination of the transaction. /// /// If not provided, you must use cast send --create. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] to: Option, /// The signature of the function to call. @@ -32,39 +32,39 @@ pub struct SendTxArgs { args: Vec, /// Only print the transaction hash and exit immediately. - #[clap(name = "async", long = "async", alias = "cast-async", env = "CAST_ASYNC")] + #[arg(id = "async", long = "async", alias = "cast-async", env = "CAST_ASYNC")] cast_async: bool, /// The number of confirmations until the receipt is fetched. - #[clap(long, default_value = "1")] - confirmations: usize, + #[arg(long, default_value = "1")] + confirmations: u64, /// Print the transaction receipt as JSON. - #[clap(long, short, help_heading = "Display options")] + #[arg(long, short, help_heading = "Display options")] json: bool, /// Reuse the latest nonce for the sender account. - #[clap(long, conflicts_with = "nonce")] + #[arg(long, conflicts_with = "nonce")] resend: bool, - #[clap(subcommand)] + #[command(subcommand)] command: Option, /// Send via `eth_sendTransaction using the `--from` argument or $ETH_FROM as sender - #[clap(long, requires = "from")] + #[arg(long, requires = "from")] unlocked: bool, - #[clap(flatten)] + #[command(flatten)] tx: TransactionOpts, - #[clap(flatten)] + #[command(flatten)] eth: EthereumOpts, } #[derive(Debug, Parser)] pub enum SendTxSubcommands { /// Use to deploy raw contract bytecode. - #[clap(name = "--create")] + #[command(name = "--create")] Create { /// The bytecode of the contract to deploy. code: String, @@ -82,7 +82,7 @@ impl SendTxArgs { let SendTxArgs { eth, to, - sig, + mut sig, cast_async, mut args, mut tx, @@ -93,30 +93,31 @@ impl SendTxArgs { unlocked, } = self; - let mut sig = sig.unwrap_or_default(); let code = if let Some(SendTxSubcommands::Create { code, sig: constructor_sig, args: constructor_args, }) = command { - sig = constructor_sig.unwrap_or_default(); + sig = constructor_sig; args = constructor_args; Some(code) } else { None }; - // ensure mandatory fields are provided - if code.is_none() && to.is_none() { - eyre::bail!("Must specify a recipient address or contract code to deploy"); - } + tx::validate_to_address(&code, &to)?; let config = Config::from(ð); let provider = utils::get_provider(&config)?; let chain = utils::get_chain(config.chain, &provider).await?; let api_key = config.get_etherscan_api_key(Some(chain)); + let to = match to { + Some(to) => Some(to.resolve(&provider).await?), + None => None, + }; + // Case 1: // Default to sending via eth_sendTransaction if the --unlocked flag is passed. // This should be the only way this RPC method is used as it requires a local node @@ -124,15 +125,15 @@ impl SendTxArgs { if unlocked { // only check current chain id if it was specified in the config if let Some(config_chain) = config.chain { - let current_chain_id = provider.get_chainid().await?.as_u64(); + let current_chain_id = provider.get_chain_id().await?; let config_chain_id = config_chain.id(); // switch chain if current chain id is not the same as the one specified in the // config if config_chain_id != current_chain_id { cli_warn!("Switching to chain {}", config_chain); provider - .request( - "wallet_switchEthereumChain", + .raw_request( + "wallet_switchEthereumChain".into(), [serde_json::json!({ "chainId": format!("0x{:x}", config_chain_id), })], @@ -142,20 +143,18 @@ impl SendTxArgs { } if resend { - tx.nonce = Some( - provider - .get_transaction_count(config.sender.to_ethers(), None) - .await? - .to_alloy(), - ); + tx.nonce = Some(U64::from( + provider.get_transaction_count(config.sender, BlockId::latest()).await?, + )); } cast_send( provider, - config.sender.to_ethers(), + config.sender, to, code, - (sig, args), + sig, + args, tx, chain, api_key, @@ -170,35 +169,27 @@ impl SendTxArgs { // enough information to sign and we must bail. } else { // Retrieve the signer, and bail if it can't be constructed. - let signer = eth.wallet.signer(chain.id()).await?; + let signer = eth.wallet.signer().await?; let from = signer.address(); - // prevent misconfigured hwlib from sending a transaction that defies - // user-specified --from - if let Some(specified_from) = eth.wallet.from { - if specified_from != from.to_alloy() { - eyre::bail!( - "\ -The specified sender via CLI/env vars does not match the sender configured via -the hardware wallet's HD Path. -Please use the `--hd-path ` parameter to specify the BIP32 Path which -corresponds to the sender, or let foundry automatically detect it by not specifying any sender address." - ) - } - } + tx::validate_from_address(eth.wallet.from, from)?; if resend { - tx.nonce = Some(provider.get_transaction_count(from, None).await?.to_alloy()); + tx.nonce = + Some(U64::from(provider.get_transaction_count(from, BlockId::latest()).await?)); } - let provider = provider.with_signer(signer); + let signer = EthereumSigner::from(signer); + let provider = + ProviderBuilder::<_, _, AnyNetwork>::default().signer(signer).on_provider(provider); cast_send( provider, from, to, code, - (sig, args), + sig, + args, tx, chain, api_key, @@ -212,51 +203,27 @@ corresponds to the sender, or let foundry automatically detect it by not specify } #[allow(clippy::too_many_arguments)] -async fn cast_send, T: Into>( - provider: M, - from: F, - to: Option, +async fn cast_send, T: Transport + Clone>( + provider: P, + from: Address, + to: Option
, code: Option, - args: (String, Vec), + sig: Option, + args: Vec, tx: TransactionOpts, chain: Chain, etherscan_api_key: Option, cast_async: bool, - confs: usize, + confs: u64, to_json: bool, -) -> Result<()> -where - M::Error: 'static, -{ - let (sig, params) = args; - let params = if !sig.is_empty() { Some((&sig[..], params)) } else { None }; - let mut builder = TxBuilder::new(&provider, from, to, chain, tx.legacy).await?; - builder - .etherscan_api_key(etherscan_api_key) - .gas(tx.gas_limit) - .gas_price(tx.gas_price) - .priority_gas_price(tx.priority_gas_price) - .value(tx.value) - .nonce(tx.nonce); - - if let Some(code) = code { - let mut data = hex::decode(code)?; - - if let Some((sig, args)) = params { - let (mut sigdata, _) = builder.create_args(sig, args).await?; - data.append(&mut sigdata); - } - - builder.set_data(data); - } else { - builder.args(params).await?; - }; - let builder_output = builder.build(); +) -> Result<()> { + let (tx, _) = + tx::build_tx(&provider, from, to, code, sig, args, tx, chain, etherscan_api_key).await?; let cast = Cast::new(provider); - let pending_tx = cast.send(builder_output).await?; - let tx_hash = *pending_tx; + let pending_tx = cast.send(tx).await?; + let tx_hash = pending_tx.inner().tx_hash(); if cast_async { println!("{tx_hash:#x}"); diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index 94efe374ce038..18411165ea130 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -1,10 +1,12 @@ use crate::opts::parse_slot; -use alloy_primitives::{B256, U256}; +use alloy_network::AnyNetwork; +use alloy_primitives::{Address, B256, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::BlockId; +use alloy_transport::Transport; use cast::Cast; use clap::Parser; use comfy_table::{presets::ASCII_MARKDOWN, Table}; -use ethers_core::types::{BlockId, NameOrAddress}; -use ethers_providers::Middleware; use eyre::Result; use foundry_block_explorers::Client; use foundry_cli::{ @@ -13,16 +15,16 @@ use foundry_cli::{ }; use foundry_common::{ abi::find_source, - compile::{compile, etherscan_project, suppress_compile}, - types::{ToAlloy, ToEthers}, - RetryProvider, + compile::{etherscan_project, ProjectCompiler}, + ens::NameOrAddress, +}; +use foundry_compilers::{ + artifacts::StorageLayout, Artifact, ConfigurableContractArtifact, Project, Solc, }; -use foundry_compilers::{artifacts::StorageLayout, ConfigurableContractArtifact, Project, Solc}; use foundry_config::{ figment::{self, value::Dict, Metadata, Profile}, impl_figment_convert_cast, Config, }; -use futures::future::join_all; use semver::Version; use std::str::FromStr; @@ -32,29 +34,29 @@ use std::str::FromStr; const MIN_SOLC: Version = Version::new(0, 6, 5); /// CLI arguments for `cast storage`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct StorageArgs { /// The contract address. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] address: NameOrAddress, /// The storage slot number. - #[clap(value_parser = parse_slot)] + #[arg(value_parser = parse_slot)] slot: Option, /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short)] + #[arg(long, short)] block: Option, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, - #[clap(flatten)] + #[command(flatten)] etherscan: EtherscanOpts, - #[clap(flatten)] + #[command(flatten)] build: CoreBuildArgs, } @@ -79,19 +81,19 @@ impl StorageArgs { let config = Config::from(&self); let Self { address, slot, block, build, .. } = self; - let provider = utils::get_provider(&config)?; + let address = address.resolve(&provider).await?; // Slot was provided, perform a simple RPC call if let Some(slot) = slot { let cast = Cast::new(provider); - println!("{}", cast.storage(address, slot.to_ethers(), block).await?); - return Ok(()) + println!("{}", cast.storage(address, slot, block).await?); + return Ok(()); } // No slot was provided // Get deployed bytecode at given address - let address_code = provider.get_code(address.clone(), block).await?.to_alloy(); + let address_code = provider.get_code_at(address, block.unwrap_or_default()).await?; if address_code.is_empty() { eyre::bail!("Provided address has no deployed code and thus no storage"); } @@ -101,16 +103,12 @@ impl StorageArgs { if project.paths.has_input_files() { // Find in artifacts and pretty print add_storage_layout_output(&mut project); - let out = compile(&project, false, false)?; - let match_code = |artifact: &ConfigurableContractArtifact| -> Option { - let bytes = - artifact.deployed_bytecode.as_ref()?.bytecode.as_ref()?.object.as_bytes()?; - Some(bytes == &address_code) - }; - let artifact = - out.artifacts().find(|(_, artifact)| match_code(artifact).unwrap_or_default()); + let out = ProjectCompiler::new().compile(&project)?; + let artifact = out.artifacts().find(|(_, artifact)| { + artifact.get_deployed_bytecode_bytes().is_some_and(|b| *b == address_code) + }); if let Some((_, artifact)) = artifact { - return fetch_and_print_storage(provider, address.clone(), artifact, true).await + return fetch_and_print_storage(provider, address, block, artifact, true).await; } } @@ -118,18 +116,14 @@ impl StorageArgs { // Get code from Etherscan eprintln!("No matching artifacts found, fetching source code from Etherscan..."); - if self.etherscan.key.is_none() { + if !self.etherscan.has_key() { eyre::bail!("You must provide an Etherscan API key if you're fetching a remote contract's storage."); } let chain = utils::get_chain(config.chain, &provider).await?; let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); let client = Client::new(chain, api_key)?; - let addr = address - .as_address() - .ok_or_else(|| eyre::eyre!("Could not resolve address"))? - .to_alloy(); - let source = find_source(client, addr).await?; + let source = find_source(client, address).await?; let metadata = source.items.first().unwrap(); if metadata.is_vyper() { eyre::bail!("Contract at provided address is not a valid Solidity contract") @@ -147,7 +141,7 @@ impl StorageArgs { project.auto_detect = auto_detect; // Compile - let mut out = suppress_compile(&project)?; + let mut out = ProjectCompiler::new().quiet(true).compile(&project)?; let artifact = { let (_, mut artifact) = out .artifacts() @@ -160,7 +154,7 @@ impl StorageArgs { let solc = Solc::find_or_install_svm_version(MIN_SOLC.to_string())?; project.solc = solc; project.auto_detect = false; - if let Ok(output) = suppress_compile(&project) { + if let Ok(output) = ProjectCompiler::new().quiet(true).compile(&project) { out = output; let (_, new_artifact) = out .artifacts() @@ -176,13 +170,45 @@ impl StorageArgs { // Clear temp directory root.close()?; - fetch_and_print_storage(provider, address, artifact, true).await + fetch_and_print_storage(provider, address, block, artifact, true).await } } -async fn fetch_and_print_storage( - provider: RetryProvider, - address: NameOrAddress, +/// Represents the value of a storage slot `eth_getStorageAt` call. +#[derive(Clone, Debug, PartialEq, Eq)] +struct StorageValue { + /// The slot number. + slot: B256, + /// The value as returned by `eth_getStorageAt`. + raw_slot_value: B256, +} + +impl StorageValue { + /// Returns the value of the storage slot, applying the offset if necessary. + fn value(&self, offset: i64, number_of_bytes: Option) -> B256 { + let offset = offset as usize; + let mut end = 32; + if let Some(number_of_bytes) = number_of_bytes { + end = offset + number_of_bytes; + if end > 32 { + end = 32; + } + } + + // reverse range, because the value is stored in big endian + let raw_sliced_value = &self.raw_slot_value.as_slice()[32 - end..32 - offset]; + + // copy the raw sliced value as tail + let mut value = [0u8; 32]; + value[32 - raw_sliced_value.len()..32].copy_from_slice(raw_sliced_value); + B256::from(value) + } +} + +async fn fetch_and_print_storage, T: Transport + Clone>( + provider: P, + address: Address, + block: Option, artifact: &ConfigurableContractArtifact, pretty: bool, ) -> Result<()> { @@ -191,30 +217,31 @@ async fn fetch_and_print_storage( Ok(()) } else { let layout = artifact.storage_layout.as_ref().unwrap().clone(); - let values = fetch_storage_slots(provider, address, &layout).await?; + let values = fetch_storage_slots(provider, address, block, &layout).await?; print_storage(layout, values, pretty) } } -async fn fetch_storage_slots( - provider: RetryProvider, - address: NameOrAddress, +async fn fetch_storage_slots, T: Transport + Clone>( + provider: P, + address: Address, + block: Option, layout: &StorageLayout, -) -> Result> { - // TODO: Batch request - let futures: Vec<_> = layout - .storage - .iter() - .map(|slot| { - let slot = B256::from(U256::from_str(&slot.slot)?); - Ok(provider.get_storage_at(address.clone(), slot.to_ethers(), None)) - }) - .collect::>()?; - - join_all(futures).await.into_iter().map(|r| Ok(r?.to_alloy())).collect() +) -> Result> { + let requests = layout.storage.iter().map(|storage_slot| async { + let slot = B256::from(U256::from_str(&storage_slot.slot)?); + let raw_slot_value = + provider.get_storage_at(address, slot.into(), block.unwrap_or_default()).await?; + + let value = StorageValue { slot, raw_slot_value: raw_slot_value.into() }; + + Ok(value) + }); + + futures::future::try_join_all(requests).await } -fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) -> Result<()> { +fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) -> Result<()> { if !pretty { println!("{}", serde_json::to_string_pretty(&serde_json::to_value(layout)?)?); return Ok(()) @@ -224,17 +251,18 @@ fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) -> Resu table.load_preset(ASCII_MARKDOWN); table.set_header(["Name", "Type", "Slot", "Offset", "Bytes", "Value", "Hex Value", "Contract"]); - for (slot, value) in layout.storage.into_iter().zip(values) { + for (slot, storage_value) in layout.storage.into_iter().zip(values) { let storage_type = layout.types.get(&slot.storage_type); - let raw_value_bytes = value.0; - let converted_value = U256::from_be_bytes(raw_value_bytes); + let value = storage_value + .value(slot.offset, storage_type.and_then(|t| t.number_of_bytes.parse::().ok())); + let converted_value = U256::from_be_bytes(value.0); table.add_row([ slot.label.as_str(), storage_type.map_or("?", |t| &t.label), &slot.slot, &slot.offset.to_string(), - &storage_type.map_or("?", |t| &t.number_of_bytes), + storage_type.map_or("?", |t| &t.number_of_bytes), &converted_value.to_string(), &value.to_string(), &slot.contract, @@ -259,3 +287,23 @@ fn is_storage_layout_empty(storage_layout: &Option) -> bool { true } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_storage_etherscan_api_key() { + let args = + StorageArgs::parse_from(["foundry-cli", "addr", "--etherscan-api-key", "dummykey"]); + assert_eq!(args.etherscan.key(), Some("dummykey".to_string())); + + std::env::set_var("ETHERSCAN_API_KEY", "FXY"); + let config = Config::from(&args); + std::env::remove_var("ETHERSCAN_API_KEY"); + assert_eq!(config.etherscan_api_key, Some("dummykey".to_string())); + + let key = config.get_etherscan_api_key(None).unwrap(); + assert_eq!(key, "dummykey".to_string()); + } +} diff --git a/crates/cast/bin/cmd/wallet/list.rs b/crates/cast/bin/cmd/wallet/list.rs new file mode 100644 index 0000000000000..e534d5713088f --- /dev/null +++ b/crates/cast/bin/cmd/wallet/list.rs @@ -0,0 +1,108 @@ +use clap::Parser; +use eyre::Result; + +use foundry_common::fs; +use foundry_config::Config; +use foundry_wallets::multi_wallet::MultiWalletOptsBuilder; + +/// CLI arguments for `cast wallet list`. +#[derive(Clone, Debug, Parser)] +pub struct ListArgs { + /// List all the accounts in the keystore directory. + /// Default keystore directory is used if no path provided. + #[arg(long, default_missing_value = "", num_args(0..=1))] + dir: Option, + + /// List accounts from a Ledger hardware wallet. + #[arg(long, short, group = "hw-wallets")] + ledger: bool, + + /// List accounts from a Trezor hardware wallet. + #[arg(long, short, group = "hw-wallets")] + trezor: bool, + + /// List accounts from AWS KMS. + #[arg(long)] + aws: bool, + + /// List all configured accounts. + #[arg(long, group = "hw-wallets")] + all: bool, + + /// Max number of addresses to display from hardware wallets. + #[arg(long, short, default_value = "3", requires = "hw-wallets")] + max_senders: Option, +} + +impl ListArgs { + pub async fn run(self) -> Result<()> { + // list local accounts as files in keystore dir, no need to unlock / provide password + if self.dir.is_some() || self.all || (!self.ledger && !self.trezor && !self.aws) { + let _ = self.list_local_senders(); + } + + // Create options for multi wallet - ledger, trezor and AWS + let list_opts = MultiWalletOptsBuilder::default() + .ledger(self.ledger || self.all) + .mnemonic_indexes(Some(vec![0])) + .trezor(self.trezor || self.all) + .aws(self.aws || self.all) + .interactives(0) + .build() + .expect("build multi wallet"); + + // macro to print senders for a list of signers + macro_rules! list_senders { + ($signers:expr, $label:literal) => { + match $signers.await { + Ok(signers) => { + for signer in signers.unwrap_or_default().iter() { + signer + .available_senders(self.max_senders.unwrap()) + .await? + .iter() + .for_each(|sender| println!("{} ({})", sender, $label)); + } + } + Err(e) => { + if !self.all { + println!("{}", e) + } + } + } + }; + } + + list_senders!(list_opts.ledgers(), "Ledger"); + list_senders!(list_opts.trezors(), "Trezor"); + list_senders!(list_opts.aws_signers(), "AWS"); + + Ok(()) + } + + fn list_local_senders(&self) -> Result<()> { + let keystore_path = self.dir.clone().unwrap_or_default(); + let keystore_dir = if keystore_path.is_empty() { + // Create the keystore default directory if it doesn't exist + let default_dir = Config::foundry_keystores_dir().unwrap(); + fs::create_dir_all(&default_dir)?; + default_dir + } else { + dunce::canonicalize(keystore_path)? + }; + + // List all files within the keystore directory. + for entry in std::fs::read_dir(keystore_dir)? { + let path = entry?.path(); + if path.is_file() { + if let Some(file_name) = path.file_name() { + if let Some(name) = file_name.to_str() { + println!("{name} (Local)"); + } + } + } + } + + Ok(()) + } +} diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index b4cec44e0879f..1ac943204e112 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -1,20 +1,16 @@ -use alloy_primitives::Address; -use clap::Parser; -use ethers_core::{ - rand::thread_rng, - types::{transaction::eip712::TypedData, Signature}, -}; -use ethers_signers::{ +use alloy_dyn_abi::TypedData; +use alloy_primitives::{Address, Signature}; +use alloy_signer::Signer; +use alloy_signer_wallet::{ coins_bip39::{English, Mnemonic}, - LocalWallet, MnemonicBuilder, Signer, + LocalWallet, MnemonicBuilder, }; +use clap::Parser; use eyre::{Context, Result}; -use foundry_cli::opts::{RawWallet, Wallet}; -use foundry_common::{ - fs, - types::{ToAlloy, ToEthers}, -}; +use foundry_common::fs; use foundry_config::Config; +use foundry_wallets::{RawWalletOpts, WalletOpts, WalletSigner}; +use rand::thread_rng; use serde_json::json; use std::path::Path; use yansi::Paint; @@ -22,11 +18,14 @@ use yansi::Paint; pub mod vanity; use vanity::VanityArgs; +pub mod list; +use list::ListArgs; + /// CLI arguments for `cast wallet`. #[derive(Debug, Parser)] pub enum WalletSubcommands { /// Create a new random keypair. - #[clap(visible_alias = "n")] + #[command(visible_alias = "n")] New { /// If provided, then keypair will be written to an encrypted JSON keystore. path: Option, @@ -34,63 +33,61 @@ pub enum WalletSubcommands { /// Triggers a hidden password prompt for the JSON keystore. /// /// Deprecated: prompting for a hidden password is now the default. - #[clap(long, short, requires = "path", conflicts_with = "unsafe_password")] + #[arg(long, short, requires = "path", conflicts_with = "unsafe_password")] password: bool, /// Password for the JSON keystore in cleartext. /// /// This is UNSAFE to use and we recommend using the --password. - #[clap(long, requires = "path", env = "CAST_PASSWORD", value_name = "PASSWORD")] + #[arg(long, requires = "path", env = "CAST_PASSWORD", value_name = "PASSWORD")] unsafe_password: Option, /// Number of wallets to generate. - #[clap(long, short, default_value = "1")] + #[arg(long, short, default_value = "1")] number: u32, /// Output generated wallets as JSON. - #[clap(long, short, default_value = "false")] + #[arg(long, short, default_value = "false")] json: bool, }, /// Generates a random BIP39 mnemonic phrase - #[clap(visible_alias = "nm")] + #[command(visible_alias = "nm")] NewMnemonic { /// Number of words for the mnemonic - #[clap(long, short, default_value = "12")] + #[arg(long, short, default_value = "12")] words: usize, /// Number of accounts to display - #[clap(long, short, default_value = "1")] + #[arg(long, short, default_value = "1")] accounts: u8, }, /// Generate a vanity address. - #[clap(visible_alias = "va")] + #[command(visible_alias = "va")] Vanity(VanityArgs), /// Convert a private key to an address. - #[clap(visible_aliases = &["a", "addr"])] + #[command(visible_aliases = &["a", "addr"])] Address { /// If provided, the address will be derived from the specified private key. - #[clap( - value_name = "PRIVATE_KEY", - value_parser = foundry_common::clap_helpers::strip_0x_prefix, - )] + #[arg(value_name = "PRIVATE_KEY")] private_key_override: Option, - #[clap(flatten)] - wallet: Wallet, + #[command(flatten)] + wallet: WalletOpts, }, /// Sign a message or typed data. - #[clap(visible_alias = "s")] + #[command(visible_alias = "s")] Sign { - /// The message or typed data to sign. + /// The message, typed data, or hash to sign. + /// + /// Messages starting with 0x are expected to be hex encoded, which get decoded before + /// being signed. /// - /// Messages starting with 0x are expected to be hex encoded, - /// which get decoded before being signed. /// The message will be prefixed with the Ethereum Signed Message header and hashed before - /// signing. + /// signing, unless `--no-hash` is provided. /// /// Typed data can be provided as a json string or a file name. /// Use --data flag to denote the message is a string of typed data. @@ -99,21 +96,24 @@ pub enum WalletSubcommands { /// The data should be formatted as JSON. message: String, - /// If provided, the message will be treated as typed data. - #[clap(long)] + /// Treat the message as JSON typed data. + #[arg(long)] data: bool, - /// If provided, the message will be treated as a file name containing typed data. Requires - /// --data. - #[clap(long, requires = "data")] + /// Treat the message as a file containing JSON typed data. Requires `--data`. + #[arg(long, requires = "data")] from_file: bool, - #[clap(flatten)] - wallet: Wallet, + /// Treat the message as a raw 32-byte hash and sign it directly without hashing it again. + #[arg(long, conflicts_with = "data")] + no_hash: bool, + + #[command(flatten)] + wallet: WalletOpts, }, /// Verify the signature of a message. - #[clap(visible_alias = "v")] + #[command(visible_alias = "v")] Verify { /// The original message. message: String, @@ -122,25 +122,35 @@ pub enum WalletSubcommands { signature: Signature, /// The address of the message signer. - #[clap(long, short)] + #[arg(long, short)] address: Address, }, + /// Import a private key into an encrypted keystore. - #[clap(visible_alias = "i")] + #[command(visible_alias = "i")] Import { /// The name for the account in the keystore. - #[clap(value_name = "ACCOUNT_NAME")] + #[arg(value_name = "ACCOUNT_NAME")] account_name: String, /// If provided, keystore will be saved here instead of the default keystores directory /// (~/.foundry/keystores) - #[clap(long, short)] + #[arg(long, short)] keystore_dir: Option, - #[clap(flatten)] - raw_wallet_options: RawWallet, + /// Password for the JSON keystore in cleartext + /// This is unsafe, we recommend using the default hidden password prompt + #[arg(long, env = "CAST_UNSAFE_PASSWORD", value_name = "PASSWORD")] + unsafe_password: Option, + #[command(flatten)] + raw_wallet_options: RawWalletOpts, }, + /// List all the accounts in the keystore default directory - #[clap(visible_alias = "ls")] - List, + #[command(visible_alias = "ls")] + List(ListArgs), + + /// Derives private key from mnemonic + #[command(name = "derive-private-key", visible_aliases = &["--derive-private-key"])] + DerivePrivateKey { mnemonic: String, mnemonic_index: Option }, } impl WalletSubcommands { @@ -151,7 +161,14 @@ impl WalletSubcommands { let mut json_values = if json { Some(vec![]) } else { None }; if let Some(path) = path { - let path = dunce::canonicalize(path)?; + let path = match dunce::canonicalize(path.clone()) { + Ok(path) => path, + // If the path doesn't exist, it will fail to be canonicalized, + // so we attach more context to the error message. + Err(e) => { + eyre::bail!("If you specified a directory, please make sure it exists, or create it before running `cast wallet new `.\n{path} is not a directory.\nError: {}", e); + } + }; if !path.is_dir() { // we require path to be an existing directory eyre::bail!("`{}` is not a directory", path.display()); @@ -170,7 +187,7 @@ impl WalletSubcommands { if let Some(json) = json_values.as_mut() { json.push(json!({ - "address": wallet.address().to_alloy().to_checksum(None), + "address": wallet.address().to_checksum(None), "path": format!("{}", path.join(uuid).display()), } )); @@ -179,7 +196,7 @@ impl WalletSubcommands { "Created new encrypted keystore file: {}", path.join(uuid).display() ); - println!("Address: {}", wallet.address().to_alloy().to_checksum(None)); + println!("Address: {}", wallet.address().to_checksum(None)); } } @@ -188,19 +205,16 @@ impl WalletSubcommands { } } else { for _ in 0..number { - let wallet = LocalWallet::new(&mut rng); + let wallet = LocalWallet::random_with(&mut rng); if let Some(json) = json_values.as_mut() { json.push(json!({ - "address": wallet.address().to_alloy().to_checksum(None), + "address": wallet.address().to_checksum(None), "private_key": format!("0x{}", hex::encode(wallet.signer().to_bytes())), })) } else { println!("Successfully created new keypair."); - println!( - "Address: {}", - wallet.address().to_alloy().to_checksum(None) - ); + println!("Address: {}", wallet.address().to_checksum(None)); println!("Private key: 0x{}", hex::encode(wallet.signer().to_bytes())); } } @@ -217,17 +231,17 @@ impl WalletSubcommands { let builder = MnemonicBuilder::::default().phrase(phrase.as_str()); let derivation_path = "m/44'/60'/0'/0/"; let wallets = (0..accounts) - .map(|i| builder.clone().derivation_path(&format!("{derivation_path}{i}"))) + .map(|i| builder.clone().derivation_path(format!("{derivation_path}{i}"))) .collect::, _>>()?; let wallets = wallets.into_iter().map(|b| b.build()).collect::, _>>()?; - println!("{}", Paint::green("Successfully generated a new mnemonic.")); + println!("{}", "Successfully generated a new mnemonic.".green()); println!("Phrase:\n{phrase}"); println!("\nAccounts:"); for (i, wallet) in wallets.iter().enumerate() { println!("- Account {i}:"); - println!("Address: {}", wallet.address().to_alloy()); + println!("Address: {}", wallet.address()); println!("Private key: 0x{}\n", hex::encode(wallet.signer().to_bytes())); } } @@ -236,18 +250,18 @@ impl WalletSubcommands { } WalletSubcommands::Address { wallet, private_key_override } => { let wallet = private_key_override - .map(|pk| Wallet { - raw: RawWallet { private_key: Some(pk), ..Default::default() }, + .map(|pk| WalletOpts { + raw: RawWalletOpts { private_key: Some(pk), ..Default::default() }, ..Default::default() }) .unwrap_or(wallet) - .signer(0) + .signer() .await?; let addr = wallet.address(); - println!("{}", addr.to_alloy().to_checksum(None)); + println!("{}", addr.to_checksum(None)); } - WalletSubcommands::Sign { message, data, from_file, wallet } => { - let wallet = wallet.signer(0).await?; + WalletSubcommands::Sign { message, data, from_file, no_hash, wallet } => { + let wallet = wallet.signer().await?; let sig = if data { let typed_data: TypedData = if from_file { // data is a file name, read json from file @@ -256,23 +270,28 @@ impl WalletSubcommands { // data is a json string serde_json::from_str(&message)? }; - wallet.sign_typed_data(&typed_data).await? + wallet.sign_dynamic_typed_data(&typed_data).await? + } else if no_hash { + wallet.sign_hash(&hex::decode(&message)?[..].try_into()?).await? } else { - wallet.sign_message(Self::hex_str_to_bytes(&message)?).await? + wallet.sign_message(&Self::hex_str_to_bytes(&message)?).await? }; - println!("0x{sig}"); + println!("0x{}", hex::encode(sig.as_bytes())); } WalletSubcommands::Verify { message, signature, address } => { - match signature.verify(Self::hex_str_to_bytes(&message)?, address.to_ethers()) { - Ok(_) => { - println!("Validation succeeded. Address {address} signed this message.") - } - Err(_) => { - println!("Validation failed. Address {address} did not sign this message.") - } + let recovered_address = Self::recover_address_from_message(&message, &signature)?; + if address == recovered_address { + println!("Validation succeeded. Address {address} signed this message."); + } else { + println!("Validation failed. Address {address} did not sign this message."); } } - WalletSubcommands::Import { account_name, keystore_dir, raw_wallet_options } => { + WalletSubcommands::Import { + account_name, + keystore_dir, + unsafe_password, + raw_wallet_options, + } => { // Set up keystore directory let dir = if let Some(path) = keystore_dir { Path::new(&path).to_path_buf() @@ -291,26 +310,36 @@ impl WalletSubcommands { } // get wallet - let wallet: Wallet = raw_wallet_options.into(); - let wallet = wallet.try_resolve_local_wallet()?.ok_or_else(|| { - eyre::eyre!( - "\ + let wallet = raw_wallet_options + .signer()? + .and_then(|s| match s { + WalletSigner::Local(s) => Some(s), + _ => None, + }) + .ok_or_else(|| { + eyre::eyre!( + "\ Did you set a private key or mnemonic? Run `cast wallet import --help` and use the corresponding CLI flag to set your key via: --private-key, --mnemonic-path or --interactive." - ) - })?; + ) + })?; let private_key = wallet.signer().to_bytes(); - let password = rpassword::prompt_password("Enter password: ")?; + let password = if let Some(password) = unsafe_password { + password + } else { + // if no --unsafe-password was provided read via stdin + rpassword::prompt_password("Enter password: ")? + }; let mut rng = thread_rng(); - eth_keystore::encrypt_key( - &dir, + let (wallet, _) = LocalWallet::encrypt_keystore( + dir, &mut rng, private_key, - &password, + password, Some(&account_name), )?; let address = wallet.address(); @@ -318,49 +347,34 @@ flag to set your key via: "`{}` keystore was saved successfully. Address: {:?}", &account_name, address, ); - println!("{}", Paint::green(success_message)); + println!("{}", success_message.green()); } - WalletSubcommands::List => { - let default_keystore_dir = Config::foundry_keystores_dir() - .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; - // Create the keystore directory if it doesn't exist - fs::create_dir_all(&default_keystore_dir)?; - // List all files in keystore directory - let keystore_files: Result, eyre::Report> = - std::fs::read_dir(&default_keystore_dir) - .wrap_err("Failed to read the directory")? - .filter_map(|entry| match entry { - Ok(entry) => { - let path = entry.path(); - if path.is_file() && path.extension().is_none() { - Some(Ok(path)) - } else { - None - } - } - Err(e) => Some(Err(e.into())), - }) - .collect::, eyre::Report>>(); - // Print the names of the keystore files - match keystore_files { - Ok(files) => { - // Print the names of the keystore files - for file in files { - if let Some(file_name) = file.file_name() { - if let Some(name) = file_name.to_str() { - println!("{}", name); - } - } - } - } - Err(e) => return Err(e), - } + WalletSubcommands::List(cmd) => { + cmd.run().await?; + } + WalletSubcommands::DerivePrivateKey { mnemonic, mnemonic_index } => { + let phrase = Mnemonic::::new_from_phrase(mnemonic.as_str())?.to_phrase(); + let builder = MnemonicBuilder::::default().phrase(phrase.as_str()); + let derivation_path = "m/44'/60'/0'/0/"; + let index = mnemonic_index.unwrap_or_default(); + let wallet = builder + .clone() + .derivation_path(format!("{derivation_path}{index}"))? + .build()?; + println!("- Account:"); + println!("Address: {}", wallet.address()); + println!("Private key: 0x{}\n", hex::encode(wallet.signer().to_bytes())); } }; Ok(()) } + /// Recovers an address from the specified message and signature + fn recover_address_from_message(message: &str, signature: &Signature) -> Result
{ + Ok(signature.recover_address_from_msg(message)?) + } + fn hex_str_to_bytes(s: &str) -> Result> { Ok(match s.strip_prefix("0x") { Some(data) => hex::decode(data).wrap_err("Could not decode 0x-prefixed string.")?, @@ -371,6 +385,10 @@ flag to set your key via: #[cfg(test)] mod tests { + use std::str::FromStr; + + use alloy_primitives::address; + use super::*; #[test] @@ -399,6 +417,17 @@ mod tests { } } + #[test] + fn can_verify_signed_hex_message() { + let message = "hello"; + let signature = Signature::from_str("f2dd00eac33840c04b6fc8a5ec8c4a47eff63575c2bc7312ecb269383de0c668045309c423484c8d097df306e690c653f8e1ec92f7f6f45d1f517027771c3e801c").unwrap(); + let address = address!("28A4F420a619974a2393365BCe5a7b560078Cc13"); + let recovered_address = + WalletSubcommands::recover_address_from_message(message, &signature); + assert!(recovered_address.is_ok()); + assert_eq!(address, recovered_address.unwrap()); + } + #[test] fn can_parse_wallet_sign_data() { let args = WalletSubcommands::parse_from(["foundry-cli", "sign", "--data", "{ ... }"]); diff --git a/crates/cast/bin/cmd/wallet/vanity.rs b/crates/cast/bin/cmd/wallet/vanity.rs index 5dacefb52d466..28ada95b11cb4 100644 --- a/crates/cast/bin/cmd/wallet/vanity.rs +++ b/crates/cast/bin/cmd/wallet/vanity.rs @@ -1,9 +1,8 @@ use alloy_primitives::Address; +use alloy_signer::{k256::ecdsa::SigningKey, utils::secret_key_to_address}; +use alloy_signer_wallet::LocalWallet; use clap::{builder::TypedValueParser, Parser}; -use ethers_core::{k256::ecdsa::SigningKey, rand, utils::secret_key_to_address}; -use ethers_signers::{LocalWallet, Signer}; use eyre::Result; -use foundry_common::types::ToAlloy; use rayon::iter::{self, ParallelIterator}; use regex::Regex; use serde::{Deserialize, Serialize}; @@ -17,10 +16,10 @@ use std::{ pub type GeneratedWallet = (SigningKey, Address); /// CLI arguments for `cast wallet vanity`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct VanityArgs { /// Prefix for the vanity address. - #[clap( + #[arg( long, required_unless_present = "ends_with", value_parser = HexAddressValidator, @@ -29,20 +28,20 @@ pub struct VanityArgs { pub starts_with: Option, /// Suffix for the vanity address. - #[clap(long, value_parser = HexAddressValidator, value_name = "HEX")] + #[arg(long, value_parser = HexAddressValidator, value_name = "HEX")] pub ends_with: Option, // 2^64-1 is max possible nonce per [eip-2681](https://eips.ethereum.org/EIPS/eip-2681). /// Generate a vanity contract address created by the generated keypair with the specified /// nonce. - #[clap(long)] + #[arg(long)] pub nonce: Option, /// Path to save the generated vanity contract address to. /// /// If provided, the generated vanity addresses will appended to a JSON array in the specified /// file. - #[clap( + #[arg( long, value_hint = clap::ValueHint::FilePath, value_name = "PATH", @@ -66,7 +65,7 @@ struct Wallets { impl WalletData { pub fn new(wallet: &LocalWallet) -> Self { WalletData { - address: wallet.address().to_alloy().to_checksum(None), + address: wallet.address().to_checksum(None), private_key: format!("0x{}", hex::encode(wallet.signer().to_bytes())), } } @@ -156,11 +155,11 @@ impl VanityArgs { timer.elapsed().as_secs(), if nonce.is_some() { "\nContract address: " } else { "" }, if nonce.is_some() { - wallet.address().to_alloy().create(nonce.unwrap()).to_checksum(None) + wallet.address().create(nonce.unwrap()).to_checksum(None) } else { String::new() }, - wallet.address().to_alloy().to_checksum(None), + wallet.address().to_checksum(None), hex::encode(wallet.signer().to_bytes()), ); @@ -229,7 +228,7 @@ pub fn wallet_generator() -> iter::Map, impl Fn(()) -> Generate pub fn generate_wallet() -> GeneratedWallet { let key = SigningKey::random(&mut rand::thread_rng()); let address = secret_key_to_address(&key); - (key, address.to_alloy()) + (key, address) } /// A trait to match vanity addresses. @@ -333,7 +332,7 @@ impl VanityMatcher for RegexMatcher { } /// Parse 40 byte addresses -#[derive(Copy, Clone, Debug, Default)] +#[derive(Clone, Copy, Debug, Default)] pub struct HexAddressValidator; impl TypedValueParser for HexAddressValidator { diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index 5ceea192a0b54..e1b411864bd29 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -2,75 +2,81 @@ extern crate tracing; use alloy_primitives::{keccak256, Address, B256}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockId, BlockNumberOrTag::Latest}; use cast::{Cast, SimpleCast}; use clap::{CommandFactory, Parser}; use clap_complete::generate; -use ethers_core::types::{BlockId, BlockNumber::Latest}; -use ethers_providers::Middleware; use eyre::Result; use foundry_cli::{handler, prompt, stdin, utils}; use foundry_common::{ abi::get_event, - fmt::format_tokens, + ens::{namehash, ProviderEnsExt}, + fmt::{format_tokens, format_uint_exp}, fs, selectors::{ - decode_calldata, decode_event_topic, decode_function_selector, import_selectors, - parse_signatures, pretty_calldata, ParsedSignatures, SelectorImportData, + decode_calldata, decode_event_topic, decode_function_selector, decode_selectors, + import_selectors, parse_signatures, pretty_calldata, ParsedSignatures, SelectorImportData, + SelectorType, }, - types::{ToAlloy, ToEthers}, }; use foundry_config::Config; use std::time::Instant; pub mod cmd; pub mod opts; +pub mod tx; -use opts::{Opts, Subcommands, ToBaseArgs}; +use opts::{Cast as Opts, CastSubcommand, ToBaseArgs}; + +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; #[tokio::main] async fn main() -> Result<()> { - handler::install()?; + handler::install(); utils::load_dotenv(); utils::subscriber(); utils::enable_paint(); let opts = Opts::parse(); - match opts.sub { + match opts.cmd { // Constants - Subcommands::MaxInt { r#type } => { + CastSubcommand::MaxInt { r#type } => { println!("{}", SimpleCast::max_int(&r#type)?); } - Subcommands::MinInt { r#type } => { + CastSubcommand::MinInt { r#type } => { println!("{}", SimpleCast::min_int(&r#type)?); } - Subcommands::MaxUint { r#type } => { + CastSubcommand::MaxUint { r#type } => { println!("{}", SimpleCast::max_int(&r#type)?); } - Subcommands::AddressZero => { + CastSubcommand::AddressZero => { println!("{:?}", Address::ZERO); } - Subcommands::HashZero => { + CastSubcommand::HashZero => { println!("{:?}", B256::ZERO); } // Conversions & transformations - Subcommands::FromUtf8 { text } => { + CastSubcommand::FromUtf8 { text } => { let value = stdin::unwrap(text, false)?; println!("{}", SimpleCast::from_utf8(&value)); } - Subcommands::ToAscii { hexdata } => { + CastSubcommand::ToAscii { hexdata } => { let value = stdin::unwrap(hexdata, false)?; println!("{}", SimpleCast::to_ascii(&value)?); } - Subcommands::FromFixedPoint { value, decimals } => { + CastSubcommand::FromFixedPoint { value, decimals } => { let (value, decimals) = stdin::unwrap2(value, decimals)?; println!("{}", SimpleCast::from_fixed_point(&value, &decimals)?); } - Subcommands::ToFixedPoint { value, decimals } => { + CastSubcommand::ToFixedPoint { value, decimals } => { let (value, decimals) = stdin::unwrap2(value, decimals)?; println!("{}", SimpleCast::to_fixed_point(&value, &decimals)?); } - Subcommands::ConcatHex { data } => { + CastSubcommand::ConcatHex { data } => { if data.is_empty() { let s = stdin::read(true)?; println!("{}", SimpleCast::concat_hex(s.split_whitespace())) @@ -78,11 +84,11 @@ async fn main() -> Result<()> { println!("{}", SimpleCast::concat_hex(data)) } } - Subcommands::FromBin => { + CastSubcommand::FromBin => { let hex = stdin::read_bytes(false)?; println!("{}", hex::encode_prefixed(hex)); } - Subcommands::ToHexdata { input } => { + CastSubcommand::ToHexdata { input } => { let value = stdin::unwrap_line(input)?; let output = match value { s if s.starts_with('@') => hex::encode(std::env::var(&s[1..])?), @@ -91,91 +97,95 @@ async fn main() -> Result<()> { }; println!("0x{output}"); } - Subcommands::ToCheckSumAddress { address } => { + CastSubcommand::ToCheckSumAddress { address } => { let value = stdin::unwrap_line(address)?; println!("{}", value.to_checksum(None)); } - Subcommands::ToUint256 { value } => { + CastSubcommand::ToUint256 { value } => { let value = stdin::unwrap_line(value)?; println!("{}", SimpleCast::to_uint256(&value)?); } - Subcommands::ToInt256 { value } => { + CastSubcommand::ToInt256 { value } => { let value = stdin::unwrap_line(value)?; println!("{}", SimpleCast::to_int256(&value)?); } - Subcommands::ToUnit { value, unit } => { + CastSubcommand::ToUnit { value, unit } => { let value = stdin::unwrap_line(value)?; println!("{}", SimpleCast::to_unit(&value, &unit)?); } - Subcommands::FromWei { value, unit } => { + CastSubcommand::FromWei { value, unit } => { let value = stdin::unwrap_line(value)?; println!("{}", SimpleCast::from_wei(&value, &unit)?); } - Subcommands::ToWei { value, unit } => { + CastSubcommand::ToWei { value, unit } => { let value = stdin::unwrap_line(value)?; println!("{}", SimpleCast::to_wei(&value, &unit)?); } - Subcommands::FromRlp { value } => { + CastSubcommand::FromRlp { value } => { let value = stdin::unwrap_line(value)?; println!("{}", SimpleCast::from_rlp(value)?); } - Subcommands::ToRlp { value } => { + CastSubcommand::ToRlp { value } => { let value = stdin::unwrap_line(value)?; println!("{}", SimpleCast::to_rlp(&value)?); } - Subcommands::ToHex(ToBaseArgs { value, base_in }) => { + CastSubcommand::ToHex(ToBaseArgs { value, base_in }) => { let value = stdin::unwrap_line(value)?; println!("{}", SimpleCast::to_base(&value, base_in.as_deref(), "hex")?); } - Subcommands::ToDec(ToBaseArgs { value, base_in }) => { + CastSubcommand::ToDec(ToBaseArgs { value, base_in }) => { let value = stdin::unwrap_line(value)?; println!("{}", SimpleCast::to_base(&value, base_in.as_deref(), "dec")?); } - Subcommands::ToBase { base: ToBaseArgs { value, base_in }, base_out } => { + CastSubcommand::ToBase { base: ToBaseArgs { value, base_in }, base_out } => { let (value, base_out) = stdin::unwrap2(value, base_out)?; println!("{}", SimpleCast::to_base(&value, base_in.as_deref(), &base_out)?); } - Subcommands::ToBytes32 { bytes } => { + CastSubcommand::ToBytes32 { bytes } => { let value = stdin::unwrap_line(bytes)?; println!("{}", SimpleCast::to_bytes32(&value)?); } - Subcommands::FormatBytes32String { string } => { + CastSubcommand::FormatBytes32String { string } => { let value = stdin::unwrap_line(string)?; println!("{}", SimpleCast::format_bytes32_string(&value)?); } - Subcommands::ParseBytes32String { bytes } => { + CastSubcommand::ParseBytes32String { bytes } => { let value = stdin::unwrap_line(bytes)?; println!("{}", SimpleCast::parse_bytes32_string(&value)?); } - Subcommands::ParseBytes32Address { bytes } => { + CastSubcommand::ParseBytes32Address { bytes } => { let value = stdin::unwrap_line(bytes)?; println!("{}", SimpleCast::parse_bytes32_address(&value)?); } // ABI encoding & decoding - Subcommands::AbiDecode { sig, calldata, input } => { + CastSubcommand::AbiDecode { sig, calldata, input } => { let tokens = SimpleCast::abi_decode(&sig, &calldata, input)?; let tokens = format_tokens(&tokens); tokens.for_each(|t| println!("{t}")); } - Subcommands::AbiEncode { sig, args } => { - println!("{}", SimpleCast::abi_encode(&sig, &args)?); + CastSubcommand::AbiEncode { sig, packed, args } => { + if !packed { + println!("{}", SimpleCast::abi_encode(&sig, &args)?); + } else { + println!("{}", SimpleCast::abi_encode_packed(&sig, &args)?); + } } - Subcommands::CalldataDecode { sig, calldata } => { + CastSubcommand::CalldataDecode { sig, calldata } => { let tokens = SimpleCast::calldata_decode(&sig, &calldata, true)?; let tokens = format_tokens(&tokens); tokens.for_each(|t| println!("{t}")); } - Subcommands::CalldataEncode { sig, args } => { + CastSubcommand::CalldataEncode { sig, args } => { println!("{}", SimpleCast::calldata_encode(sig, &args)?); } - Subcommands::Interface(cmd) => cmd.run().await?, - Subcommands::Bind(cmd) => cmd.run().await?, - Subcommands::PrettyCalldata { calldata, offline } => { + CastSubcommand::Interface(cmd) => cmd.run().await?, + CastSubcommand::Bind(cmd) => cmd.run().await?, + CastSubcommand::PrettyCalldata { calldata, offline } => { let calldata = stdin::unwrap_line(calldata)?; println!("{}", pretty_calldata(&calldata, offline).await?); } - Subcommands::Sig { sig, optimize } => { + CastSubcommand::Sig { sig, optimize } => { let sig = stdin::unwrap_line(sig)?; match optimize { Some(opt) => { @@ -191,8 +201,8 @@ async fn main() -> Result<()> { } // Blockchain & RPC queries - Subcommands::AccessList(cmd) => cmd.run().await?, - Subcommands::Age { block, rpc } => { + CastSubcommand::AccessList(cmd) => cmd.run().await?, + CastSubcommand::Age { block, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; println!( @@ -200,17 +210,28 @@ async fn main() -> Result<()> { Cast::new(provider).age(block.unwrap_or(BlockId::Number(Latest))).await? ); } - Subcommands::Balance { block, who, ether, rpc } => { + CastSubcommand::Balance { block, who, ether, rpc, erc20 } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; - let value = Cast::new(provider).balance(who, block).await?; - if ether { - println!("{}", SimpleCast::from_wei(&value.to_string(), "eth")?); - } else { - println!("{value}"); + let account_addr = who.resolve(&provider).await?; + + match erc20 { + Some(token) => { + let balance = + Cast::new(&provider).erc20_balance(token, account_addr, block).await?; + println!("{}", format_uint_exp(balance)); + } + None => { + let value = Cast::new(&provider).balance(account_addr, block).await?; + if ether { + println!("{}", SimpleCast::from_wei(&value.to_string(), "eth")?); + } else { + println!("{value}"); + } + } } } - Subcommands::BaseFee { block, rpc } => { + CastSubcommand::BaseFee { block, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; println!( @@ -218,7 +239,7 @@ async fn main() -> Result<()> { Cast::new(provider).base_fee(block.unwrap_or(BlockId::Number(Latest))).await? ); } - Subcommands::Block { block, full, field, json, rpc } => { + CastSubcommand::Block { block, full, field, json, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; println!( @@ -228,101 +249,130 @@ async fn main() -> Result<()> { .await? ); } - Subcommands::BlockNumber { rpc } => { + CastSubcommand::BlockNumber { rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; println!("{}", Cast::new(provider).block_number().await?); } - Subcommands::Chain { rpc } => { + CastSubcommand::Chain { rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; println!("{}", Cast::new(provider).chain().await?); } - Subcommands::ChainId { rpc } => { + CastSubcommand::ChainId { rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; println!("{}", Cast::new(provider).chain_id().await?); } - Subcommands::Client { rpc } => { + CastSubcommand::Client { rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; - println!("{}", provider.client_version().await?); + println!("{}", provider.get_client_version().await?); } - Subcommands::Code { block, who, disassemble, rpc } => { + CastSubcommand::Code { block, who, disassemble, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; + let who = who.resolve(&provider).await?; println!("{}", Cast::new(provider).code(who, block, disassemble).await?); } - Subcommands::Codesize { block, who, rpc } => { + CastSubcommand::Codesize { block, who, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; + let who = who.resolve(&provider).await?; println!("{}", Cast::new(provider).codesize(who, block).await?); } - Subcommands::ComputeAddress { address, nonce, rpc } => { + CastSubcommand::ComputeAddress { address, nonce, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; let address: Address = stdin::unwrap_line(address)?.parse()?; - let computed = Cast::new(&provider).compute_address(address, nonce).await?; + let computed = Cast::new(provider).compute_address(address, nonce).await?; println!("Computed Address: {}", computed.to_checksum(None)); } - Subcommands::Disassemble { bytecode } => { + CastSubcommand::Disassemble { bytecode } => { println!("{}", SimpleCast::disassemble(&bytecode)?); } - Subcommands::FindBlock(cmd) => cmd.run().await?, - Subcommands::GasPrice { rpc } => { + CastSubcommand::Selectors { bytecode, resolve } => { + let selectors_and_args = SimpleCast::extract_selectors(&bytecode)?; + if resolve { + let selectors_it = selectors_and_args.iter().map(|r| &r.0); + let resolve_results = + decode_selectors(SelectorType::Function, selectors_it).await?; + + let max_args_len = selectors_and_args.iter().map(|r| r.1.len()).max().unwrap_or(0); + for ((selector, arguments), func_names) in + selectors_and_args.into_iter().zip(resolve_results.into_iter()) + { + let resolved = match func_names { + Some(v) => v.join("|"), + None => "".to_string(), + }; + println!("{selector}\t{arguments:max_args_len$}\t{resolved}"); + } + } else { + for (selector, arguments) in selectors_and_args { + println!("{selector}\t{arguments}"); + } + } + } + CastSubcommand::FindBlock(cmd) => cmd.run().await?, + CastSubcommand::GasPrice { rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; println!("{}", Cast::new(provider).gas_price().await?); } - Subcommands::Index { key_type, key, slot_number } => { + CastSubcommand::Index { key_type, key, slot_number } => { println!("{}", SimpleCast::index(&key_type, &key, &slot_number)?); } - Subcommands::Implementation { block, who, rpc } => { + CastSubcommand::Implementation { block, who, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; + let who = who.resolve(&provider).await?; println!("{}", Cast::new(provider).implementation(who, block).await?); } - Subcommands::Admin { block, who, rpc } => { + CastSubcommand::Admin { block, who, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; + let who = who.resolve(&provider).await?; println!("{}", Cast::new(provider).admin(who, block).await?); } - Subcommands::Nonce { block, who, rpc } => { + CastSubcommand::Nonce { block, who, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; + let who = who.resolve(&provider).await?; println!("{}", Cast::new(provider).nonce(who, block).await?); } - Subcommands::Proof { address, slots, rpc, block } => { + CastSubcommand::Proof { address, slots, rpc, block } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; + let address = address.resolve(&provider).await?; let value = provider - .get_proof(address, slots.into_iter().map(|s| s.to_ethers()).collect(), block) + .get_proof(address, slots.into_iter().collect(), block.unwrap_or(BlockId::latest())) .await?; println!("{}", serde_json::to_string(&value)?); } - Subcommands::Rpc(cmd) => cmd.run().await?, - Subcommands::Storage(cmd) => cmd.run().await?, + CastSubcommand::Rpc(cmd) => cmd.run().await?, + CastSubcommand::Storage(cmd) => cmd.run().await?, // Calls & transactions - Subcommands::Call(cmd) => cmd.run().await?, - Subcommands::Estimate(cmd) => cmd.run().await?, - Subcommands::PublishTx { raw_tx, cast_async, rpc } => { + CastSubcommand::Call(cmd) => cmd.run().await?, + CastSubcommand::Estimate(cmd) => cmd.run().await?, + CastSubcommand::MakeTx(cmd) => cmd.run().await?, + CastSubcommand::PublishTx { raw_tx, cast_async, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; let cast = Cast::new(&provider); let pending_tx = cast.publish(raw_tx).await?; - let tx_hash = *pending_tx; + let tx_hash = pending_tx.inner().tx_hash(); if cast_async { println!("{tx_hash:#x}"); } else { - let receipt = - pending_tx.await?.ok_or_else(|| eyre::eyre!("tx {tx_hash} not found"))?; + let receipt = pending_tx.get_receipt().await?; println!("{}", serde_json::json!(receipt)); } } - Subcommands::Receipt { tx_hash, field, json, cast_async, confirmations, rpc } => { + CastSubcommand::Receipt { tx_hash, field, json, cast_async, confirmations, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; println!( @@ -332,9 +382,9 @@ async fn main() -> Result<()> { .await? ); } - Subcommands::Run(cmd) => cmd.run().await?, - Subcommands::SendTx(cmd) => cmd.run().await?, - Subcommands::Tx { tx_hash, field, raw, json, rpc } => { + CastSubcommand::Run(cmd) => cmd.run().await?, + CastSubcommand::SendTx(cmd) => cmd.run().await?, + CastSubcommand::Tx { tx_hash, field, raw, json, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; @@ -345,7 +395,7 @@ async fn main() -> Result<()> { } // 4Byte - Subcommands::FourByte { selector } => { + CastSubcommand::FourByte { selector } => { let selector = stdin::unwrap_line(selector)?; let sigs = decode_function_selector(&selector).await?; if sigs.is_empty() { @@ -355,7 +405,7 @@ async fn main() -> Result<()> { println!("{sig}"); } } - Subcommands::FourByteDecode { calldata } => { + CastSubcommand::FourByteDecode { calldata } => { let calldata = stdin::unwrap_line(calldata)?; let sigs = decode_calldata(&calldata).await?; sigs.iter().enumerate().for_each(|(i, sig)| println!("{}) \"{sig}\"", i + 1)); @@ -374,7 +424,7 @@ async fn main() -> Result<()> { println!("{token}"); } } - Subcommands::FourByteEvent { topic } => { + CastSubcommand::FourByteEvent { topic } => { let topic = stdin::unwrap_line(topic)?; let sigs = decode_event_topic(&topic).await?; if sigs.is_empty() { @@ -384,7 +434,7 @@ async fn main() -> Result<()> { println!("{sig}"); } } - Subcommands::UploadSignature { signatures } => { + CastSubcommand::UploadSignature { signatures } => { let signatures = stdin::unwrap_vec(signatures)?; let ParsedSignatures { signatures, abis } = parse_signatures(signatures); if !abis.is_empty() { @@ -396,43 +446,43 @@ async fn main() -> Result<()> { } // ENS - Subcommands::Namehash { name } => { + CastSubcommand::Namehash { name } => { let name = stdin::unwrap_line(name)?; - println!("{}", SimpleCast::namehash(&name)?); + println!("{}", namehash(&name)); } - Subcommands::LookupAddress { who, rpc, verify } => { + CastSubcommand::LookupAddress { who, rpc, verify } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; let who = stdin::unwrap_line(who)?; - let name = provider.lookup_address(who.to_ethers()).await?; + let name = provider.lookup_address(&who).await?; if verify { - let address = provider.resolve_name(&name).await?.to_alloy(); + let address = provider.resolve_name(&name).await?; eyre::ensure!( address == who, - "Forward lookup verification failed: got `{name:?}`, expected `{who:?}`" + "Reverse lookup verification failed: got `{address}`, expected `{who}`" ); } println!("{name}"); } - Subcommands::ResolveName { who, rpc, verify } => { + CastSubcommand::ResolveName { who, rpc, verify } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; let who = stdin::unwrap_line(who)?; let address = provider.resolve_name(&who).await?; if verify { - let name = provider.lookup_address(address).await?; - assert_eq!( - name, who, - "forward lookup verification failed. got {name}, expected {who}" + let name = provider.lookup_address(&address).await?; + eyre::ensure!( + name == who, + "Forward lookup verification failed: got `{name}`, expected `{who}`" ); } - println!("{}", address.to_alloy().to_checksum(None)); + println!("{address}"); } // Misc - Subcommands::Keccak { data } => { + CastSubcommand::Keccak { data } => { let bytes = match data { Some(data) => data.into_bytes(), None => stdin::read_bytes(false)?, @@ -449,18 +499,18 @@ async fn main() -> Result<()> { } }; } - Subcommands::SigEvent { event_string } => { + CastSubcommand::SigEvent { event_string } => { let event_string = stdin::unwrap_line(event_string)?; let parsed_event = get_event(&event_string)?; println!("{:?}", parsed_event.selector()); } - Subcommands::LeftShift { value, bits, base_in, base_out } => { + CastSubcommand::LeftShift { value, bits, base_in, base_out } => { println!("{}", SimpleCast::left_shift(&value, &bits, base_in.as_deref(), &base_out)?); } - Subcommands::RightShift { value, bits, base_in, base_out } => { + CastSubcommand::RightShift { value, bits, base_in, base_out } => { println!("{}", SimpleCast::right_shift(&value, &bits, base_in.as_deref(), &base_out)?); } - Subcommands::EtherscanSource { address, directory, etherscan } => { + CastSubcommand::EtherscanSource { address, directory, etherscan } => { 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(); @@ -474,30 +524,23 @@ async fn main() -> Result<()> { } } } - Subcommands::Create2(cmd) => { + CastSubcommand::Create2(cmd) => { cmd.run()?; } - Subcommands::Wallet { command } => command.run().await?, - Subcommands::Completions { shell } => { + CastSubcommand::Wallet { command } => command.run().await?, + CastSubcommand::Completions { shell } => { generate(shell, &mut Opts::command(), "cast", &mut std::io::stdout()) } - Subcommands::GenerateFigSpec => clap_complete::generate( + CastSubcommand::GenerateFigSpec => clap_complete::generate( clap_complete_fig::Fig, &mut Opts::command(), "cast", &mut std::io::stdout(), ), - Subcommands::Logs(cmd) => cmd.run().await?, - Subcommands::DecodeTransaction { tx } => { + CastSubcommand::Logs(cmd) => cmd.run().await?, + CastSubcommand::DecodeTransaction { tx } => { let tx = stdin::unwrap_line(tx)?; - let (tx, sig) = SimpleCast::decode_raw_transaction(&tx)?; - - // Serialize tx, sig and constructed a merged json string - let mut tx = serde_json::to_value(&tx)?; - let tx_map = tx.as_object_mut().unwrap(); - serde_json::to_value(sig)?.as_object().unwrap().iter().for_each(|(k, v)| { - tx_map.entry(k).or_insert(v.clone()); - }); + let tx = SimpleCast::decode_raw_transaction(&tx)?; println!("{}", serde_json::to_string_pretty(&tx)?); } diff --git a/crates/cast/bin/opts.rs b/crates/cast/bin/opts.rs index dcd557d310fae..51f32b145c838 100644 --- a/crates/cast/bin/opts.rs +++ b/crates/cast/bin/opts.rs @@ -1,13 +1,15 @@ use crate::cmd::{ access_list::AccessListArgs, bind::BindArgs, call::CallArgs, create2::Create2Args, estimate::EstimateArgs, find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs, - rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, wallet::WalletSubcommands, + mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, + wallet::WalletSubcommands, }; use alloy_primitives::{Address, B256, U256}; +use alloy_rpc_types::BlockId; use clap::{Parser, Subcommand, ValueHint}; -use ethers_core::types::{BlockId, NameOrAddress}; use eyre::Result; use foundry_cli::opts::{EtherscanOpts, RpcOpts}; +use foundry_common::ens::NameOrAddress; use std::{path::PathBuf, str::FromStr}; const VERSION_MESSAGE: &str = concat!( @@ -19,54 +21,55 @@ const VERSION_MESSAGE: &str = concat!( ")" ); -#[derive(Debug, Parser)] -#[clap(name = "cast", version = VERSION_MESSAGE)] -pub struct Opts { - #[clap(subcommand)] - pub sub: Subcommands, -} - /// Perform Ethereum RPC calls from the comfort of your command line. -#[derive(Debug, Subcommand)] -#[clap( +#[derive(Parser)] +#[command( + name = "cast", + version = VERSION_MESSAGE, after_help = "Find more information in the book: http://book.getfoundry.sh/reference/cast/cast.html", - next_display_order = None + next_display_order = None, )] -pub enum Subcommands { +pub struct Cast { + #[command(subcommand)] + pub cmd: CastSubcommand, +} + +#[derive(Subcommand)] +pub enum CastSubcommand { /// Prints the maximum value of the given integer type. - #[clap(visible_aliases = &["--max-int", "maxi"])] + #[command(visible_aliases = &["--max-int", "maxi"])] MaxInt { /// The integer type to get the maximum value of. - #[clap(default_value = "int256")] + #[arg(default_value = "int256")] r#type: String, }, /// Prints the minimum value of the given integer type. - #[clap(visible_aliases = &["--min-int", "mini"])] + #[command(visible_aliases = &["--min-int", "mini"])] MinInt { /// The integer type to get the minimum value of. - #[clap(default_value = "int256")] + #[arg(default_value = "int256")] r#type: String, }, /// Prints the maximum value of the given integer type. - #[clap(visible_aliases = &["--max-uint", "maxu"])] + #[command(visible_aliases = &["--max-uint", "maxu"])] MaxUint { /// The unsigned integer type to get the maximum value of. - #[clap(default_value = "uint256")] + #[arg(default_value = "uint256")] r#type: String, }, /// Prints the zero address. - #[clap(visible_aliases = &["--address-zero", "az"])] + #[command(visible_aliases = &["--address-zero", "az"])] AddressZero, /// Prints the zero hash. - #[clap(visible_aliases = &["--hash-zero", "hz"])] + #[command(visible_aliases = &["--hash-zero", "hz"])] HashZero, /// Convert UTF8 text to hex. - #[clap( + #[command( visible_aliases = &[ "--from-ascii", "--from-utf8", @@ -80,14 +83,14 @@ pub enum Subcommands { }, /// Concatenate hex strings. - #[clap(visible_aliases = &["--concat-hex", "ch"])] + #[command(visible_aliases = &["--concat-hex", "ch"])] ConcatHex { /// The data to concatenate. data: Vec, }, /// Convert binary data into hex data. - #[clap(visible_aliases = &["--from-bin", "from-binx", "fb"])] + #[command(visible_aliases = &["--from-bin", "from-binx", "fb"])] FromBin, /// Normalize the input to lowercase, 0x-prefixed hex. @@ -97,14 +100,14 @@ pub enum Subcommands { /// - 0x prefixed hex, concatenated with a ':' /// - an absolute path to file /// - @tag, where the tag is defined in an environment variable - #[clap(visible_aliases = &["--to-hexdata", "thd", "2hd"])] + #[command(visible_aliases = &["--to-hexdata", "thd", "2hd"])] ToHexdata { /// The input to normalize. input: Option, }, /// Convert an address to a checksummed format (EIP-55). - #[clap( + #[command( visible_aliases = &["--to-checksum-address", "--to-checksum", "to-checksum", @@ -117,57 +120,57 @@ pub enum Subcommands { }, /// Convert hex data to an ASCII string. - #[clap(visible_aliases = &["--to-ascii", "tas", "2as"])] + #[command(visible_aliases = &["--to-ascii", "tas", "2as"])] ToAscii { /// The hex data to convert. hexdata: Option, }, /// Convert a fixed point number into an integer. - #[clap(visible_aliases = &["--from-fix", "ff"])] + #[command(visible_aliases = &["--from-fix", "ff"])] FromFixedPoint { /// The number of decimals to use. decimals: Option, /// The value to convert. - #[clap(allow_hyphen_values = true)] + #[arg(allow_hyphen_values = true)] value: Option, }, /// Right-pads hex data to 32 bytes. - #[clap(visible_aliases = &["--to-bytes32", "tb", "2b"])] + #[command(visible_aliases = &["--to-bytes32", "tb", "2b"])] ToBytes32 { /// The hex data to convert. bytes: Option, }, /// Convert an integer into a fixed point number. - #[clap(visible_aliases = &["--to-fix", "tf", "2f"])] + #[command(visible_aliases = &["--to-fix", "tf", "2f"])] ToFixedPoint { /// The number of decimals to use. decimals: Option, /// The value to convert. - #[clap(allow_hyphen_values = true)] + #[arg(allow_hyphen_values = true)] value: Option, }, /// Convert a number to a hex-encoded uint256. - #[clap(name = "to-uint256", visible_aliases = &["--to-uint256", "tu", "2u"])] + #[command(name = "to-uint256", visible_aliases = &["--to-uint256", "tu", "2u"])] ToUint256 { /// The value to convert. value: Option, }, /// Convert a number to a hex-encoded int256. - #[clap(name = "to-int256", visible_aliases = &["--to-int256", "ti", "2i"])] + #[command(name = "to-int256", visible_aliases = &["--to-int256", "ti", "2i"])] ToInt256 { /// The value to convert. value: Option, }, /// Perform a left shifting operation - #[clap(name = "shl")] + #[command(name = "shl")] LeftShift { /// The value to shift. value: String, @@ -176,16 +179,16 @@ pub enum Subcommands { bits: String, /// The input base. - #[clap(long)] + #[arg(long)] base_in: Option, /// The output base. - #[clap(long, default_value = "16")] + #[arg(long, default_value = "16")] base_out: String, }, /// Perform a right shifting operation - #[clap(name = "shr")] + #[command(name = "shr")] RightShift { /// The value to shift. value: String, @@ -194,11 +197,11 @@ pub enum Subcommands { bits: String, /// The input base, - #[clap(long)] + #[arg(long)] base_in: Option, /// The output base, - #[clap(long, default_value = "16")] + #[arg(long, default_value = "16")] base_out: String, }, @@ -210,46 +213,46 @@ pub enum Subcommands { /// - 1ether /// - 1 gwei /// - 1gwei ether - #[clap(visible_aliases = &["--to-unit", "tun", "2un"])] + #[command(visible_aliases = &["--to-unit", "tun", "2un"])] ToUnit { /// The value to convert. value: Option, /// The unit to convert to (ether, gwei, wei). - #[clap(default_value = "wei")] + #[arg(default_value = "wei")] unit: String, }, /// Convert an ETH amount to wei. /// /// Consider using --to-unit. - #[clap(visible_aliases = &["--to-wei", "tw", "2w"])] + #[command(visible_aliases = &["--to-wei", "tw", "2w"])] ToWei { /// The value to convert. - #[clap(allow_hyphen_values = true)] + #[arg(allow_hyphen_values = true)] value: Option, /// The unit to convert from (ether, gwei, wei). - #[clap(default_value = "eth")] + #[arg(default_value = "eth")] unit: String, }, /// Convert wei into an ETH amount. /// /// Consider using --to-unit. - #[clap(visible_aliases = &["--from-wei", "fw"])] + #[command(visible_aliases = &["--from-wei", "fw"])] FromWei { /// The value to convert. - #[clap(allow_hyphen_values = true)] + #[arg(allow_hyphen_values = true)] value: Option, /// The unit to convert from (ether, gwei, wei). - #[clap(default_value = "eth")] + #[arg(default_value = "eth")] unit: String, }, - /// RLP encodes hex data, or an array of hex data - #[clap(visible_aliases = &["--to-rlp"])] + /// RLP encodes hex data, or an array of hex data. + #[command(visible_aliases = &["--to-rlp"])] ToRlp { /// The value to convert. value: Option, @@ -258,22 +261,22 @@ pub enum Subcommands { /// Decodes RLP encoded data. /// /// Input must be hexadecimal. - #[clap(visible_aliases = &["--from-rlp"])] + #[command(visible_aliases = &["--from-rlp"])] FromRlp { /// The value to convert. value: Option, }, /// Converts a number of one base to another - #[clap(visible_aliases = &["--to-hex", "th", "2h"])] + #[command(visible_aliases = &["--to-hex", "th", "2h"])] ToHex(ToBaseArgs), /// Converts a number of one base to decimal - #[clap(visible_aliases = &["--to-dec", "td", "2d"])] + #[command(visible_aliases = &["--to-dec", "td", "2d"])] ToDec(ToBaseArgs), /// Converts a number of one base to another - #[clap( + #[command( visible_aliases = &["--to-base", "--to-radix", "to-radix", @@ -281,21 +284,21 @@ pub enum Subcommands { "2r"] )] ToBase { - #[clap(flatten)] + #[command(flatten)] base: ToBaseArgs, /// The output base. - #[clap(value_name = "BASE")] + #[arg(value_name = "BASE")] base_out: Option, }, /// Create an access list for a transaction. - #[clap(visible_aliases = &["ac", "acl"])] + #[command(visible_aliases = &["ac", "acl"])] AccessList(AccessListArgs), /// Get logs by signature or topic. - #[clap(visible_alias = "l")] + #[command(visible_alias = "l")] Logs(LogsArgs), /// Get information about a block. - #[clap(visible_alias = "bl")] + #[command(visible_alias = "bl")] Block { /// The block height to query at. /// @@ -303,89 +306,93 @@ pub enum Subcommands { block: Option, /// If specified, only get the given field of the block. - #[clap(long, short)] + #[arg(long, short)] field: Option, - #[clap(long, env = "CAST_FULL_BLOCK")] + #[arg(long, env = "CAST_FULL_BLOCK")] full: bool, /// Print the block as JSON. - #[clap(long, short, help_heading = "Display options")] + #[arg(long, short, help_heading = "Display options")] json: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the latest block number. - #[clap(visible_alias = "bn")] + #[command(visible_alias = "bn")] BlockNumber { - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Perform a call on an account without publishing a transaction. - #[clap(visible_alias = "c")] + #[command(visible_alias = "c")] Call(CallArgs), /// ABI-encode a function with arguments. - #[clap(name = "calldata", visible_alias = "cd")] + #[command(name = "calldata", visible_alias = "cd")] CalldataEncode { /// The function signature in the format `()()` sig: String, /// The arguments to encode. - #[clap(allow_hyphen_values = true)] + #[arg(allow_hyphen_values = true)] args: Vec, }, /// Get the symbolic name of the current chain. Chain { - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the Ethereum chain ID. - #[clap(visible_aliases = &["ci", "cid"])] + #[command(visible_aliases = &["ci", "cid"])] ChainId { - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the current client version. - #[clap(visible_alias = "cl")] + #[command(visible_alias = "cl")] Client { - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Compute the contract address from a given nonce and deployer address. - #[clap(visible_alias = "ca")] + #[command(visible_alias = "ca")] ComputeAddress { /// The deployer address. address: Option, /// The nonce of the deployer address. - #[clap(long)] + #[arg(long)] nonce: Option, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Disassembles hex encoded bytecode into individual / human readable opcodes - #[clap(visible_alias = "da")] + #[command(visible_alias = "da")] Disassemble { /// The hex encoded bytecode. bytecode: String, }, + /// Build and sign a transaction. + #[command(name = "mktx", visible_alias = "m")] + MakeTx(MakeTxArgs), + /// Calculate the ENS namehash of a name. - #[clap(visible_aliases = &["na", "nh"])] + #[command(visible_aliases = &["na", "nh"])] Namehash { name: Option }, /// Get information about a transaction. - #[clap(visible_alias = "t")] + #[command(visible_alias = "t")] Tx { /// The transaction hash. tx_hash: String, @@ -395,19 +402,19 @@ pub enum Subcommands { field: Option, /// Print the raw RLP encoded transaction. - #[clap(long, conflicts_with = "field")] + #[arg(long, conflicts_with = "field")] raw: bool, /// Print as JSON. - #[clap(long, short, help_heading = "Display options")] + #[arg(long, short, help_heading = "Display options")] json: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the transaction receipt for a transaction. - #[clap(visible_alias = "re")] + #[command(visible_alias = "re")] Receipt { /// The transaction hash. tx_hash: String, @@ -416,48 +423,48 @@ pub enum Subcommands { field: Option, /// The number of confirmations until the receipt is fetched - #[clap(long, default_value = "1")] - confirmations: usize, + #[arg(long, default_value = "1")] + confirmations: u64, /// Exit immediately if the transaction was not found. - #[clap(long = "async", env = "CAST_ASYNC", name = "async", alias = "cast-async")] + #[arg(id = "async", long = "async", env = "CAST_ASYNC", alias = "cast-async")] cast_async: bool, /// Print as JSON. - #[clap(long, short, help_heading = "Display options")] + #[arg(long, short, help_heading = "Display options")] json: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Sign and publish a transaction. - #[clap(name = "send", visible_alias = "s")] + #[command(name = "send", visible_alias = "s")] SendTx(SendTxArgs), /// Publish a raw transaction to the network. - #[clap(name = "publish", visible_alias = "p")] + #[command(name = "publish", visible_alias = "p")] PublishTx { /// The raw transaction raw_tx: String, /// Only print the transaction hash and exit immediately. - #[clap(long = "async", env = "CAST_ASYNC", name = "async", alias = "cast-async")] + #[arg(id = "async", long = "async", env = "CAST_ASYNC", alias = "cast-async")] cast_async: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Estimate the gas cost of a transaction. - #[clap(visible_alias = "e")] + #[command(visible_alias = "e")] Estimate(EstimateArgs), /// Decode ABI-encoded input data. /// /// Similar to `abi-decode --input`, but function selector MUST be prefixed in `calldata` /// string - #[clap(visible_aliases = &["--calldata-decode","cdd"])] + #[command(visible_aliases = &["--calldata-decode","cdd"])] CalldataDecode { /// The function signature in the format `()()`. sig: String, @@ -471,7 +478,7 @@ pub enum Subcommands { /// Defaults to decoding output data. To decode input data pass --input. /// /// When passing `--input`, function selector must NOT be prefixed in `calldata` string - #[clap(name = "abi-decode", visible_aliases = &["ad", "--abi-decode"])] + #[command(name = "abi-decode", visible_aliases = &["ad", "--abi-decode"])] AbiDecode { /// The function signature in the format `()()`. sig: String, @@ -480,23 +487,27 @@ pub enum Subcommands { calldata: String, /// Whether to decode the input or output data. - #[clap(long, short, help_heading = "Decode input data instead of output data")] + #[arg(long, short, help_heading = "Decode input data instead of output data")] input: bool, }, /// ABI encode the given function argument, excluding the selector. - #[clap(visible_alias = "ae")] + #[command(visible_alias = "ae")] AbiEncode { /// The function signature. sig: String, + /// Whether to use packed encoding. + #[arg(long)] + packed: bool, + /// The arguments of the function. - #[clap(allow_hyphen_values = true)] + #[arg(allow_hyphen_values = true)] args: Vec, }, /// Compute the storage slot for an entry in a mapping. - #[clap(visible_alias = "in")] + #[command(visible_alias = "in")] Index { /// The mapping key type. key_type: String, @@ -509,58 +520,58 @@ pub enum Subcommands { }, /// Fetch the EIP-1967 implementation account - #[clap(visible_alias = "impl")] + #[command(visible_alias = "impl")] Implementation { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, /// The address to get the nonce for. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Fetch the EIP-1967 admin account - #[clap(visible_alias = "adm")] + #[command(visible_alias = "adm")] Admin { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, /// The address to get the nonce for. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the function signatures for the given selector from https://openchain.xyz. - #[clap(name = "4byte", visible_aliases = &["4", "4b"])] + #[command(name = "4byte", visible_aliases = &["4", "4b"])] FourByte { /// The function selector. selector: Option, }, /// Decode ABI-encoded calldata using https://openchain.xyz. - #[clap(name = "4byte-decode", visible_aliases = &["4d", "4bd"])] + #[command(name = "4byte-decode", visible_aliases = &["4d", "4bd"])] FourByteDecode { /// The ABI-encoded calldata. calldata: Option, }, /// Get the event signature for a given topic 0 from https://openchain.xyz. - #[clap(name = "4byte-event", visible_aliases = &["4e", "4be", "topic0-event", "t0e"])] + #[command(name = "4byte-event", visible_aliases = &["4e", "4be", "topic0-event", "t0e"])] FourByteEvent { /// Topic 0 - #[clap(value_name = "TOPIC_0")] + #[arg(value_name = "TOPIC_0")] topic: Option, }, @@ -571,7 +582,7 @@ pub enum Subcommands { /// - "function transfer(address,uint256)" /// - "function transfer(address,uint256)" "event Transfer(address,address,uint256)" /// - "./out/Contract.sol/Contract.json" - #[clap(visible_aliases = &["ups"])] + #[command(visible_aliases = &["ups"])] UploadSignature { /// The signatures to upload. /// @@ -583,223 +594,228 @@ pub enum Subcommands { /// Pretty print calldata. /// /// Tries to decode the calldata using https://openchain.xyz unless --offline is passed. - #[clap(visible_alias = "pc")] + #[command(visible_alias = "pc")] PrettyCalldata { /// The calldata. calldata: Option, /// Skip the https://openchain.xyz lookup. - #[clap(long, short)] + #[arg(long, short)] offline: bool, }, /// Get the timestamp of a block. - #[clap(visible_alias = "a")] + #[command(visible_alias = "a")] Age { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. block: Option, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the balance of an account in wei. - #[clap(visible_alias = "b")] + #[command(visible_alias = "b")] Balance { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, /// The account to query. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, /// Format the balance in ether. - #[clap(long, short)] + #[arg(long, short)] ether: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, + + /// erc20 address to query, with the method `balanceOf(address) return (uint256)`, alias + /// with '--erc721' + #[arg(long, alias = "erc721")] + erc20: Option
, }, /// Get the basefee of a block. - #[clap(visible_aliases = &["ba", "fee", "basefee"])] + #[command(visible_aliases = &["ba", "fee", "basefee"])] BaseFee { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. block: Option, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the runtime bytecode of a contract. - #[clap(visible_alias = "co")] + #[command(visible_alias = "co")] Code { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, /// The contract address. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, /// Disassemble bytecodes into individual opcodes. - #[clap(long, short)] + #[arg(long, short)] disassemble: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the runtime bytecode size of a contract. - #[clap(visible_alias = "cs")] + #[command(visible_alias = "cs")] Codesize { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, /// The contract address. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the current gas price. - #[clap(visible_alias = "g")] + #[command(visible_alias = "g")] GasPrice { - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Generate event signatures from event string. - #[clap(visible_alias = "se")] + #[command(visible_alias = "se")] SigEvent { /// The event string. event_string: Option, }, /// Hash arbitrary data using Keccak-256. - #[clap(visible_alias = "k")] + #[command(visible_alias = "k")] Keccak { /// The data to hash. data: Option, }, /// Perform an ENS lookup. - #[clap(visible_alias = "rn")] + #[command(visible_alias = "rn")] ResolveName { /// The name to lookup. who: Option, /// Perform a reverse lookup to verify that the name is correct. - #[clap(long, short)] + #[arg(long, short)] verify: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Perform an ENS reverse lookup. - #[clap(visible_alias = "la")] + #[command(visible_alias = "la")] LookupAddress { /// The account to perform the lookup for. who: Option
, /// Perform a normal lookup to verify that the address is correct. - #[clap(long, short)] + #[arg(long, short)] verify: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the raw value of a contract's storage slot. - #[clap(visible_alias = "st")] + #[command(visible_alias = "st")] Storage(StorageArgs), /// Generate a storage proof for a given storage slot. - #[clap(visible_alias = "pr")] + #[command(visible_alias = "pr")] Proof { /// The contract address. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] address: NameOrAddress, /// The storage slot numbers (hex or decimal). - #[clap(value_parser = parse_slot)] + #[arg(value_parser = parse_slot)] slots: Vec, /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the nonce for an account. - #[clap(visible_alias = "n")] + #[command(visible_alias = "n")] Nonce { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, /// The address to get the nonce for. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the source code of a contract from Etherscan. - #[clap(visible_aliases = &["et", "src"])] + #[command(visible_aliases = &["et", "src"])] EtherscanSource { /// The contract's address. address: String, /// The output directory to expand source tree into. - #[clap(short, value_hint = ValueHint::DirPath)] + #[arg(short, value_hint = ValueHint::DirPath)] directory: Option, - #[clap(flatten)] + #[command(flatten)] etherscan: EtherscanOpts, }, /// Wallet management utilities. - #[clap(visible_alias = "w")] + #[command(visible_alias = "w")] Wallet { - #[clap(subcommand)] + #[command(subcommand)] command: WalletSubcommands, }, /// Generate a Solidity interface from a given ABI. /// /// Currently does not support ABI encoder v2. - #[clap(visible_alias = "i")] + #[command(visible_alias = "i")] Interface(InterfaceArgs), /// Generate a rust binding from a given ABI. - #[clap(visible_alias = "bi")] + #[command(visible_alias = "bi")] Bind(BindArgs), /// Get the selector for a function. - #[clap(visible_alias = "si")] + #[command(visible_alias = "si")] Sig { /// The function signature, e.g. transfer(address,uint256). sig: Option, @@ -809,66 +825,77 @@ pub enum Subcommands { }, /// Generate a deterministic contract address using CREATE2. - #[clap(visible_alias = "c2")] + #[command(visible_alias = "c2")] Create2(Create2Args), /// Get the block number closest to the provided timestamp. - #[clap(visible_alias = "f")] + #[command(visible_alias = "f")] FindBlock(FindBlockArgs), /// Generate shell completions script. - #[clap(visible_alias = "com")] + #[command(visible_alias = "com")] Completions { - #[clap(value_enum)] + #[arg(value_enum)] shell: clap_complete::Shell, }, /// Generate Fig autocompletion spec. - #[clap(visible_alias = "fig")] + #[command(visible_alias = "fig")] GenerateFigSpec, /// Runs a published transaction in a local environment and prints the trace. - #[clap(visible_alias = "r")] + #[command(visible_alias = "r")] Run(RunArgs), /// Perform a raw JSON-RPC request. - #[clap(visible_alias = "rp")] + #[command(visible_alias = "rp")] Rpc(RpcArgs), /// Formats a string into bytes32 encoding. - #[clap(name = "format-bytes32-string", visible_aliases = &["--format-bytes32-string"])] + #[command(name = "format-bytes32-string", visible_aliases = &["--format-bytes32-string"])] FormatBytes32String { /// The string to format. string: Option, }, /// Parses a string from bytes32 encoding. - #[clap(name = "parse-bytes32-string", visible_aliases = &["--parse-bytes32-string"])] + #[command(name = "parse-bytes32-string", visible_aliases = &["--parse-bytes32-string"])] ParseBytes32String { /// The string to parse. bytes: Option, }, - #[clap(name = "parse-bytes32-address", visible_aliases = &["--parse-bytes32-address"])] - #[clap(about = "Parses a checksummed address from bytes32 encoding.")] + #[command(name = "parse-bytes32-address", visible_aliases = &["--parse-bytes32-address"])] + #[command(about = "Parses a checksummed address from bytes32 encoding.")] ParseBytes32Address { - #[clap(value_name = "BYTES")] + #[arg(value_name = "BYTES")] bytes: Option, }, /// Decodes a raw signed EIP 2718 typed transaction - #[clap(visible_alias = "dt")] + #[command(visible_alias = "dt")] DecodeTransaction { tx: Option }, + + /// Extracts function selectors and arguments from bytecode + #[command(visible_alias = "sel")] + Selectors { + /// The hex encoded bytecode. + bytecode: String, + + /// Resolve the function signatures for the extracted selectors using https://openchain.xyz + #[arg(long, short)] + resolve: bool, + }, } /// CLI arguments for `cast --to-base`. #[derive(Debug, Parser)] pub struct ToBaseArgs { /// The value to convert. - #[clap(allow_hyphen_values = true)] + #[arg(allow_hyphen_values = true)] pub value: Option, /// The input base. - #[clap(long, short = 'i')] + #[arg(long, short = 'i')] pub base_in: Option, } @@ -880,12 +907,18 @@ pub fn parse_slot(s: &str) -> Result { #[cfg(test)] mod tests { use super::*; + use alloy_rpc_types::{BlockNumberOrTag, RpcBlockHash}; use cast::SimpleCast; - use ethers_core::types::BlockNumber; + use clap::CommandFactory; + + #[test] + fn verify_cli() { + Cast::command().debug_assert(); + } #[test] fn parse_proof_slot() { - let args: Opts = Opts::parse_from([ + let args: Cast = Cast::parse_from([ "foundry-cli", "proof", "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", @@ -895,8 +928,8 @@ mod tests { "0x1", "0x01", ]); - match args.sub { - Subcommands::Proof { slots, .. } => { + match args.cmd { + CastSubcommand::Proof { slots, .. } => { assert_eq!( slots, vec![ @@ -914,15 +947,15 @@ mod tests { #[test] fn parse_call_data() { - let args: Opts = Opts::parse_from([ + let args: Cast = Cast::parse_from([ "foundry-cli", "calldata", "f()", "5c9d55b78febcc2061715ba4f57ecf8ea2711f2c", "2", ]); - match args.sub { - Subcommands::CalldataEncode { args, .. } => { + match args.cmd { + CastSubcommand::CalldataEncode { args, .. } => { assert_eq!( args, vec!["5c9d55b78febcc2061715ba4f57ecf8ea2711f2c".to_string(), "2".to_string()] @@ -935,13 +968,13 @@ mod tests { // #[test] fn parse_signature() { - let args: Opts = Opts::parse_from([ + let args: Cast = Cast::parse_from([ "foundry-cli", "sig", "__$_$__$$$$$__$$_$$$_$$__$$___$$(address,address,uint256)", ]); - match args.sub { - Subcommands::Sig { sig, .. } => { + match args.cmd { + CastSubcommand::Sig { sig, .. } => { let sig = sig.unwrap(); assert_eq!( sig, @@ -965,30 +998,34 @@ mod tests { let test_cases = [ TestCase { input: "0".to_string(), - expect: BlockId::Number(BlockNumber::Number(0u64.into())), + expect: BlockId::Number(BlockNumberOrTag::Number(0u64)), }, TestCase { input: "0x56462c47c03df160f66819f0a79ea07def1569f8aac0fe91bb3a081159b61b4a" .to_string(), - expect: BlockId::Hash( + expect: BlockId::Hash(RpcBlockHash::from_hash( "0x56462c47c03df160f66819f0a79ea07def1569f8aac0fe91bb3a081159b61b4a" .parse() .unwrap(), - ), + None, + )), + }, + TestCase { + input: "latest".to_string(), + expect: BlockId::Number(BlockNumberOrTag::Latest), }, - TestCase { input: "latest".to_string(), expect: BlockId::Number(BlockNumber::Latest) }, TestCase { input: "earliest".to_string(), - expect: BlockId::Number(BlockNumber::Earliest), + expect: BlockId::Number(BlockNumberOrTag::Earliest), }, TestCase { input: "pending".to_string(), - expect: BlockId::Number(BlockNumber::Pending), + expect: BlockId::Number(BlockNumberOrTag::Pending), }, - TestCase { input: "safe".to_string(), expect: BlockId::Number(BlockNumber::Safe) }, + TestCase { input: "safe".to_string(), expect: BlockId::Number(BlockNumberOrTag::Safe) }, TestCase { input: "finalized".to_string(), - expect: BlockId::Number(BlockNumber::Finalized), + expect: BlockId::Number(BlockNumberOrTag::Finalized), }, ]; diff --git a/crates/cast/bin/tx.rs b/crates/cast/bin/tx.rs new file mode 100644 index 0000000000000..1ad0ee2a9679e --- /dev/null +++ b/crates/cast/bin/tx.rs @@ -0,0 +1,124 @@ +use alloy_json_abi::Function; +use alloy_network::{AnyNetwork, TransactionBuilder}; +use alloy_primitives::{Address, Bytes, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockId, TransactionRequest, WithOtherFields}; +use alloy_transport::Transport; +use eyre::Result; +use foundry_cli::{opts::TransactionOpts, utils::parse_function_args}; +use foundry_common::ens::NameOrAddress; +use foundry_config::Chain; + +/// Prevents a misconfigured hwlib from sending a transaction that defies user-specified --from +pub fn validate_from_address( + specified_from: Option
, + signer_address: Address, +) -> Result<()> { + if let Some(specified_from) = specified_from { + if specified_from != signer_address { + eyre::bail!( + "\ +The specified sender via CLI/env vars does not match the sender configured via +the hardware wallet's HD Path. +Please use the `--hd-path ` parameter to specify the BIP32 Path which +corresponds to the sender, or let foundry automatically detect it by not specifying any sender address." + ) + } + } + Ok(()) +} + +/// Ensures the transaction is either a contract deployment or a recipient address is specified +pub fn validate_to_address(code: &Option, to: &Option) -> Result<()> { + if code.is_none() && to.is_none() { + eyre::bail!("Must specify a recipient address or contract code to deploy"); + } + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +pub async fn build_tx< + P: Provider, + T: Transport + Clone, + F: Into, + TO: Into, +>( + provider: &P, + from: F, + to: Option, + code: Option, + sig: Option, + args: Vec, + tx: TransactionOpts, + chain: impl Into, + etherscan_api_key: Option, +) -> Result<(WithOtherFields, Option)> { + let chain = chain.into(); + + let from = from.into().resolve(provider).await?; + + let to: Option
= + if let Some(to) = to { Some(to.into().resolve(provider).await?) } else { None }; + + let mut req = WithOtherFields::new(TransactionRequest::default()) + .with_to(to.unwrap_or_default()) + .with_from(from) + .with_value(tx.value.unwrap_or_default()) + .with_chain_id(chain.id()); + + req.set_nonce(if let Some(nonce) = tx.nonce { + nonce.to() + } else { + provider.get_transaction_count(from, BlockId::latest()).await? + }); + + if tx.legacy || chain.is_legacy() { + req.set_gas_price(if let Some(gas_price) = tx.gas_price { + gas_price.to() + } else { + provider.get_gas_price().await? + }); + } else { + let (max_fee, priority_fee) = match (tx.gas_price, tx.priority_gas_price) { + (Some(gas_price), Some(priority_gas_price)) => (gas_price, priority_gas_price), + (_, _) => { + let estimate = provider.estimate_eip1559_fees(None).await?; + ( + tx.gas_price.unwrap_or(U256::from(estimate.max_fee_per_gas)), + tx.priority_gas_price.unwrap_or(U256::from(estimate.max_priority_fee_per_gas)), + ) + } + }; + + req.set_max_fee_per_gas(max_fee.to()); + req.set_max_priority_fee_per_gas(priority_fee.to()); + } + + let params = sig.as_deref().map(|sig| (sig, args)); + let (data, func) = if let Some(code) = code { + let mut data = hex::decode(code)?; + + if let Some((sig, args)) = params { + let (mut sigdata, _) = + parse_function_args(sig, args, None, chain, provider, etherscan_api_key.as_deref()) + .await?; + data.append(&mut sigdata); + } + + (data, None) + } else if let Some((sig, args)) = params { + parse_function_args(sig, args, None, chain, provider, etherscan_api_key.as_deref()).await? + } else { + (Vec::new(), None) + }; + + req.set_input::(data.into()); + + req.set_gas_limit(if let Some(gas_limit) = tx.gas_limit { + gas_limit.to() + } else { + provider.estimate_gas(&req, BlockId::latest()).await? + }); + + Ok((req, func)) +} diff --git a/crates/cast/src/base.rs b/crates/cast/src/base.rs index 80d2f6047fc4b..44f58091e4193 100644 --- a/crates/cast/src/base.rs +++ b/crates/cast/src/base.rs @@ -1,7 +1,5 @@ -use alloy_primitives::{Sign, I256, U256}; -use ethers_core::utils::ParseUnits; +use alloy_primitives::{utils::ParseUnits, Sign, I256, U256}; use eyre::Result; -use foundry_common::types::ToAlloy; use std::{ convert::Infallible, fmt::{Binary, Debug, Display, Formatter, LowerHex, Octal, Result as FmtResult, UpperHex}, @@ -41,12 +39,12 @@ impl FromStr for Base { "10" | "d" | "dec" | "decimal" => Ok(Self::Decimal), "16" | "h" | "hex" | "hexadecimal" => Ok(Self::Hexadecimal), s => Err(eyre::eyre!( - r#"Invalid base "{}". Possible values: -2, b, bin, binary -8, o, oct, octal + "\ +Invalid base \"{s}\". Possible values: + 2, b, bin, binary + 8, o, oct, octal 10, d, dec, decimal -16, h, hex, hexadecimal"#, - s +16, h, hex, hexadecimal" )), } } @@ -281,8 +279,8 @@ impl From for NumberWithBase { impl From for NumberWithBase { fn from(value: ParseUnits) -> Self { match value { - ParseUnits::U256(val) => val.to_alloy().into(), - ParseUnits::I256(val) => val.to_alloy().into(), + ParseUnits::U256(val) => val.into(), + ParseUnits::I256(val) => val.into(), } } } diff --git a/crates/cast/src/errors.rs b/crates/cast/src/errors.rs index 235ec4d7ca44f..40a8e9755b112 100644 --- a/crates/cast/src/errors.rs +++ b/crates/cast/src/errors.rs @@ -4,7 +4,7 @@ use foundry_config::Chain; use std::fmt; /// An error thrown when resolving a function via signature failed -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub enum FunctionSignatureError { MissingSignature, MissingEtherscan { sig: String }, diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index 8ff767354a782..da4103b74c180 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -1,113 +1,126 @@ +use alloy_consensus::TxEnvelope; use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt}; use alloy_json_abi::{ContractObject, Function}; -use alloy_primitives::{Address, I256, U256}; +use alloy_network::AnyNetwork; +use alloy_primitives::{ + utils::{keccak256, ParseUnits, Unit}, + Address, Keccak256, TxHash, TxKind, B256, I256, U256, +}; +use alloy_provider::{ + network::eip2718::{Decodable2718, Encodable2718}, + PendingTransactionBuilder, Provider, +}; use alloy_rlp::Decodable; +use alloy_rpc_types::{BlockId, BlockNumberOrTag, Filter, TransactionRequest, WithOtherFields}; +use alloy_sol_types::sol; +use alloy_transport::Transport; use base::{Base, NumberWithBase, ToBase}; -use chrono::NaiveDateTime; -use ethers_core::{ - types::{transaction::eip2718::TypedTransaction, *}, - utils::{ - format_bytes32_string, format_units, keccak256, parse_bytes32_string, parse_units, rlp, - Units, - }, -}; -use ethers_providers::{Middleware, PendingTransaction, PubsubClient}; +use chrono::DateTime; use evm_disassembler::{disassemble_bytes, disassemble_str, format_operations}; use eyre::{Context, ContextCompat, Result}; use foundry_block_explorers::Client; use foundry_common::{ - abi::encode_function_args, + abi::{encode_function_args, get_func}, fmt::*, - types::{ToAlloy, ToEthers}, TransactionReceiptWithRevertReason, }; use foundry_config::Chain; use futures::{future::Either, FutureExt, StreamExt}; use rayon::prelude::*; use std::{ + borrow::Cow, io, + marker::PhantomData, path::PathBuf, str::FromStr, sync::atomic::{AtomicBool, Ordering}, }; use tokio::signal::ctrl_c; -use tx::{TxBuilderOutput, TxBuilderPeekOutput}; +use foundry_common::abi::encode_function_args_packed; pub use foundry_evm::*; -pub use rusoto_core::{ - credential::ChainProvider as AwsChainProvider, region::Region as AwsRegion, - request::HttpClient as AwsHttpClient, Client as AwsClient, -}; -pub use rusoto_kms::KmsClient; -pub use tx::TxBuilder; pub mod base; pub mod errors; mod rlp_converter; -mod tx; use rlp_converter::Item; // TODO: CastContract with common contract initializers? Same for CastProviders? -pub struct Cast { - provider: M, +sol! { + #[sol(rpc)] + interface IERC20 { + #[derive(Debug)] + function balanceOf(address owner) external view returns (uint256); + } } -impl Cast +pub struct Cast { + provider: P, + transport: PhantomData, +} + +impl Cast where - M::Error: 'static, + T: Transport + Clone, + P: Provider, { /// Creates a new Cast instance from the provided client /// /// # Example /// /// ``` + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Http, Provider}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// # Ok(()) /// # } /// ``` - pub fn new(provider: M) -> Self { - Self { provider } + pub fn new(provider: P) -> Self { + Self { provider, transport: PhantomData } } /// Makes a read-only call to the specified address /// /// # Example /// - /// ```ignore - /// use cast::{Cast, TxBuilder}; - /// use ethers_core::types::Address; - /// use ethers_providers::{Http, Provider}; + /// ``` + /// use alloy_primitives::{Address, U256, Bytes}; + /// use alloy_rpc_types::{TransactionRequest, WithOtherFields}; + /// use cast::Cast; + /// use alloy_provider::{RootProvider, ProviderBuilder, network::AnyNetwork}; /// use std::str::FromStr; + /// use alloy_sol_types::{sol, SolCall}; + /// + /// sol!( + /// function greeting(uint256 i) public returns (string); + /// ); /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let alloy_provider = ProviderBuilder::<_,_, AnyNetwork>::default().on_builtin("http://localhost:8545").await?;; /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; - /// let sig = "function greeting(uint256 i) public returns (string)"; - /// let args = vec!["5".to_owned()]; - /// let mut builder = - /// TxBuilder::new(&provider, Address::zero(), Some(to), Chain::Mainnet, false).await?; - /// builder.set_args(sig, args).await?; - /// let builder_output = builder.build(); - /// let cast = Cast::new(provider); - /// let data = cast.call(builder_output, None).await?; + /// let greeting = greetingCall { i: U256::from(5) }.abi_encode(); + /// let bytes = Bytes::from_iter(greeting.iter()); + /// let tx = TransactionRequest::default().to(to).input(bytes.into()); + /// let tx = WithOtherFields::new(tx); + /// let cast = Cast::new(alloy_provider); + /// let data = cast.call(&tx, None, None).await?; /// println!("{}", data); /// # Ok(()) /// # } /// ``` pub async fn call<'a>( &self, - builder_output: TxBuilderOutput, + req: &WithOtherFields, + func: Option<&Function>, block: Option, ) -> Result { - let (tx, func) = builder_output; - let res = self.provider.call(&tx, block).await?; + let res = self.provider.call(req, block.unwrap_or_default()).await?; let mut decoded = vec![]; @@ -119,12 +132,18 @@ where // ensure the address is a contract if res.is_empty() { // check that the recipient is a contract that can be called - if let Some(NameOrAddress::Address(addr)) = tx.to() { - if let Ok(code) = self.provider.get_code(*addr, block).await { + if let Some(TxKind::Call(addr)) = req.to { + if let Ok(code) = + self.provider.get_code_at(addr, block.unwrap_or_default()).await + { if code.is_empty() { eyre::bail!("contract {addr:?} does not have any code") } } + } else if Some(TxKind::Create) == req.to { + eyre::bail!("tx req is a contract deployment"); + } else { + eyre::bail!("recipient is None"); } } return Err(err).wrap_err( @@ -147,42 +166,46 @@ where /// /// # Example /// - /// ```ignore - /// use cast::{Cast, TxBuilder}; - /// use ethers_core::types::Address; - /// use ethers_providers::{Http, Provider}; + /// ``` + /// use cast::{Cast}; + /// use alloy_primitives::{Address, U256, Bytes}; + /// use alloy_rpc_types::{TransactionRequest, WithOtherFields}; + /// use alloy_provider::{RootProvider, ProviderBuilder, network::AnyNetwork}; /// use std::str::FromStr; + /// use alloy_sol_types::{sol, SolCall}; + /// + /// sol!( + /// function greeting(uint256 i) public returns (string); + /// ); /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = ProviderBuilder::<_,_, AnyNetwork>::default().on_builtin("http://localhost:8545").await?;; /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; - /// let sig = "greeting(uint256)(string)"; - /// let args = vec!["5".to_owned()]; - /// let mut builder = - /// TxBuilder::new(&provider, Address::zero(), Some(to), Chain::Mainnet, false).await?; - /// builder.set_args(sig, args).await?; - /// let builder_output = builder.peek(); + /// let greeting = greetingCall { i: U256::from(5) }.abi_encode(); + /// let bytes = Bytes::from_iter(greeting.iter()); + /// let tx = TransactionRequest::default().to(to).input(bytes.into()); + /// let tx = WithOtherFields::new(tx); /// let cast = Cast::new(&provider); - /// let access_list = cast.access_list(builder_output, None, false).await?; + /// let access_list = cast.access_list(&tx, None, false).await?; /// println!("{}", access_list); /// # Ok(()) /// # } /// ``` pub async fn access_list( &self, - builder_output: TxBuilderPeekOutput<'_>, + req: &WithOtherFields, block: Option, to_json: bool, ) -> Result { - let (tx, _) = builder_output; - let access_list = self.provider.create_access_list(tx, block).await?; + let access_list = + self.provider.create_access_list(req, block.unwrap_or(BlockId::latest())).await?; let res = if to_json { serde_json::to_string(&access_list)? } else { let mut s = vec![format!("gas used: {}", access_list.gas_used), "access list:".to_string()]; for al in access_list.access_list.0 { - s.push(format!("- address: {}", &al.address.to_alloy().to_checksum(None))); + s.push(format!("- address: {}", &al.address.to_checksum(None))); if !al.storage_keys.is_empty() { s.push(" keys:".to_string()); for key in al.storage_keys { @@ -196,175 +219,131 @@ where Ok(res) } - pub async fn balance + Send + Sync>( - &self, - who: T, - block: Option, - ) -> Result { - Ok(self.provider.get_balance(who, block).await?.to_alloy()) + pub async fn balance(&self, who: Address, block: Option) -> Result { + Ok(self.provider.get_balance(who, block.unwrap_or(BlockId::latest())).await?) } /// Sends a transaction to the specified address /// /// # Example /// - /// ```ignore - /// use cast::{Cast, TxBuilder}; - /// use ethers_core::types::{Address, U256}; - /// use ethers_providers::{Http, Provider}; + /// ``` + /// use cast::{Cast}; + /// use alloy_primitives::{Address, U256, Bytes}; + /// use alloy_rpc_types::{TransactionRequest, WithOtherFields}; + /// use alloy_provider::{RootProvider, ProviderBuilder, network::AnyNetwork}; /// use std::str::FromStr; + /// use alloy_sol_types::{sol, SolCall}; + /// + /// sol!( + /// function greet(string greeting) public; + /// ); /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; - /// let from = "vitalik.eth"; - /// let to = eAddress::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; - /// let sig = "greet(string)()"; - /// let args = vec!["hello".to_owned()]; + /// let provider = ProviderBuilder::<_,_, AnyNetwork>::default().on_builtin("http://localhost:8545").await?;; + /// let from = Address::from_str("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")?; + /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; + /// let greeting = greetCall { greeting: "hello".to_string() }.abi_encode(); + /// let bytes = Bytes::from_iter(greeting.iter()); /// let gas = U256::from_str("200000").unwrap(); /// let value = U256::from_str("1").unwrap(); /// let nonce = U256::from_str("1").unwrap(); - /// let mut builder = TxBuilder::new(&provider, from, Some(to), Chain::Mainnet, false).await?; - /// builder.set_args(sig, args).await?.set_gas(gas).set_value(value).set_nonce(nonce); - /// let builder_output = builder.build(); + /// let tx = TransactionRequest::default().to(to).input(bytes.into()).from(from); + /// let tx = WithOtherFields::new(tx); /// let cast = Cast::new(provider); - /// let data = cast.send(builder_output).await?; - /// println!("{}", *data); + /// let data = cast.send(tx).await?; + /// println!("{:#?}", data); /// # Ok(()) /// # } /// ``` - pub async fn send<'a>( + pub async fn send( &self, - builder_output: TxBuilderOutput, - ) -> Result> { - let (tx, _) = builder_output; - let res = self.provider.send_transaction(tx, None).await?; + tx: WithOtherFields, + ) -> Result> { + let res = self.provider.send_transaction(tx).await?; - Ok::<_, eyre::Error>(res) + Ok(res) } /// Publishes a raw transaction to the network /// /// # Example /// - /// ```ignore + /// ``` + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Http, Provider}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let res = cast.publish("0x1234".to_string()).await?; /// println!("{:?}", res); /// # Ok(()) /// # } /// ``` - pub async fn publish(&self, mut raw_tx: String) -> Result> { + pub async fn publish( + &self, + mut raw_tx: String, + ) -> Result> { raw_tx = match raw_tx.strip_prefix("0x") { Some(s) => s.to_string(), None => raw_tx, }; - let tx = Bytes::from(hex::decode(raw_tx)?); - let res = self.provider.send_raw_transaction(tx).await?; + let tx = hex::decode(raw_tx)?; + let res = self.provider.send_raw_transaction(&tx).await?; - Ok::<_, eyre::Error>(res) + Ok(res) } - /// Estimates the gas cost of a transaction - /// /// # Example /// - /// ```ignore - /// use alloy_primitives::U256; - /// use cast::{Cast, TxBuilder}; - /// use ethers_core::types::Address; - /// use ethers_providers::{Http, Provider}; - /// use std::str::FromStr; - /// - /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; - /// let from = Address::from_str("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")?; - /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; - /// let sig = "greet(string)()"; - /// let args = vec!["5".to_owned()]; - /// let value = U256::from_str("1").unwrap(); - /// let mut builder = TxBuilder::new(&provider, from, Some(to), Chain::Mainnet, false).await?; - /// builder.set_value(value).set_args(sig, args).await?; - /// let builder_output = builder.peek(); - /// let cast = Cast::new(&provider); - /// let data = cast.estimate(builder_output).await?; - /// println!("{}", data); - /// # Ok(()) - /// # } /// ``` - pub async fn estimate(&self, builder_output: TxBuilderPeekOutput<'_>) -> Result { - let (tx, _) = builder_output; - - let res = self.provider.estimate_gas(tx, None).await?; - - Ok::<_, eyre::Error>(res.to_alloy()) - } - - /// # Example - /// - /// ```ignore + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Http, Provider}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let block = cast.block(5, true, None, false).await?; /// println!("{}", block); /// # Ok(()) /// # } /// ``` - pub async fn block>( + pub async fn block>( &self, - block: T, + block: B, full: bool, field: Option, to_json: bool, ) -> Result { let block = block.into(); - let block = if full { - let block = self - .provider - .get_block_with_txs(block) - .await? - .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; - if let Some(ref field) = field { - get_pretty_block_attr(&block, field) - .unwrap_or_else(|| format!("{field} is not a valid block field")) - } else if to_json { - serde_json::to_value(&block).unwrap().to_string() - } else { - block.pretty() + if let Some(ref field) = field { + if field == "transactions" && !full { + eyre::bail!("use --full to view transactions") } + } + + let block = self + .provider + .get_block(block, full) + .await? + .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; + + let block = if let Some(ref field) = field { + get_pretty_block_attr(&block, field) + .unwrap_or_else(|| format!("{field} is not a valid block field")) + } else if to_json { + serde_json::to_value(&block).unwrap().to_string() } else { - let block = self - .provider - .get_block(block) - .await? - .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; - - if let Some(ref field) = field { - if field == "transactions" { - "use --full to view transactions".to_string() - } else { - get_pretty_block_attr(&block, field) - .unwrap_or_else(|| format!("{field} is not a valid block field")) - } - } else if to_json { - serde_json::to_value(&block).unwrap().to_string() - } else { - block.pretty() - } + block.pretty() }; Ok(block) } - async fn block_field_as_num>(&self, block: T, field: String) -> Result { + async fn block_field_as_num>(&self, block: B, field: String) -> Result { let block = block.into(); let block_field = Cast::block( self, @@ -384,19 +363,18 @@ where Ok(ret) } - pub async fn base_fee>(&self, block: T) -> Result { + pub async fn base_fee>(&self, block: B) -> Result { Cast::block_field_as_num(self, block, String::from("baseFeePerGas")).await } - pub async fn age>(&self, block: T) -> Result { + pub async fn age>(&self, block: B) -> Result { let timestamp_str = Cast::block_field_as_num(self, block, String::from("timestamp")).await?.to_string(); - let datetime = - NaiveDateTime::from_timestamp_opt(timestamp_str.parse::().unwrap(), 0).unwrap(); + let datetime = DateTime::from_timestamp(timestamp_str.parse::().unwrap(), 0).unwrap(); Ok(datetime.format("%a %b %e %H:%M:%S %Y").to_string()) } - pub async fn timestamp>(&self, block: T) -> Result { + pub async fn timestamp>(&self, block: B) -> Result { Cast::block_field_as_num(self, block, "timestamp".to_string()).await } @@ -432,6 +410,10 @@ where "0x02adc9b449ff5f2467b8c674ece7ff9b21319d76c4ad62a67a70d552655927e5" => { "optimism-kovan" } + "0x521982bd54239dc71269eefb58601762cc15cfb2978e0becb46af7962ed6bfaa" => "fraxtal", + "0x910f5c4084b63fd860d0c2f9a04615115a5a991254700b39ba072290dbd77489" => { + "fraxtal-testnet" + } "0x7ee576b35482195fc49205cec9af72ce14f003b9ae69f6ba0faef4514be8b442" => { "arbitrum-mainnet" } @@ -459,28 +441,29 @@ where }) } - pub async fn chain_id(&self) -> Result { - Ok(self.provider.get_chainid().await?.to_alloy()) + pub async fn chain_id(&self) -> Result { + Ok(self.provider.get_chain_id().await?) } - pub async fn block_number(&self) -> Result { + pub async fn block_number(&self) -> Result { Ok(self.provider.get_block_number().await?) } - pub async fn gas_price(&self) -> Result { - Ok(self.provider.get_gas_price().await?.to_alloy()) + pub async fn gas_price(&self) -> Result { + Ok(self.provider.get_gas_price().await?) } /// # Example /// - /// ```ignore + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_core::types::Address; - /// use ethers_providers::{Http, Provider}; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; /// let nonce = cast.nonce(addr, None).await?; @@ -488,24 +471,21 @@ where /// # Ok(()) /// # } /// ``` - pub async fn nonce + Send + Sync>( - &self, - who: T, - block: Option, - ) -> Result { - Ok(self.provider.get_transaction_count(who, block).await?.to_alloy().to()) + pub async fn nonce(&self, who: Address, block: Option) -> Result { + Ok(self.provider.get_transaction_count(who, block.unwrap_or(BlockId::latest())).await?) } /// # Example /// - /// ```ignore + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_core::types::Address; - /// use ethers_providers::{Http, Provider}; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; /// let implementation = cast.implementation(addr, None).await?; @@ -513,28 +493,28 @@ where /// # Ok(()) /// # } /// ``` - pub async fn implementation + Send + Sync>( - &self, - who: T, - block: Option, - ) -> Result { + pub async fn implementation(&self, who: Address, block: Option) -> Result { let slot = - H256::from_str("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")?; - let value = self.provider.get_storage_at(who, slot, block).await?; - let addr: H160 = value.into(); + B256::from_str("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")?; + let value = self + .provider + .get_storage_at(who, slot.into(), block.unwrap_or(BlockId::latest())) + .await?; + let addr = Address::from_word(value.into()); Ok(format!("{addr:?}")) } /// # Example /// - /// ```ignore + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_core::types::Address; - /// use ethers_providers::{Http, Provider}; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; /// let admin = cast.admin(addr, None).await?; @@ -542,28 +522,28 @@ where /// # Ok(()) /// # } /// ``` - pub async fn admin + Send + Sync>( - &self, - who: T, - block: Option, - ) -> Result { + pub async fn admin(&self, who: Address, block: Option) -> Result { let slot = - H256::from_str("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")?; - let value = self.provider.get_storage_at(who, slot, block).await?; - let addr: H160 = value.into(); + B256::from_str("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")?; + let value = self + .provider + .get_storage_at(who, slot.into(), block.unwrap_or(BlockId::latest())) + .await?; + let addr = Address::from_word(value.into()); Ok(format!("{addr:?}")) } /// # Example /// - /// ```ignore + /// ``` /// use alloy_primitives::{Address, U256}; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Http, Provider}; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; /// let computed_address = cast.compute_address(addr, None).await?; @@ -572,21 +552,21 @@ where /// # } /// ``` pub async fn compute_address(&self, address: Address, nonce: Option) -> Result
{ - let unpacked = - if let Some(n) = nonce { n } else { self.nonce(address.to_ethers(), None).await? }; + let unpacked = if let Some(n) = nonce { n } else { self.nonce(address, None).await? }; Ok(address.create(unpacked)) } /// # Example /// - /// ```ignore + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_core::types::Address; - /// use ethers_providers::{Http, Provider}; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x00000000219ab540356cbb839cbe05303d7705fa")?; /// let code = cast.code(addr, None, false).await?; @@ -594,30 +574,31 @@ where /// # Ok(()) /// # } /// ``` - pub async fn code + Send + Sync>( + pub async fn code( &self, - who: T, + who: Address, block: Option, disassemble: bool, ) -> Result { if disassemble { - let code = self.provider.get_code(who, block).await?.to_vec(); + let code = self.provider.get_code_at(who, block.unwrap_or_default()).await?.to_vec(); Ok(format_operations(disassemble_bytes(code)?)?) } else { - Ok(format!("{}", self.provider.get_code(who, block).await?)) + Ok(format!("{}", self.provider.get_code_at(who, block.unwrap_or_default()).await?)) } } /// Example /// - /// ```ignore + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_core::types::Address; - /// use ethers_providers::{Http, Provider}; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x00000000219ab540356cbb839cbe05303d7705fa")?; /// let codesize = cast.codesize(addr, None).await?; @@ -625,23 +606,20 @@ where /// # Ok(()) /// # } /// ``` - pub async fn codesize + Send + Sync>( - &self, - who: T, - block: Option, - ) -> Result { - let code = self.provider.get_code(who, block).await?.to_vec(); + pub async fn codesize(&self, who: Address, block: Option) -> Result { + let code = self.provider.get_code_at(who, block.unwrap_or_default()).await?.to_vec(); Ok(format!("{}", code.len())) } /// # Example /// - /// ```ignore + /// ``` + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Http, Provider}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; /// let tx = cast.transaction(tx_hash.to_string(), None, false, false).await?; @@ -656,15 +634,11 @@ where raw: bool, to_json: bool, ) -> Result { - let tx_hash = H256::from_str(&tx_hash).wrap_err("invalid tx hash")?; - let tx = self - .provider - .get_transaction(tx_hash) - .await? - .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))?; + let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; + let tx = self.provider.get_transaction_by_hash(tx_hash).await?; Ok(if raw { - format!("0x{}", hex::encode(tx.rlp())) + format!("0x{}", hex::encode(TxEnvelope::try_from(tx.inner)?.encoded_2718())) } else if let Some(field) = field { get_pretty_tx_attr(&tx, field.as_str()) .ok_or_else(|| eyre::eyre!("invalid tx field: {}", field.to_string()))? @@ -678,12 +652,13 @@ where /// # Example /// - /// ```ignore + /// ``` + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Http, Provider}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; /// let receipt = cast.receipt(tx_hash.to_string(), None, 1, false, false).await?; @@ -695,11 +670,11 @@ where &self, tx_hash: String, field: Option, - confs: usize, + confs: u64, cast_async: bool, to_json: bool, ) -> Result { - let tx_hash = H256::from_str(&tx_hash).wrap_err("invalid tx hash")?; + let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; let mut receipt: TransactionReceiptWithRevertReason = match self.provider.get_transaction_receipt(tx_hash).await? { @@ -710,13 +685,10 @@ where if cast_async { eyre::bail!("tx not found: {:?}", tx_hash) } else { - let tx = PendingTransaction::new(tx_hash, self.provider.provider()); - tx.confirmations(confs).await?.ok_or_else(|| { - eyre::eyre!( - "tx not found, might have been dropped from mempool: {:?}", - tx_hash - ) - })? + PendingTransactionBuilder::new(self.provider.root(), tx_hash) + .with_required_confirmations(confs) + .get_receipt() + .await? } } } @@ -740,12 +712,13 @@ where /// /// # Example /// - /// ```ignore + /// ``` + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Http, Provider}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let result = cast /// .rpc("eth_getBalance", &["0xc94770007dda54cF92009BFF0dE90c06F603a09f", "latest"]) @@ -754,11 +727,14 @@ where /// # Ok(()) /// # } /// ``` - pub async fn rpc(&self, method: &str, params: T) -> Result + pub async fn rpc(&self, method: &str, params: V) -> Result where - T: std::fmt::Debug + serde::Serialize + Send + Sync, + V: alloy_json_rpc::RpcParam, { - let res = self.provider.provider().request::(method, params).await?; + let res = self + .provider + .raw_request::(Cow::Owned(method.to_string()), params) + .await?; Ok(serde_json::to_string(&res)?) } @@ -766,29 +742,37 @@ where /// /// # Example /// - /// ```ignore + /// ``` + /// use alloy_primitives::{Address, B256}; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_core::types::{Address, H256}; - /// use ethers_providers::{Http, Provider}; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x00000000006c3852cbEf3e08E8dF289169EdE581")?; - /// let slot = H256::zero(); + /// let slot = B256::ZERO; /// let storage = cast.storage(addr, slot, None).await?; /// println!("{}", storage); /// # Ok(()) /// # } /// ``` - pub async fn storage + Send + Sync>( + pub async fn storage( &self, - from: T, - slot: H256, + from: Address, + slot: B256, block: Option, ) -> Result { - Ok(format!("{:?}", self.provider.get_storage_at(from, slot, block).await?)) + Ok(format!( + "{:?}", + B256::from( + self.provider + .get_storage_at(from, slot.into(), block.unwrap_or(BlockId::latest())) + .await? + ) + )) } pub async fn filter_logs(&self, filter: Filter, to_json: bool) -> Result { @@ -818,23 +802,27 @@ where /// /// # Example /// - /// ```ignore + /// ``` + /// use alloy_primitives::fixed_bytes; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_rpc_types::{BlockId, BlockNumberOrTag}; /// use cast::Cast; - /// use ethers_core::types::{BlockId, BlockNumber}; - /// use ethers_providers::{Http, Provider}; - /// use std::convert::TryFrom; + /// use std::{convert::TryFrom, str::FromStr}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// - /// let block_number = - /// cast.convert_block_number(Some(BlockId::Number(BlockNumber::from(5)))).await?; - /// assert_eq!(block_number, Some(BlockNumber::from(5))); + /// let block_number = cast.convert_block_number(Some(BlockId::number(5))).await?; + /// assert_eq!(block_number, Some(BlockNumberOrTag::Number(5))); /// - /// let block_number = - /// cast.convert_block_number(Some(BlockId::Hash("0x1234".parse().unwrap()))).await?; - /// assert_eq!(block_number, Some(BlockNumber::from(1234))); + /// let block_number = cast + /// .convert_block_number(Some(BlockId::hash(fixed_bytes!( + /// "0000000000000000000000000000000000000000000000000000000000001234" + /// )))) + /// .await?; + /// assert_eq!(block_number, Some(BlockNumberOrTag::Number(4660))); /// /// let block_number = cast.convert_block_number(None).await?; /// assert_eq!(block_number, None); @@ -844,13 +832,13 @@ where pub async fn convert_block_number( &self, block: Option, - ) -> Result, eyre::Error> { + ) -> Result, eyre::Error> { match block { Some(block) => match block { BlockId::Number(block_number) => Ok(Some(block_number)), BlockId::Hash(hash) => { - let block = self.provider.get_block(hash).await?; - Ok(block.map(|block| block.number.unwrap()).map(BlockNumber::from)) + let block = self.provider.get_block_by_hash(hash.block_hash, false).await?; + Ok(block.map(|block| block.header.number.unwrap()).map(BlockNumberOrTag::from)) } }, None => Ok(None), @@ -861,14 +849,17 @@ where /// /// # Example /// - /// ```ignore + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_rpc_types::Filter; + /// use alloy_transport::BoxTransport; /// use cast::Cast; - /// use ethers_core::{abi::Address, types::Filter}; - /// use ethers_providers::{Provider, Ws}; /// use std::{io, str::FromStr}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::new(Ws::connect("wss://localhost:8545").await?); + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("wss://localhost:8545").await?; /// let cast = Cast::new(provider); /// /// let filter = @@ -883,16 +874,13 @@ where filter: Filter, output: &mut dyn io::Write, to_json: bool, - ) -> Result<()> - where - ::Provider: PubsubClient, - { + ) -> Result<()> { // Initialize the subscription stream for logs - let mut subscription = self.provider.subscribe_logs(&filter).await?; + let mut subscription = self.provider.subscribe_logs(&filter).await?.into_stream(); // Check if a to_block is specified, if so, subscribe to blocks let mut block_subscription = if filter.get_to_block().is_some() { - Some(self.provider.subscribe_blocks().await?) + Some(self.provider.subscribe_blocks().await?.into_stream()) } else { None }; @@ -915,7 +903,7 @@ where Either::Right(futures::future::pending()) } => { if let (Some(block), Some(to_block)) = (block, to_block_number) { - if block.number.map_or(false, |bn| bn > to_block) { + if block.header.number.map_or(false, |bn| bn > to_block) { break; } } @@ -951,6 +939,20 @@ where Ok(()) } + + pub async fn erc20_balance( + &self, + token: Address, + owner: Address, + block: Option, + ) -> Result { + Ok(IERC20::new(token, &self.provider) + .balanceOf(owner) + .block(block.unwrap_or_default()) + .call() + .await? + ._0) + } } pub struct InterfaceSource { @@ -974,8 +976,8 @@ impl SimpleCast { /// # Example /// /// ``` + /// use alloy_primitives::{I256, U256}; /// use cast::SimpleCast; - /// use ethers_core::types::{I256, U256}; /// /// assert_eq!(SimpleCast::max_int("uint256")?, U256::MAX.to_string()); /// assert_eq!(SimpleCast::max_int("int256")?, I256::MAX.to_string()); @@ -991,8 +993,8 @@ impl SimpleCast { /// # Example /// /// ``` + /// use alloy_primitives::{I256, U256}; /// use cast::SimpleCast; - /// use ethers_core::types::{I256, U256}; /// /// assert_eq!(SimpleCast::min_int("uint256")?, "0"); /// assert_eq!(SimpleCast::min_int("int256")?, I256::MIN.to_string()); @@ -1069,8 +1071,8 @@ impl SimpleCast { /// Converts fixed point number into specified number of decimals /// ``` + /// use alloy_primitives::U256; /// use cast::SimpleCast as Cast; - /// use ethers_core::types::U256; /// /// assert_eq!(Cast::from_fixed_point("10", "0")?, "10"); /// assert_eq!(Cast::from_fixed_point("1.0", "1")?, "10"); @@ -1079,13 +1081,14 @@ impl SimpleCast { /// # Ok::<_, eyre::Report>(()) /// ``` pub fn from_fixed_point(value: &str, decimals: &str) -> Result { - // first try u32 as Units assumes a string can only be "ether", "gwei"... and not a number - let units = match decimals.parse::() { - Ok(d) => Units::Other(d), - Err(_) => Units::try_from(decimals)?, + // TODO: https://github.com/alloy-rs/core/pull/461 + let units: Unit = if let Ok(x) = decimals.parse() { + Unit::new(x).ok_or_else(|| eyre::eyre!("invalid unit"))? + } else { + decimals.parse()? }; - let n: NumberWithBase = parse_units(value, units.as_num())?.into(); - Ok(format!("{n}")) + let n = ParseUnits::parse_units(value, units)?; + Ok(n.to_string()) } /// Converts integers with specified decimals into fixed point numbers @@ -1093,8 +1096,8 @@ impl SimpleCast { /// # Example /// /// ``` + /// use alloy_primitives::U256; /// use cast::SimpleCast as Cast; - /// use ethers_core::types::U256; /// /// assert_eq!(Cast::to_fixed_point("10", "0")?, "10."); /// assert_eq!(Cast::to_fixed_point("10", "1")?, "1.0"); @@ -1234,7 +1237,6 @@ impl SimpleCast { /// assert_eq!(Cast::to_unit("1 wei", "wei")?, "1"); /// assert_eq!(Cast::to_unit("1", "wei")?, "1"); /// assert_eq!(Cast::to_unit("1ether", "wei")?, "1000000000000000000"); - /// assert_eq!(Cast::to_unit("100 gwei", "gwei")?, "100"); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn to_unit(value: &str, unit: &str) -> Result { @@ -1242,31 +1244,18 @@ impl SimpleCast { .as_uint() .wrap_err("Could not convert to uint")? .0; - - Ok(match unit { - "eth" | "ether" => foundry_common::units::format_units(value, 18)? - .trim_end_matches(".000000000000000000") - .to_string(), - "milli" | "milliether" => foundry_common::units::format_units(value, 15)? - .trim_end_matches(".000000000000000") - .to_string(), - "micro" | "microether" => foundry_common::units::format_units(value, 12)? - .trim_end_matches(".000000000000") - .to_string(), - "gwei" | "nano" | "nanoether" => foundry_common::units::format_units(value, 9)? - .trim_end_matches(".000000000") - .to_string(), - "mwei" | "mega" | "megaether" => foundry_common::units::format_units(value, 6)? - .trim_end_matches(".000000") - .to_string(), - "kwei" | "kilo" | "kiloether" => { - foundry_common::units::format_units(value, 3)?.trim_end_matches(".000").to_string() - } - "wei" => { - foundry_common::units::format_units(value, 0)?.trim_end_matches(".0").to_string() + let unit = unit.parse().wrap_err("could not parse units")?; + let mut formatted = ParseUnits::U256(value).format_units(unit); + + // Trim empty fractional part. + if let Some(dot) = formatted.find('.') { + let fractional = &formatted[dot + 1..]; + if fractional.chars().all(|c: char| c == '0') { + formatted = formatted[..dot].to_string(); } - _ => eyre::bail!("invalid unit: \"{}\"", unit), - }) + } + + Ok(formatted) } /// Converts wei into an eth amount @@ -1280,15 +1269,12 @@ impl SimpleCast { /// assert_eq!(Cast::from_wei("12340000005", "gwei")?, "12.340000005"); /// assert_eq!(Cast::from_wei("10", "ether")?, "0.000000000000000010"); /// assert_eq!(Cast::from_wei("100", "eth")?, "0.000000000000000100"); - /// assert_eq!(Cast::from_wei("17", "")?, "0.000000000000000017"); + /// assert_eq!(Cast::from_wei("17", "ether")?, "0.000000000000000017"); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn from_wei(value: &str, unit: &str) -> Result { - let value = NumberWithBase::parse_int(value, None)?.number().to_ethers(); - Ok(match unit { - "gwei" => format_units(value, 9), - _ => format_units(value, 18), - }?) + let value = NumberWithBase::parse_int(value, None)?.number(); + Ok(ParseUnits::U256(value).format_units(unit.parse()?)) } /// Converts an eth amount into wei @@ -1298,18 +1284,14 @@ impl SimpleCast { /// ``` /// use cast::SimpleCast as Cast; /// - /// assert_eq!(Cast::to_wei("1", "")?, "1000000000000000000"); /// assert_eq!(Cast::to_wei("100", "gwei")?, "100000000000"); /// assert_eq!(Cast::to_wei("100", "eth")?, "100000000000000000000"); /// assert_eq!(Cast::to_wei("1000", "ether")?, "1000000000000000000000"); /// # Ok::<_, eyre::Report>(()) /// ``` pub fn to_wei(value: &str, unit: &str) -> Result { - let wei = match unit { - "gwei" => parse_units(value, 9), - _ => parse_units(value, 18), - }?; - Ok(wei.to_string()) + let unit = unit.parse().wrap_err("could not parse units")?; + Ok(ParseUnits::parse_units(value, unit)?.to_string()) } /// Decodes rlp encoded list with hex data @@ -1417,27 +1399,25 @@ impl SimpleCast { } let padded = format!("{s:0<64}"); - // need to use the Debug implementation - Ok(format!("{:?}", H256::from_str(&padded)?)) + Ok(padded.parse::()?.to_string()) } /// Encodes string into bytes32 value pub fn format_bytes32_string(s: &str) -> Result { - let formatted = format_bytes32_string(s)?; - Ok(hex::encode_prefixed(formatted)) + let str_bytes: &[u8] = s.as_bytes(); + eyre::ensure!(str_bytes.len() <= 32, "bytes32 strings must not exceed 32 bytes in length"); + + let mut bytes32: [u8; 32] = [0u8; 32]; + bytes32[..str_bytes.len()].copy_from_slice(str_bytes); + Ok(hex::encode_prefixed(bytes32)) } /// Decodes string from bytes32 value pub fn parse_bytes32_string(s: &str) -> Result { let bytes = hex::decode(s)?; - if bytes.len() != 32 { - eyre::bail!("expected 64 byte hex-string, got {}", strip_0x(s)); - } - - let mut buffer = [0u8; 32]; - buffer.copy_from_slice(&bytes); - - Ok(parse_bytes32_string(&buffer)?.to_owned()) + eyre::ensure!(bytes.len() == 32, "expected 32 byte hex-string"); + let len = bytes.iter().take_while(|x| **x != 0).count(); + Ok(std::str::from_utf8(&bytes[..len])?.into()) } /// Decodes checksummed address from bytes32 value @@ -1561,12 +1541,7 @@ impl SimpleCast { /// # Ok::<_, eyre::Report>(()) /// ``` pub fn abi_encode(sig: &str, args: &[impl AsRef]) -> Result { - let func = match Function::parse(sig) { - Ok(func) => func, - Err(err) => { - eyre::bail!("Could not process human-readable ABI. Please check if you've left the parenthesis unclosed or if some type is incomplete.\nError:\n{}", err) - } - }; + let func = get_func(sig)?; let calldata = match encode_function_args(&func, args) { Ok(res) => hex::encode(res), Err(e) => eyre::bail!("Could not ABI encode the function and arguments. Did you pass in the right types?\nError\n{}", e), @@ -1575,6 +1550,37 @@ impl SimpleCast { Ok(format!("0x{encoded}")) } + /// Performs packed ABI encoding based off of the function signature or tuple. + /// + /// # Examplez + /// + /// ``` + /// use cast::SimpleCast as Cast; + /// + /// assert_eq!( + /// "0x0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000012c00000000000000c8", + /// Cast::abi_encode_packed("(uint128[] a, uint64 b)", &["[100, 300]", "200"]).unwrap().as_str() + /// ); + /// + /// assert_eq!( + /// "0x8dbd1b711dc621e1404633da156fcc779e1c6f3e68656c6c6f20776f726c64", + /// Cast::abi_encode_packed("foo(address a, string b)", &["0x8dbd1b711dc621e1404633da156fcc779e1c6f3e", "hello world"]).unwrap().as_str() + /// ); + /// # Ok::<_, eyre::Report>(()) + /// ``` + pub fn abi_encode_packed(sig: &str, args: &[impl AsRef]) -> Result { + // If the signature is a tuple, we need to prefix it to make it a function + let sig = + if sig.trim_start().starts_with('(') { format!("foo{sig}") } else { sig.to_string() }; + + let func = get_func(sig.as_str())?; + let encoded = match encode_function_args_packed(&func, args) { + Ok(res) => hex::encode(res), + Err(e) => eyre::bail!("Could not ABI encode the function and arguments. Did you pass in the right types?\nError\n{}", e), + }; + Ok(format!("0x{encoded}")) + } + /// Performs ABI encoding to produce the hexadecimal calldata with the given arguments. /// /// # Example @@ -1589,7 +1595,7 @@ impl SimpleCast { /// # Ok::<_, eyre::Report>(()) /// ``` pub fn calldata_encode(sig: impl AsRef, args: &[impl AsRef]) -> Result { - let func = Function::parse(sig.as_ref())?; + let func = get_func(sig.as_ref())?; let calldata = encode_function_args(&func, args)?; Ok(hex::encode_prefixed(calldata)) } @@ -1597,7 +1603,10 @@ impl SimpleCast { /// Generates an interface in solidity from either a local file ABI or a verified contract on /// Etherscan. It returns a vector of [`InterfaceSource`] structs that contain the source of the /// interface and their name. - /// ```ignore + /// + /// Note: This removes the constructor from the ABI before generating the interface. + /// + /// ```no_run /// use cast::{AbiPath, SimpleCast as Cast}; /// # async fn foo() -> eyre::Result<()> { /// let path = @@ -1608,7 +1617,7 @@ impl SimpleCast { /// # } /// ``` pub async fn generate_interface(address_or_path: AbiPath) -> Result> { - let (contract_abis, contract_names) = match address_or_path { + let (mut contract_abis, contract_names) = match address_or_path { AbiPath::Local { path, name } => { let file = std::fs::read_to_string(&path).wrap_err("unable to read abi file")?; let obj: ContractObject = serde_json::from_str(&file)?; @@ -1631,9 +1640,12 @@ impl SimpleCast { } }; contract_abis - .iter() + .iter_mut() .zip(contract_names) .map(|(contract_abi, name)| { + // need to filter out the constructor + contract_abi.constructor.take(); + let source = foundry_cli::utils::abi_to_solidity(contract_abi, &name)?; Ok(InterfaceSource { name, @@ -1644,16 +1656,20 @@ impl SimpleCast { .collect::>>() } - /// Prints the slot number for the specified mapping type and input data - /// Uses abi_encode to pad the data to 32 bytes. - /// For value types v, slot number of v is keccak256(concat(h(v) , p)) where h is the padding - /// function and p is slot number of the mapping. + /// Prints the slot number for the specified mapping type and input data. + /// + /// For value types `v`, slot number of `v` is `keccak256(concat(h(v), p))` where `h` is the + /// padding function for `v`'s type, and `p` is slot number of the mapping. + /// + /// See [the Solidity documentation](https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html#mappings-and-dynamic-arrays) + /// for more details. /// /// # Example /// /// ``` /// # use cast::SimpleCast as Cast; /// + /// // Value types. /// assert_eq!( /// Cast::index("address", "0xD0074F4E6490ae3f888d1d4f7E3E43326bD3f0f5", "2").unwrap().as_str(), /// "0x9525a448a9000053a4d151336329d6563b7e80b24f8e628e95527f218e8ab5fb" @@ -1662,60 +1678,48 @@ impl SimpleCast { /// Cast::index("uint256", "42", "6").unwrap().as_str(), /// "0xfc808b0f31a1e6b9cf25ff6289feae9b51017b392cc8e25620a94a38dcdafcc1" /// ); - /// # Ok::<_, eyre::Report>(()) - /// ``` - pub fn index(from_type: &str, from_value: &str, slot_number: &str) -> Result { - let sig = format!("x({from_type},uint256)"); - let encoded = Self::abi_encode(&sig, &[from_value, slot_number])?; - let location: String = Self::keccak(&encoded)?; - Ok(location) - } - - /// Converts ENS names to their namehash representation - /// [Namehash reference](https://docs.ens.domains/contract-api-reference/name-processing#hashing-names) - /// [namehash-rust reference](https://github.com/InstateDev/namehash-rust/blob/master/src/lib.rs) /// - /// # Example - /// - /// ``` - /// use cast::SimpleCast as Cast; - /// - /// assert_eq!( - /// Cast::namehash("")?, - /// "0x0000000000000000000000000000000000000000000000000000000000000000" - /// ); + /// // Strings and byte arrays. /// assert_eq!( - /// Cast::namehash("eth")?, - /// "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae" - /// ); - /// assert_eq!( - /// Cast::namehash("foo.eth")?, - /// "0xde9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f" - /// ); - /// assert_eq!( - /// Cast::namehash("sub.foo.eth")?, - /// "0x500d86f9e663479e5aaa6e99276e55fc139c597211ee47d17e1e92da16a83402" + /// Cast::index("string", "hello", "1").unwrap().as_str(), + /// "0x8404bb4d805e9ca2bd5dd5c43a107e935c8ec393caa7851b353b3192cd5379ae" /// ); /// # Ok::<_, eyre::Report>(()) /// ``` - pub fn namehash(ens: &str) -> Result { - let mut node = vec![0u8; 32]; - - if !ens.is_empty() { - let ens_lower = ens.to_lowercase(); - let mut labels: Vec<&str> = ens_lower.split('.').collect(); - labels.reverse(); - - for label in labels { - let mut label_hash = keccak256(label.as_bytes()); - node.append(&mut label_hash.to_vec()); - - label_hash = keccak256(node.as_slice()); - node = label_hash.to_vec(); + pub fn index(from_type: &str, from_value: &str, slot_number: &str) -> Result { + let mut hasher = Keccak256::new(); + + let v_ty = DynSolType::parse(from_type).wrap_err("Could not parse type")?; + let v = v_ty.coerce_str(from_value).wrap_err("Could not parse value")?; + match v_ty { + // For value types, `h` pads the value to 32 bytes in the same way as when storing the + // value in memory. + DynSolType::Bool | + DynSolType::Int(_) | + DynSolType::Uint(_) | + DynSolType::FixedBytes(_) | + DynSolType::Address | + DynSolType::Function => hasher.update(v.as_word().unwrap()), + + // For strings and byte arrays, `h(k)` is just the unpadded data. + DynSolType::String | DynSolType::Bytes => hasher.update(v.as_packed_seq().unwrap()), + + DynSolType::Array(..) | + DynSolType::FixedArray(..) | + DynSolType::Tuple(..) | + DynSolType::CustomStruct { .. } => { + eyre::bail!("Type `{v_ty}` is not supported as a mapping key") } } - Ok(hex::encode_prefixed(node)) + let p = DynSolType::Uint(256) + .coerce_str(slot_number) + .wrap_err("Could not parse slot number")?; + let p = p.as_word().unwrap(); + hasher.update(p); + + let location = hasher.finalize(); + Ok(location.to_string()) } /// Keccak-256 hashes arbitrary data @@ -1744,14 +1748,10 @@ impl SimpleCast { /// # Ok::<_, eyre::Report>(()) /// ``` pub fn keccak(data: &str) -> Result { - let hash = match data.as_bytes() { - // 0x prefix => read as hex data - [b'0', b'x', rest @ ..] => keccak256(hex::decode(rest)?), - // No 0x prefix => read as text - _ => keccak256(data), - }; - - Ok(format!("{:?}", H256(hash))) + // Hex-decode if data starts with 0x. + let hash = + if data.starts_with("0x") { keccak256(hex::decode(data)?) } else { keccak256(data) }; + Ok(hash.to_string()) } /// Performs the left shift operation (<<) on a number @@ -1879,7 +1879,7 @@ impl SimpleCast { /// /// # Example /// - /// ```ignore + /// ``` /// use cast::SimpleCast as Cast; /// /// # async fn foo() -> eyre::Result<()> { @@ -1909,7 +1909,7 @@ impl SimpleCast { eyre::bail!("number of leading zeroes must not be greater than 4"); } if optimize == 0 { - let selector = Function::parse(signature)?.selector(); + let selector = get_func(signature)?.selector(); return Ok((selector.to_string(), String::from(signature))) } let Some((name, params)) = signature.split_once('(') else { @@ -1946,6 +1946,27 @@ impl SimpleCast { } } + /// Extracts function selectors and arguments from bytecode + /// + /// # Example + /// + /// ``` + /// use cast::SimpleCast as Cast; + /// + /// let bytecode = "6080604052348015600e575f80fd5b50600436106026575f3560e01c80632125b65b14602a575b5f80fd5b603a6035366004603c565b505050565b005b5f805f60608486031215604d575f80fd5b833563ffffffff81168114605f575f80fd5b925060208401356001600160a01b03811681146079575f80fd5b915060408401356001600160e01b03811681146093575f80fd5b80915050925092509256"; + /// let selectors = Cast::extract_selectors(bytecode)?; + /// assert_eq!(selectors, vec![("0x2125b65b".to_string(), "uint32,address,uint224".to_string())]); + /// # Ok::<(), eyre::Report>(()) + /// ``` + pub fn extract_selectors(bytecode: &str) -> Result> { + let code = hex::decode(strip_0x(bytecode))?; + let s = evmole::function_selectors(&code, 0); + + Ok(s.iter() + .map(|s| (hex::encode_prefixed(s), evmole::function_arguments(&code, s, 0))) + .collect()) + } + /// Decodes a raw EIP2718 transaction payload /// Returns details about the typed transaction and ECSDA signature components /// @@ -1954,13 +1975,13 @@ impl SimpleCast { /// ``` /// use cast::SimpleCast as Cast; /// - /// let tx = "0x02f8f582a86a82058d8459682f008508351050808303fd84948e42f2f4101563bf679975178e880fd87d3efd4e80b884659ac74b00000000000000000000000080f0c1c49891dcfdd40b6e0f960f84e6042bcb6f000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000007ff4e20000000000000000000000000000000000000000000000000000000000000064c001a05d429597befe2835396206781b199122f2e8297327ed4a05483339e7a8b2022aa04c23a7f70fb29dda1b4ee342fb10a625e9b8ddc6a603fb4e170d4f6f37700cb8"; - /// let (tx, sig) = Cast::decode_raw_transaction(&tx)?; + /// let tx = "0x02f8f582a86a82058d8459682f008508351050808303fd84948e42f2f4101563bf679975178e880fd87d3efd4e80b884659ac74b00000000000000000000000080f0c1c49891dcfdd40b6e0f960f84e6042bcb6f000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000007ff4e20000000000000000000000000000000000000000000000000000000000000064c001a05d429597befe2835396206781b199122f2e8297327ed4a05483339e7a8b2022aa04c23a7f70fb29dda1b4ee342fb10a625e9b8ddc6a603fb4e170d4f6f37700cb8"; + /// let tx_envelope = Cast::decode_raw_transaction(&tx)?; /// # Ok::<(), eyre::Report>(()) - pub fn decode_raw_transaction(tx: &str) -> Result<(TypedTransaction, Signature)> { + pub fn decode_raw_transaction(tx: &str) -> Result { let tx_hex = hex::decode(strip_0x(tx))?; - let tx_rlp = rlp::Rlp::new(tx_hex.as_slice()); - Ok(TypedTransaction::decode_signed(&tx_rlp)?) + let tx = TxEnvelope::decode_2718(&mut tx_hex.as_slice())?; + Ok(tx) } } diff --git a/crates/cast/src/rlp_converter.rs b/crates/cast/src/rlp_converter.rs index c67c99cf52048..417a7df672299 100644 --- a/crates/cast/src/rlp_converter.rs +++ b/crates/cast/src/rlp_converter.rs @@ -5,7 +5,7 @@ use std::fmt; /// Arbitrary nested data /// Item::Array(vec![]); is equivalent to [] /// Item::Array(vec![Item::Data(vec![])]); is equivalent to [""] or [null] -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Item { Data(Vec), Array(Vec), diff --git a/crates/cast/src/tx.rs b/crates/cast/src/tx.rs deleted file mode 100644 index 1bfd0c40ff67c..0000000000000 --- a/crates/cast/src/tx.rs +++ /dev/null @@ -1,402 +0,0 @@ -use crate::errors::FunctionSignatureError; -use alloy_json_abi::Function; -use alloy_primitives::{Address, U256}; -use ethers_core::types::{ - transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, NameOrAddress, - TransactionRequest, -}; -use ethers_providers::Middleware; -use eyre::{eyre, Result}; -use foundry_common::{ - abi::{encode_function_args, get_func, get_func_etherscan}, - types::{ToAlloy, ToEthers}, -}; -use foundry_config::Chain; -use futures::future::join_all; - -pub type TxBuilderOutput = (TypedTransaction, Option); -pub type TxBuilderPeekOutput<'a> = (&'a TypedTransaction, &'a Option); - -/// Transaction builder -/// -/// # Examples -/// -/// ``` -/// # async fn foo() -> eyre::Result<()> { -/// # use alloy_primitives::U256; -/// # use cast::TxBuilder; -/// # use foundry_config::NamedChain; -/// let provider = ethers_providers::test_provider::MAINNET.provider(); -/// let mut builder = -/// TxBuilder::new(&provider, "a.eth", Some("b.eth"), NamedChain::Mainnet, false).await?; -/// builder.gas(Some(U256::from(1))); -/// let (tx, _) = builder.build(); -/// # Ok(()) -/// # } -/// ``` -pub struct TxBuilder<'a, M: Middleware> { - to: Option
, - chain: Chain, - tx: TypedTransaction, - func: Option, - etherscan_api_key: Option, - provider: &'a M, -} - -impl<'a, M: Middleware> TxBuilder<'a, M> { - /// Create a new TxBuilder - /// `provider` - provider to use - /// `from` - 'from' field. Could be an ENS name - /// `to` - `to`. Could be a ENS - /// `chain` - chain to construct the tx for - /// `legacy` - use type 1 transaction - pub async fn new, T: Into>( - provider: &'a M, - from: F, - to: Option, - chain: impl Into, - legacy: bool, - ) -> Result> { - let chain = chain.into(); - let from_addr = resolve_ens(provider, from).await?; - - let mut tx: TypedTransaction = if chain.is_legacy() || legacy { - TransactionRequest::new().from(from_addr.to_ethers()).chain_id(chain.id()).into() - } else { - Eip1559TransactionRequest::new().from(from_addr.to_ethers()).chain_id(chain.id()).into() - }; - - let to_addr = if let Some(to) = to { - let addr = resolve_ens(provider, to).await?; - tx.set_to(addr.to_ethers()); - Some(addr) - } else { - None - }; - Ok(Self { to: to_addr, chain, tx, func: None, etherscan_api_key: None, provider }) - } - - /// Set gas for tx - pub fn set_gas(&mut self, v: U256) -> &mut Self { - self.tx.set_gas(v.to_ethers()); - self - } - - /// Set gas for tx, if `v` is not None - pub fn gas(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_gas(value); - } - self - } - - /// Set gas price - pub fn set_gas_price(&mut self, v: U256) -> &mut Self { - self.tx.set_gas_price(v.to_ethers()); - self - } - - /// Set gas price, if `v` is not None - pub fn gas_price(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_gas_price(value); - } - self - } - - /// Set priority gas price - pub fn set_priority_gas_price(&mut self, v: U256) -> &mut Self { - if let TypedTransaction::Eip1559(tx) = &mut self.tx { - tx.max_priority_fee_per_gas = Some(v.to_ethers()) - } - self - } - - /// Set priority gas price, if `v` is not None - pub fn priority_gas_price(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_priority_gas_price(value); - } - self - } - - /// Set value - pub fn set_value(&mut self, v: U256) -> &mut Self { - self.tx.set_value(v.to_ethers()); - self - } - - /// Set value, if `v` is not None - pub fn value(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_value(value); - } - self - } - - /// Set nonce - pub fn set_nonce(&mut self, v: U256) -> &mut Self { - self.tx.set_nonce(v.to_ethers()); - self - } - - /// Set nonce, if `v` is not None - pub fn nonce(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_nonce(value); - } - self - } - - /// Set etherscan API key. Used to look up function signature buy name - pub fn set_etherscan_api_key(&mut self, v: String) -> &mut Self { - self.etherscan_api_key = Some(v); - self - } - - /// Set etherscan API key, if `v` is not None - pub fn etherscan_api_key(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_etherscan_api_key(value); - } - self - } - - pub fn set_data(&mut self, v: Vec) -> &mut Self { - self.tx.set_data(v.into()); - self - } - - pub async fn create_args( - &mut self, - sig: &str, - args: Vec, - ) -> Result<(Vec, Function)> { - if sig.trim().is_empty() { - return Err(FunctionSignatureError::MissingSignature.into()) - } - - let args = resolve_name_args(&args, self.provider).await; - - let func = if sig.contains('(') { - // a regular function signature with parentheses - get_func(sig)? - } else if sig.starts_with("0x") { - // if only calldata is provided, returning a dummy function - get_func("x()")? - } else { - get_func_etherscan( - sig, - self.to.ok_or(FunctionSignatureError::MissingToAddress)?, - &args, - self.chain, - self.etherscan_api_key.as_ref().ok_or_else(|| { - FunctionSignatureError::MissingEtherscan { sig: sig.to_string() } - })?, - ) - .await? - }; - - if sig.starts_with("0x") { - Ok((hex::decode(sig)?, func)) - } else { - Ok((encode_function_args(&func, &args)?, func)) - } - } - - /// Set function arguments - /// `sig` can be: - /// * a fragment (`do(uint32,string)`) - /// * selector + abi-encoded calldata - /// (`0xcdba2fd40000000000000000000000000000000000000000000000000000000000007a69`) - /// * only function name (`do`) - in this case, etherscan lookup is performed on `tx.to`'s - /// contract - pub async fn set_args( - &mut self, - sig: &str, - args: Vec, - ) -> Result<&mut TxBuilder<'a, M>> { - let (data, func) = self.create_args(sig, args).await?; - self.tx.set_data(data.into()); - self.func = Some(func); - Ok(self) - } - - /// Set function arguments, if `value` is not None - pub async fn args( - &mut self, - value: Option<(&str, Vec)>, - ) -> Result<&mut TxBuilder<'a, M>> { - if let Some((sig, args)) = value { - return self.set_args(sig, args).await - } - Ok(self) - } - - /// Consuming build: returns typed transaction and optional function call - pub fn build(self) -> TxBuilderOutput { - (self.tx, self.func) - } - - /// Non-consuming build: peek into the tx content - pub fn peek(&self) -> TxBuilderPeekOutput { - (&self.tx, &self.func) - } -} - -async fn resolve_ens>( - provider: &M, - addr: T, -) -> Result
{ - let from_addr = match addr.into() { - NameOrAddress::Name(ref ens_name) => provider.resolve_name(ens_name).await, - NameOrAddress::Address(addr) => Ok(addr), - } - .map_err(|x| eyre!("Failed to resolve ENS name: {x}"))?; - Ok(from_addr.to_alloy()) -} - -async fn resolve_name_args(args: &[String], provider: &M) -> Vec { - join_all(args.iter().map(|arg| async { - if arg.contains('.') { - let addr = provider.resolve_name(arg).await; - match addr { - Ok(addr) => format!("{addr:?}"), - Err(_) => arg.to_string(), - } - } else { - arg.to_string() - } - })) - .await -} - -#[cfg(test)] -mod tests { - use crate::TxBuilder; - use alloy_primitives::{Address, U256}; - use async_trait::async_trait; - use ethers_core::types::{transaction::eip2718::TypedTransaction, NameOrAddress, H160}; - use ethers_providers::{JsonRpcClient, Middleware, ProviderError}; - use foundry_common::types::ToEthers; - use foundry_config::NamedChain; - use serde::{de::DeserializeOwned, Serialize}; - use std::str::FromStr; - - const ADDR_1: &str = "0000000000000000000000000000000000000001"; - const ADDR_2: &str = "0000000000000000000000000000000000000002"; - - #[derive(Debug)] - struct MyProvider {} - - #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] - #[cfg_attr(not(target_arch = "wasm32"), async_trait)] - impl JsonRpcClient for MyProvider { - type Error = ProviderError; - - async fn request( - &self, - _method: &str, - _params: T, - ) -> Result { - Err(ProviderError::CustomError("There is no request".to_string())) - } - } - #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] - #[cfg_attr(not(target_arch = "wasm32"), async_trait)] - impl Middleware for MyProvider { - type Error = ProviderError; - type Provider = MyProvider; - type Inner = MyProvider; - - fn inner(&self) -> &Self::Inner { - self - } - - async fn resolve_name(&self, ens_name: &str) -> Result { - match ens_name { - "a.eth" => Ok(H160::from_str(ADDR_1).unwrap()), - "b.eth" => Ok(H160::from_str(ADDR_2).unwrap()), - _ => unreachable!("don't know how to resolve {ens_name}"), - } - } - } - #[tokio::test(flavor = "multi_thread")] - async fn builder_new_non_legacy() -> eyre::Result<()> { - let provider = MyProvider {}; - let builder = - TxBuilder::new(&provider, "a.eth", Some("b.eth"), NamedChain::Mainnet, false).await?; - let (tx, args) = builder.build(); - assert_eq!(*tx.from().unwrap(), Address::from_str(ADDR_1).unwrap().to_ethers()); - assert_eq!( - *tx.to().unwrap(), - NameOrAddress::Address(Address::from_str(ADDR_2).unwrap().to_ethers()) - ); - assert_eq!(args, None); - - match tx { - TypedTransaction::Eip1559(_) => {} - _ => { - panic!("Wrong tx type"); - } - } - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn builder_new_legacy() -> eyre::Result<()> { - let provider = MyProvider {}; - let builder = - TxBuilder::new(&provider, "a.eth", Some("b.eth"), NamedChain::Mainnet, true).await?; - // don't check anything other than the tx type - the rest is covered in the non-legacy case - let (tx, _) = builder.build(); - match tx { - TypedTransaction::Legacy(_) => {} - _ => { - panic!("Wrong tx type"); - } - } - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn builder_fields() -> eyre::Result<()> { - let provider = MyProvider {}; - let mut builder = - TxBuilder::new(&provider, "a.eth", Some("b.eth"), NamedChain::Mainnet, false) - .await - .unwrap(); - builder - .gas(Some(U256::from(12u32))) - .gas_price(Some(U256::from(34u32))) - .value(Some(U256::from(56u32))) - .nonce(Some(U256::from(78u32))); - - builder.etherscan_api_key(Some(String::from("what a lovely day"))); // not testing for this :-/ - let (tx, _) = builder.build(); - - assert_eq!(tx.gas().unwrap().as_u32(), 12); - assert_eq!(tx.gas_price().unwrap().as_u32(), 34); - assert_eq!(tx.value().unwrap().as_u32(), 56); - assert_eq!(tx.nonce().unwrap().as_u32(), 78); - assert_eq!(tx.chain_id().unwrap().as_u32(), 1); - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn builder_args() -> eyre::Result<()> { - let provider = MyProvider {}; - let mut builder = - TxBuilder::new(&provider, "a.eth", Some("b.eth"), NamedChain::Mainnet, false) - .await - .unwrap(); - builder.args(Some(("what_a_day(int)", vec![String::from("31337")]))).await?; - let (_, function_maybe) = builder.build(); - - assert_ne!(function_maybe, None); - let function = function_maybe.unwrap(); - assert_eq!(function.name, String::from("what_a_day")); - // could test function.inputs() but that should be covered by utils's unit test - Ok(()) - } -} diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index c685584863753..c6f2b04501235 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1,8 +1,12 @@ //! Contains various tests for checking cast commands -use foundry_common::rpc::{next_http_rpc_endpoint, next_ws_rpc_endpoint}; -use foundry_test_utils::{casttest, util::OutputExt}; -use std::{io::Write, path::Path}; +use alloy_primitives::{address, b256, Address, B256}; +use foundry_test_utils::{ + casttest, + rpc::{next_http_rpc_endpoint, next_ws_rpc_endpoint}, + util::OutputExt, +}; +use std::{fs, io::Write, path::Path}; // tests `--help` is printed to std out casttest!(print_help, |_prj, cmd| { @@ -131,6 +135,27 @@ casttest!(wallet_sign_typed_data_file, |_prj, cmd| { assert_eq!(output.trim(), "0x06c18bdc8163219fddc9afaf5a0550e381326474bb757c86dc32317040cf384e07a2c72ce66c1a0626b6750ca9b6c035bf6f03e7ed67ae2d1134171e9085c0b51b"); }); +// tests that `cast wallet list` outputs the local accounts +casttest!(wallet_list_local_accounts, |prj, cmd| { + let keystore_path = prj.root().join("keystore"); + fs::create_dir_all(keystore_path).unwrap(); + cmd.set_current_dir(prj.root()); + + // empty results + cmd.cast_fuse().args(["wallet", "list", "--dir", "keystore"]); + let list_output = cmd.stdout_lossy(); + assert!(list_output.is_empty()); + + // create 10 wallets + cmd.cast_fuse().args(["wallet", "new", "keystore", "-n", "10", "--unsafe-password", "test"]); + cmd.stdout_lossy(); + + // test list new wallet + cmd.cast_fuse().args(["wallet", "list", "--dir", "keystore"]); + let list_output = cmd.stdout_lossy(); + assert_eq!(list_output.matches('\n').count(), 10); +}); + // tests that `cast estimate` is working correctly. casttest!(estimate_function_gas, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); @@ -493,6 +518,89 @@ casttest!(logs_sig_2, |_prj, cmd| { ); }); +casttest!(mktx, |_prj, cmd| { + cmd.args([ + "mktx", + "--private-key", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "--chain", + "1", + "--nonce", + "0", + "--value", + "100", + "--gas-limit", + "21000", + "--gas-price", + "10000000000", + "--priority-gas-price", + "1000000000", + "0x0000000000000000000000000000000000000001", + ]); + let output = cmd.stdout_lossy(); + assert_eq!( + output.trim(), + "0x02f86b0180843b9aca008502540be4008252089400000000000000000000000000000000000000016480c001a070d55e79ed3ac9fc8f51e78eb91fd054720d943d66633f2eb1bc960f0126b0eca052eda05a792680de3181e49bab4093541f75b49d1ecbe443077b3660c836016a" + ); +}); + +// ensure recipient or code is required +casttest!(mktx_requires_to, |_prj, cmd| { + cmd.args([ + "mktx", + "--private-key", + "0x0000000000000000000000000000000000000000000000000000000000000001", + ]); + let output = cmd.stderr_lossy(); + assert_eq!( + output.trim(), + "Error: \nMust specify a recipient address or contract code to deploy" + ); +}); + +casttest!(mktx_signer_from_mismatch, |_prj, cmd| { + cmd.args([ + "mktx", + "--private-key", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "--from", + "0x0000000000000000000000000000000000000001", + "--chain", + "1", + "0x0000000000000000000000000000000000000001", + ]); + let output = cmd.stderr_lossy(); + assert!( + output.contains("The specified sender via CLI/env vars does not match the sender configured via\nthe hardware wallet's HD Path.") + ); +}); + +casttest!(mktx_signer_from_match, |_prj, cmd| { + cmd.args([ + "mktx", + "--private-key", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "--from", + "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf", + "--chain", + "1", + "--nonce", + "0", + "--gas-limit", + "21000", + "--gas-price", + "10000000000", + "--priority-gas-price", + "1000000000", + "0x0000000000000000000000000000000000000001", + ]); + let output = cmd.stdout_lossy(); + assert_eq!( + output.trim(), + "0x02f86b0180843b9aca008502540be4008252089400000000000000000000000000000000000000018080c001a0cce9a61187b5d18a89ecd27ec675e3b3f10d37f165627ef89a15a7fe76395ce8a07537f5bffb358ffbef22cda84b1c92f7211723f9e09ae037e81686805d3e5505" + ); +}); + // tests that the raw encoded transaction is returned casttest!(tx_raw, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); @@ -555,4 +663,168 @@ casttest!(storage, |_prj, cmd| { let six = "0x0000000000000000000000000000000000000000000000000000000000000006"; cmd.cast_fuse().args(["storage", usdt, decimals_slot, "--rpc-url", &rpc]); assert_eq!(cmd.stdout_lossy().trim(), six); + + let rpc = next_http_rpc_endpoint(); + let total_supply_slot = "0x01"; + let issued = "0x000000000000000000000000000000000000000000000000000000174876e800"; + let block_before = "4634747"; + let block_after = "4634748"; + cmd.cast_fuse().args([ + "storage", + usdt, + total_supply_slot, + "--rpc-url", + &rpc, + "--block", + block_before, + ]); + assert_eq!(cmd.stdout_lossy().trim(), empty); + cmd.cast_fuse().args([ + "storage", + usdt, + total_supply_slot, + "--rpc-url", + &rpc, + "--block", + block_after, + ]); + assert_eq!(cmd.stdout_lossy().trim(), issued); +}); + +// +casttest!(storage_layout, |_prj, cmd| { + cmd.cast_fuse().args([ + "storage", + "--rpc-url", + "https://mainnet.optimism.io", + "--block", + "110000000", + "--etherscan-api-key", + "JQNGFHINKS1W7Y5FRXU4SPBYF43J3NYK46", + "0xB67c152E69217b5aCB85A2e19dF13423351b0E27", + ]); + let output = r#"| Name | Type | Slot | Offset | Bytes | Value | Hex Value | Contract | +|-------------------------------|-----------------------------------------------------------------|------|--------|-------|---------------------------------------------------|--------------------------------------------------------------------|----------------------------------------------------| +| gov | address | 0 | 0 | 20 | 1352965747418285184211909460723571462248744342032 | 0x000000000000000000000000ecfd15165d994c2766fbe0d6bacdc2e8dedfd210 | contracts/perp/PositionManager.sol:PositionManager | +| _status | uint256 | 1 | 0 | 32 | 1 | 0x0000000000000000000000000000000000000000000000000000000000000001 | contracts/perp/PositionManager.sol:PositionManager | +| admin | address | 2 | 0 | 20 | 1352965747418285184211909460723571462248744342032 | 0x000000000000000000000000ecfd15165d994c2766fbe0d6bacdc2e8dedfd210 | contracts/perp/PositionManager.sol:PositionManager | +| feeCalculator | address | 3 | 0 | 20 | 1297482016264593221714872710065075000476194625473 | 0x000000000000000000000000e3451b170806aab3e24b5cd03a331c1ccdb4d7c1 | contracts/perp/PositionManager.sol:PositionManager | +| oracle | address | 4 | 0 | 20 | 241116142622541106669066767052022920958068430970 | 0x0000000000000000000000002a3c0592dcb58accd346ccee2bb46e3fb744987a | contracts/perp/PositionManager.sol:PositionManager | +| referralStorage | address | 5 | 0 | 20 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/perp/PositionManager.sol:PositionManager | +| minExecutionFee | uint256 | 6 | 0 | 32 | 20000 | 0x0000000000000000000000000000000000000000000000000000000000004e20 | contracts/perp/PositionManager.sol:PositionManager | +| minBlockDelayKeeper | uint256 | 7 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/perp/PositionManager.sol:PositionManager | +| minTimeExecuteDelayPublic | uint256 | 8 | 0 | 32 | 180 | 0x00000000000000000000000000000000000000000000000000000000000000b4 | contracts/perp/PositionManager.sol:PositionManager | +| minTimeCancelDelayPublic | uint256 | 9 | 0 | 32 | 180 | 0x00000000000000000000000000000000000000000000000000000000000000b4 | contracts/perp/PositionManager.sol:PositionManager | +| maxTimeDelay | uint256 | 10 | 0 | 32 | 1800 | 0x0000000000000000000000000000000000000000000000000000000000000708 | contracts/perp/PositionManager.sol:PositionManager | +| isUserExecuteEnabled | bool | 11 | 0 | 1 | 1 | 0x0000000000000000000000000000000000000000000000000000000000000001 | contracts/perp/PositionManager.sol:PositionManager | +| isUserCancelEnabled | bool | 11 | 1 | 1 | 1 | 0x0000000000000000000000000000000000000000000000000000000000000001 | contracts/perp/PositionManager.sol:PositionManager | +| allowPublicKeeper | bool | 11 | 2 | 1 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/perp/PositionManager.sol:PositionManager | +| allowUserCloseOnly | bool | 11 | 3 | 1 | 1 | 0x0000000000000000000000000000000000000000000000000000000000000001 | contracts/perp/PositionManager.sol:PositionManager | +| openPositionRequestKeys | bytes32[] | 12 | 0 | 32 | 9287 | 0x0000000000000000000000000000000000000000000000000000000000002447 | contracts/perp/PositionManager.sol:PositionManager | +| closePositionRequestKeys | bytes32[] | 13 | 0 | 32 | 5782 | 0x0000000000000000000000000000000000000000000000000000000000001696 | contracts/perp/PositionManager.sol:PositionManager | +| openPositionRequestKeysStart | uint256 | 14 | 0 | 32 | 9287 | 0x0000000000000000000000000000000000000000000000000000000000002447 | contracts/perp/PositionManager.sol:PositionManager | +| closePositionRequestKeysStart | uint256 | 15 | 0 | 32 | 5782 | 0x0000000000000000000000000000000000000000000000000000000000001696 | contracts/perp/PositionManager.sol:PositionManager | +| isPositionKeeper | mapping(address => bool) | 16 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/perp/PositionManager.sol:PositionManager | +| openPositionsIndex | mapping(address => uint256) | 17 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/perp/PositionManager.sol:PositionManager | +| openPositionRequests | mapping(bytes32 => struct PositionManager.OpenPositionRequest) | 18 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/perp/PositionManager.sol:PositionManager | +| closePositionsIndex | mapping(address => uint256) | 19 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/perp/PositionManager.sol:PositionManager | +| closePositionRequests | mapping(bytes32 => struct PositionManager.ClosePositionRequest) | 20 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/perp/PositionManager.sol:PositionManager | +| managers | mapping(address => bool) | 21 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/perp/PositionManager.sol:PositionManager | +| approvedManagers | mapping(address => mapping(address => bool)) | 22 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/perp/PositionManager.sol:PositionManager | +"#; + assert_eq!(cmd.stdout_lossy(), output); +}); + +casttest!(balance, |_prj, cmd| { + let rpc = next_http_rpc_endpoint(); + let usdt = "0xdac17f958d2ee523a2206206994597c13d831ec7"; + cmd.cast_fuse().args([ + "balance", + "0x0000000000000000000000000000000000000000", + "--erc20", + usdt, + "--rpc-url", + &rpc, + ]); + cmd.cast_fuse().args([ + "balance", + "0x0000000000000000000000000000000000000000", + "--erc721", + usdt, + "--rpc-url", + &rpc, + ]); + + let usdt_result = cmd.stdout_lossy(); + let alias_result = cmd.stdout_lossy(); + + assert_ne!(usdt_result, "0x0000000000000000000000000000000000000000000000000000000000000000"); + assert_eq!(alias_result, usdt_result); +}); + +// tests that `cast interface` excludes the constructor +// +casttest!(interface_no_constructor, |prj, cmd| { + let interface = include_str!("../fixtures/interface.json"); + + let path = prj.root().join("interface.json"); + fs::write(&path, interface).unwrap(); + // Call `cast find-block` + cmd.args(["interface"]).arg(&path); + let output = cmd.stdout_lossy(); + + let s = r#"// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +interface Interface { + type SpendAssetsHandleType is uint8; + + function getIntegrationManager() external view returns (address integrationManager_); + function lend(address _vaultProxy, bytes memory, bytes memory _assetData) external; + function parseAssetsForAction(address, bytes4 _selector, bytes memory _actionData) + external + view + returns ( + SpendAssetsHandleType spendAssetsHandleType_, + address[] memory spendAssets_, + uint256[] memory spendAssetAmounts_, + address[] memory incomingAssets_, + uint256[] memory minIncomingAssetAmounts_ + ); + function redeem(address _vaultProxy, bytes memory, bytes memory _assetData) external; +}"#; + assert_eq!(output.trim(), s); +}); + +const ENS_NAME: &str = "emo.eth"; +const ENS_NAMEHASH: B256 = + b256!("0a21aaf2f6414aa664deb341d1114351fdb023cad07bf53b28e57c26db681910"); +const ENS_ADDRESS: Address = address!("28679A1a632125fbBf7A68d850E50623194A709E"); + +casttest!(ens_namehash, |_prj, cmd| { + cmd.args(["namehash", ENS_NAME]); + let out = cmd.stdout_lossy().trim().parse::(); + assert_eq!(out, Ok(ENS_NAMEHASH)); +}); + +casttest!(ens_lookup, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args(["lookup-address", &ENS_ADDRESS.to_string(), "--rpc-url", ð_rpc_url, "--verify"]); + let out = cmd.stdout_lossy(); + assert_eq!(out.trim(), ENS_NAME); +}); + +casttest!(ens_resolve, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args(["resolve-name", ENS_NAME, "--rpc-url", ð_rpc_url, "--verify"]); + let out = cmd.stdout_lossy().trim().parse::
(); + assert_eq!(out, Ok(ENS_ADDRESS)); +}); + +casttest!(ens_resolve_no_dot_eth, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + let name = ENS_NAME.strip_suffix(".eth").unwrap(); + cmd.args(["resolve-name", name, "--rpc-url", ð_rpc_url, "--verify"]); + let (_out, err) = cmd.unchecked_output_lossy(); + assert!(err.contains("not found"), "{err:?}"); }); diff --git a/crates/cast/tests/fixtures/ERC20Artifact.json b/crates/cast/tests/fixtures/ERC20Artifact.json index 4f4fd6e61fa33..508464c246ada 100644 --- a/crates/cast/tests/fixtures/ERC20Artifact.json +++ b/crates/cast/tests/fixtures/ERC20Artifact.json @@ -1,385 +1 @@ -{ - "abi": [ - { - "inputs": [ - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "string", - "name": "symbol", - "type": "string" - }, - { - "internalType": "uint8", - "name": "decimals", - "type": "uint8" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [], - "name": "DOMAIN_SEPARATOR", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "mint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "nonces", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "uint8", - "name": "v", - "type": "uint8" - }, - { - "internalType": "bytes32", - "name": "r", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "s", - "type": "bytes32" - } - ], - "name": "permit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bytecode": { - "object": "0x60e06040523480156200001157600080fd5b5060405162000f7738038062000f7783398101604081905262000034916200029a565b82828282600090805190602001906200004f92919062000127565b5081516200006590600190602085019062000127565b5060ff81166080524660a0526200007b6200008b565b60c0525062000400945050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6000604051620000bf91906200035c565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b82805462000135906200031f565b90600052602060002090601f016020900481019282620001595760008555620001a4565b82601f106200017457805160ff1916838001178555620001a4565b82800160010185558215620001a4579182015b82811115620001a457825182559160200191906001019062000187565b50620001b2929150620001b6565b5090565b5b80821115620001b25760008155600101620001b7565b634e487b7160e01b600052604160045260246000fd5b600082601f830112620001f557600080fd5b81516001600160401b0380821115620002125762000212620001cd565b604051601f8301601f19908116603f011681019082821181831017156200023d576200023d620001cd565b816040528381526020925086838588010111156200025a57600080fd5b600091505b838210156200027e57858201830151818301840152908201906200025f565b83821115620002905760008385830101525b9695505050505050565b600080600060608486031215620002b057600080fd5b83516001600160401b0380821115620002c857600080fd5b620002d687838801620001e3565b94506020860151915080821115620002ed57600080fd5b50620002fc86828701620001e3565b925050604084015160ff811681146200031457600080fd5b809150509250925092565b600181811c908216806200033457607f821691505b602082108114156200035657634e487b7160e01b600052602260045260246000fd5b50919050565b600080835481600182811c9150808316806200037957607f831692505b60208084108214156200039a57634e487b7160e01b86526022600452602486fd5b818015620003b15760018114620003c357620003f2565b60ff19861689528489019650620003f2565b60008a81526020902060005b86811015620003ea5781548b820152908501908301620003cf565b505084890196505b509498975050505050505050565b60805160a05160c051610b476200043060003960006104530152600061041e015260006101440152610b476000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c806340c10f191161008c57806395d89b411161006657806395d89b41146101d5578063a9059cbb146101dd578063d505accf146101f0578063dd62ed3e1461020357600080fd5b806340c10f191461018057806370a08231146101955780637ecebe00146101b557600080fd5b806306fdde03146100d4578063095ea7b3146100f257806318160ddd1461011557806323b872dd1461012c578063313ce5671461013f5780633644e51514610178575b600080fd5b6100dc61022e565b6040516100e99190610856565b60405180910390f35b6101056101003660046108c7565b6102bc565b60405190151581526020016100e9565b61011e60025481565b6040519081526020016100e9565b61010561013a3660046108f1565b610328565b6101667f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff90911681526020016100e9565b61011e61041a565b61019361018e3660046108c7565b610475565b005b61011e6101a336600461092d565b60036020526000908152604090205481565b61011e6101c336600461092d565b60056020526000908152604090205481565b6100dc610483565b6101056101eb3660046108c7565b610490565b6101936101fe36600461094f565b610508565b61011e6102113660046109c2565b600460209081526000928352604080842090915290825290205481565b6000805461023b906109f5565b80601f0160208091040260200160405190810160405280929190818152602001828054610267906109f5565b80156102b45780601f10610289576101008083540402835291602001916102b4565b820191906000526020600020905b81548152906001019060200180831161029757829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103179086815260200190565b60405180910390a350600192915050565b6001600160a01b038316600090815260046020908152604080832033845290915281205460001981146103845761035f8382610a46565b6001600160a01b03861660009081526004602090815260408083203384529091529020555b6001600160a01b038516600090815260036020526040812080548592906103ac908490610a46565b90915550506001600160a01b03808516600081815260036020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906104079087815260200190565b60405180910390a3506001949350505050565b60007f000000000000000000000000000000000000000000000000000000000000000046146104505761044b610751565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b61047f82826107eb565b5050565b6001805461023b906109f5565b336000908152600360205260408120805483919083906104b1908490610a46565b90915550506001600160a01b038316600081815260036020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906103179086815260200190565b4284101561055d5760405162461bcd60e51b815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b6000600161056961041a565b6001600160a01b038a811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e08301909152805192019190912061190160f01b6101008301526101028201929092526101228101919091526101420160408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610675573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116158015906106ab5750876001600160a01b0316816001600160a01b0316145b6106e85760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b6044820152606401610554565b6001600160a01b0390811660009081526004602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60006040516107839190610a5d565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b80600260008282546107fd9190610af9565b90915550506001600160a01b0382166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060208083528351808285015260005b8181101561088357858101830151858201604001528201610867565b81811115610895576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b03811681146108c257600080fd5b919050565b600080604083850312156108da57600080fd5b6108e3836108ab565b946020939093013593505050565b60008060006060848603121561090657600080fd5b61090f846108ab565b925061091d602085016108ab565b9150604084013590509250925092565b60006020828403121561093f57600080fd5b610948826108ab565b9392505050565b600080600080600080600060e0888a03121561096a57600080fd5b610973886108ab565b9650610981602089016108ab565b95506040880135945060608801359350608088013560ff811681146109a557600080fd5b9699959850939692959460a0840135945060c09093013592915050565b600080604083850312156109d557600080fd5b6109de836108ab565b91506109ec602084016108ab565b90509250929050565b600181811c90821680610a0957607f821691505b60208210811415610a2a57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600082821015610a5857610a58610a30565b500390565b600080835481600182811c915080831680610a7957607f831692505b6020808410821415610a9957634e487b7160e01b86526022600452602486fd5b818015610aad5760018114610abe57610aeb565b60ff19861689528489019650610aeb565b60008a81526020902060005b86811015610ae35781548b820152908501908301610aca565b505084890196505b509498975050505050505050565b60008219821115610b0c57610b0c610a30565b50019056fea26469706673582212209ca55739676d094f8ef4b3b5cfcc08f600b805843c4c3027beb2b4159a02f63664736f6c634300080a0033", - "sourceMap": "113:230:22:-:0;;;148:102;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;224:4;230:6;238:8;2098:5:10;2091:4;:12;;;;;;;;;;;;:::i;:::-;-1:-1:-1;2113:16:10;;;;:6;;:16;;;;;:::i;:::-;-1:-1:-1;2139:20:10;;;;;2189:13;2170:32;;2239:24;:22;:24::i;:::-;2212:51;;-1:-1:-1;113:230:22;;-1:-1:-1;;;;;113:230:22;5507:446:10;5572:7;5669:95;5802:4;5786:22;;;;;;:::i;:::-;;;;;;;;;;5637:295;;;3635:25:23;;;;3676:18;;3669:34;;;;5830:14:10;3719:18:23;;;3712:34;5866:13:10;3762:18:23;;;3755:34;5909:4:10;3805:19:23;;;3798:61;3607:19;;5637:295:10;;;;;;;;;;;;5610:336;;;;;;5591:355;;5507:446;:::o;113:230:22:-;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;113:230:22;;;-1:-1:-1;113:230:22;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;14:127:23;75:10;70:3;66:20;63:1;56:31;106:4;103:1;96:15;130:4;127:1;120:15;146:885;200:5;253:3;246:4;238:6;234:17;230:27;220:55;;271:1;268;261:12;220:55;294:13;;-1:-1:-1;;;;;356:10:23;;;353:36;;;369:18;;:::i;:::-;444:2;438:9;412:2;498:13;;-1:-1:-1;;494:22:23;;;518:2;490:31;486:40;474:53;;;542:18;;;562:22;;;539:46;536:72;;;588:18;;:::i;:::-;628:10;624:2;617:22;663:2;655:6;648:18;685:4;675:14;;730:3;725:2;720;712:6;708:15;704:24;701:33;698:53;;;747:1;744;737:12;698:53;769:1;760:10;;779:133;793:2;790:1;787:9;779:133;;;881:14;;;877:23;;871:30;850:14;;;846:23;;839:63;804:10;;;;779:133;;;930:2;927:1;924:9;921:80;;;989:1;984:2;979;971:6;967:15;963:24;956:35;921:80;1019:6;146:885;-1:-1:-1;;;;;;146:885:23:o;1036:712::-;1142:6;1150;1158;1211:2;1199:9;1190:7;1186:23;1182:32;1179:52;;;1227:1;1224;1217:12;1179:52;1254:16;;-1:-1:-1;;;;;1319:14:23;;;1316:34;;;1346:1;1343;1336:12;1316:34;1369:61;1422:7;1413:6;1402:9;1398:22;1369:61;:::i;:::-;1359:71;;1476:2;1465:9;1461:18;1455:25;1439:41;;1505:2;1495:8;1492:16;1489:36;;;1521:1;1518;1511:12;1489:36;;1544:63;1599:7;1588:8;1577:9;1573:24;1544:63;:::i;:::-;1534:73;;;1650:2;1639:9;1635:18;1629:25;1694:4;1687:5;1683:16;1676:5;1673:27;1663:55;;1714:1;1711;1704:12;1663:55;1737:5;1727:15;;;1036:712;;;;;:::o;1753:380::-;1832:1;1828:12;;;;1875;;;1896:61;;1950:4;1942:6;1938:17;1928:27;;1896:61;2003:2;1995:6;1992:14;1972:18;1969:38;1966:161;;;2049:10;2044:3;2040:20;2037:1;2030:31;2084:4;2081:1;2074:15;2112:4;2109:1;2102:15;1966:161;;1753:380;;;:::o;2267:1104::-;2397:3;2426:1;2459:6;2453:13;2489:3;2511:1;2539:9;2535:2;2531:18;2521:28;;2599:2;2588:9;2584:18;2621;2611:61;;2665:4;2657:6;2653:17;2643:27;;2611:61;2691:2;2739;2731:6;2728:14;2708:18;2705:38;2702:165;;;-1:-1:-1;;;2766:33:23;;2822:4;2819:1;2812:15;2852:4;2773:3;2840:17;2702:165;2883:18;2910:104;;;;3028:1;3023:323;;;;2876:470;;2910:104;-1:-1:-1;;2943:24:23;;2931:37;;2988:16;;;;-1:-1:-1;2910:104:23;;3023:323;2214:1;2207:14;;;2251:4;2238:18;;3121:1;3135:165;3149:6;3146:1;3143:13;3135:165;;;3227:14;;3214:11;;;3207:35;3270:16;;;;3164:10;;3135:165;;;3139:3;;3329:6;3324:3;3320:16;3313:23;;2876:470;-1:-1:-1;3362:3:23;;2267:1104;-1:-1:-1;;;;;;;;2267:1104:23:o;3376:489::-;113:230:22;;;;;;;;;;;;;;;;;;;;;;;;", - "linkReferences": {} - }, - "deployedBytecode": { - "object": "0x608060405234801561001057600080fd5b50600436106100cf5760003560e01c806340c10f191161008c57806395d89b411161006657806395d89b41146101d5578063a9059cbb146101dd578063d505accf146101f0578063dd62ed3e1461020357600080fd5b806340c10f191461018057806370a08231146101955780637ecebe00146101b557600080fd5b806306fdde03146100d4578063095ea7b3146100f257806318160ddd1461011557806323b872dd1461012c578063313ce5671461013f5780633644e51514610178575b600080fd5b6100dc61022e565b6040516100e99190610856565b60405180910390f35b6101056101003660046108c7565b6102bc565b60405190151581526020016100e9565b61011e60025481565b6040519081526020016100e9565b61010561013a3660046108f1565b610328565b6101667f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff90911681526020016100e9565b61011e61041a565b61019361018e3660046108c7565b610475565b005b61011e6101a336600461092d565b60036020526000908152604090205481565b61011e6101c336600461092d565b60056020526000908152604090205481565b6100dc610483565b6101056101eb3660046108c7565b610490565b6101936101fe36600461094f565b610508565b61011e6102113660046109c2565b600460209081526000928352604080842090915290825290205481565b6000805461023b906109f5565b80601f0160208091040260200160405190810160405280929190818152602001828054610267906109f5565b80156102b45780601f10610289576101008083540402835291602001916102b4565b820191906000526020600020905b81548152906001019060200180831161029757829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103179086815260200190565b60405180910390a350600192915050565b6001600160a01b038316600090815260046020908152604080832033845290915281205460001981146103845761035f8382610a46565b6001600160a01b03861660009081526004602090815260408083203384529091529020555b6001600160a01b038516600090815260036020526040812080548592906103ac908490610a46565b90915550506001600160a01b03808516600081815260036020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906104079087815260200190565b60405180910390a3506001949350505050565b60007f000000000000000000000000000000000000000000000000000000000000000046146104505761044b610751565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b61047f82826107eb565b5050565b6001805461023b906109f5565b336000908152600360205260408120805483919083906104b1908490610a46565b90915550506001600160a01b038316600081815260036020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906103179086815260200190565b4284101561055d5760405162461bcd60e51b815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b6000600161056961041a565b6001600160a01b038a811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e08301909152805192019190912061190160f01b6101008301526101028201929092526101228101919091526101420160408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610675573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116158015906106ab5750876001600160a01b0316816001600160a01b0316145b6106e85760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b6044820152606401610554565b6001600160a01b0390811660009081526004602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60006040516107839190610a5d565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b80600260008282546107fd9190610af9565b90915550506001600160a01b0382166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060208083528351808285015260005b8181101561088357858101830151858201604001528201610867565b81811115610895576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b03811681146108c257600080fd5b919050565b600080604083850312156108da57600080fd5b6108e3836108ab565b946020939093013593505050565b60008060006060848603121561090657600080fd5b61090f846108ab565b925061091d602085016108ab565b9150604084013590509250925092565b60006020828403121561093f57600080fd5b610948826108ab565b9392505050565b600080600080600080600060e0888a03121561096a57600080fd5b610973886108ab565b9650610981602089016108ab565b95506040880135945060608801359350608088013560ff811681146109a557600080fd5b9699959850939692959460a0840135945060c09093013592915050565b600080604083850312156109d557600080fd5b6109de836108ab565b91506109ec602084016108ab565b90509250929050565b600181811c90821680610a0957607f821691505b60208210811415610a2a57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600082821015610a5857610a58610a30565b500390565b600080835481600182811c915080831680610a7957607f831692505b6020808410821415610a9957634e487b7160e01b86526022600452602486fd5b818015610aad5760018114610abe57610aeb565b60ff19861689528489019650610aeb565b60008a81526020902060005b86811015610ae35781548b820152908501908301610aca565b505084890196505b509498975050505050505050565b60008219821115610b0c57610b0c610a30565b50019056fea26469706673582212209ca55739676d094f8ef4b3b5cfcc08f600b805843c4c3027beb2b4159a02f63664736f6c634300080a0033", - "sourceMap": "113:230:22:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1028:18:10;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;2458:211;;;;;;:::i;:::-;;:::i;:::-;;;1218:14:23;;1211:22;1193:41;;1181:2;1166:18;2458:211:10;1053:187:23;1301:26:10;;;;;;;;;1391:25:23;;;1379:2;1364:18;1301:26:10;1245:177:23;3054:592:10;;;;;;:::i;:::-;;:::i;1080:31::-;;;;;;;;1932:4:23;1920:17;;;1902:36;;1890:2;1875:18;1080:31:10;1760:184:23;5324:177:10;;;:::i;256:85:22:-;;;;;;:::i;:::-;;:::i;:::-;;1334:44:10;;;;;;:::i;:::-;;;;;;;;;;;;;;1748:41;;;;;;:::i;:::-;;;;;;;;;;;;;;1053:20;;;:::i;2675:373::-;;;;;;:::i;:::-;;:::i;3835:1483::-;;;;;;:::i;:::-;;:::i;1385:64::-;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;1028:18;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;2458:211::-;2558:10;2532:4;2548:21;;;:9;:21;;;;;;;;-1:-1:-1;;;;;2548:30:10;;;;;;;;;;:39;;;2603:37;2532:4;;2548:30;;2603:37;;;;2581:6;1391:25:23;;1379:2;1364:18;;1245:177;2603:37:10;;;;;;;;-1:-1:-1;2658:4:10;2458:211;;;;:::o;3054:592::-;-1:-1:-1;;;;;3206:15:10;;3172:4;3206:15;;;:9;:15;;;;;;;;3222:10;3206:27;;;;;;;;-1:-1:-1;;3284:28:10;;3280:80;;3344:16;3354:6;3344:7;:16;:::i;:::-;-1:-1:-1;;;;;3314:15:10;;;;;;:9;:15;;;;;;;;3330:10;3314:27;;;;;;;:46;3280:80;-1:-1:-1;;;;;3371:15:10;;;;;;:9;:15;;;;;:25;;3390:6;;3371:15;:25;;3390:6;;3371:25;:::i;:::-;;;;-1:-1:-1;;;;;;;3542:13:10;;;;;;;:9;:13;;;;;;;:23;;;;;;3591:26;3542:13;;3591:26;;;;;;;3559:6;1391:25:23;;1379:2;1364:18;;1245:177;3591:26:10;;;;;;;;-1:-1:-1;3635:4:10;;3054:592;-1:-1:-1;;;;3054:592:10:o;5324:177::-;5381:7;5424:16;5407:13;:33;:87;;5470:24;:22;:24::i;:::-;5400:94;;5324:177;:::o;5407:87::-;-1:-1:-1;5443:24:10;;5324:177::o;256:85:22:-;317:17;323:2;327:6;317:5;:17::i;:::-;256:85;;:::o;1053:20:10:-;;;;;;;:::i;2675:373::-;2771:10;2745:4;2761:21;;;:9;:21;;;;;:31;;2786:6;;2761:21;2745:4;;2761:31;;2786:6;;2761:31;:::i;:::-;;;;-1:-1:-1;;;;;;;2938:13:10;;;;;;:9;:13;;;;;;;:23;;;;;;2987:32;2996:10;;2987:32;;;;2955:6;1391:25:23;;1379:2;1364:18;;1245:177;3835:1483:10;4054:15;4042:8;:27;;4034:63;;;;-1:-1:-1;;;4034:63:10;;4134:2:23;4034:63:10;;;4116:21:23;4173:2;4153:18;;;4146:30;4212:25;4192:18;;;4185:53;4255:18;;4034:63:10;;;;;;;;;4262:24;4289:805;4425:18;:16;:18::i;:::-;-1:-1:-1;;;;;4870:13:10;;;;;;;:6;:13;;;;;;;;;:15;;;;;;;;4508:449;;4552:165;4508:449;;;4571:25:23;4650:18;;;4643:43;;;;4722:15;;;4702:18;;;4695:43;4754:18;;;4747:34;;;4797:19;;;4790:35;;;;4841:19;;;;4834:35;;;4508:449:10;;;;;;;;;;4543:19:23;;;4508:449:10;;;4469:514;;;;;;;;-1:-1:-1;;;4347:658:10;;;5138:27:23;5181:11;;;5174:27;;;;5217:12;;;5210:28;;;;5254:12;;4347:658:10;;;-1:-1:-1;;4347:658:10;;;;;;;;;4316:707;;4347:658;4316:707;;;;4289:805;;;;;;;;;5504:25:23;5577:4;5565:17;;5545:18;;;5538:45;5599:18;;;5592:34;;;5642:18;;;5635:34;;;5476:19;;4289:805:10;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4289:805:10;;-1:-1:-1;;4289:805:10;;;-1:-1:-1;;;;;;;5117:30:10;;;;;;:59;;;5171:5;-1:-1:-1;;;;;5151:25:10;:16;-1:-1:-1;;;;;5151:25:10;;5117:59;5109:86;;;;-1:-1:-1;;;5109:86:10;;5882:2:23;5109:86:10;;;5864:21:23;5921:2;5901:18;;;5894:30;-1:-1:-1;;;5940:18:23;;;5933:44;5994:18;;5109:86:10;5680:338:23;5109:86:10;-1:-1:-1;;;;;5210:27:10;;;;;;;:9;:27;;;;;;;;:36;;;;;;;;;;;;;:44;;;5280:31;1391:25:23;;;5210:36:10;;5280:31;;;;;1364:18:23;5280:31:10;;;;;;;3835:1483;;;;;;;:::o;5507:446::-;5572:7;5669:95;5802:4;5786:22;;;;;;:::i;:::-;;;;;;;;;;5637:295;;;7520:25:23;;;;7561:18;;7554:34;;;;5830:14:10;7604:18:23;;;7597:34;5866:13:10;7647:18:23;;;7640:34;5909:4:10;7690:19:23;;;7683:61;7492:19;;5637:295:10;;;;;;;;;;;;5610:336;;;;;;5591:355;;5507:446;:::o;6147:325::-;6232:6;6217:11;;:21;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;;;6384:13:10;;;;;;:9;:13;;;;;;;;:23;;;;;;6433:32;1391:25:23;;;6433:32:10;;1364:18:23;6433:32:10;;;;;;;6147:325;;:::o;14:597:23:-;126:4;155:2;184;173:9;166:21;216:6;210:13;259:6;254:2;243:9;239:18;232:34;284:1;294:140;308:6;305:1;302:13;294:140;;;403:14;;;399:23;;393:30;369:17;;;388:2;365:26;358:66;323:10;;294:140;;;452:6;449:1;446:13;443:91;;;522:1;517:2;508:6;497:9;493:22;489:31;482:42;443:91;-1:-1:-1;595:2:23;574:15;-1:-1:-1;;570:29:23;555:45;;;;602:2;551:54;;14:597;-1:-1:-1;;;14:597:23:o;616:173::-;684:20;;-1:-1:-1;;;;;733:31:23;;723:42;;713:70;;779:1;776;769:12;713:70;616:173;;;:::o;794:254::-;862:6;870;923:2;911:9;902:7;898:23;894:32;891:52;;;939:1;936;929:12;891:52;962:29;981:9;962:29;:::i;:::-;952:39;1038:2;1023:18;;;;1010:32;;-1:-1:-1;;;794:254:23:o;1427:328::-;1504:6;1512;1520;1573:2;1561:9;1552:7;1548:23;1544:32;1541:52;;;1589:1;1586;1579:12;1541:52;1612:29;1631:9;1612:29;:::i;:::-;1602:39;;1660:38;1694:2;1683:9;1679:18;1660:38;:::i;:::-;1650:48;;1745:2;1734:9;1730:18;1717:32;1707:42;;1427:328;;;;;:::o;2131:186::-;2190:6;2243:2;2231:9;2222:7;2218:23;2214:32;2211:52;;;2259:1;2256;2249:12;2211:52;2282:29;2301:9;2282:29;:::i;:::-;2272:39;2131:186;-1:-1:-1;;;2131:186:23:o;2322:693::-;2433:6;2441;2449;2457;2465;2473;2481;2534:3;2522:9;2513:7;2509:23;2505:33;2502:53;;;2551:1;2548;2541:12;2502:53;2574:29;2593:9;2574:29;:::i;:::-;2564:39;;2622:38;2656:2;2645:9;2641:18;2622:38;:::i;:::-;2612:48;;2707:2;2696:9;2692:18;2679:32;2669:42;;2758:2;2747:9;2743:18;2730:32;2720:42;;2812:3;2801:9;2797:19;2784:33;2857:4;2850:5;2846:16;2839:5;2836:27;2826:55;;2877:1;2874;2867:12;2826:55;2322:693;;;;-1:-1:-1;2322:693:23;;;;2900:5;2952:3;2937:19;;2924:33;;-1:-1:-1;3004:3:23;2989:19;;;2976:33;;2322:693;-1:-1:-1;;2322:693:23:o;3020:260::-;3088:6;3096;3149:2;3137:9;3128:7;3124:23;3120:32;3117:52;;;3165:1;3162;3155:12;3117:52;3188:29;3207:9;3188:29;:::i;:::-;3178:39;;3236:38;3270:2;3259:9;3255:18;3236:38;:::i;:::-;3226:48;;3020:260;;;;;:::o;3285:380::-;3364:1;3360:12;;;;3407;;;3428:61;;3482:4;3474:6;3470:17;3460:27;;3428:61;3535:2;3527:6;3524:14;3504:18;3501:38;3498:161;;;3581:10;3576:3;3572:20;3569:1;3562:31;3616:4;3613:1;3606:15;3644:4;3641:1;3634:15;3498:161;;3285:380;;;:::o;3670:127::-;3731:10;3726:3;3722:20;3719:1;3712:31;3762:4;3759:1;3752:15;3786:4;3783:1;3776:15;3802:125;3842:4;3870:1;3867;3864:8;3861:34;;;3875:18;;:::i;:::-;-1:-1:-1;3912:9:23;;3802:125::o;6152:1104::-;6282:3;6311:1;6344:6;6338:13;6374:3;6396:1;6424:9;6420:2;6416:18;6406:28;;6484:2;6473:9;6469:18;6506;6496:61;;6550:4;6542:6;6538:17;6528:27;;6496:61;6576:2;6624;6616:6;6613:14;6593:18;6590:38;6587:165;;;-1:-1:-1;;;6651:33:23;;6707:4;6704:1;6697:15;6737:4;6658:3;6725:17;6587:165;6768:18;6795:104;;;;6913:1;6908:323;;;;6761:470;;6795:104;-1:-1:-1;;6828:24:23;;6816:37;;6873:16;;;;-1:-1:-1;6795:104:23;;6908:323;6099:1;6092:14;;;6136:4;6123:18;;7006:1;7020:165;7034:6;7031:1;7028:13;7020:165;;;7112:14;;7099:11;;;7092:35;7155:16;;;;7049:10;;7020:165;;;7024:3;;7214:6;7209:3;7205:16;7198:23;;6761:470;-1:-1:-1;7247:3:23;;6152:1104;-1:-1:-1;;;;;;;;6152:1104:23:o;7755:128::-;7795:3;7826:1;7822:6;7819:1;7816:13;7813:39;;;7832:18;;:::i;:::-;-1:-1:-1;7868:9:23;;7755:128::o", - "linkReferences": {}, - "immutableReferences": { - "3499": [ - { - "start": 324, - "length": 32 - } - ], - "3513": [ - { - "start": 1054, - "length": 32 - } - ], - "3515": [ - { - "start": 1107, - "length": 32 - } - ] - } - }, - "methodIdentifiers": { - "DOMAIN_SEPARATOR()": "3644e515", - "allowance(address,address)": "dd62ed3e", - "approve(address,uint256)": "095ea7b3", - "balanceOf(address)": "70a08231", - "decimals()": "313ce567", - "mint(address,uint256)": "40c10f19", - "name()": "06fdde03", - "nonces(address)": "7ecebe00", - "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)": "d505accf", - "symbol()": "95d89b41", - "totalSupply()": "18160ddd", - "transfer(address,uint256)": "a9059cbb", - "transferFrom(address,address,uint256)": "23b872dd" - } -} +{"abi":[{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":{"object":"0x60e06040523480156200001157600080fd5b5060405162000f7738038062000f7783398101604081905262000034916200029a565b82828282600090805190602001906200004f92919062000127565b5081516200006590600190602085019062000127565b5060ff81166080524660a0526200007b6200008b565b60c0525062000400945050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6000604051620000bf91906200035c565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b82805462000135906200031f565b90600052602060002090601f016020900481019282620001595760008555620001a4565b82601f106200017457805160ff1916838001178555620001a4565b82800160010185558215620001a4579182015b82811115620001a457825182559160200191906001019062000187565b50620001b2929150620001b6565b5090565b5b80821115620001b25760008155600101620001b7565b634e487b7160e01b600052604160045260246000fd5b600082601f830112620001f557600080fd5b81516001600160401b0380821115620002125762000212620001cd565b604051601f8301601f19908116603f011681019082821181831017156200023d576200023d620001cd565b816040528381526020925086838588010111156200025a57600080fd5b600091505b838210156200027e57858201830151818301840152908201906200025f565b83821115620002905760008385830101525b9695505050505050565b600080600060608486031215620002b057600080fd5b83516001600160401b0380821115620002c857600080fd5b620002d687838801620001e3565b94506020860151915080821115620002ed57600080fd5b50620002fc86828701620001e3565b925050604084015160ff811681146200031457600080fd5b809150509250925092565b600181811c908216806200033457607f821691505b602082108114156200035657634e487b7160e01b600052602260045260246000fd5b50919050565b600080835481600182811c9150808316806200037957607f831692505b60208084108214156200039a57634e487b7160e01b86526022600452602486fd5b818015620003b15760018114620003c357620003f2565b60ff19861689528489019650620003f2565b60008a81526020902060005b86811015620003ea5781548b820152908501908301620003cf565b505084890196505b509498975050505050505050565b60805160a05160c051610b476200043060003960006104530152600061041e015260006101440152610b476000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c806340c10f191161008c57806395d89b411161006657806395d89b41146101d5578063a9059cbb146101dd578063d505accf146101f0578063dd62ed3e1461020357600080fd5b806340c10f191461018057806370a08231146101955780637ecebe00146101b557600080fd5b806306fdde03146100d4578063095ea7b3146100f257806318160ddd1461011557806323b872dd1461012c578063313ce5671461013f5780633644e51514610178575b600080fd5b6100dc61022e565b6040516100e99190610856565b60405180910390f35b6101056101003660046108c7565b6102bc565b60405190151581526020016100e9565b61011e60025481565b6040519081526020016100e9565b61010561013a3660046108f1565b610328565b6101667f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff90911681526020016100e9565b61011e61041a565b61019361018e3660046108c7565b610475565b005b61011e6101a336600461092d565b60036020526000908152604090205481565b61011e6101c336600461092d565b60056020526000908152604090205481565b6100dc610483565b6101056101eb3660046108c7565b610490565b6101936101fe36600461094f565b610508565b61011e6102113660046109c2565b600460209081526000928352604080842090915290825290205481565b6000805461023b906109f5565b80601f0160208091040260200160405190810160405280929190818152602001828054610267906109f5565b80156102b45780601f10610289576101008083540402835291602001916102b4565b820191906000526020600020905b81548152906001019060200180831161029757829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103179086815260200190565b60405180910390a350600192915050565b6001600160a01b038316600090815260046020908152604080832033845290915281205460001981146103845761035f8382610a46565b6001600160a01b03861660009081526004602090815260408083203384529091529020555b6001600160a01b038516600090815260036020526040812080548592906103ac908490610a46565b90915550506001600160a01b03808516600081815260036020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906104079087815260200190565b60405180910390a3506001949350505050565b60007f000000000000000000000000000000000000000000000000000000000000000046146104505761044b610751565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b61047f82826107eb565b5050565b6001805461023b906109f5565b336000908152600360205260408120805483919083906104b1908490610a46565b90915550506001600160a01b038316600081815260036020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906103179086815260200190565b4284101561055d5760405162461bcd60e51b815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b6000600161056961041a565b6001600160a01b038a811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e08301909152805192019190912061190160f01b6101008301526101028201929092526101228101919091526101420160408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610675573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116158015906106ab5750876001600160a01b0316816001600160a01b0316145b6106e85760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b6044820152606401610554565b6001600160a01b0390811660009081526004602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60006040516107839190610a5d565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b80600260008282546107fd9190610af9565b90915550506001600160a01b0382166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060208083528351808285015260005b8181101561088357858101830151858201604001528201610867565b81811115610895576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b03811681146108c257600080fd5b919050565b600080604083850312156108da57600080fd5b6108e3836108ab565b946020939093013593505050565b60008060006060848603121561090657600080fd5b61090f846108ab565b925061091d602085016108ab565b9150604084013590509250925092565b60006020828403121561093f57600080fd5b610948826108ab565b9392505050565b600080600080600080600060e0888a03121561096a57600080fd5b610973886108ab565b9650610981602089016108ab565b95506040880135945060608801359350608088013560ff811681146109a557600080fd5b9699959850939692959460a0840135945060c09093013592915050565b600080604083850312156109d557600080fd5b6109de836108ab565b91506109ec602084016108ab565b90509250929050565b600181811c90821680610a0957607f821691505b60208210811415610a2a57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600082821015610a5857610a58610a30565b500390565b600080835481600182811c915080831680610a7957607f831692505b6020808410821415610a9957634e487b7160e01b86526022600452602486fd5b818015610aad5760018114610abe57610aeb565b60ff19861689528489019650610aeb565b60008a81526020902060005b86811015610ae35781548b820152908501908301610aca565b505084890196505b509498975050505050505050565b60008219821115610b0c57610b0c610a30565b50019056fea26469706673582212209ca55739676d094f8ef4b3b5cfcc08f600b805843c4c3027beb2b4159a02f63664736f6c634300080a0033","sourceMap":"113:230:22:-:0;;;148:102;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;224:4;230:6;238:8;2098:5:10;2091:4;:12;;;;;;;;;;;;:::i;:::-;-1:-1:-1;2113:16:10;;;;:6;;:16;;;;;:::i;:::-;-1:-1:-1;2139:20:10;;;;;2189:13;2170:32;;2239:24;:22;:24::i;:::-;2212:51;;-1:-1:-1;113:230:22;;-1:-1:-1;;;;;113:230:22;5507:446:10;5572:7;5669:95;5802:4;5786:22;;;;;;:::i;:::-;;;;;;;;;;5637:295;;;3635:25:23;;;;3676:18;;3669:34;;;;5830:14:10;3719:18:23;;;3712:34;5866:13:10;3762:18:23;;;3755:34;5909:4:10;3805:19:23;;;3798:61;3607:19;;5637:295:10;;;;;;;;;;;;5610:336;;;;;;5591:355;;5507:446;:::o;113:230:22:-;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;113:230:22;;;-1:-1:-1;113:230:22;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;14:127:23;75:10;70:3;66:20;63:1;56:31;106:4;103:1;96:15;130:4;127:1;120:15;146:885;200:5;253:3;246:4;238:6;234:17;230:27;220:55;;271:1;268;261:12;220:55;294:13;;-1:-1:-1;;;;;356:10:23;;;353:36;;;369:18;;:::i;:::-;444:2;438:9;412:2;498:13;;-1:-1:-1;;494:22:23;;;518:2;490:31;486:40;474:53;;;542:18;;;562:22;;;539:46;536:72;;;588:18;;:::i;:::-;628:10;624:2;617:22;663:2;655:6;648:18;685:4;675:14;;730:3;725:2;720;712:6;708:15;704:24;701:33;698:53;;;747:1;744;737:12;698:53;769:1;760:10;;779:133;793:2;790:1;787:9;779:133;;;881:14;;;877:23;;871:30;850:14;;;846:23;;839:63;804:10;;;;779:133;;;930:2;927:1;924:9;921:80;;;989:1;984:2;979;971:6;967:15;963:24;956:35;921:80;1019:6;146:885;-1:-1:-1;;;;;;146:885:23:o;1036:712::-;1142:6;1150;1158;1211:2;1199:9;1190:7;1186:23;1182:32;1179:52;;;1227:1;1224;1217:12;1179:52;1254:16;;-1:-1:-1;;;;;1319:14:23;;;1316:34;;;1346:1;1343;1336:12;1316:34;1369:61;1422:7;1413:6;1402:9;1398:22;1369:61;:::i;:::-;1359:71;;1476:2;1465:9;1461:18;1455:25;1439:41;;1505:2;1495:8;1492:16;1489:36;;;1521:1;1518;1511:12;1489:36;;1544:63;1599:7;1588:8;1577:9;1573:24;1544:63;:::i;:::-;1534:73;;;1650:2;1639:9;1635:18;1629:25;1694:4;1687:5;1683:16;1676:5;1673:27;1663:55;;1714:1;1711;1704:12;1663:55;1737:5;1727:15;;;1036:712;;;;;:::o;1753:380::-;1832:1;1828:12;;;;1875;;;1896:61;;1950:4;1942:6;1938:17;1928:27;;1896:61;2003:2;1995:6;1992:14;1972:18;1969:38;1966:161;;;2049:10;2044:3;2040:20;2037:1;2030:31;2084:4;2081:1;2074:15;2112:4;2109:1;2102:15;1966:161;;1753:380;;;:::o;2267:1104::-;2397:3;2426:1;2459:6;2453:13;2489:3;2511:1;2539:9;2535:2;2531:18;2521:28;;2599:2;2588:9;2584:18;2621;2611:61;;2665:4;2657:6;2653:17;2643:27;;2611:61;2691:2;2739;2731:6;2728:14;2708:18;2705:38;2702:165;;;-1:-1:-1;;;2766:33:23;;2822:4;2819:1;2812:15;2852:4;2773:3;2840:17;2702:165;2883:18;2910:104;;;;3028:1;3023:323;;;;2876:470;;2910:104;-1:-1:-1;;2943:24:23;;2931:37;;2988:16;;;;-1:-1:-1;2910:104:23;;3023:323;2214:1;2207:14;;;2251:4;2238:18;;3121:1;3135:165;3149:6;3146:1;3143:13;3135:165;;;3227:14;;3214:11;;;3207:35;3270:16;;;;3164:10;;3135:165;;;3139:3;;3329:6;3324:3;3320:16;3313:23;;2876:470;-1:-1:-1;3362:3:23;;2267:1104;-1:-1:-1;;;;;;;;2267:1104:23:o;3376:489::-;113:230:22;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561001057600080fd5b50600436106100cf5760003560e01c806340c10f191161008c57806395d89b411161006657806395d89b41146101d5578063a9059cbb146101dd578063d505accf146101f0578063dd62ed3e1461020357600080fd5b806340c10f191461018057806370a08231146101955780637ecebe00146101b557600080fd5b806306fdde03146100d4578063095ea7b3146100f257806318160ddd1461011557806323b872dd1461012c578063313ce5671461013f5780633644e51514610178575b600080fd5b6100dc61022e565b6040516100e99190610856565b60405180910390f35b6101056101003660046108c7565b6102bc565b60405190151581526020016100e9565b61011e60025481565b6040519081526020016100e9565b61010561013a3660046108f1565b610328565b6101667f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff90911681526020016100e9565b61011e61041a565b61019361018e3660046108c7565b610475565b005b61011e6101a336600461092d565b60036020526000908152604090205481565b61011e6101c336600461092d565b60056020526000908152604090205481565b6100dc610483565b6101056101eb3660046108c7565b610490565b6101936101fe36600461094f565b610508565b61011e6102113660046109c2565b600460209081526000928352604080842090915290825290205481565b6000805461023b906109f5565b80601f0160208091040260200160405190810160405280929190818152602001828054610267906109f5565b80156102b45780601f10610289576101008083540402835291602001916102b4565b820191906000526020600020905b81548152906001019060200180831161029757829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103179086815260200190565b60405180910390a350600192915050565b6001600160a01b038316600090815260046020908152604080832033845290915281205460001981146103845761035f8382610a46565b6001600160a01b03861660009081526004602090815260408083203384529091529020555b6001600160a01b038516600090815260036020526040812080548592906103ac908490610a46565b90915550506001600160a01b03808516600081815260036020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906104079087815260200190565b60405180910390a3506001949350505050565b60007f000000000000000000000000000000000000000000000000000000000000000046146104505761044b610751565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b61047f82826107eb565b5050565b6001805461023b906109f5565b336000908152600360205260408120805483919083906104b1908490610a46565b90915550506001600160a01b038316600081815260036020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906103179086815260200190565b4284101561055d5760405162461bcd60e51b815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b6000600161056961041a565b6001600160a01b038a811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e08301909152805192019190912061190160f01b6101008301526101028201929092526101228101919091526101420160408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610675573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116158015906106ab5750876001600160a01b0316816001600160a01b0316145b6106e85760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b6044820152606401610554565b6001600160a01b0390811660009081526004602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60006040516107839190610a5d565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b80600260008282546107fd9190610af9565b90915550506001600160a01b0382166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060208083528351808285015260005b8181101561088357858101830151858201604001528201610867565b81811115610895576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b03811681146108c257600080fd5b919050565b600080604083850312156108da57600080fd5b6108e3836108ab565b946020939093013593505050565b60008060006060848603121561090657600080fd5b61090f846108ab565b925061091d602085016108ab565b9150604084013590509250925092565b60006020828403121561093f57600080fd5b610948826108ab565b9392505050565b600080600080600080600060e0888a03121561096a57600080fd5b610973886108ab565b9650610981602089016108ab565b95506040880135945060608801359350608088013560ff811681146109a557600080fd5b9699959850939692959460a0840135945060c09093013592915050565b600080604083850312156109d557600080fd5b6109de836108ab565b91506109ec602084016108ab565b90509250929050565b600181811c90821680610a0957607f821691505b60208210811415610a2a57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600082821015610a5857610a58610a30565b500390565b600080835481600182811c915080831680610a7957607f831692505b6020808410821415610a9957634e487b7160e01b86526022600452602486fd5b818015610aad5760018114610abe57610aeb565b60ff19861689528489019650610aeb565b60008a81526020902060005b86811015610ae35781548b820152908501908301610aca565b505084890196505b509498975050505050505050565b60008219821115610b0c57610b0c610a30565b50019056fea26469706673582212209ca55739676d094f8ef4b3b5cfcc08f600b805843c4c3027beb2b4159a02f63664736f6c634300080a0033","sourceMap":"113:230:22:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1028:18:10;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;2458:211;;;;;;:::i;:::-;;:::i;:::-;;;1218:14:23;;1211:22;1193:41;;1181:2;1166:18;2458:211:10;1053:187:23;1301:26:10;;;;;;;;;1391:25:23;;;1379:2;1364:18;1301:26:10;1245:177:23;3054:592:10;;;;;;:::i;:::-;;:::i;1080:31::-;;;;;;;;1932:4:23;1920:17;;;1902:36;;1890:2;1875:18;1080:31:10;1760:184:23;5324:177:10;;;:::i;256:85:22:-;;;;;;:::i;:::-;;:::i;:::-;;1334:44:10;;;;;;:::i;:::-;;;;;;;;;;;;;;1748:41;;;;;;:::i;:::-;;;;;;;;;;;;;;1053:20;;;:::i;2675:373::-;;;;;;:::i;:::-;;:::i;3835:1483::-;;;;;;:::i;:::-;;:::i;1385:64::-;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;1028:18;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;2458:211::-;2558:10;2532:4;2548:21;;;:9;:21;;;;;;;;-1:-1:-1;;;;;2548:30:10;;;;;;;;;;:39;;;2603:37;2532:4;;2548:30;;2603:37;;;;2581:6;1391:25:23;;1379:2;1364:18;;1245:177;2603:37:10;;;;;;;;-1:-1:-1;2658:4:10;2458:211;;;;:::o;3054:592::-;-1:-1:-1;;;;;3206:15:10;;3172:4;3206:15;;;:9;:15;;;;;;;;3222:10;3206:27;;;;;;;;-1:-1:-1;;3284:28:10;;3280:80;;3344:16;3354:6;3344:7;:16;:::i;:::-;-1:-1:-1;;;;;3314:15:10;;;;;;:9;:15;;;;;;;;3330:10;3314:27;;;;;;;:46;3280:80;-1:-1:-1;;;;;3371:15:10;;;;;;:9;:15;;;;;:25;;3390:6;;3371:15;:25;;3390:6;;3371:25;:::i;:::-;;;;-1:-1:-1;;;;;;;3542:13:10;;;;;;;:9;:13;;;;;;;:23;;;;;;3591:26;3542:13;;3591:26;;;;;;;3559:6;1391:25:23;;1379:2;1364:18;;1245:177;3591:26:10;;;;;;;;-1:-1:-1;3635:4:10;;3054:592;-1:-1:-1;;;;3054:592:10:o;5324:177::-;5381:7;5424:16;5407:13;:33;:87;;5470:24;:22;:24::i;:::-;5400:94;;5324:177;:::o;5407:87::-;-1:-1:-1;5443:24:10;;5324:177::o;256:85:22:-;317:17;323:2;327:6;317:5;:17::i;:::-;256:85;;:::o;1053:20:10:-;;;;;;;:::i;2675:373::-;2771:10;2745:4;2761:21;;;:9;:21;;;;;:31;;2786:6;;2761:21;2745:4;;2761:31;;2786:6;;2761:31;:::i;:::-;;;;-1:-1:-1;;;;;;;2938:13:10;;;;;;:9;:13;;;;;;;:23;;;;;;2987:32;2996:10;;2987:32;;;;2955:6;1391:25:23;;1379:2;1364:18;;1245:177;3835:1483:10;4054:15;4042:8;:27;;4034:63;;;;-1:-1:-1;;;4034:63:10;;4134:2:23;4034:63:10;;;4116:21:23;4173:2;4153:18;;;4146:30;4212:25;4192:18;;;4185:53;4255:18;;4034:63:10;;;;;;;;;4262:24;4289:805;4425:18;:16;:18::i;:::-;-1:-1:-1;;;;;4870:13:10;;;;;;;:6;:13;;;;;;;;;:15;;;;;;;;4508:449;;4552:165;4508:449;;;4571:25:23;4650:18;;;4643:43;;;;4722:15;;;4702:18;;;4695:43;4754:18;;;4747:34;;;4797:19;;;4790:35;;;;4841:19;;;;4834:35;;;4508:449:10;;;;;;;;;;4543:19:23;;;4508:449:10;;;4469:514;;;;;;;;-1:-1:-1;;;4347:658:10;;;5138:27:23;5181:11;;;5174:27;;;;5217:12;;;5210:28;;;;5254:12;;4347:658:10;;;-1:-1:-1;;4347:658:10;;;;;;;;;4316:707;;4347:658;4316:707;;;;4289:805;;;;;;;;;5504:25:23;5577:4;5565:17;;5545:18;;;5538:45;5599:18;;;5592:34;;;5642:18;;;5635:34;;;5476:19;;4289:805:10;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4289:805:10;;-1:-1:-1;;4289:805:10;;;-1:-1:-1;;;;;;;5117:30:10;;;;;;:59;;;5171:5;-1:-1:-1;;;;;5151:25:10;:16;-1:-1:-1;;;;;5151:25:10;;5117:59;5109:86;;;;-1:-1:-1;;;5109:86:10;;5882:2:23;5109:86:10;;;5864:21:23;5921:2;5901:18;;;5894:30;-1:-1:-1;;;5940:18:23;;;5933:44;5994:18;;5109:86:10;5680:338:23;5109:86:10;-1:-1:-1;;;;;5210:27:10;;;;;;;:9;:27;;;;;;;;:36;;;;;;;;;;;;;:44;;;5280:31;1391:25:23;;;5210:36:10;;5280:31;;;;;1364:18:23;5280:31:10;;;;;;;3835:1483;;;;;;;:::o;5507:446::-;5572:7;5669:95;5802:4;5786:22;;;;;;:::i;:::-;;;;;;;;;;5637:295;;;7520:25:23;;;;7561:18;;7554:34;;;;5830:14:10;7604:18:23;;;7597:34;5866:13:10;7647:18:23;;;7640:34;5909:4:10;7690:19:23;;;7683:61;7492:19;;5637:295:10;;;;;;;;;;;;5610:336;;;;;;5591:355;;5507:446;:::o;6147:325::-;6232:6;6217:11;;:21;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;;;6384:13:10;;;;;;:9;:13;;;;;;;;:23;;;;;;6433:32;1391:25:23;;;6433:32:10;;1364:18:23;6433:32:10;;;;;;;6147:325;;:::o;14:597:23:-;126:4;155:2;184;173:9;166:21;216:6;210:13;259:6;254:2;243:9;239:18;232:34;284:1;294:140;308:6;305:1;302:13;294:140;;;403:14;;;399:23;;393:30;369:17;;;388:2;365:26;358:66;323:10;;294:140;;;452:6;449:1;446:13;443:91;;;522:1;517:2;508:6;497:9;493:22;489:31;482:42;443:91;-1:-1:-1;595:2:23;574:15;-1:-1:-1;;570:29:23;555:45;;;;602:2;551:54;;14:597;-1:-1:-1;;;14:597:23:o;616:173::-;684:20;;-1:-1:-1;;;;;733:31:23;;723:42;;713:70;;779:1;776;769:12;713:70;616:173;;;:::o;794:254::-;862:6;870;923:2;911:9;902:7;898:23;894:32;891:52;;;939:1;936;929:12;891:52;962:29;981:9;962:29;:::i;:::-;952:39;1038:2;1023:18;;;;1010:32;;-1:-1:-1;;;794:254:23:o;1427:328::-;1504:6;1512;1520;1573:2;1561:9;1552:7;1548:23;1544:32;1541:52;;;1589:1;1586;1579:12;1541:52;1612:29;1631:9;1612:29;:::i;:::-;1602:39;;1660:38;1694:2;1683:9;1679:18;1660:38;:::i;:::-;1650:48;;1745:2;1734:9;1730:18;1717:32;1707:42;;1427:328;;;;;:::o;2131:186::-;2190:6;2243:2;2231:9;2222:7;2218:23;2214:32;2211:52;;;2259:1;2256;2249:12;2211:52;2282:29;2301:9;2282:29;:::i;:::-;2272:39;2131:186;-1:-1:-1;;;2131:186:23:o;2322:693::-;2433:6;2441;2449;2457;2465;2473;2481;2534:3;2522:9;2513:7;2509:23;2505:33;2502:53;;;2551:1;2548;2541:12;2502:53;2574:29;2593:9;2574:29;:::i;:::-;2564:39;;2622:38;2656:2;2645:9;2641:18;2622:38;:::i;:::-;2612:48;;2707:2;2696:9;2692:18;2679:32;2669:42;;2758:2;2747:9;2743:18;2730:32;2720:42;;2812:3;2801:9;2797:19;2784:33;2857:4;2850:5;2846:16;2839:5;2836:27;2826:55;;2877:1;2874;2867:12;2826:55;2322:693;;;;-1:-1:-1;2322:693:23;;;;2900:5;2952:3;2937:19;;2924:33;;-1:-1:-1;3004:3:23;2989:19;;;2976:33;;2322:693;-1:-1:-1;;2322:693:23:o;3020:260::-;3088:6;3096;3149:2;3137:9;3128:7;3124:23;3120:32;3117:52;;;3165:1;3162;3155:12;3117:52;3188:29;3207:9;3188:29;:::i;:::-;3178:39;;3236:38;3270:2;3259:9;3255:18;3236:38;:::i;:::-;3226:48;;3020:260;;;;;:::o;3285:380::-;3364:1;3360:12;;;;3407;;;3428:61;;3482:4;3474:6;3470:17;3460:27;;3428:61;3535:2;3527:6;3524:14;3504:18;3501:38;3498:161;;;3581:10;3576:3;3572:20;3569:1;3562:31;3616:4;3613:1;3606:15;3644:4;3641:1;3634:15;3498:161;;3285:380;;;:::o;3670:127::-;3731:10;3726:3;3722:20;3719:1;3712:31;3762:4;3759:1;3752:15;3786:4;3783:1;3776:15;3802:125;3842:4;3870:1;3867;3864:8;3861:34;;;3875:18;;:::i;:::-;-1:-1:-1;3912:9:23;;3802:125::o;6152:1104::-;6282:3;6311:1;6344:6;6338:13;6374:3;6396:1;6424:9;6420:2;6416:18;6406:28;;6484:2;6473:9;6469:18;6506;6496:61;;6550:4;6542:6;6538:17;6528:27;;6496:61;6576:2;6624;6616:6;6613:14;6593:18;6590:38;6587:165;;;-1:-1:-1;;;6651:33:23;;6707:4;6704:1;6697:15;6737:4;6658:3;6725:17;6587:165;6768:18;6795:104;;;;6913:1;6908:323;;;;6761:470;;6795:104;-1:-1:-1;;6828:24:23;;6816:37;;6873:16;;;;-1:-1:-1;6795:104:23;;6908:323;6099:1;6092:14;;;6136:4;6123:18;;7006:1;7020:165;7034:6;7031:1;7028:13;7020:165;;;7112:14;;7099:11;;;7092:35;7155:16;;;;7049:10;;7020:165;;;7024:3;;7214:6;7209:3;7205:16;7198:23;;6761:470;-1:-1:-1;7247:3:23;;6152:1104;-1:-1:-1;;;;;;;;6152:1104:23:o;7755:128::-;7795:3;7826:1;7822:6;7819:1;7816:13;7813:39;;;7832:18;;:::i;:::-;-1:-1:-1;7868:9:23;;7755:128::o","linkReferences":{},"immutableReferences":{"3499":[{"start":324,"length":32}],"3513":[{"start":1054,"length":32}],"3515":[{"start":1107,"length":32}]}},"methodIdentifiers":{"DOMAIN_SEPARATOR()":"3644e515","allowance(address,address)":"dd62ed3e","approve(address,uint256)":"095ea7b3","balanceOf(address)":"70a08231","decimals()":"313ce567","mint(address,uint256)":"40c10f19","name()":"06fdde03","nonces(address)":"7ecebe00","permit(address,address,uint256,uint256,uint8,bytes32,bytes32)":"d505accf","symbol()":"95d89b41","totalSupply()":"18160ddd","transfer(address,uint256)":"a9059cbb","transferFrom(address,address,uint256)":"23b872dd"}} \ No newline at end of file diff --git a/crates/cast/tests/fixtures/interface.json b/crates/cast/tests/fixtures/interface.json new file mode 100644 index 0000000000000..26163abeec0d9 --- /dev/null +++ b/crates/cast/tests/fixtures/interface.json @@ -0,0 +1 @@ +[{"type":"constructor","inputs":[{"name":"_integrationManager","type":"address","internalType":"address"},{"name":"_addressListRegistry","type":"address","internalType":"address"},{"name":"_aTokenListId","type":"uint256","internalType":"uint256"},{"name":"_pool","type":"address","internalType":"address"},{"name":"_referralCode","type":"uint16","internalType":"uint16"}],"stateMutability":"nonpayable"},{"type":"function","name":"getIntegrationManager","inputs":[],"outputs":[{"name":"integrationManager_","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"lend","inputs":[{"name":"_vaultProxy","type":"address","internalType":"address"},{"name":"","type":"bytes","internalType":"bytes"},{"name":"_assetData","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"parseAssetsForAction","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"_selector","type":"bytes4","internalType":"bytes4"},{"name":"_actionData","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"spendAssetsHandleType_","type":"uint8","internalType":"enum IIntegrationManager.SpendAssetsHandleType"},{"name":"spendAssets_","type":"address[]","internalType":"address[]"},{"name":"spendAssetAmounts_","type":"uint256[]","internalType":"uint256[]"},{"name":"incomingAssets_","type":"address[]","internalType":"address[]"},{"name":"minIncomingAssetAmounts_","type":"uint256[]","internalType":"uint256[]"}],"stateMutability":"view"},{"type":"function","name":"redeem","inputs":[{"name":"_vaultProxy","type":"address","internalType":"address"},{"name":"","type":"bytes","internalType":"bytes"},{"name":"_assetData","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"}] \ No newline at end of file diff --git a/crates/cast/tests/fixtures/sign_typed_data.json b/crates/cast/tests/fixtures/sign_typed_data.json index a6002810a6ee5..8dc45f2e74097 100644 --- a/crates/cast/tests/fixtures/sign_typed_data.json +++ b/crates/cast/tests/fixtures/sign_typed_data.json @@ -1,38 +1 @@ -{ - "types": { - "EIP712Domain": [ - { - "name": "name", - "type": "string" - }, - { - "name": "version", - "type": "string" - }, - { - "name": "chainId", - "type": "uint256" - }, - { - "name": "verifyingContract", - "type": "address" - } - ], - "Message": [ - { - "name": "data", - "type": "string" - } - ] - }, - "primaryType": "Message", - "domain": { - "name": "example.metamask.io", - "version": "1", - "chainId": "1", - "verifyingContract": "0x0000000000000000000000000000000000000000" - }, - "message": { - "data": "Hello!" - } -} \ No newline at end of file +{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Message":[{"name":"data","type":"string"}]},"primaryType":"Message","domain":{"name":"example.metamask.io","version":"1","chainId":"1","verifyingContract":"0x0000000000000000000000000000000000000000"},"message":{"data":"Hello!"}} \ No newline at end of file diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index d4b87891b3fa5..a4e9a0ad85743 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -17,15 +17,21 @@ foundry-common.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true foundry-evm-core.workspace = true +foundry-wallets.workspace = true alloy-dyn-abi.workspace = true alloy-json-abi.workspace = true alloy-primitives.workspace = true +alloy-genesis.workspace = true alloy-sol-types.workspace = true - -ethers-core.workspace = true -ethers-providers.workspace = true -ethers-signers.workspace = true +alloy-provider.workspace = true +alloy-rpc-types.workspace = true +alloy-signer.workspace = true +alloy-signer-wallet = { workspace = true, features = [ + "mnemonic-all-languages", + "keystore", +] } +parking_lot = "0.12" eyre.workspace = true hex.workspace = true @@ -33,5 +39,13 @@ itertools.workspace = true jsonpath_lib.workspace = true revm.workspace = true serde_json.workspace = true +base64.workspace = true +toml = { workspace = true, features = ["preserve_order"] } tracing.workspace = true +k256.workspace = true walkdir = "2" +p256 = "0.13.2" +thiserror = "1" +semver = "1" +rustc-hash.workspace = true +dialoguer = "0.11.0" diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index c835e46339970..39b58ff5e7fe3 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -65,6 +65,64 @@ { "name": "Resume", "description": "Synthetic access indicating the current context has resumed after a previous sub-context (AccountAccess)." + }, + { + "name": "Balance", + "description": "The account's balance was read." + }, + { + "name": "Extcodesize", + "description": "The account's codesize was read." + }, + { + "name": "Extcodehash", + "description": "The account's codehash was read." + }, + { + "name": "Extcodecopy", + "description": "The account's code was copied." + } + ] + }, + { + "name": "ForgeContext", + "description": "Forge execution contexts.", + "variants": [ + { + "name": "TestGroup", + "description": "Test group execution context (test, coverage or snapshot)." + }, + { + "name": "Test", + "description": "`forge test` execution context." + }, + { + "name": "Coverage", + "description": "`forge coverage` execution context." + }, + { + "name": "Snapshot", + "description": "`forge snapshot` execution context." + }, + { + "name": "ScriptGroup", + "description": "Script group execution context (dry run, broadcast or resume)." + }, + { + "name": "ScriptDryRun", + "description": "`forge script` execution context." + }, + { + "name": "ScriptBroadcast", + "description": "`forge script --broadcast` execution context." + }, + { + "name": "ScriptResume", + "description": "`forge script --resume` execution context." + }, + { + "name": "Unknown", + "description": "Unknown `forge` execution context." } ] } @@ -356,6 +414,11 @@ "name": "storageAccesses", "ty": "StorageAccess[]", "description": "An ordered list of storage accesses made during an account access operation." + }, + { + "name": "depth", + "ty": "uint64", + "description": "Call depth traversed during the recording of state differences" } ] }, @@ -394,9 +457,100 @@ "description": "If the access was reverted." } ] + }, + { + "name": "Gas", + "description": "Gas used. Returned by `lastCallGas`.", + "fields": [ + { + "name": "gasLimit", + "ty": "uint64", + "description": "The gas limit of the call." + }, + { + "name": "gasTotalUsed", + "ty": "uint64", + "description": "The total gas used." + }, + { + "name": "gasMemoryUsed", + "ty": "uint64", + "description": "The amount of gas used for memory expansion." + }, + { + "name": "gasRefunded", + "ty": "int64", + "description": "The amount of gas refunded." + }, + { + "name": "gasRemaining", + "ty": "uint64", + "description": "The amount of gas remaining." + } + ] } ], "cheatcodes": [ + { + "func": { + "id": "_expectCheatcodeRevert_0", + "description": "Expects an error on next cheatcode call with any revert data.", + "declaration": "function _expectCheatcodeRevert() external;", + "visibility": "external", + "mutability": "", + "signature": "_expectCheatcodeRevert()", + "selector": "0x79a4f48a", + "selectorBytes": [ + 121, + 164, + 244, + 138 + ] + }, + "group": "testing", + "status": "internal", + "safety": "unsafe" + }, + { + "func": { + "id": "_expectCheatcodeRevert_1", + "description": "Expects an error on next cheatcode call that starts with the revert data.", + "declaration": "function _expectCheatcodeRevert(bytes4 revertData) external;", + "visibility": "external", + "mutability": "", + "signature": "_expectCheatcodeRevert(bytes4)", + "selector": "0x884cb0ae", + "selectorBytes": [ + 136, + 76, + 176, + 174 + ] + }, + "group": "testing", + "status": "internal", + "safety": "unsafe" + }, + { + "func": { + "id": "_expectCheatcodeRevert_2", + "description": "Expects an error on next cheatcode call that exactly matches the revert data.", + "declaration": "function _expectCheatcodeRevert(bytes calldata revertData) external;", + "visibility": "external", + "mutability": "", + "signature": "_expectCheatcodeRevert(bytes)", + "selector": "0x7843b44d", + "selectorBytes": [ + 120, + 67, + 180, + 77 + ] + }, + "group": "testing", + "status": "internal", + "safety": "unsafe" + }, { "func": { "id": "accesses", @@ -479,18 +633,18 @@ }, { "func": { - "id": "assume", - "description": "If the condition is false, discard this run's fuzz inputs and generate new ones.", - "declaration": "function assume(bool condition) external pure;", + "id": "assertApproxEqAbsDecimal_0", + "description": "Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.\nFormats values with decimals in failure message.", + "declaration": "function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", - "signature": "assume(bool)", - "selector": "0x4c63e562", + "signature": "assertApproxEqAbsDecimal(uint256,uint256,uint256,uint256)", + "selector": "0x045c55ce", "selectorBytes": [ - 76, - 99, - 229, - 98 + 4, + 92, + 85, + 206 ] }, "group": "testing", @@ -499,18 +653,18 @@ }, { "func": { - "id": "breakpoint_0", - "description": "Writes a breakpoint to jump to in the debugger.", - "declaration": "function breakpoint(string calldata char) external;", + "id": "assertApproxEqAbsDecimal_1", + "description": "Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "breakpoint(string)", - "selector": "0xf0259e92", + "mutability": "pure", + "signature": "assertApproxEqAbsDecimal(uint256,uint256,uint256,uint256,string)", + "selector": "0x60429eb2", "selectorBytes": [ - 240, - 37, + 96, + 66, 158, - 146 + 178 ] }, "group": "testing", @@ -519,18 +673,18 @@ }, { "func": { - "id": "breakpoint_1", - "description": "Writes a conditional breakpoint to jump to in the debugger.", - "declaration": "function breakpoint(string calldata char, bool value) external;", + "id": "assertApproxEqAbsDecimal_2", + "description": "Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.\nFormats values with decimals in failure message.", + "declaration": "function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals) external pure;", "visibility": "external", - "mutability": "", - "signature": "breakpoint(string,bool)", - "selector": "0xf7d39a8d", + "mutability": "pure", + "signature": "assertApproxEqAbsDecimal(int256,int256,uint256,uint256)", + "selector": "0x3d5bc8bc", "selectorBytes": [ - 247, - 211, - 154, - 141 + 61, + 91, + 200, + 188 ] }, "group": "testing", @@ -539,2501 +693,5461 @@ }, { "func": { - "id": "broadcast_0", - "description": "Using the address that calls the test contract, has the next call (at this call depth only)\ncreate a transaction that can later be signed and sent onchain.", - "declaration": "function broadcast() external;", + "id": "assertApproxEqAbsDecimal_3", + "description": "Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "broadcast()", - "selector": "0xafc98040", + "mutability": "pure", + "signature": "assertApproxEqAbsDecimal(int256,int256,uint256,uint256,string)", + "selector": "0x6a5066d4", "selectorBytes": [ - 175, - 201, - 128, - 64 + 106, + 80, + 102, + 212 ] }, - "group": "scripting", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "broadcast_1", - "description": "Has the next call (at this call depth only) create a transaction with the address provided\nas the sender that can later be signed and sent onchain.", - "declaration": "function broadcast(address signer) external;", + "id": "assertApproxEqAbs_0", + "description": "Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.", + "declaration": "function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta) external pure;", "visibility": "external", - "mutability": "", - "signature": "broadcast(address)", - "selector": "0xe6962cdb", + "mutability": "pure", + "signature": "assertApproxEqAbs(uint256,uint256,uint256)", + "selector": "0x16d207c6", "selectorBytes": [ - 230, - 150, - 44, - 219 + 22, + 210, + 7, + 198 ] }, - "group": "scripting", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "broadcast_2", - "description": "Has the next call (at this call depth only) create a transaction with the private key\nprovided as the sender that can later be signed and sent onchain.", - "declaration": "function broadcast(uint256 privateKey) external;", + "id": "assertApproxEqAbs_1", + "description": "Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.\nIncludes error message into revert string on failure.", + "declaration": "function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "broadcast(uint256)", - "selector": "0xf67a965b", + "mutability": "pure", + "signature": "assertApproxEqAbs(uint256,uint256,uint256,string)", + "selector": "0xf710b062", "selectorBytes": [ - 246, - 122, - 150, - 91 + 247, + 16, + 176, + 98 ] }, - "group": "scripting", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "chainId", - "description": "Sets `block.chainid`.", - "declaration": "function chainId(uint256 newChainId) external;", + "id": "assertApproxEqAbs_2", + "description": "Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.", + "declaration": "function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta) external pure;", "visibility": "external", - "mutability": "", - "signature": "chainId(uint256)", - "selector": "0x4049ddd2", + "mutability": "pure", + "signature": "assertApproxEqAbs(int256,int256,uint256)", + "selector": "0x240f839d", "selectorBytes": [ - 64, - 73, - 221, - 210 + 36, + 15, + 131, + 157 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "clearMockedCalls", - "description": "Clears all mocked calls.", - "declaration": "function clearMockedCalls() external;", + "id": "assertApproxEqAbs_3", + "description": "Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.\nIncludes error message into revert string on failure.", + "declaration": "function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "clearMockedCalls()", - "selector": "0x3fdf4e15", + "mutability": "pure", + "signature": "assertApproxEqAbs(int256,int256,uint256,string)", + "selector": "0x8289e621", "selectorBytes": [ - 63, - 223, - 78, - 21 + 130, + 137, + 230, + 33 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "closeFile", - "description": "Closes file for reading, resetting the offset and allowing to read it from beginning with readLine.\n`path` is relative to the project root.", - "declaration": "function closeFile(string calldata path) external;", + "id": "assertApproxEqRelDecimal_0", + "description": "Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nFormats values with decimals in failure message.", + "declaration": "function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals) external pure;", "visibility": "external", - "mutability": "", - "signature": "closeFile(string)", - "selector": "0x48c3241f", + "mutability": "pure", + "signature": "assertApproxEqRelDecimal(uint256,uint256,uint256,uint256)", + "selector": "0x21ed2977", "selectorBytes": [ - 72, - 195, - 36, - 31 + 33, + 237, + 41, + 119 ] }, - "group": "filesystem", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "coinbase", - "description": "Sets `block.coinbase`.", - "declaration": "function coinbase(address newCoinbase) external;", + "id": "assertApproxEqRelDecimal_1", + "description": "Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "coinbase(address)", - "selector": "0xff483c54", + "mutability": "pure", + "signature": "assertApproxEqRelDecimal(uint256,uint256,uint256,uint256,string)", + "selector": "0x82d6c8fd", "selectorBytes": [ - 255, - 72, - 60, - 84 + 130, + 214, + 200, + 253 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "computeCreate2Address_0", - "description": "Compute the address of a contract created with CREATE2 using the given CREATE2 deployer.", - "declaration": "function computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer) external pure returns (address);", + "id": "assertApproxEqRelDecimal_2", + "description": "Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nFormats values with decimals in failure message.", + "declaration": "function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals) external pure;", "visibility": "external", "mutability": "pure", - "signature": "computeCreate2Address(bytes32,bytes32,address)", - "selector": "0xd323826a", + "signature": "assertApproxEqRelDecimal(int256,int256,uint256,uint256)", + "selector": "0xabbf21cc", "selectorBytes": [ - 211, - 35, - 130, - 106 + 171, + 191, + 33, + 204 ] }, - "group": "utilities", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "computeCreate2Address_1", - "description": "Compute the address of a contract created with CREATE2 using the default CREATE2 deployer.", - "declaration": "function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external pure returns (address);", + "id": "assertApproxEqRelDecimal_3", + "description": "Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure;", "visibility": "external", "mutability": "pure", - "signature": "computeCreate2Address(bytes32,bytes32)", - "selector": "0x890c283b", + "signature": "assertApproxEqRelDecimal(int256,int256,uint256,uint256,string)", + "selector": "0xfccc11c4", "selectorBytes": [ - 137, - 12, - 40, - 59 + 252, + 204, + 17, + 196 ] }, - "group": "utilities", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "computeCreateAddress", - "description": "Compute the address a contract will be deployed at for a given deployer address and nonce.", - "declaration": "function computeCreateAddress(address deployer, uint256 nonce) external pure returns (address);", + "id": "assertApproxEqRel_0", + "description": "Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%", + "declaration": "function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta) external pure;", "visibility": "external", "mutability": "pure", - "signature": "computeCreateAddress(address,uint256)", - "selector": "0x74637a7a", + "signature": "assertApproxEqRel(uint256,uint256,uint256)", + "selector": "0x8cf25ef4", "selectorBytes": [ - 116, - 99, - 122, - 122 + 140, + 242, + 94, + 244 ] }, - "group": "utilities", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "cool", - "description": "Marks the slots of an account and the account address as cold.", - "declaration": "function cool(address target) external;", + "id": "assertApproxEqRel_1", + "description": "Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nIncludes error message into revert string on failure.", + "declaration": "function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "cool(address)", - "selector": "0x40ff9f21", + "mutability": "pure", + "signature": "assertApproxEqRel(uint256,uint256,uint256,string)", + "selector": "0x1ecb7d33", "selectorBytes": [ - 64, - 255, - 159, - 33 + 30, + 203, + 125, + 51 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "copyFile", - "description": "Copies the contents of one file to another. This function will **overwrite** the contents of `to`.\nOn success, the total number of bytes copied is returned and it is equal to the length of the `to` file as reported by `metadata`.\nBoth `from` and `to` are relative to the project root.", - "declaration": "function copyFile(string calldata from, string calldata to) external returns (uint64 copied);", + "id": "assertApproxEqRel_2", + "description": "Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%", + "declaration": "function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta) external pure;", "visibility": "external", - "mutability": "", - "signature": "copyFile(string,string)", - "selector": "0xa54a87d8", + "mutability": "pure", + "signature": "assertApproxEqRel(int256,int256,uint256)", + "selector": "0xfea2d14f", "selectorBytes": [ - 165, - 74, - 135, - 216 + 254, + 162, + 209, + 79 ] }, - "group": "filesystem", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "createDir", - "description": "Creates a new, empty directory at the provided path.\nThis cheatcode will revert in the following situations, but is not limited to just these cases:\n- User lacks permissions to modify `path`.\n- A parent of the given path doesn't exist and `recursive` is false.\n- `path` already exists and `recursive` is false.\n`path` is relative to the project root.", - "declaration": "function createDir(string calldata path, bool recursive) external;", + "id": "assertApproxEqRel_3", + "description": "Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nIncludes error message into revert string on failure.", + "declaration": "function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "createDir(string,bool)", - "selector": "0x168b64d3", + "mutability": "pure", + "signature": "assertApproxEqRel(int256,int256,uint256,string)", + "selector": "0xef277d72", "selectorBytes": [ - 22, - 139, - 100, - 211 + 239, + 39, + 125, + 114 ] }, - "group": "filesystem", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "createFork_0", - "description": "Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork.", - "declaration": "function createFork(string calldata urlOrAlias) external returns (uint256 forkId);", + "id": "assertEqDecimal_0", + "description": "Asserts that two `uint256` values are equal, formatting them with decimals in failure message.", + "declaration": "function assertEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", "visibility": "external", - "mutability": "", - "signature": "createFork(string)", - "selector": "0x31ba3498", + "mutability": "pure", + "signature": "assertEqDecimal(uint256,uint256,uint256)", + "selector": "0x27af7d9c", "selectorBytes": [ - 49, - 186, - 52, - 152 + 39, + 175, + 125, + 156 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "createFork_1", - "description": "Creates a new fork with the given endpoint and block and returns the identifier of the fork.", - "declaration": "function createFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId);", + "id": "assertEqDecimal_1", + "description": "Asserts that two `uint256` values are equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", + "declaration": "function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "createFork(string,uint256)", - "selector": "0x6ba3ba2b", + "mutability": "pure", + "signature": "assertEqDecimal(uint256,uint256,uint256,string)", + "selector": "0xd0cbbdef", "selectorBytes": [ - 107, - 163, - 186, - 43 + 208, + 203, + 189, + 239 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "createFork_2", - "description": "Creates a new fork with the given endpoint and at the block the given transaction was mined in,\nreplays all transaction mined in the block before the transaction, and returns the identifier of the fork.", - "declaration": "function createFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId);", + "id": "assertEqDecimal_2", + "description": "Asserts that two `int256` values are equal, formatting them with decimals in failure message.", + "declaration": "function assertEqDecimal(int256 left, int256 right, uint256 decimals) external pure;", "visibility": "external", - "mutability": "", - "signature": "createFork(string,bytes32)", - "selector": "0x7ca29682", + "mutability": "pure", + "signature": "assertEqDecimal(int256,int256,uint256)", + "selector": "0x48016c04", "selectorBytes": [ - 124, - 162, - 150, - 130 + 72, + 1, + 108, + 4 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "createSelectFork_0", - "description": "Creates and also selects a new fork with the given endpoint and the latest block and returns the identifier of the fork.", - "declaration": "function createSelectFork(string calldata urlOrAlias) external returns (uint256 forkId);", + "id": "assertEqDecimal_3", + "description": "Asserts that two `int256` values are equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", + "declaration": "function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "createSelectFork(string)", - "selector": "0x98680034", + "mutability": "pure", + "signature": "assertEqDecimal(int256,int256,uint256,string)", + "selector": "0x7e77b0c5", "selectorBytes": [ - 152, - 104, - 0, - 52 + 126, + 119, + 176, + 197 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "createSelectFork_1", - "description": "Creates and also selects a new fork with the given endpoint and block and returns the identifier of the fork.", - "declaration": "function createSelectFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId);", + "id": "assertEq_0", + "description": "Asserts that two `bool` values are equal.", + "declaration": "function assertEq(bool left, bool right) external pure;", "visibility": "external", - "mutability": "", - "signature": "createSelectFork(string,uint256)", - "selector": "0x71ee464d", + "mutability": "pure", + "signature": "assertEq(bool,bool)", + "selector": "0xf7fe3477", "selectorBytes": [ - 113, - 238, - 70, - 77 + 247, + 254, + 52, + 119 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "createSelectFork_2", - "description": "Creates and also selects new fork with the given endpoint and at the block the given transaction was mined in,\nreplays all transaction mined in the block before the transaction, returns the identifier of the fork.", - "declaration": "function createSelectFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId);", + "id": "assertEq_1", + "description": "Asserts that two `bool` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(bool left, bool right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "createSelectFork(string,bytes32)", - "selector": "0x84d52b7a", + "mutability": "pure", + "signature": "assertEq(bool,bool,string)", + "selector": "0x4db19e7e", "selectorBytes": [ - 132, - 213, - 43, - 122 + 77, + 177, + 158, + 126 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "createWallet_0", - "description": "Derives a private key from the name, labels the account with that name, and returns the wallet.", - "declaration": "function createWallet(string calldata walletLabel) external returns (Wallet memory wallet);", + "id": "assertEq_10", + "description": "Asserts that two `string` values are equal.", + "declaration": "function assertEq(string calldata left, string calldata right) external pure;", "visibility": "external", - "mutability": "", - "signature": "createWallet(string)", - "selector": "0x7404f1d2", + "mutability": "pure", + "signature": "assertEq(string,string)", + "selector": "0xf320d963", "selectorBytes": [ - 116, - 4, - 241, - 210 + 243, + 32, + 217, + 99 ] }, - "group": "utilities", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "createWallet_1", - "description": "Generates a wallet from the private key and returns the wallet.", - "declaration": "function createWallet(uint256 privateKey) external returns (Wallet memory wallet);", + "id": "assertEq_11", + "description": "Asserts that two `string` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(string calldata left, string calldata right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "createWallet(uint256)", - "selector": "0x7a675bb6", + "mutability": "pure", + "signature": "assertEq(string,string,string)", + "selector": "0x36f656d8", "selectorBytes": [ - 122, - 103, - 91, - 182 + 54, + 246, + 86, + 216 ] }, - "group": "utilities", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "createWallet_2", - "description": "Generates a wallet from the private key, labels the account with that name, and returns the wallet.", - "declaration": "function createWallet(uint256 privateKey, string calldata walletLabel) external returns (Wallet memory wallet);", + "id": "assertEq_12", + "description": "Asserts that two `bytes` values are equal.", + "declaration": "function assertEq(bytes calldata left, bytes calldata right) external pure;", "visibility": "external", - "mutability": "", - "signature": "createWallet(uint256,string)", - "selector": "0xed7c5462", + "mutability": "pure", + "signature": "assertEq(bytes,bytes)", + "selector": "0x97624631", "selectorBytes": [ - 237, - 124, - 84, - 98 + 151, + 98, + 70, + 49 ] }, - "group": "utilities", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "deal", - "description": "Sets an address' balance.", - "declaration": "function deal(address account, uint256 newBalance) external;", + "id": "assertEq_13", + "description": "Asserts that two `bytes` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(bytes calldata left, bytes calldata right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "deal(address,uint256)", - "selector": "0xc88a5e6d", + "mutability": "pure", + "signature": "assertEq(bytes,bytes,string)", + "selector": "0xe24fed00", "selectorBytes": [ - 200, - 138, - 94, - 109 + 226, + 79, + 237, + 0 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "deriveKey_0", - "description": "Derive a private key from a provided mnenomic string (or mnenomic file path)\nat the derivation path `m/44'/60'/0'/0/{index}`.", - "declaration": "function deriveKey(string calldata mnemonic, uint32 index) external pure returns (uint256 privateKey);", + "id": "assertEq_14", + "description": "Asserts that two arrays of `bool` values are equal.", + "declaration": "function assertEq(bool[] calldata left, bool[] calldata right) external pure;", "visibility": "external", "mutability": "pure", - "signature": "deriveKey(string,uint32)", - "selector": "0x6229498b", + "signature": "assertEq(bool[],bool[])", + "selector": "0x707df785", "selectorBytes": [ - 98, - 41, - 73, - 139 + 112, + 125, + 247, + 133 ] }, - "group": "utilities", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "deriveKey_1", - "description": "Derive a private key from a provided mnenomic string (or mnenomic file path)\nat `{derivationPath}{index}`.", - "declaration": "function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index) external pure returns (uint256 privateKey);", + "id": "assertEq_15", + "description": "Asserts that two arrays of `bool` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", - "signature": "deriveKey(string,string,uint32)", - "selector": "0x6bcb2c1b", + "signature": "assertEq(bool[],bool[],string)", + "selector": "0xe48a8f8d", "selectorBytes": [ - 107, - 203, - 44, - 27 + 228, + 138, + 143, + 141 ] }, - "group": "utilities", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "deriveKey_2", - "description": "Derive a private key from a provided mnenomic string (or mnenomic file path) in the specified language\nat the derivation path `m/44'/60'/0'/0/{index}`.", - "declaration": "function deriveKey(string calldata mnemonic, uint32 index, string calldata language) external pure returns (uint256 privateKey);", + "id": "assertEq_16", + "description": "Asserts that two arrays of `uint256 values are equal.", + "declaration": "function assertEq(uint256[] calldata left, uint256[] calldata right) external pure;", "visibility": "external", "mutability": "pure", - "signature": "deriveKey(string,uint32,string)", - "selector": "0x32c8176d", + "signature": "assertEq(uint256[],uint256[])", + "selector": "0x975d5a12", "selectorBytes": [ - 50, - 200, - 23, - 109 + 151, + 93, + 90, + 18 ] }, - "group": "utilities", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "deriveKey_3", - "description": "Derive a private key from a provided mnenomic string (or mnenomic file path) in the specified language\nat `{derivationPath}{index}`.", - "declaration": "function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index, string calldata language) external pure returns (uint256 privateKey);", + "id": "assertEq_17", + "description": "Asserts that two arrays of `uint256` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", - "signature": "deriveKey(string,string,uint32,string)", - "selector": "0x29233b1f", + "signature": "assertEq(uint256[],uint256[],string)", + "selector": "0x5d18c73a", "selectorBytes": [ - 41, - 35, - 59, - 31 + 93, + 24, + 199, + 58 ] }, - "group": "utilities", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "difficulty", - "description": "Sets `block.difficulty`.\nNot available on EVM versions from Paris onwards. Use `prevrandao` instead.\nReverts if used on unsupported EVM versions.", - "declaration": "function difficulty(uint256 newDifficulty) external;", + "id": "assertEq_18", + "description": "Asserts that two arrays of `int256` values are equal.", + "declaration": "function assertEq(int256[] calldata left, int256[] calldata right) external pure;", "visibility": "external", - "mutability": "", - "signature": "difficulty(uint256)", - "selector": "0x46cc92d9", + "mutability": "pure", + "signature": "assertEq(int256[],int256[])", + "selector": "0x711043ac", "selectorBytes": [ - 70, - 204, - 146, - 217 + 113, + 16, + 67, + 172 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "envAddress_0", - "description": "Gets the environment variable `name` and parses it as `address`.\nReverts if the variable was not found or could not be parsed.", - "declaration": "function envAddress(string calldata name) external view returns (address value);", + "id": "assertEq_19", + "description": "Asserts that two arrays of `int256` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure;", "visibility": "external", - "mutability": "view", - "signature": "envAddress(string)", - "selector": "0x350d56bf", + "mutability": "pure", + "signature": "assertEq(int256[],int256[],string)", + "selector": "0x191f1b30", "selectorBytes": [ - 53, - 13, - 86, - 191 + 25, + 31, + 27, + 48 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envAddress_1", - "description": "Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", - "declaration": "function envAddress(string calldata name, string calldata delim) external view returns (address[] memory value);", + "id": "assertEq_2", + "description": "Asserts that two `uint256` values are equal.", + "declaration": "function assertEq(uint256 left, uint256 right) external pure;", "visibility": "external", - "mutability": "view", - "signature": "envAddress(string,string)", - "selector": "0xad31b9fa", + "mutability": "pure", + "signature": "assertEq(uint256,uint256)", + "selector": "0x98296c54", "selectorBytes": [ - 173, - 49, - 185, - 250 + 152, + 41, + 108, + 84 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envBool_0", - "description": "Gets the environment variable `name` and parses it as `bool`.\nReverts if the variable was not found or could not be parsed.", - "declaration": "function envBool(string calldata name) external view returns (bool value);", + "id": "assertEq_20", + "description": "Asserts that two arrays of `address` values are equal.", + "declaration": "function assertEq(address[] calldata left, address[] calldata right) external pure;", "visibility": "external", - "mutability": "view", - "signature": "envBool(string)", - "selector": "0x7ed1ec7d", + "mutability": "pure", + "signature": "assertEq(address[],address[])", + "selector": "0x3868ac34", "selectorBytes": [ - 126, - 209, - 236, - 125 + 56, + 104, + 172, + 52 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envBool_1", - "description": "Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", - "declaration": "function envBool(string calldata name, string calldata delim) external view returns (bool[] memory value);", + "id": "assertEq_21", + "description": "Asserts that two arrays of `address` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(address[] calldata left, address[] calldata right, string calldata error) external pure;", "visibility": "external", - "mutability": "view", - "signature": "envBool(string,string)", - "selector": "0xaaaddeaf", + "mutability": "pure", + "signature": "assertEq(address[],address[],string)", + "selector": "0x3e9173c5", "selectorBytes": [ - 170, - 173, - 222, - 175 + 62, + 145, + 115, + 197 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envBytes32_0", - "description": "Gets the environment variable `name` and parses it as `bytes32`.\nReverts if the variable was not found or could not be parsed.", - "declaration": "function envBytes32(string calldata name) external view returns (bytes32 value);", + "id": "assertEq_22", + "description": "Asserts that two arrays of `bytes32` values are equal.", + "declaration": "function assertEq(bytes32[] calldata left, bytes32[] calldata right) external pure;", "visibility": "external", - "mutability": "view", - "signature": "envBytes32(string)", - "selector": "0x97949042", + "mutability": "pure", + "signature": "assertEq(bytes32[],bytes32[])", + "selector": "0x0cc9ee84", "selectorBytes": [ - 151, - 148, - 144, - 66 + 12, + 201, + 238, + 132 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envBytes32_1", - "description": "Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", - "declaration": "function envBytes32(string calldata name, string calldata delim) external view returns (bytes32[] memory value);", + "id": "assertEq_23", + "description": "Asserts that two arrays of `bytes32` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure;", "visibility": "external", - "mutability": "view", - "signature": "envBytes32(string,string)", - "selector": "0x5af231c1", + "mutability": "pure", + "signature": "assertEq(bytes32[],bytes32[],string)", + "selector": "0xe03e9177", "selectorBytes": [ - 90, - 242, - 49, - 193 + 224, + 62, + 145, + 119 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envBytes_0", - "description": "Gets the environment variable `name` and parses it as `bytes`.\nReverts if the variable was not found or could not be parsed.", - "declaration": "function envBytes(string calldata name) external view returns (bytes memory value);", + "id": "assertEq_24", + "description": "Asserts that two arrays of `string` values are equal.", + "declaration": "function assertEq(string[] calldata left, string[] calldata right) external pure;", "visibility": "external", - "mutability": "view", - "signature": "envBytes(string)", - "selector": "0x4d7baf06", + "mutability": "pure", + "signature": "assertEq(string[],string[])", + "selector": "0xcf1c049c", "selectorBytes": [ - 77, - 123, - 175, - 6 + 207, + 28, + 4, + 156 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envBytes_1", - "description": "Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", - "declaration": "function envBytes(string calldata name, string calldata delim) external view returns (bytes[] memory value);", + "id": "assertEq_25", + "description": "Asserts that two arrays of `string` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(string[] calldata left, string[] calldata right, string calldata error) external pure;", "visibility": "external", - "mutability": "view", - "signature": "envBytes(string,string)", - "selector": "0xddc2651b", + "mutability": "pure", + "signature": "assertEq(string[],string[],string)", + "selector": "0xeff6b27d", "selectorBytes": [ - 221, - 194, - 101, - 27 + 239, + 246, + 178, + 125 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envInt_0", - "description": "Gets the environment variable `name` and parses it as `int256`.\nReverts if the variable was not found or could not be parsed.", - "declaration": "function envInt(string calldata name) external view returns (int256 value);", + "id": "assertEq_26", + "description": "Asserts that two arrays of `bytes` values are equal.", + "declaration": "function assertEq(bytes[] calldata left, bytes[] calldata right) external pure;", "visibility": "external", - "mutability": "view", - "signature": "envInt(string)", - "selector": "0x892a0c61", + "mutability": "pure", + "signature": "assertEq(bytes[],bytes[])", + "selector": "0xe5fb9b4a", "selectorBytes": [ - 137, - 42, - 12, - 97 + 229, + 251, + 155, + 74 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envInt_1", - "description": "Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", - "declaration": "function envInt(string calldata name, string calldata delim) external view returns (int256[] memory value);", + "id": "assertEq_27", + "description": "Asserts that two arrays of `bytes` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure;", "visibility": "external", - "mutability": "view", - "signature": "envInt(string,string)", - "selector": "0x42181150", + "mutability": "pure", + "signature": "assertEq(bytes[],bytes[],string)", + "selector": "0xf413f0b6", "selectorBytes": [ - 66, - 24, - 17, - 80 + 244, + 19, + 240, + 182 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envOr_0", - "description": "Gets the environment variable `name` and parses it as `bool`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", - "declaration": "function envOr(string calldata name, bool defaultValue) external returns (bool value);", + "id": "assertEq_3", + "description": "Asserts that two `uint256` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(uint256 left, uint256 right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "envOr(string,bool)", - "selector": "0x4777f3cf", + "mutability": "pure", + "signature": "assertEq(uint256,uint256,string)", + "selector": "0x88b44c85", "selectorBytes": [ - 71, - 119, - 243, - 207 + 136, + 180, + 76, + 133 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envOr_1", - "description": "Gets the environment variable `name` and parses it as `uint256`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", - "declaration": "function envOr(string calldata name, uint256 defaultValue) external returns (uint256 value);", + "id": "assertEq_4", + "description": "Asserts that two `int256` values are equal.", + "declaration": "function assertEq(int256 left, int256 right) external pure;", "visibility": "external", - "mutability": "", - "signature": "envOr(string,uint256)", - "selector": "0x5e97348f", + "mutability": "pure", + "signature": "assertEq(int256,int256)", + "selector": "0xfe74f05b", "selectorBytes": [ - 94, - 151, - 52, - 143 + 254, + 116, + 240, + 91 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envOr_10", - "description": "Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", - "declaration": "function envOr(string calldata name, string calldata delim, address[] calldata defaultValue) external returns (address[] memory value);", + "id": "assertEq_5", + "description": "Asserts that two `int256` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(int256 left, int256 right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "envOr(string,string,address[])", - "selector": "0xc74e9deb", + "mutability": "pure", + "signature": "assertEq(int256,int256,string)", + "selector": "0x714a2f13", "selectorBytes": [ - 199, - 78, - 157, - 235 + 113, + 74, + 47, + 19 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envOr_11", - "description": "Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", - "declaration": "function envOr(string calldata name, string calldata delim, bytes32[] calldata defaultValue) external returns (bytes32[] memory value);", + "id": "assertEq_6", + "description": "Asserts that two `address` values are equal.", + "declaration": "function assertEq(address left, address right) external pure;", "visibility": "external", - "mutability": "", - "signature": "envOr(string,string,bytes32[])", - "selector": "0x2281f367", + "mutability": "pure", + "signature": "assertEq(address,address)", + "selector": "0x515361f6", "selectorBytes": [ - 34, - 129, - 243, - 103 + 81, + 83, + 97, + 246 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envOr_12", - "description": "Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", - "declaration": "function envOr(string calldata name, string calldata delim, string[] calldata defaultValue) external returns (string[] memory value);", + "id": "assertEq_7", + "description": "Asserts that two `address` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(address left, address right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "envOr(string,string,string[])", - "selector": "0x859216bc", + "mutability": "pure", + "signature": "assertEq(address,address,string)", + "selector": "0x2f2769d1", "selectorBytes": [ - 133, - 146, - 22, - 188 + 47, + 39, + 105, + 209 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envOr_13", - "description": "Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", - "declaration": "function envOr(string calldata name, string calldata delim, bytes[] calldata defaultValue) external returns (bytes[] memory value);", + "id": "assertEq_8", + "description": "Asserts that two `bytes32` values are equal.", + "declaration": "function assertEq(bytes32 left, bytes32 right) external pure;", "visibility": "external", - "mutability": "", - "signature": "envOr(string,string,bytes[])", - "selector": "0x64bc3e64", + "mutability": "pure", + "signature": "assertEq(bytes32,bytes32)", + "selector": "0x7c84c69b", "selectorBytes": [ - 100, - 188, - 62, - 100 + 124, + 132, + 198, + 155 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envOr_2", - "description": "Gets the environment variable `name` and parses it as `int256`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", - "declaration": "function envOr(string calldata name, int256 defaultValue) external returns (int256 value);", + "id": "assertEq_9", + "description": "Asserts that two `bytes32` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(bytes32 left, bytes32 right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "envOr(string,int256)", - "selector": "0xbbcb713e", + "mutability": "pure", + "signature": "assertEq(bytes32,bytes32,string)", + "selector": "0xc1fa1ed0", "selectorBytes": [ - 187, - 203, - 113, - 62 + 193, + 250, + 30, + 208 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envOr_3", - "description": "Gets the environment variable `name` and parses it as `address`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", - "declaration": "function envOr(string calldata name, address defaultValue) external returns (address value);", + "id": "assertFalse_0", + "description": "Asserts that the given condition is false.", + "declaration": "function assertFalse(bool condition) external pure;", "visibility": "external", - "mutability": "", - "signature": "envOr(string,address)", - "selector": "0x561fe540", + "mutability": "pure", + "signature": "assertFalse(bool)", + "selector": "0xa5982885", "selectorBytes": [ - 86, - 31, - 229, - 64 + 165, + 152, + 40, + 133 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envOr_4", - "description": "Gets the environment variable `name` and parses it as `bytes32`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", - "declaration": "function envOr(string calldata name, bytes32 defaultValue) external returns (bytes32 value);", + "id": "assertFalse_1", + "description": "Asserts that the given condition is false and includes error message into revert string on failure.", + "declaration": "function assertFalse(bool condition, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "envOr(string,bytes32)", - "selector": "0xb4a85892", + "mutability": "pure", + "signature": "assertFalse(bool,string)", + "selector": "0x7ba04809", "selectorBytes": [ - 180, - 168, - 88, - 146 + 123, + 160, + 72, + 9 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envOr_5", - "description": "Gets the environment variable `name` and parses it as `string`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", - "declaration": "function envOr(string calldata name, string calldata defaultValue) external returns (string memory value);", + "id": "assertGeDecimal_0", + "description": "Compares two `uint256` values. Expects first value to be greater than or equal to second.\nFormats values with decimals in failure message.", + "declaration": "function assertGeDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", "visibility": "external", - "mutability": "", - "signature": "envOr(string,string)", - "selector": "0xd145736c", + "mutability": "pure", + "signature": "assertGeDecimal(uint256,uint256,uint256)", + "selector": "0x3d1fe08a", "selectorBytes": [ - 209, - 69, - 115, - 108 + 61, + 31, + 224, + 138 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envOr_6", - "description": "Gets the environment variable `name` and parses it as `bytes`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", - "declaration": "function envOr(string calldata name, bytes calldata defaultValue) external returns (bytes memory value);", + "id": "assertGeDecimal_1", + "description": "Compares two `uint256` values. Expects first value to be greater than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "envOr(string,bytes)", - "selector": "0xb3e47705", + "mutability": "pure", + "signature": "assertGeDecimal(uint256,uint256,uint256,string)", + "selector": "0x8bff9133", "selectorBytes": [ - 179, - 228, - 119, - 5 + 139, + 255, + 145, + 51 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envOr_7", - "description": "Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", - "declaration": "function envOr(string calldata name, string calldata delim, bool[] calldata defaultValue) external returns (bool[] memory value);", + "id": "assertGeDecimal_2", + "description": "Compares two `int256` values. Expects first value to be greater than or equal to second.\nFormats values with decimals in failure message.", + "declaration": "function assertGeDecimal(int256 left, int256 right, uint256 decimals) external pure;", "visibility": "external", - "mutability": "", - "signature": "envOr(string,string,bool[])", - "selector": "0xeb85e83b", + "mutability": "pure", + "signature": "assertGeDecimal(int256,int256,uint256)", + "selector": "0xdc28c0f1", "selectorBytes": [ - 235, - 133, - 232, - 59 + 220, + 40, + 192, + 241 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envOr_8", - "description": "Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", - "declaration": "function envOr(string calldata name, string calldata delim, uint256[] calldata defaultValue) external returns (uint256[] memory value);", + "id": "assertGeDecimal_3", + "description": "Compares two `int256` values. Expects first value to be greater than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "envOr(string,string,uint256[])", - "selector": "0x74318528", + "mutability": "pure", + "signature": "assertGeDecimal(int256,int256,uint256,string)", + "selector": "0x5df93c9b", "selectorBytes": [ - 116, - 49, - 133, - 40 + 93, + 249, + 60, + 155 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envOr_9", - "description": "Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", - "declaration": "function envOr(string calldata name, string calldata delim, int256[] calldata defaultValue) external returns (int256[] memory value);", + "id": "assertGe_0", + "description": "Compares two `uint256` values. Expects first value to be greater than or equal to second.", + "declaration": "function assertGe(uint256 left, uint256 right) external pure;", "visibility": "external", - "mutability": "", - "signature": "envOr(string,string,int256[])", - "selector": "0x4700d74b", + "mutability": "pure", + "signature": "assertGe(uint256,uint256)", + "selector": "0xa8d4d1d9", "selectorBytes": [ - 71, - 0, - 215, - 75 + 168, + 212, + 209, + 217 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envString_0", - "description": "Gets the environment variable `name` and parses it as `string`.\nReverts if the variable was not found or could not be parsed.", - "declaration": "function envString(string calldata name) external view returns (string memory value);", + "id": "assertGe_1", + "description": "Compares two `uint256` values. Expects first value to be greater than or equal to second.\nIncludes error message into revert string on failure.", + "declaration": "function assertGe(uint256 left, uint256 right, string calldata error) external pure;", "visibility": "external", - "mutability": "view", - "signature": "envString(string)", - "selector": "0xf877cb19", + "mutability": "pure", + "signature": "assertGe(uint256,uint256,string)", + "selector": "0xe25242c0", "selectorBytes": [ - 248, - 119, - 203, - 25 + 226, + 82, + 66, + 192 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envString_1", - "description": "Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", - "declaration": "function envString(string calldata name, string calldata delim) external view returns (string[] memory value);", + "id": "assertGe_2", + "description": "Compares two `int256` values. Expects first value to be greater than or equal to second.", + "declaration": "function assertGe(int256 left, int256 right) external pure;", "visibility": "external", - "mutability": "view", - "signature": "envString(string,string)", - "selector": "0x14b02bc9", + "mutability": "pure", + "signature": "assertGe(int256,int256)", + "selector": "0x0a30b771", "selectorBytes": [ - 20, - 176, - 43, - 201 + 10, + 48, + 183, + 113 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envUint_0", - "description": "Gets the environment variable `name` and parses it as `uint256`.\nReverts if the variable was not found or could not be parsed.", - "declaration": "function envUint(string calldata name) external view returns (uint256 value);", + "id": "assertGe_3", + "description": "Compares two `int256` values. Expects first value to be greater than or equal to second.\nIncludes error message into revert string on failure.", + "declaration": "function assertGe(int256 left, int256 right, string calldata error) external pure;", "visibility": "external", - "mutability": "view", - "signature": "envUint(string)", - "selector": "0xc1978d1f", + "mutability": "pure", + "signature": "assertGe(int256,int256,string)", + "selector": "0xa84328dd", "selectorBytes": [ - 193, - 151, - 141, - 31 + 168, + 67, + 40, + 221 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "envUint_1", - "description": "Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", - "declaration": "function envUint(string calldata name, string calldata delim) external view returns (uint256[] memory value);", + "id": "assertGtDecimal_0", + "description": "Compares two `uint256` values. Expects first value to be greater than second.\nFormats values with decimals in failure message.", + "declaration": "function assertGtDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", "visibility": "external", - "mutability": "view", - "signature": "envUint(string,string)", - "selector": "0xf3dec099", + "mutability": "pure", + "signature": "assertGtDecimal(uint256,uint256,uint256)", + "selector": "0xeccd2437", "selectorBytes": [ - 243, - 222, - 192, - 153 + 236, + 205, + 36, + 55 ] }, - "group": "environment", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "etch", - "description": "Sets an address' code.", - "declaration": "function etch(address target, bytes calldata newRuntimeBytecode) external;", + "id": "assertGtDecimal_1", + "description": "Compares two `uint256` values. Expects first value to be greater than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "etch(address,bytes)", - "selector": "0xb4d6c782", + "mutability": "pure", + "signature": "assertGtDecimal(uint256,uint256,uint256,string)", + "selector": "0x64949a8d", "selectorBytes": [ - 180, - 214, - 199, - 130 + 100, + 148, + 154, + 141 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "eth_getLogs", - "description": "Gets all the logs according to specified filter.", - "declaration": "function eth_getLogs(uint256 fromBlock, uint256 toBlock, address addr, bytes32[] memory topics) external returns (EthGetLogs[] memory logs);", + "id": "assertGtDecimal_2", + "description": "Compares two `int256` values. Expects first value to be greater than second.\nFormats values with decimals in failure message.", + "declaration": "function assertGtDecimal(int256 left, int256 right, uint256 decimals) external pure;", "visibility": "external", - "mutability": "", - "signature": "eth_getLogs(uint256,uint256,address,bytes32[])", - "selector": "0x35e1349b", + "mutability": "pure", + "signature": "assertGtDecimal(int256,int256,uint256)", + "selector": "0x78611f0e", "selectorBytes": [ - 53, - 225, - 52, - 155 + 120, + 97, + 31, + 14 ] }, - "group": "evm", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "exists", - "description": "Returns true if the given path points to an existing entity, else returns false.", - "declaration": "function exists(string calldata path) external returns (bool result);", + "id": "assertGtDecimal_3", + "description": "Compares two `int256` values. Expects first value to be greater than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "exists(string)", - "selector": "0x261a323e", + "mutability": "pure", + "signature": "assertGtDecimal(int256,int256,uint256,string)", + "selector": "0x04a5c7ab", "selectorBytes": [ - 38, - 26, - 50, - 62 + 4, + 165, + 199, + 171 ] }, - "group": "filesystem", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "expectCallMinGas_0", - "description": "Expect a call to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas.", - "declaration": "function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) external;", + "id": "assertGt_0", + "description": "Compares two `uint256` values. Expects first value to be greater than second.", + "declaration": "function assertGt(uint256 left, uint256 right) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectCallMinGas(address,uint256,uint64,bytes)", - "selector": "0x08e4e116", + "mutability": "pure", + "signature": "assertGt(uint256,uint256)", + "selector": "0xdb07fcd2", "selectorBytes": [ - 8, - 228, - 225, - 22 + 219, + 7, + 252, + 210 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "expectCallMinGas_1", - "description": "Expect given number of calls to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas.", - "declaration": "function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) external;", + "id": "assertGt_1", + "description": "Compares two `uint256` values. Expects first value to be greater than second.\nIncludes error message into revert string on failure.", + "declaration": "function assertGt(uint256 left, uint256 right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectCallMinGas(address,uint256,uint64,bytes,uint64)", - "selector": "0xe13a1834", + "mutability": "pure", + "signature": "assertGt(uint256,uint256,string)", + "selector": "0xd9a3c4d2", "selectorBytes": [ - 225, - 58, - 24, - 52 + 217, + 163, + 196, + 210 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "expectCall_0", - "description": "Expects a call to an address with the specified calldata.\nCalldata can either be a strict or a partial match.", - "declaration": "function expectCall(address callee, bytes calldata data) external;", + "id": "assertGt_2", + "description": "Compares two `int256` values. Expects first value to be greater than second.", + "declaration": "function assertGt(int256 left, int256 right) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectCall(address,bytes)", - "selector": "0xbd6af434", + "mutability": "pure", + "signature": "assertGt(int256,int256)", + "selector": "0x5a362d45", "selectorBytes": [ - 189, - 106, - 244, - 52 + 90, + 54, + 45, + 69 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "expectCall_1", - "description": "Expects given number of calls to an address with the specified calldata.", - "declaration": "function expectCall(address callee, bytes calldata data, uint64 count) external;", + "id": "assertGt_3", + "description": "Compares two `int256` values. Expects first value to be greater than second.\nIncludes error message into revert string on failure.", + "declaration": "function assertGt(int256 left, int256 right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectCall(address,bytes,uint64)", - "selector": "0xc1adbbff", + "mutability": "pure", + "signature": "assertGt(int256,int256,string)", + "selector": "0xf8d33b9b", "selectorBytes": [ - 193, - 173, - 187, - 255 + 248, + 211, + 59, + 155 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "expectCall_2", - "description": "Expects a call to an address with the specified `msg.value` and calldata.", - "declaration": "function expectCall(address callee, uint256 msgValue, bytes calldata data) external;", + "id": "assertLeDecimal_0", + "description": "Compares two `uint256` values. Expects first value to be less than or equal to second.\nFormats values with decimals in failure message.", + "declaration": "function assertLeDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectCall(address,uint256,bytes)", - "selector": "0xf30c7ba3", + "mutability": "pure", + "signature": "assertLeDecimal(uint256,uint256,uint256)", + "selector": "0xc304aab7", "selectorBytes": [ - 243, - 12, - 123, - 163 + 195, + 4, + 170, + 183 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "expectCall_3", - "description": "Expects given number of calls to an address with the specified `msg.value` and calldata.", - "declaration": "function expectCall(address callee, uint256 msgValue, bytes calldata data, uint64 count) external;", + "id": "assertLeDecimal_1", + "description": "Compares two `uint256` values. Expects first value to be less than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectCall(address,uint256,bytes,uint64)", - "selector": "0xa2b1a1ae", + "mutability": "pure", + "signature": "assertLeDecimal(uint256,uint256,uint256,string)", + "selector": "0x7fefbbe0", "selectorBytes": [ - 162, - 177, - 161, - 174 + 127, + 239, + 187, + 224 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "expectCall_4", - "description": "Expect a call to an address with the specified `msg.value`, gas, and calldata.", - "declaration": "function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data) external;", + "id": "assertLeDecimal_2", + "description": "Compares two `int256` values. Expects first value to be less than or equal to second.\nFormats values with decimals in failure message.", + "declaration": "function assertLeDecimal(int256 left, int256 right, uint256 decimals) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectCall(address,uint256,uint64,bytes)", - "selector": "0x23361207", + "mutability": "pure", + "signature": "assertLeDecimal(int256,int256,uint256)", + "selector": "0x11d1364a", "selectorBytes": [ - 35, + 17, + 209, 54, - 18, - 7 + 74 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "expectCall_5", - "description": "Expects given number of calls to an address with the specified `msg.value`, gas, and calldata.", - "declaration": "function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data, uint64 count) external;", + "id": "assertLeDecimal_3", + "description": "Compares two `int256` values. Expects first value to be less than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectCall(address,uint256,uint64,bytes,uint64)", - "selector": "0x65b7b7cc", + "mutability": "pure", + "signature": "assertLeDecimal(int256,int256,uint256,string)", + "selector": "0xaa5cf788", "selectorBytes": [ - 101, - 183, - 183, - 204 + 170, + 92, + 247, + 136 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "expectEmit_0", - "description": "Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.).\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data (as specified by the booleans).", - "declaration": "function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external;", + "id": "assertLe_0", + "description": "Compares two `uint256` values. Expects first value to be less than or equal to second.", + "declaration": "function assertLe(uint256 left, uint256 right) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectEmit(bool,bool,bool,bool)", - "selector": "0x491cc7c2", + "mutability": "pure", + "signature": "assertLe(uint256,uint256)", + "selector": "0x8466f415", "selectorBytes": [ - 73, - 28, - 199, - 194 + 132, + 102, + 244, + 21 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "expectEmit_1", - "description": "Same as the previous method, but also checks supplied address against emitting contract.", - "declaration": "function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter) external;", + "id": "assertLe_1", + "description": "Compares two `uint256` values. Expects first value to be less than or equal to second.\nIncludes error message into revert string on failure.", + "declaration": "function assertLe(uint256 left, uint256 right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectEmit(bool,bool,bool,bool,address)", - "selector": "0x81bad6f3", + "mutability": "pure", + "signature": "assertLe(uint256,uint256,string)", + "selector": "0xd17d4b0d", "selectorBytes": [ - 129, - 186, - 214, - 243 + 209, + 125, + 75, + 13 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "expectEmit_2", - "description": "Prepare an expected log with all topic and data checks enabled.\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data.", - "declaration": "function expectEmit() external;", + "id": "assertLe_2", + "description": "Compares two `int256` values. Expects first value to be less than or equal to second.", + "declaration": "function assertLe(int256 left, int256 right) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectEmit()", - "selector": "0x440ed10d", + "mutability": "pure", + "signature": "assertLe(int256,int256)", + "selector": "0x95fd154e", "selectorBytes": [ - 68, - 14, - 209, - 13 + 149, + 253, + 21, + 78 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "expectEmit_3", - "description": "Same as the previous method, but also checks supplied address against emitting contract.", - "declaration": "function expectEmit(address emitter) external;", + "id": "assertLe_3", + "description": "Compares two `int256` values. Expects first value to be less than or equal to second.\nIncludes error message into revert string on failure.", + "declaration": "function assertLe(int256 left, int256 right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectEmit(address)", - "selector": "0x86b9620d", + "mutability": "pure", + "signature": "assertLe(int256,int256,string)", + "selector": "0x4dfe692c", "selectorBytes": [ - 134, - 185, - 98, - 13 + 77, + 254, + 105, + 44 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "expectRevert_0", - "description": "Expects an error on next call with any revert data.", - "declaration": "function expectRevert() external;", + "id": "assertLtDecimal_0", + "description": "Compares two `uint256` values. Expects first value to be less than second.\nFormats values with decimals in failure message.", + "declaration": "function assertLtDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectRevert()", - "selector": "0xf4844814", + "mutability": "pure", + "signature": "assertLtDecimal(uint256,uint256,uint256)", + "selector": "0x2077337e", "selectorBytes": [ - 244, - 132, - 72, - 20 + 32, + 119, + 51, + 126 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "expectRevert_1", - "description": "Expects an error on next call that starts with the revert data.", - "declaration": "function expectRevert(bytes4 revertData) external;", + "id": "assertLtDecimal_1", + "description": "Compares two `uint256` values. Expects first value to be less than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectRevert(bytes4)", - "selector": "0xc31eb0e0", + "mutability": "pure", + "signature": "assertLtDecimal(uint256,uint256,uint256,string)", + "selector": "0xa972d037", "selectorBytes": [ - 195, - 30, - 176, - 224 + 169, + 114, + 208, + 55 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "expectRevert_2", - "description": "Expects an error on next call that exactly matches the revert data.", - "declaration": "function expectRevert(bytes calldata revertData) external;", + "id": "assertLtDecimal_2", + "description": "Compares two `int256` values. Expects first value to be less than second.\nFormats values with decimals in failure message.", + "declaration": "function assertLtDecimal(int256 left, int256 right, uint256 decimals) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectRevert(bytes)", - "selector": "0xf28dceb3", + "mutability": "pure", + "signature": "assertLtDecimal(int256,int256,uint256)", + "selector": "0xdbe8d88b", "selectorBytes": [ - 242, - 141, - 206, - 179 + 219, + 232, + 216, + 139 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "expectSafeMemory", - "description": "Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the current subcontext. If any other\nmemory is written to, the test will fail. Can be called multiple times to add more ranges to the set.", - "declaration": "function expectSafeMemory(uint64 min, uint64 max) external;", + "id": "assertLtDecimal_3", + "description": "Compares two `int256` values. Expects first value to be less than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectSafeMemory(uint64,uint64)", - "selector": "0x6d016688", + "mutability": "pure", + "signature": "assertLtDecimal(int256,int256,uint256,string)", + "selector": "0x40f0b4e0", "selectorBytes": [ - 109, - 1, - 102, - 136 + 64, + 240, + 180, + 224 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "expectSafeMemoryCall", - "description": "Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the next created subcontext.\nIf any other memory is written to, the test will fail. Can be called multiple times to add more ranges\nto the set.", - "declaration": "function expectSafeMemoryCall(uint64 min, uint64 max) external;", + "id": "assertLt_0", + "description": "Compares two `uint256` values. Expects first value to be less than second.", + "declaration": "function assertLt(uint256 left, uint256 right) external pure;", "visibility": "external", - "mutability": "", - "signature": "expectSafeMemoryCall(uint64,uint64)", - "selector": "0x05838bf4", + "mutability": "pure", + "signature": "assertLt(uint256,uint256)", + "selector": "0xb12fc005", "selectorBytes": [ - 5, - 131, - 139, - 244 + 177, + 47, + 192, + 5 ] }, "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "fee", - "description": "Sets `block.basefee`.", - "declaration": "function fee(uint256 newBasefee) external;", + "id": "assertLt_1", + "description": "Compares two `uint256` values. Expects first value to be less than second.\nIncludes error message into revert string on failure.", + "declaration": "function assertLt(uint256 left, uint256 right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "fee(uint256)", - "selector": "0x39b37ab0", + "mutability": "pure", + "signature": "assertLt(uint256,uint256,string)", + "selector": "0x65d5c135", "selectorBytes": [ - 57, - 179, - 122, - 176 + 101, + 213, + 193, + 53 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "ffi", - "description": "Performs a foreign function call via the terminal.", - "declaration": "function ffi(string[] calldata commandInput) external returns (bytes memory result);", + "id": "assertLt_2", + "description": "Compares two `int256` values. Expects first value to be less than second.", + "declaration": "function assertLt(int256 left, int256 right) external pure;", "visibility": "external", - "mutability": "", - "signature": "ffi(string[])", - "selector": "0x89160467", + "mutability": "pure", + "signature": "assertLt(int256,int256)", + "selector": "0x3e914080", "selectorBytes": [ - 137, - 22, - 4, - 103 + 62, + 145, + 64, + 128 ] }, - "group": "filesystem", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "fsMetadata", - "description": "Given a path, query the file system to get information about a file, directory, etc.", - "declaration": "function fsMetadata(string calldata path) external view returns (FsMetadata memory metadata);", + "id": "assertLt_3", + "description": "Compares two `int256` values. Expects first value to be less than second.\nIncludes error message into revert string on failure.", + "declaration": "function assertLt(int256 left, int256 right, string calldata error) external pure;", "visibility": "external", - "mutability": "view", - "signature": "fsMetadata(string)", - "selector": "0xaf368a08", + "mutability": "pure", + "signature": "assertLt(int256,int256,string)", + "selector": "0x9ff531e3", "selectorBytes": [ - 175, - 54, - 138, - 8 + 159, + 245, + 49, + 227 ] }, - "group": "filesystem", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "getCode", - "description": "Gets the creation bytecode from an artifact file. Takes in the relative path to the json file.", - "declaration": "function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode);", + "id": "assertNotEqDecimal_0", + "description": "Asserts that two `uint256` values are not equal, formatting them with decimals in failure message.", + "declaration": "function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", "visibility": "external", - "mutability": "view", - "signature": "getCode(string)", - "selector": "0x8d1cc925", + "mutability": "pure", + "signature": "assertNotEqDecimal(uint256,uint256,uint256)", + "selector": "0x669efca7", "selectorBytes": [ - 141, - 28, - 201, - 37 + 102, + 158, + 252, + 167 ] }, - "group": "filesystem", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "getDeployedCode", - "description": "Gets the deployed bytecode from an artifact file. Takes in the relative path to the json file.", - "declaration": "function getDeployedCode(string calldata artifactPath) external view returns (bytes memory runtimeBytecode);", + "id": "assertNotEqDecimal_1", + "description": "Asserts that two `uint256` values are not equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", + "declaration": "function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", - "mutability": "view", - "signature": "getDeployedCode(string)", - "selector": "0x3ebf73b4", + "mutability": "pure", + "signature": "assertNotEqDecimal(uint256,uint256,uint256,string)", + "selector": "0xf5a55558", "selectorBytes": [ - 62, - 191, - 115, - 180 + 245, + 165, + 85, + 88 ] }, - "group": "filesystem", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "getLabel", - "description": "Gets the label for the specified address.", - "declaration": "function getLabel(address account) external returns (string memory currentLabel);", + "id": "assertNotEqDecimal_2", + "description": "Asserts that two `int256` values are not equal, formatting them with decimals in failure message.", + "declaration": "function assertNotEqDecimal(int256 left, int256 right, uint256 decimals) external pure;", "visibility": "external", - "mutability": "", - "signature": "getLabel(address)", - "selector": "0x28a249b0", + "mutability": "pure", + "signature": "assertNotEqDecimal(int256,int256,uint256)", + "selector": "0x14e75680", "selectorBytes": [ - 40, - 162, - 73, - 176 + 20, + 231, + 86, + 128 ] }, - "group": "utilities", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "getMappingKeyAndParentOf", - "description": "Gets the map key and parent of a mapping at a given slot, for a given address.", - "declaration": "function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external returns (bool found, bytes32 key, bytes32 parent);", + "id": "assertNotEqDecimal_3", + "description": "Asserts that two `int256` values are not equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", + "declaration": "function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "getMappingKeyAndParentOf(address,bytes32)", - "selector": "0x876e24e6", + "mutability": "pure", + "signature": "assertNotEqDecimal(int256,int256,uint256,string)", + "selector": "0x33949f0b", "selectorBytes": [ - 135, - 110, - 36, - 230 + 51, + 148, + 159, + 11 ] }, - "group": "evm", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "getMappingLength", - "description": "Gets the number of elements in the mapping at the given slot, for a given address.", - "declaration": "function getMappingLength(address target, bytes32 mappingSlot) external returns (uint256 length);", + "id": "assertNotEq_0", + "description": "Asserts that two `bool` values are not equal.", + "declaration": "function assertNotEq(bool left, bool right) external pure;", "visibility": "external", - "mutability": "", - "signature": "getMappingLength(address,bytes32)", - "selector": "0x2f2fd63f", + "mutability": "pure", + "signature": "assertNotEq(bool,bool)", + "selector": "0x236e4d66", "selectorBytes": [ - 47, - 47, - 214, - 63 + 35, + 110, + 77, + 102 ] }, - "group": "evm", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "getMappingSlotAt", - "description": "Gets the elements at index idx of the mapping at the given slot, for a given address. The\nindex must be less than the length of the mapping (i.e. the number of keys in the mapping).", - "declaration": "function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external returns (bytes32 value);", + "id": "assertNotEq_1", + "description": "Asserts that two `bool` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(bool left, bool right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "getMappingSlotAt(address,bytes32,uint256)", - "selector": "0xebc73ab4", + "mutability": "pure", + "signature": "assertNotEq(bool,bool,string)", + "selector": "0x1091a261", "selectorBytes": [ - 235, - 199, - 58, - 180 + 16, + 145, + 162, + 97 ] }, - "group": "evm", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "getNonce_0", - "description": "Gets the nonce of an account.", - "declaration": "function getNonce(address account) external view returns (uint64 nonce);", + "id": "assertNotEq_10", + "description": "Asserts that two `string` values are not equal.", + "declaration": "function assertNotEq(string calldata left, string calldata right) external pure;", "visibility": "external", - "mutability": "view", - "signature": "getNonce(address)", - "selector": "0x2d0335ab", + "mutability": "pure", + "signature": "assertNotEq(string,string)", + "selector": "0x6a8237b3", "selectorBytes": [ - 45, - 3, - 53, - 171 + 106, + 130, + 55, + 179 ] }, - "group": "evm", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "getNonce_1", - "description": "Get a `Wallet`'s nonce.", - "declaration": "function getNonce(Wallet calldata wallet) external returns (uint64 nonce);", + "id": "assertNotEq_11", + "description": "Asserts that two `string` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(string calldata left, string calldata right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "getNonce((address,uint256,uint256,uint256))", - "selector": "0xa5748aad", + "mutability": "pure", + "signature": "assertNotEq(string,string,string)", + "selector": "0x78bdcea7", "selectorBytes": [ - 165, - 116, - 138, - 173 + 120, + 189, + 206, + 167 ] }, - "group": "utilities", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "getRecordedLogs", - "description": "Gets all the recorded logs.", - "declaration": "function getRecordedLogs() external returns (Log[] memory logs);", + "id": "assertNotEq_12", + "description": "Asserts that two `bytes` values are not equal.", + "declaration": "function assertNotEq(bytes calldata left, bytes calldata right) external pure;", "visibility": "external", - "mutability": "", - "signature": "getRecordedLogs()", - "selector": "0x191553a4", + "mutability": "pure", + "signature": "assertNotEq(bytes,bytes)", + "selector": "0x3cf78e28", "selectorBytes": [ - 25, - 21, - 83, - 164 + 60, + 247, + 142, + 40 ] }, - "group": "evm", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "isDir", - "description": "Returns true if the path exists on disk and is pointing at a directory, else returns false.", - "declaration": "function isDir(string calldata path) external returns (bool result);", + "id": "assertNotEq_13", + "description": "Asserts that two `bytes` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(bytes calldata left, bytes calldata right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "isDir(string)", - "selector": "0x7d15d019", + "mutability": "pure", + "signature": "assertNotEq(bytes,bytes,string)", + "selector": "0x9507540e", "selectorBytes": [ - 125, - 21, - 208, - 25 + 149, + 7, + 84, + 14 ] }, - "group": "filesystem", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "isFile", - "description": "Returns true if the path exists on disk and is pointing at a regular file, else returns false.", - "declaration": "function isFile(string calldata path) external returns (bool result);", + "id": "assertNotEq_14", + "description": "Asserts that two arrays of `bool` values are not equal.", + "declaration": "function assertNotEq(bool[] calldata left, bool[] calldata right) external pure;", "visibility": "external", - "mutability": "", - "signature": "isFile(string)", - "selector": "0xe0eb04d4", + "mutability": "pure", + "signature": "assertNotEq(bool[],bool[])", + "selector": "0x286fafea", "selectorBytes": [ - 224, - 235, - 4, - 212 + 40, + 111, + 175, + 234 ] }, - "group": "filesystem", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "isPersistent", - "description": "Returns true if the account is marked as persistent.", - "declaration": "function isPersistent(address account) external view returns (bool persistent);", + "id": "assertNotEq_15", + "description": "Asserts that two arrays of `bool` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure;", "visibility": "external", - "mutability": "view", - "signature": "isPersistent(address)", - "selector": "0xd92d8efd", + "mutability": "pure", + "signature": "assertNotEq(bool[],bool[],string)", + "selector": "0x62c6f9fb", "selectorBytes": [ - 217, - 45, - 142, - 253 + 98, + 198, + 249, + 251 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "keyExists", - "description": "Checks if `key` exists in a JSON object.", - "declaration": "function keyExists(string calldata json, string calldata key) external view returns (bool);", + "id": "assertNotEq_16", + "description": "Asserts that two arrays of `uint256` values are not equal.", + "declaration": "function assertNotEq(uint256[] calldata left, uint256[] calldata right) external pure;", "visibility": "external", - "mutability": "view", - "signature": "keyExists(string,string)", - "selector": "0x528a683c", + "mutability": "pure", + "signature": "assertNotEq(uint256[],uint256[])", + "selector": "0x56f29cba", "selectorBytes": [ - 82, - 138, - 104, - 60 + 86, + 242, + 156, + 186 ] }, - "group": "json", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "label", - "description": "Labels an address in call traces.", - "declaration": "function label(address account, string calldata newLabel) external;", + "id": "assertNotEq_17", + "description": "Asserts that two arrays of `uint256` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "label(address,string)", - "selector": "0xc657c718", + "mutability": "pure", + "signature": "assertNotEq(uint256[],uint256[],string)", + "selector": "0x9a7fbd8f", "selectorBytes": [ - 198, - 87, - 199, - 24 + 154, + 127, + 189, + 143 ] }, - "group": "utilities", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "load", - "description": "Loads a storage slot from an address.", - "declaration": "function load(address target, bytes32 slot) external view returns (bytes32 data);", + "id": "assertNotEq_18", + "description": "Asserts that two arrays of `int256` values are not equal.", + "declaration": "function assertNotEq(int256[] calldata left, int256[] calldata right) external pure;", "visibility": "external", - "mutability": "view", - "signature": "load(address,bytes32)", - "selector": "0x667f9d70", + "mutability": "pure", + "signature": "assertNotEq(int256[],int256[])", + "selector": "0x0b72f4ef", "selectorBytes": [ - 102, - 127, - 157, - 112 + 11, + 114, + 244, + 239 ] }, - "group": "evm", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "loadAllocs", - "description": "Load a genesis JSON file's `allocs` into the in-memory revm state.", - "declaration": "function loadAllocs(string calldata pathToAllocsJson) external;", + "id": "assertNotEq_19", + "description": "Asserts that two arrays of `int256` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "loadAllocs(string)", - "selector": "0xb3a056d7", + "mutability": "pure", + "signature": "assertNotEq(int256[],int256[],string)", + "selector": "0xd3977322", "selectorBytes": [ - 179, - 160, - 86, - 215 + 211, + 151, + 115, + 34 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "makePersistent_0", - "description": "Marks that the account(s) should use persistent storage across fork swaps in a multifork setup\nMeaning, changes made to the state of this account will be kept when switching forks.", - "declaration": "function makePersistent(address account) external;", + "id": "assertNotEq_2", + "description": "Asserts that two `uint256` values are not equal.", + "declaration": "function assertNotEq(uint256 left, uint256 right) external pure;", "visibility": "external", - "mutability": "", - "signature": "makePersistent(address)", - "selector": "0x57e22dde", + "mutability": "pure", + "signature": "assertNotEq(uint256,uint256)", + "selector": "0xb7909320", "selectorBytes": [ - 87, - 226, - 45, - 222 + 183, + 144, + 147, + 32 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "makePersistent_1", - "description": "See `makePersistent(address)`.", - "declaration": "function makePersistent(address account0, address account1) external;", + "id": "assertNotEq_20", + "description": "Asserts that two arrays of `address` values are not equal.", + "declaration": "function assertNotEq(address[] calldata left, address[] calldata right) external pure;", "visibility": "external", - "mutability": "", - "signature": "makePersistent(address,address)", - "selector": "0x4074e0a8", + "mutability": "pure", + "signature": "assertNotEq(address[],address[])", + "selector": "0x46d0b252", "selectorBytes": [ - 64, - 116, - 224, - 168 + 70, + 208, + 178, + 82 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "makePersistent_2", - "description": "See `makePersistent(address)`.", - "declaration": "function makePersistent(address account0, address account1, address account2) external;", + "id": "assertNotEq_21", + "description": "Asserts that two arrays of `address` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(address[] calldata left, address[] calldata right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "makePersistent(address,address,address)", - "selector": "0xefb77a75", + "mutability": "pure", + "signature": "assertNotEq(address[],address[],string)", + "selector": "0x72c7e0b5", "selectorBytes": [ - 239, - 183, - 122, - 117 + 114, + 199, + 224, + 181 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "makePersistent_3", - "description": "See `makePersistent(address)`.", - "declaration": "function makePersistent(address[] calldata accounts) external;", + "id": "assertNotEq_22", + "description": "Asserts that two arrays of `bytes32` values are not equal.", + "declaration": "function assertNotEq(bytes32[] calldata left, bytes32[] calldata right) external pure;", "visibility": "external", - "mutability": "", - "signature": "makePersistent(address[])", - "selector": "0x1d9e269e", + "mutability": "pure", + "signature": "assertNotEq(bytes32[],bytes32[])", + "selector": "0x0603ea68", "selectorBytes": [ - 29, - 158, - 38, - 158 + 6, + 3, + 234, + 104 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "mockCallRevert_0", - "description": "Reverts a call to an address with specified revert data.", - "declaration": "function mockCallRevert(address callee, bytes calldata data, bytes calldata revertData) external;", + "id": "assertNotEq_23", + "description": "Asserts that two arrays of `bytes32` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "mockCallRevert(address,bytes,bytes)", - "selector": "0xdbaad147", + "mutability": "pure", + "signature": "assertNotEq(bytes32[],bytes32[],string)", + "selector": "0xb873634c", "selectorBytes": [ - 219, - 170, - 209, - 71 + 184, + 115, + 99, + 76 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "mockCallRevert_1", - "description": "Reverts a call to an address with a specific `msg.value`, with specified revert data.", - "declaration": "function mockCallRevert(address callee, uint256 msgValue, bytes calldata data, bytes calldata revertData) external;", + "id": "assertNotEq_24", + "description": "Asserts that two arrays of `string` values are not equal.", + "declaration": "function assertNotEq(string[] calldata left, string[] calldata right) external pure;", "visibility": "external", - "mutability": "", - "signature": "mockCallRevert(address,uint256,bytes,bytes)", - "selector": "0xd23cd037", + "mutability": "pure", + "signature": "assertNotEq(string[],string[])", + "selector": "0xbdfacbe8", "selectorBytes": [ - 210, - 60, - 208, - 55 + 189, + 250, + 203, + 232 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "mockCall_0", - "description": "Mocks a call to an address, returning specified data.\nCalldata can either be strict or a partial match, e.g. if you only\npass a Solidity selector to the expected calldata, then the entire Solidity\nfunction will be mocked.", - "declaration": "function mockCall(address callee, bytes calldata data, bytes calldata returnData) external;", + "id": "assertNotEq_25", + "description": "Asserts that two arrays of `string` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(string[] calldata left, string[] calldata right, string calldata error) external pure;", "visibility": "external", - "mutability": "", - "signature": "mockCall(address,bytes,bytes)", - "selector": "0xb96213e4", + "mutability": "pure", + "signature": "assertNotEq(string[],string[],string)", + "selector": "0xb67187f3", "selectorBytes": [ - 185, - 98, - 19, - 228 + 182, + 113, + 135, + 243 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "mockCall_1", - "description": "Mocks a call to an address with a specific `msg.value`, returning specified data.\nCalldata match takes precedence over `msg.value` in case of ambiguity.", - "declaration": "function mockCall(address callee, uint256 msgValue, bytes calldata data, bytes calldata returnData) external;", + "id": "assertNotEq_26", + "description": "Asserts that two arrays of `bytes` values are not equal.", + "declaration": "function assertNotEq(bytes[] calldata left, bytes[] calldata right) external pure;", "visibility": "external", - "mutability": "", - "signature": "mockCall(address,uint256,bytes,bytes)", - "selector": "0x81409b91", + "mutability": "pure", + "signature": "assertNotEq(bytes[],bytes[])", + "selector": "0xedecd035", "selectorBytes": [ - 129, - 64, - 155, - 145 + 237, + 236, + 208, + 53 ] }, - "group": "evm", + "group": "testing", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "func": { - "id": "parseAddress", - "description": "Parses the given `string` into an `address`.", - "declaration": "function parseAddress(string calldata stringifiedValue) external pure returns (address parsedValue);", + "id": "assertNotEq_27", + "description": "Asserts that two arrays of `bytes` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", - "signature": "parseAddress(string)", - "selector": "0xc6ce059d", + "signature": "assertNotEq(bytes[],bytes[],string)", + "selector": "0x1dcd1f68", "selectorBytes": [ - 198, - 206, - 5, - 157 + 29, + 205, + 31, + 104 ] }, - "group": "string", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseBool", - "description": "Parses the given `string` into a `bool`.", - "declaration": "function parseBool(string calldata stringifiedValue) external pure returns (bool parsedValue);", + "id": "assertNotEq_3", + "description": "Asserts that two `uint256` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(uint256 left, uint256 right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", - "signature": "parseBool(string)", - "selector": "0x974ef924", + "signature": "assertNotEq(uint256,uint256,string)", + "selector": "0x98f9bdbd", "selectorBytes": [ - 151, - 78, + 152, 249, - 36 + 189, + 189 ] }, - "group": "string", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseBytes", - "description": "Parses the given `string` into `bytes`.", - "declaration": "function parseBytes(string calldata stringifiedValue) external pure returns (bytes memory parsedValue);", + "id": "assertNotEq_4", + "description": "Asserts that two `int256` values are not equal.", + "declaration": "function assertNotEq(int256 left, int256 right) external pure;", "visibility": "external", "mutability": "pure", - "signature": "parseBytes(string)", - "selector": "0x8f5d232d", + "signature": "assertNotEq(int256,int256)", + "selector": "0xf4c004e3", "selectorBytes": [ - 143, - 93, - 35, - 45 + 244, + 192, + 4, + 227 ] }, - "group": "string", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseBytes32", - "description": "Parses the given `string` into a `bytes32`.", - "declaration": "function parseBytes32(string calldata stringifiedValue) external pure returns (bytes32 parsedValue);", + "id": "assertNotEq_5", + "description": "Asserts that two `int256` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(int256 left, int256 right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", - "signature": "parseBytes32(string)", - "selector": "0x087e6e81", + "signature": "assertNotEq(int256,int256,string)", + "selector": "0x4724c5b9", "selectorBytes": [ - 8, - 126, - 110, - 129 + 71, + 36, + 197, + 185 ] }, - "group": "string", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseInt", - "description": "Parses the given `string` into a `int256`.", - "declaration": "function parseInt(string calldata stringifiedValue) external pure returns (int256 parsedValue);", + "id": "assertNotEq_6", + "description": "Asserts that two `address` values are not equal.", + "declaration": "function assertNotEq(address left, address right) external pure;", "visibility": "external", "mutability": "pure", - "signature": "parseInt(string)", - "selector": "0x42346c5e", + "signature": "assertNotEq(address,address)", + "selector": "0xb12e1694", "selectorBytes": [ - 66, - 52, - 108, - 94 + 177, + 46, + 22, + 148 ] }, - "group": "string", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJsonAddress", - "description": "Parses a string of JSON data at `key` and coerces it to `address`.", - "declaration": "function parseJsonAddress(string calldata json, string calldata key) external pure returns (address);", + "id": "assertNotEq_7", + "description": "Asserts that two `address` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(address left, address right, string calldata error) external pure;", "visibility": "external", "mutability": "pure", - "signature": "parseJsonAddress(string,string)", - "selector": "0x1e19e657", + "signature": "assertNotEq(address,address,string)", + "selector": "0x8775a591", "selectorBytes": [ - 30, - 25, - 230, - 87 + 135, + 117, + 165, + 145 ] }, - "group": "json", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJsonAddressArray", - "description": "Parses a string of JSON data at `key` and coerces it to `address[]`.", - "declaration": "function parseJsonAddressArray(string calldata json, string calldata key) external pure returns (address[] memory);", + "id": "assertNotEq_8", + "description": "Asserts that two `bytes32` values are not equal.", + "declaration": "function assertNotEq(bytes32 left, bytes32 right) external pure;", "visibility": "external", "mutability": "pure", - "signature": "parseJsonAddressArray(string,string)", - "selector": "0x2fce7883", + "signature": "assertNotEq(bytes32,bytes32)", + "selector": "0x898e83fc", + "selectorBytes": [ + 137, + 142, + 131, + 252 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_9", + "description": "Asserts that two `bytes32` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(bytes32 left, bytes32 right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(bytes32,bytes32,string)", + "selector": "0xb2332f51", "selectorBytes": [ + 178, + 51, 47, - 206, - 120, - 131 + 81 ] }, - "group": "json", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJsonBool", - "description": "Parses a string of JSON data at `key` and coerces it to `bool`.", - "declaration": "function parseJsonBool(string calldata json, string calldata key) external pure returns (bool);", + "id": "assertTrue_0", + "description": "Asserts that the given condition is true.", + "declaration": "function assertTrue(bool condition) external pure;", "visibility": "external", "mutability": "pure", - "signature": "parseJsonBool(string,string)", - "selector": "0x9f86dc91", + "signature": "assertTrue(bool)", + "selector": "0x0c9fd581", "selectorBytes": [ + 12, 159, - 134, + 213, + 129 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertTrue_1", + "description": "Asserts that the given condition is true and includes error message into revert string on failure.", + "declaration": "function assertTrue(bool condition, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertTrue(bool,string)", + "selector": "0xa34edc03", + "selectorBytes": [ + 163, + 78, 220, - 145 + 3 ] }, - "group": "json", + "group": "testing", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJsonBoolArray", - "description": "Parses a string of JSON data at `key` and coerces it to `bool[]`.", - "declaration": "function parseJsonBoolArray(string calldata json, string calldata key) external pure returns (bool[] memory);", + "id": "assume", + "description": "If the condition is false, discard this run's fuzz inputs and generate new ones.", + "declaration": "function assume(bool condition) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assume(bool)", + "selector": "0x4c63e562", + "selectorBytes": [ + 76, + 99, + 229, + 98 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "blobBaseFee", + "description": "Sets `block.blobbasefee`", + "declaration": "function blobBaseFee(uint256 newBlobBaseFee) external;", + "visibility": "external", + "mutability": "", + "signature": "blobBaseFee(uint256)", + "selector": "0x6d315d7e", + "selectorBytes": [ + 109, + 49, + 93, + 126 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "blobhashes", + "description": "Sets the blobhashes in the transaction.\nNot available on EVM versions before Cancun.\nIf used on unsupported EVM versions it will revert.", + "declaration": "function blobhashes(bytes32[] calldata hashes) external;", + "visibility": "external", + "mutability": "", + "signature": "blobhashes(bytes32[])", + "selector": "0x129de7eb", + "selectorBytes": [ + 18, + 157, + 231, + 235 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "breakpoint_0", + "description": "Writes a breakpoint to jump to in the debugger.", + "declaration": "function breakpoint(string calldata char) external;", + "visibility": "external", + "mutability": "", + "signature": "breakpoint(string)", + "selector": "0xf0259e92", + "selectorBytes": [ + 240, + 37, + 158, + 146 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "breakpoint_1", + "description": "Writes a conditional breakpoint to jump to in the debugger.", + "declaration": "function breakpoint(string calldata char, bool value) external;", + "visibility": "external", + "mutability": "", + "signature": "breakpoint(string,bool)", + "selector": "0xf7d39a8d", + "selectorBytes": [ + 247, + 211, + 154, + 141 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "broadcast_0", + "description": "Has the next call (at this call depth only) create transactions that can later be signed and sent onchain.\nBroadcasting address is determined by checking the following in order:\n1. If `--sender` argument was provided, that address is used.\n2. If exactly one signer (e.g. private key, hw wallet, keystore) is set when `forge broadcast` is invoked, that signer is used.\n3. Otherwise, default foundry sender (1804c8AB1F12E6bbf3894d4083f33e07309d1f38) is used.", + "declaration": "function broadcast() external;", + "visibility": "external", + "mutability": "", + "signature": "broadcast()", + "selector": "0xafc98040", + "selectorBytes": [ + 175, + 201, + 128, + 64 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "broadcast_1", + "description": "Has the next call (at this call depth only) create a transaction with the address provided\nas the sender that can later be signed and sent onchain.", + "declaration": "function broadcast(address signer) external;", + "visibility": "external", + "mutability": "", + "signature": "broadcast(address)", + "selector": "0xe6962cdb", + "selectorBytes": [ + 230, + 150, + 44, + 219 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "broadcast_2", + "description": "Has the next call (at this call depth only) create a transaction with the private key\nprovided as the sender that can later be signed and sent onchain.", + "declaration": "function broadcast(uint256 privateKey) external;", + "visibility": "external", + "mutability": "", + "signature": "broadcast(uint256)", + "selector": "0xf67a965b", + "selectorBytes": [ + 246, + 122, + 150, + 91 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "chainId", + "description": "Sets `block.chainid`.", + "declaration": "function chainId(uint256 newChainId) external;", + "visibility": "external", + "mutability": "", + "signature": "chainId(uint256)", + "selector": "0x4049ddd2", + "selectorBytes": [ + 64, + 73, + 221, + 210 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "clearMockedCalls", + "description": "Clears all mocked calls.", + "declaration": "function clearMockedCalls() external;", + "visibility": "external", + "mutability": "", + "signature": "clearMockedCalls()", + "selector": "0x3fdf4e15", + "selectorBytes": [ + 63, + 223, + 78, + 21 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "closeFile", + "description": "Closes file for reading, resetting the offset and allowing to read it from beginning with readLine.\n`path` is relative to the project root.", + "declaration": "function closeFile(string calldata path) external;", + "visibility": "external", + "mutability": "", + "signature": "closeFile(string)", + "selector": "0x48c3241f", + "selectorBytes": [ + 72, + 195, + 36, + 31 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "coinbase", + "description": "Sets `block.coinbase`.", + "declaration": "function coinbase(address newCoinbase) external;", + "visibility": "external", + "mutability": "", + "signature": "coinbase(address)", + "selector": "0xff483c54", + "selectorBytes": [ + 255, + 72, + 60, + 84 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "computeCreate2Address_0", + "description": "Compute the address of a contract created with CREATE2 using the given CREATE2 deployer.", + "declaration": "function computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer) external pure returns (address);", + "visibility": "external", + "mutability": "pure", + "signature": "computeCreate2Address(bytes32,bytes32,address)", + "selector": "0xd323826a", + "selectorBytes": [ + 211, + 35, + 130, + 106 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "computeCreate2Address_1", + "description": "Compute the address of a contract created with CREATE2 using the default CREATE2 deployer.", + "declaration": "function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external pure returns (address);", + "visibility": "external", + "mutability": "pure", + "signature": "computeCreate2Address(bytes32,bytes32)", + "selector": "0x890c283b", + "selectorBytes": [ + 137, + 12, + 40, + 59 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "computeCreateAddress", + "description": "Compute the address a contract will be deployed at for a given deployer address and nonce.", + "declaration": "function computeCreateAddress(address deployer, uint256 nonce) external pure returns (address);", + "visibility": "external", + "mutability": "pure", + "signature": "computeCreateAddress(address,uint256)", + "selector": "0x74637a7a", + "selectorBytes": [ + 116, + 99, + 122, + 122 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "cool", + "description": "Marks the slots of an account and the account address as cold.", + "declaration": "function cool(address target) external;", + "visibility": "external", + "mutability": "", + "signature": "cool(address)", + "selector": "0x40ff9f21", + "selectorBytes": [ + 64, + 255, + 159, + 33 + ] + }, + "group": "evm", + "status": "experimental", + "safety": "unsafe" + }, + { + "func": { + "id": "copyFile", + "description": "Copies the contents of one file to another. This function will **overwrite** the contents of `to`.\nOn success, the total number of bytes copied is returned and it is equal to the length of the `to` file as reported by `metadata`.\nBoth `from` and `to` are relative to the project root.", + "declaration": "function copyFile(string calldata from, string calldata to) external returns (uint64 copied);", + "visibility": "external", + "mutability": "", + "signature": "copyFile(string,string)", + "selector": "0xa54a87d8", + "selectorBytes": [ + 165, + 74, + 135, + 216 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "createDir", + "description": "Creates a new, empty directory at the provided path.\nThis cheatcode will revert in the following situations, but is not limited to just these cases:\n- User lacks permissions to modify `path`.\n- A parent of the given path doesn't exist and `recursive` is false.\n- `path` already exists and `recursive` is false.\n`path` is relative to the project root.", + "declaration": "function createDir(string calldata path, bool recursive) external;", + "visibility": "external", + "mutability": "", + "signature": "createDir(string,bool)", + "selector": "0x168b64d3", + "selectorBytes": [ + 22, + 139, + 100, + 211 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "createFork_0", + "description": "Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork.", + "declaration": "function createFork(string calldata urlOrAlias) external returns (uint256 forkId);", + "visibility": "external", + "mutability": "", + "signature": "createFork(string)", + "selector": "0x31ba3498", + "selectorBytes": [ + 49, + 186, + 52, + 152 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "createFork_1", + "description": "Creates a new fork with the given endpoint and block and returns the identifier of the fork.", + "declaration": "function createFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId);", + "visibility": "external", + "mutability": "", + "signature": "createFork(string,uint256)", + "selector": "0x6ba3ba2b", + "selectorBytes": [ + 107, + 163, + 186, + 43 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "createFork_2", + "description": "Creates a new fork with the given endpoint and at the block the given transaction was mined in,\nreplays all transaction mined in the block before the transaction, and returns the identifier of the fork.", + "declaration": "function createFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId);", + "visibility": "external", + "mutability": "", + "signature": "createFork(string,bytes32)", + "selector": "0x7ca29682", + "selectorBytes": [ + 124, + 162, + 150, + 130 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "createSelectFork_0", + "description": "Creates and also selects a new fork with the given endpoint and the latest block and returns the identifier of the fork.", + "declaration": "function createSelectFork(string calldata urlOrAlias) external returns (uint256 forkId);", + "visibility": "external", + "mutability": "", + "signature": "createSelectFork(string)", + "selector": "0x98680034", + "selectorBytes": [ + 152, + 104, + 0, + 52 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "createSelectFork_1", + "description": "Creates and also selects a new fork with the given endpoint and block and returns the identifier of the fork.", + "declaration": "function createSelectFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId);", + "visibility": "external", + "mutability": "", + "signature": "createSelectFork(string,uint256)", + "selector": "0x71ee464d", + "selectorBytes": [ + 113, + 238, + 70, + 77 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "createSelectFork_2", + "description": "Creates and also selects new fork with the given endpoint and at the block the given transaction was mined in,\nreplays all transaction mined in the block before the transaction, returns the identifier of the fork.", + "declaration": "function createSelectFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId);", + "visibility": "external", + "mutability": "", + "signature": "createSelectFork(string,bytes32)", + "selector": "0x84d52b7a", + "selectorBytes": [ + 132, + 213, + 43, + 122 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "createWallet_0", + "description": "Derives a private key from the name, labels the account with that name, and returns the wallet.", + "declaration": "function createWallet(string calldata walletLabel) external returns (Wallet memory wallet);", + "visibility": "external", + "mutability": "", + "signature": "createWallet(string)", + "selector": "0x7404f1d2", + "selectorBytes": [ + 116, + 4, + 241, + 210 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "createWallet_1", + "description": "Generates a wallet from the private key and returns the wallet.", + "declaration": "function createWallet(uint256 privateKey) external returns (Wallet memory wallet);", + "visibility": "external", + "mutability": "", + "signature": "createWallet(uint256)", + "selector": "0x7a675bb6", + "selectorBytes": [ + 122, + 103, + 91, + 182 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "createWallet_2", + "description": "Generates a wallet from the private key, labels the account with that name, and returns the wallet.", + "declaration": "function createWallet(uint256 privateKey, string calldata walletLabel) external returns (Wallet memory wallet);", + "visibility": "external", + "mutability": "", + "signature": "createWallet(uint256,string)", + "selector": "0xed7c5462", + "selectorBytes": [ + 237, + 124, + 84, + 98 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "deal", + "description": "Sets an address' balance.", + "declaration": "function deal(address account, uint256 newBalance) external;", + "visibility": "external", + "mutability": "", + "signature": "deal(address,uint256)", + "selector": "0xc88a5e6d", + "selectorBytes": [ + 200, + 138, + 94, + 109 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "deleteSnapshot", + "description": "Removes the snapshot with the given ID created by `snapshot`.\nTakes the snapshot ID to delete.\nReturns `true` if the snapshot was successfully deleted.\nReturns `false` if the snapshot does not exist.", + "declaration": "function deleteSnapshot(uint256 snapshotId) external returns (bool success);", + "visibility": "external", + "mutability": "", + "signature": "deleteSnapshot(uint256)", + "selector": "0xa6368557", + "selectorBytes": [ + 166, + 54, + 133, + 87 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "deleteSnapshots", + "description": "Removes _all_ snapshots previously created by `snapshot`.", + "declaration": "function deleteSnapshots() external;", + "visibility": "external", + "mutability": "", + "signature": "deleteSnapshots()", + "selector": "0x421ae469", + "selectorBytes": [ + 66, + 26, + 228, + 105 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "deriveKey_0", + "description": "Derive a private key from a provided mnenomic string (or mnenomic file path)\nat the derivation path `m/44'/60'/0'/0/{index}`.", + "declaration": "function deriveKey(string calldata mnemonic, uint32 index) external pure returns (uint256 privateKey);", + "visibility": "external", + "mutability": "pure", + "signature": "deriveKey(string,uint32)", + "selector": "0x6229498b", + "selectorBytes": [ + 98, + 41, + 73, + 139 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "deriveKey_1", + "description": "Derive a private key from a provided mnenomic string (or mnenomic file path)\nat `{derivationPath}{index}`.", + "declaration": "function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index) external pure returns (uint256 privateKey);", + "visibility": "external", + "mutability": "pure", + "signature": "deriveKey(string,string,uint32)", + "selector": "0x6bcb2c1b", + "selectorBytes": [ + 107, + 203, + 44, + 27 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "deriveKey_2", + "description": "Derive a private key from a provided mnenomic string (or mnenomic file path) in the specified language\nat the derivation path `m/44'/60'/0'/0/{index}`.", + "declaration": "function deriveKey(string calldata mnemonic, uint32 index, string calldata language) external pure returns (uint256 privateKey);", + "visibility": "external", + "mutability": "pure", + "signature": "deriveKey(string,uint32,string)", + "selector": "0x32c8176d", + "selectorBytes": [ + 50, + 200, + 23, + 109 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "deriveKey_3", + "description": "Derive a private key from a provided mnenomic string (or mnenomic file path) in the specified language\nat `{derivationPath}{index}`.", + "declaration": "function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index, string calldata language) external pure returns (uint256 privateKey);", + "visibility": "external", + "mutability": "pure", + "signature": "deriveKey(string,string,uint32,string)", + "selector": "0x29233b1f", + "selectorBytes": [ + 41, + 35, + 59, + 31 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "difficulty", + "description": "Sets `block.difficulty`.\nNot available on EVM versions from Paris onwards. Use `prevrandao` instead.\nReverts if used on unsupported EVM versions.", + "declaration": "function difficulty(uint256 newDifficulty) external;", + "visibility": "external", + "mutability": "", + "signature": "difficulty(uint256)", + "selector": "0x46cc92d9", + "selectorBytes": [ + 70, + 204, + 146, + 217 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "dumpState", + "description": "Dump a genesis JSON file's `allocs` to disk.", + "declaration": "function dumpState(string calldata pathToStateJson) external;", + "visibility": "external", + "mutability": "", + "signature": "dumpState(string)", + "selector": "0x709ecd3f", + "selectorBytes": [ + 112, + 158, + 205, + 63 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "envAddress_0", + "description": "Gets the environment variable `name` and parses it as `address`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envAddress(string calldata name) external view returns (address value);", + "visibility": "external", + "mutability": "view", + "signature": "envAddress(string)", + "selector": "0x350d56bf", + "selectorBytes": [ + 53, + 13, + 86, + 191 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envAddress_1", + "description": "Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envAddress(string calldata name, string calldata delim) external view returns (address[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envAddress(string,string)", + "selector": "0xad31b9fa", + "selectorBytes": [ + 173, + 49, + 185, + 250 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envBool_0", + "description": "Gets the environment variable `name` and parses it as `bool`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envBool(string calldata name) external view returns (bool value);", + "visibility": "external", + "mutability": "view", + "signature": "envBool(string)", + "selector": "0x7ed1ec7d", + "selectorBytes": [ + 126, + 209, + 236, + 125 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envBool_1", + "description": "Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envBool(string calldata name, string calldata delim) external view returns (bool[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envBool(string,string)", + "selector": "0xaaaddeaf", + "selectorBytes": [ + 170, + 173, + 222, + 175 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envBytes32_0", + "description": "Gets the environment variable `name` and parses it as `bytes32`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envBytes32(string calldata name) external view returns (bytes32 value);", + "visibility": "external", + "mutability": "view", + "signature": "envBytes32(string)", + "selector": "0x97949042", + "selectorBytes": [ + 151, + 148, + 144, + 66 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envBytes32_1", + "description": "Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envBytes32(string calldata name, string calldata delim) external view returns (bytes32[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envBytes32(string,string)", + "selector": "0x5af231c1", + "selectorBytes": [ + 90, + 242, + 49, + 193 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envBytes_0", + "description": "Gets the environment variable `name` and parses it as `bytes`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envBytes(string calldata name) external view returns (bytes memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envBytes(string)", + "selector": "0x4d7baf06", + "selectorBytes": [ + 77, + 123, + 175, + 6 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envBytes_1", + "description": "Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envBytes(string calldata name, string calldata delim) external view returns (bytes[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envBytes(string,string)", + "selector": "0xddc2651b", + "selectorBytes": [ + 221, + 194, + 101, + 27 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envExists", + "description": "Gets the environment variable `name` and returns true if it exists, else returns false.", + "declaration": "function envExists(string calldata name) external view returns (bool result);", + "visibility": "external", + "mutability": "view", + "signature": "envExists(string)", + "selector": "0xce8365f9", + "selectorBytes": [ + 206, + 131, + 101, + 249 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envInt_0", + "description": "Gets the environment variable `name` and parses it as `int256`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envInt(string calldata name) external view returns (int256 value);", + "visibility": "external", + "mutability": "view", + "signature": "envInt(string)", + "selector": "0x892a0c61", + "selectorBytes": [ + 137, + 42, + 12, + 97 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envInt_1", + "description": "Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envInt(string calldata name, string calldata delim) external view returns (int256[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envInt(string,string)", + "selector": "0x42181150", + "selectorBytes": [ + 66, + 24, + 17, + 80 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_0", + "description": "Gets the environment variable `name` and parses it as `bool`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, bool defaultValue) external view returns (bool value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,bool)", + "selector": "0x4777f3cf", + "selectorBytes": [ + 71, + 119, + 243, + 207 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_1", + "description": "Gets the environment variable `name` and parses it as `uint256`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, uint256 defaultValue) external view returns (uint256 value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,uint256)", + "selector": "0x5e97348f", + "selectorBytes": [ + 94, + 151, + 52, + 143 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_10", + "description": "Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, string calldata delim, address[] calldata defaultValue) external view returns (address[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,string,address[])", + "selector": "0xc74e9deb", + "selectorBytes": [ + 199, + 78, + 157, + 235 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_11", + "description": "Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, string calldata delim, bytes32[] calldata defaultValue) external view returns (bytes32[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,string,bytes32[])", + "selector": "0x2281f367", + "selectorBytes": [ + 34, + 129, + 243, + 103 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_12", + "description": "Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, string calldata delim, string[] calldata defaultValue) external view returns (string[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,string,string[])", + "selector": "0x859216bc", + "selectorBytes": [ + 133, + 146, + 22, + 188 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_13", + "description": "Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, string calldata delim, bytes[] calldata defaultValue) external view returns (bytes[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,string,bytes[])", + "selector": "0x64bc3e64", + "selectorBytes": [ + 100, + 188, + 62, + 100 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_2", + "description": "Gets the environment variable `name` and parses it as `int256`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, int256 defaultValue) external view returns (int256 value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,int256)", + "selector": "0xbbcb713e", + "selectorBytes": [ + 187, + 203, + 113, + 62 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_3", + "description": "Gets the environment variable `name` and parses it as `address`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, address defaultValue) external view returns (address value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,address)", + "selector": "0x561fe540", + "selectorBytes": [ + 86, + 31, + 229, + 64 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_4", + "description": "Gets the environment variable `name` and parses it as `bytes32`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, bytes32 defaultValue) external view returns (bytes32 value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,bytes32)", + "selector": "0xb4a85892", + "selectorBytes": [ + 180, + 168, + 88, + 146 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_5", + "description": "Gets the environment variable `name` and parses it as `string`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, string calldata defaultValue) external view returns (string memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,string)", + "selector": "0xd145736c", + "selectorBytes": [ + 209, + 69, + 115, + 108 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_6", + "description": "Gets the environment variable `name` and parses it as `bytes`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, bytes calldata defaultValue) external view returns (bytes memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,bytes)", + "selector": "0xb3e47705", + "selectorBytes": [ + 179, + 228, + 119, + 5 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_7", + "description": "Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, string calldata delim, bool[] calldata defaultValue) external view returns (bool[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,string,bool[])", + "selector": "0xeb85e83b", + "selectorBytes": [ + 235, + 133, + 232, + 59 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_8", + "description": "Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, string calldata delim, uint256[] calldata defaultValue) external view returns (uint256[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,string,uint256[])", + "selector": "0x74318528", + "selectorBytes": [ + 116, + 49, + 133, + 40 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_9", + "description": "Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, string calldata delim, int256[] calldata defaultValue) external view returns (int256[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,string,int256[])", + "selector": "0x4700d74b", + "selectorBytes": [ + 71, + 0, + 215, + 75 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envString_0", + "description": "Gets the environment variable `name` and parses it as `string`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envString(string calldata name) external view returns (string memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envString(string)", + "selector": "0xf877cb19", + "selectorBytes": [ + 248, + 119, + 203, + 25 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envString_1", + "description": "Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envString(string calldata name, string calldata delim) external view returns (string[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envString(string,string)", + "selector": "0x14b02bc9", + "selectorBytes": [ + 20, + 176, + 43, + 201 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envUint_0", + "description": "Gets the environment variable `name` and parses it as `uint256`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envUint(string calldata name) external view returns (uint256 value);", + "visibility": "external", + "mutability": "view", + "signature": "envUint(string)", + "selector": "0xc1978d1f", + "selectorBytes": [ + 193, + 151, + 141, + 31 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envUint_1", + "description": "Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envUint(string calldata name, string calldata delim) external view returns (uint256[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envUint(string,string)", + "selector": "0xf3dec099", + "selectorBytes": [ + 243, + 222, + 192, + 153 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "etch", + "description": "Sets an address' code.", + "declaration": "function etch(address target, bytes calldata newRuntimeBytecode) external;", + "visibility": "external", + "mutability": "", + "signature": "etch(address,bytes)", + "selector": "0xb4d6c782", + "selectorBytes": [ + 180, + 214, + 199, + 130 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "eth_getLogs", + "description": "Gets all the logs according to specified filter.", + "declaration": "function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] memory topics) external returns (EthGetLogs[] memory logs);", + "visibility": "external", + "mutability": "", + "signature": "eth_getLogs(uint256,uint256,address,bytes32[])", + "selector": "0x35e1349b", + "selectorBytes": [ + 53, + 225, + 52, + 155 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "exists", + "description": "Returns true if the given path points to an existing entity, else returns false.", + "declaration": "function exists(string calldata path) external returns (bool result);", + "visibility": "external", + "mutability": "", + "signature": "exists(string)", + "selector": "0x261a323e", + "selectorBytes": [ + 38, + 26, + 50, + 62 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "expectCallMinGas_0", + "description": "Expect a call to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas.", + "declaration": "function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) external;", + "visibility": "external", + "mutability": "", + "signature": "expectCallMinGas(address,uint256,uint64,bytes)", + "selector": "0x08e4e116", + "selectorBytes": [ + 8, + 228, + 225, + 22 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectCallMinGas_1", + "description": "Expect given number of calls to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas.", + "declaration": "function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectCallMinGas(address,uint256,uint64,bytes,uint64)", + "selector": "0xe13a1834", + "selectorBytes": [ + 225, + 58, + 24, + 52 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectCall_0", + "description": "Expects a call to an address with the specified calldata.\nCalldata can either be a strict or a partial match.", + "declaration": "function expectCall(address callee, bytes calldata data) external;", + "visibility": "external", + "mutability": "", + "signature": "expectCall(address,bytes)", + "selector": "0xbd6af434", + "selectorBytes": [ + 189, + 106, + 244, + 52 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectCall_1", + "description": "Expects given number of calls to an address with the specified calldata.", + "declaration": "function expectCall(address callee, bytes calldata data, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectCall(address,bytes,uint64)", + "selector": "0xc1adbbff", + "selectorBytes": [ + 193, + 173, + 187, + 255 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectCall_2", + "description": "Expects a call to an address with the specified `msg.value` and calldata.", + "declaration": "function expectCall(address callee, uint256 msgValue, bytes calldata data) external;", + "visibility": "external", + "mutability": "", + "signature": "expectCall(address,uint256,bytes)", + "selector": "0xf30c7ba3", + "selectorBytes": [ + 243, + 12, + 123, + 163 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectCall_3", + "description": "Expects given number of calls to an address with the specified `msg.value` and calldata.", + "declaration": "function expectCall(address callee, uint256 msgValue, bytes calldata data, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectCall(address,uint256,bytes,uint64)", + "selector": "0xa2b1a1ae", + "selectorBytes": [ + 162, + 177, + 161, + 174 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectCall_4", + "description": "Expect a call to an address with the specified `msg.value`, gas, and calldata.", + "declaration": "function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data) external;", + "visibility": "external", + "mutability": "", + "signature": "expectCall(address,uint256,uint64,bytes)", + "selector": "0x23361207", + "selectorBytes": [ + 35, + 54, + 18, + 7 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectCall_5", + "description": "Expects given number of calls to an address with the specified `msg.value`, gas, and calldata.", + "declaration": "function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectCall(address,uint256,uint64,bytes,uint64)", + "selector": "0x65b7b7cc", + "selectorBytes": [ + 101, + 183, + 183, + 204 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectEmit_0", + "description": "Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.).\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data (as specified by the booleans).", + "declaration": "function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external;", + "visibility": "external", + "mutability": "", + "signature": "expectEmit(bool,bool,bool,bool)", + "selector": "0x491cc7c2", + "selectorBytes": [ + 73, + 28, + 199, + 194 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectEmit_1", + "description": "Same as the previous method, but also checks supplied address against emitting contract.", + "declaration": "function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter) external;", + "visibility": "external", + "mutability": "", + "signature": "expectEmit(bool,bool,bool,bool,address)", + "selector": "0x81bad6f3", + "selectorBytes": [ + 129, + 186, + 214, + 243 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectEmit_2", + "description": "Prepare an expected log with all topic and data checks enabled.\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data.", + "declaration": "function expectEmit() external;", + "visibility": "external", + "mutability": "", + "signature": "expectEmit()", + "selector": "0x440ed10d", + "selectorBytes": [ + 68, + 14, + 209, + 13 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectEmit_3", + "description": "Same as the previous method, but also checks supplied address against emitting contract.", + "declaration": "function expectEmit(address emitter) external;", + "visibility": "external", + "mutability": "", + "signature": "expectEmit(address)", + "selector": "0x86b9620d", + "selectorBytes": [ + 134, + 185, + 98, + 13 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_0", + "description": "Expects an error on next call with any revert data.", + "declaration": "function expectRevert() external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert()", + "selector": "0xf4844814", + "selectorBytes": [ + 244, + 132, + 72, + 20 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_1", + "description": "Expects an error on next call that starts with the revert data.", + "declaration": "function expectRevert(bytes4 revertData) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(bytes4)", + "selector": "0xc31eb0e0", + "selectorBytes": [ + 195, + 30, + 176, + 224 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_2", + "description": "Expects an error on next call that exactly matches the revert data.", + "declaration": "function expectRevert(bytes calldata revertData) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(bytes)", + "selector": "0xf28dceb3", + "selectorBytes": [ + 242, + 141, + 206, + 179 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectSafeMemory", + "description": "Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the current subcontext. If any other\nmemory is written to, the test will fail. Can be called multiple times to add more ranges to the set.", + "declaration": "function expectSafeMemory(uint64 min, uint64 max) external;", + "visibility": "external", + "mutability": "", + "signature": "expectSafeMemory(uint64,uint64)", + "selector": "0x6d016688", + "selectorBytes": [ + 109, + 1, + 102, + 136 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectSafeMemoryCall", + "description": "Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the next created subcontext.\nIf any other memory is written to, the test will fail. Can be called multiple times to add more ranges\nto the set.", + "declaration": "function expectSafeMemoryCall(uint64 min, uint64 max) external;", + "visibility": "external", + "mutability": "", + "signature": "expectSafeMemoryCall(uint64,uint64)", + "selector": "0x05838bf4", + "selectorBytes": [ + 5, + 131, + 139, + 244 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "fee", + "description": "Sets `block.basefee`.", + "declaration": "function fee(uint256 newBasefee) external;", + "visibility": "external", + "mutability": "", + "signature": "fee(uint256)", + "selector": "0x39b37ab0", + "selectorBytes": [ + 57, + 179, + 122, + 176 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "ffi", + "description": "Performs a foreign function call via the terminal.", + "declaration": "function ffi(string[] calldata commandInput) external returns (bytes memory result);", + "visibility": "external", + "mutability": "", + "signature": "ffi(string[])", + "selector": "0x89160467", + "selectorBytes": [ + 137, + 22, + 4, + 103 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "fsMetadata", + "description": "Given a path, query the file system to get information about a file, directory, etc.", + "declaration": "function fsMetadata(string calldata path) external view returns (FsMetadata memory metadata);", + "visibility": "external", + "mutability": "view", + "signature": "fsMetadata(string)", + "selector": "0xaf368a08", + "selectorBytes": [ + 175, + 54, + 138, + 8 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getBlobBaseFee", + "description": "Gets the current `block.blobbasefee`.\nYou should use this instead of `block.blobbasefee` if you use `vm.blobBaseFee`, as `block.blobbasefee` is assumed to be constant across a transaction,\nand as a result will get optimized out by the compiler.\nSee https://github.com/foundry-rs/foundry/issues/6180", + "declaration": "function getBlobBaseFee() external view returns (uint256 blobBaseFee);", + "visibility": "external", + "mutability": "view", + "signature": "getBlobBaseFee()", + "selector": "0x1f6d6ef7", + "selectorBytes": [ + 31, + 109, + 110, + 247 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getBlobhashes", + "description": "Gets the blockhashes from the current transaction.\nNot available on EVM versions before Cancun.\nIf used on unsupported EVM versions it will revert.", + "declaration": "function getBlobhashes() external view returns (bytes32[] memory hashes);", + "visibility": "external", + "mutability": "view", + "signature": "getBlobhashes()", + "selector": "0xf56ff18b", + "selectorBytes": [ + 245, + 111, + 241, + 139 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "getBlockNumber", + "description": "Gets the current `block.number`.\nYou should use this instead of `block.number` if you use `vm.roll`, as `block.number` is assumed to be constant across a transaction,\nand as a result will get optimized out by the compiler.\nSee https://github.com/foundry-rs/foundry/issues/6180", + "declaration": "function getBlockNumber() external view returns (uint256 height);", + "visibility": "external", + "mutability": "view", + "signature": "getBlockNumber()", + "selector": "0x42cbb15c", + "selectorBytes": [ + 66, + 203, + 177, + 92 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getBlockTimestamp", + "description": "Gets the current `block.timestamp`.\nYou should use this instead of `block.timestamp` if you use `vm.warp`, as `block.timestamp` is assumed to be constant across a transaction,\nand as a result will get optimized out by the compiler.\nSee https://github.com/foundry-rs/foundry/issues/6180", + "declaration": "function getBlockTimestamp() external view returns (uint256 timestamp);", + "visibility": "external", + "mutability": "view", + "signature": "getBlockTimestamp()", + "selector": "0x796b89b9", + "selectorBytes": [ + 121, + 107, + 137, + 185 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getCode", + "description": "Gets the creation bytecode from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.", + "declaration": "function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode);", + "visibility": "external", + "mutability": "view", + "signature": "getCode(string)", + "selector": "0x8d1cc925", + "selectorBytes": [ + 141, + 28, + 201, + 37 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getDeployedCode", + "description": "Gets the deployed bytecode from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.", + "declaration": "function getDeployedCode(string calldata artifactPath) external view returns (bytes memory runtimeBytecode);", + "visibility": "external", + "mutability": "view", + "signature": "getDeployedCode(string)", + "selector": "0x3ebf73b4", + "selectorBytes": [ + 62, + 191, + 115, + 180 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getLabel", + "description": "Gets the label for the specified address.", + "declaration": "function getLabel(address account) external view returns (string memory currentLabel);", + "visibility": "external", + "mutability": "view", + "signature": "getLabel(address)", + "selector": "0x28a249b0", + "selectorBytes": [ + 40, + 162, + 73, + 176 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getMappingKeyAndParentOf", + "description": "Gets the map key and parent of a mapping at a given slot, for a given address.", + "declaration": "function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external returns (bool found, bytes32 key, bytes32 parent);", + "visibility": "external", + "mutability": "", + "signature": "getMappingKeyAndParentOf(address,bytes32)", + "selector": "0x876e24e6", + "selectorBytes": [ + 135, + 110, + 36, + 230 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getMappingLength", + "description": "Gets the number of elements in the mapping at the given slot, for a given address.", + "declaration": "function getMappingLength(address target, bytes32 mappingSlot) external returns (uint256 length);", + "visibility": "external", + "mutability": "", + "signature": "getMappingLength(address,bytes32)", + "selector": "0x2f2fd63f", + "selectorBytes": [ + 47, + 47, + 214, + 63 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getMappingSlotAt", + "description": "Gets the elements at index idx of the mapping at the given slot, for a given address. The\nindex must be less than the length of the mapping (i.e. the number of keys in the mapping).", + "declaration": "function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external returns (bytes32 value);", + "visibility": "external", + "mutability": "", + "signature": "getMappingSlotAt(address,bytes32,uint256)", + "selector": "0xebc73ab4", + "selectorBytes": [ + 235, + 199, + 58, + 180 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getNonce_0", + "description": "Gets the nonce of an account.", + "declaration": "function getNonce(address account) external view returns (uint64 nonce);", + "visibility": "external", + "mutability": "view", + "signature": "getNonce(address)", + "selector": "0x2d0335ab", + "selectorBytes": [ + 45, + 3, + 53, + 171 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getNonce_1", + "description": "Get a `Wallet`'s nonce.", + "declaration": "function getNonce(Wallet calldata wallet) external returns (uint64 nonce);", + "visibility": "external", + "mutability": "", + "signature": "getNonce((address,uint256,uint256,uint256))", + "selector": "0xa5748aad", + "selectorBytes": [ + 165, + 116, + 138, + 173 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getRecordedLogs", + "description": "Gets all the recorded logs.", + "declaration": "function getRecordedLogs() external returns (Log[] memory logs);", + "visibility": "external", + "mutability": "", + "signature": "getRecordedLogs()", + "selector": "0x191553a4", + "selectorBytes": [ + 25, + 21, + 83, + 164 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "indexOf", + "description": "Returns the index of the first occurrence of a `key` in an `input` string.\nReturns `NOT_FOUND` (i.e. `type(uint256).max`) if the `key` is not found.\nReturns 0 in case of an empty `key`.", + "declaration": "function indexOf(string calldata input, string calldata key) external pure returns (uint256);", + "visibility": "external", + "mutability": "pure", + "signature": "indexOf(string,string)", + "selector": "0x8a0807b7", + "selectorBytes": [ + 138, + 8, + 7, + 183 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "isContext", + "description": "Returns true if `forge` command was executed in given context.", + "declaration": "function isContext(ForgeContext context) external view returns (bool result);", + "visibility": "external", + "mutability": "view", + "signature": "isContext(uint8)", + "selector": "0x64af255d", + "selectorBytes": [ + 100, + 175, + 37, + 93 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "isDir", + "description": "Returns true if the path exists on disk and is pointing at a directory, else returns false.", + "declaration": "function isDir(string calldata path) external returns (bool result);", + "visibility": "external", + "mutability": "", + "signature": "isDir(string)", + "selector": "0x7d15d019", + "selectorBytes": [ + 125, + 21, + 208, + 25 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "isFile", + "description": "Returns true if the path exists on disk and is pointing at a regular file, else returns false.", + "declaration": "function isFile(string calldata path) external returns (bool result);", + "visibility": "external", + "mutability": "", + "signature": "isFile(string)", + "selector": "0xe0eb04d4", + "selectorBytes": [ + 224, + 235, + 4, + 212 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "isPersistent", + "description": "Returns true if the account is marked as persistent.", + "declaration": "function isPersistent(address account) external view returns (bool persistent);", + "visibility": "external", + "mutability": "view", + "signature": "isPersistent(address)", + "selector": "0xd92d8efd", + "selectorBytes": [ + 217, + 45, + 142, + 253 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "keyExists", + "description": "Checks if `key` exists in a JSON object\n`keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions.", + "declaration": "function keyExists(string calldata json, string calldata key) external view returns (bool);", + "visibility": "external", + "mutability": "view", + "signature": "keyExists(string,string)", + "selector": "0x528a683c", + "selectorBytes": [ + 82, + 138, + 104, + 60 + ] + }, + "group": "json", + "status": "deprecated", + "safety": "safe" + }, + { + "func": { + "id": "keyExistsJson", + "description": "Checks if `key` exists in a JSON object.", + "declaration": "function keyExistsJson(string calldata json, string calldata key) external view returns (bool);", + "visibility": "external", + "mutability": "view", + "signature": "keyExistsJson(string,string)", + "selector": "0xdb4235f6", + "selectorBytes": [ + 219, + 66, + 53, + 246 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "keyExistsToml", + "description": "Checks if `key` exists in a TOML table.", + "declaration": "function keyExistsToml(string calldata toml, string calldata key) external view returns (bool);", + "visibility": "external", + "mutability": "view", + "signature": "keyExistsToml(string,string)", + "selector": "0x600903ad", + "selectorBytes": [ + 96, + 9, + 3, + 173 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "label", + "description": "Labels an address in call traces.", + "declaration": "function label(address account, string calldata newLabel) external;", + "visibility": "external", + "mutability": "", + "signature": "label(address,string)", + "selector": "0xc657c718", + "selectorBytes": [ + 198, + 87, + 199, + 24 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "lastCallGas", + "description": "Gets the gas used in the last call.", + "declaration": "function lastCallGas() external view returns (Gas memory gas);", + "visibility": "external", + "mutability": "view", + "signature": "lastCallGas()", + "selector": "0x2b589b28", + "selectorBytes": [ + 43, + 88, + 155, + 40 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "load", + "description": "Loads a storage slot from an address.", + "declaration": "function load(address target, bytes32 slot) external view returns (bytes32 data);", + "visibility": "external", + "mutability": "view", + "signature": "load(address,bytes32)", + "selector": "0x667f9d70", + "selectorBytes": [ + 102, + 127, + 157, + 112 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "loadAllocs", + "description": "Load a genesis JSON file's `allocs` into the in-memory revm state.", + "declaration": "function loadAllocs(string calldata pathToAllocsJson) external;", + "visibility": "external", + "mutability": "", + "signature": "loadAllocs(string)", + "selector": "0xb3a056d7", + "selectorBytes": [ + 179, + 160, + 86, + 215 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "makePersistent_0", + "description": "Marks that the account(s) should use persistent storage across fork swaps in a multifork setup\nMeaning, changes made to the state of this account will be kept when switching forks.", + "declaration": "function makePersistent(address account) external;", + "visibility": "external", + "mutability": "", + "signature": "makePersistent(address)", + "selector": "0x57e22dde", + "selectorBytes": [ + 87, + 226, + 45, + 222 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "makePersistent_1", + "description": "See `makePersistent(address)`.", + "declaration": "function makePersistent(address account0, address account1) external;", + "visibility": "external", + "mutability": "", + "signature": "makePersistent(address,address)", + "selector": "0x4074e0a8", + "selectorBytes": [ + 64, + 116, + 224, + 168 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "makePersistent_2", + "description": "See `makePersistent(address)`.", + "declaration": "function makePersistent(address account0, address account1, address account2) external;", + "visibility": "external", + "mutability": "", + "signature": "makePersistent(address,address,address)", + "selector": "0xefb77a75", + "selectorBytes": [ + 239, + 183, + 122, + 117 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "makePersistent_3", + "description": "See `makePersistent(address)`.", + "declaration": "function makePersistent(address[] calldata accounts) external;", + "visibility": "external", + "mutability": "", + "signature": "makePersistent(address[])", + "selector": "0x1d9e269e", + "selectorBytes": [ + 29, + 158, + 38, + 158 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "mockCallRevert_0", + "description": "Reverts a call to an address with specified revert data.", + "declaration": "function mockCallRevert(address callee, bytes calldata data, bytes calldata revertData) external;", + "visibility": "external", + "mutability": "", + "signature": "mockCallRevert(address,bytes,bytes)", + "selector": "0xdbaad147", + "selectorBytes": [ + 219, + 170, + 209, + 71 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "mockCallRevert_1", + "description": "Reverts a call to an address with a specific `msg.value`, with specified revert data.", + "declaration": "function mockCallRevert(address callee, uint256 msgValue, bytes calldata data, bytes calldata revertData) external;", + "visibility": "external", + "mutability": "", + "signature": "mockCallRevert(address,uint256,bytes,bytes)", + "selector": "0xd23cd037", + "selectorBytes": [ + 210, + 60, + 208, + 55 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "mockCall_0", + "description": "Mocks a call to an address, returning specified data.\nCalldata can either be strict or a partial match, e.g. if you only\npass a Solidity selector to the expected calldata, then the entire Solidity\nfunction will be mocked.", + "declaration": "function mockCall(address callee, bytes calldata data, bytes calldata returnData) external;", + "visibility": "external", + "mutability": "", + "signature": "mockCall(address,bytes,bytes)", + "selector": "0xb96213e4", + "selectorBytes": [ + 185, + 98, + 19, + 228 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "mockCall_1", + "description": "Mocks a call to an address with a specific `msg.value`, returning specified data.\nCalldata match takes precedence over `msg.value` in case of ambiguity.", + "declaration": "function mockCall(address callee, uint256 msgValue, bytes calldata data, bytes calldata returnData) external;", + "visibility": "external", + "mutability": "", + "signature": "mockCall(address,uint256,bytes,bytes)", + "selector": "0x81409b91", + "selectorBytes": [ + 129, + 64, + 155, + 145 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "parseAddress", + "description": "Parses the given `string` into an `address`.", + "declaration": "function parseAddress(string calldata stringifiedValue) external pure returns (address parsedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "parseAddress(string)", + "selector": "0xc6ce059d", + "selectorBytes": [ + 198, + 206, + 5, + 157 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseBool", + "description": "Parses the given `string` into a `bool`.", + "declaration": "function parseBool(string calldata stringifiedValue) external pure returns (bool parsedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "parseBool(string)", + "selector": "0x974ef924", + "selectorBytes": [ + 151, + 78, + 249, + 36 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseBytes", + "description": "Parses the given `string` into `bytes`.", + "declaration": "function parseBytes(string calldata stringifiedValue) external pure returns (bytes memory parsedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "parseBytes(string)", + "selector": "0x8f5d232d", + "selectorBytes": [ + 143, + 93, + 35, + 45 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseBytes32", + "description": "Parses the given `string` into a `bytes32`.", + "declaration": "function parseBytes32(string calldata stringifiedValue) external pure returns (bytes32 parsedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "parseBytes32(string)", + "selector": "0x087e6e81", + "selectorBytes": [ + 8, + 126, + 110, + 129 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseInt", + "description": "Parses the given `string` into a `int256`.", + "declaration": "function parseInt(string calldata stringifiedValue) external pure returns (int256 parsedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "parseInt(string)", + "selector": "0x42346c5e", + "selectorBytes": [ + 66, + 52, + 108, + 94 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonAddress", + "description": "Parses a string of JSON data at `key` and coerces it to `address`.", + "declaration": "function parseJsonAddress(string calldata json, string calldata key) external pure returns (address);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonAddress(string,string)", + "selector": "0x1e19e657", + "selectorBytes": [ + 30, + 25, + 230, + 87 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonAddressArray", + "description": "Parses a string of JSON data at `key` and coerces it to `address[]`.", + "declaration": "function parseJsonAddressArray(string calldata json, string calldata key) external pure returns (address[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonAddressArray(string,string)", + "selector": "0x2fce7883", + "selectorBytes": [ + 47, + 206, + 120, + 131 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonBool", + "description": "Parses a string of JSON data at `key` and coerces it to `bool`.", + "declaration": "function parseJsonBool(string calldata json, string calldata key) external pure returns (bool);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonBool(string,string)", + "selector": "0x9f86dc91", + "selectorBytes": [ + 159, + 134, + 220, + 145 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonBoolArray", + "description": "Parses a string of JSON data at `key` and coerces it to `bool[]`.", + "declaration": "function parseJsonBoolArray(string calldata json, string calldata key) external pure returns (bool[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonBoolArray(string,string)", + "selector": "0x91f3b94f", + "selectorBytes": [ + 145, + 243, + 185, + 79 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonBytes", + "description": "Parses a string of JSON data at `key` and coerces it to `bytes`.", + "declaration": "function parseJsonBytes(string calldata json, string calldata key) external pure returns (bytes memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonBytes(string,string)", + "selector": "0xfd921be8", + "selectorBytes": [ + 253, + 146, + 27, + 232 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonBytes32", + "description": "Parses a string of JSON data at `key` and coerces it to `bytes32`.", + "declaration": "function parseJsonBytes32(string calldata json, string calldata key) external pure returns (bytes32);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonBytes32(string,string)", + "selector": "0x1777e59d", + "selectorBytes": [ + 23, + 119, + 229, + 157 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonBytes32Array", + "description": "Parses a string of JSON data at `key` and coerces it to `bytes32[]`.", + "declaration": "function parseJsonBytes32Array(string calldata json, string calldata key) external pure returns (bytes32[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonBytes32Array(string,string)", + "selector": "0x91c75bc3", + "selectorBytes": [ + 145, + 199, + 91, + 195 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonBytesArray", + "description": "Parses a string of JSON data at `key` and coerces it to `bytes[]`.", + "declaration": "function parseJsonBytesArray(string calldata json, string calldata key) external pure returns (bytes[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonBytesArray(string,string)", + "selector": "0x6631aa99", + "selectorBytes": [ + 102, + 49, + 170, + 153 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonInt", + "description": "Parses a string of JSON data at `key` and coerces it to `int256`.", + "declaration": "function parseJsonInt(string calldata json, string calldata key) external pure returns (int256);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonInt(string,string)", + "selector": "0x7b048ccd", + "selectorBytes": [ + 123, + 4, + 140, + 205 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonIntArray", + "description": "Parses a string of JSON data at `key` and coerces it to `int256[]`.", + "declaration": "function parseJsonIntArray(string calldata json, string calldata key) external pure returns (int256[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonIntArray(string,string)", + "selector": "0x9983c28a", + "selectorBytes": [ + 153, + 131, + 194, + 138 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonKeys", + "description": "Returns an array of all the keys in a JSON object.", + "declaration": "function parseJsonKeys(string calldata json, string calldata key) external pure returns (string[] memory keys);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonKeys(string,string)", + "selector": "0x213e4198", + "selectorBytes": [ + 33, + 62, + 65, + 152 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonString", + "description": "Parses a string of JSON data at `key` and coerces it to `string`.", + "declaration": "function parseJsonString(string calldata json, string calldata key) external pure returns (string memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonString(string,string)", + "selector": "0x49c4fac8", + "selectorBytes": [ + 73, + 196, + 250, + 200 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonStringArray", + "description": "Parses a string of JSON data at `key` and coerces it to `string[]`.", + "declaration": "function parseJsonStringArray(string calldata json, string calldata key) external pure returns (string[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonStringArray(string,string)", + "selector": "0x498fdcf4", + "selectorBytes": [ + 73, + 143, + 220, + 244 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonUint", + "description": "Parses a string of JSON data at `key` and coerces it to `uint256`.", + "declaration": "function parseJsonUint(string calldata json, string calldata key) external pure returns (uint256);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonUint(string,string)", + "selector": "0xaddde2b6", + "selectorBytes": [ + 173, + 221, + 226, + 182 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonUintArray", + "description": "Parses a string of JSON data at `key` and coerces it to `uint256[]`.", + "declaration": "function parseJsonUintArray(string calldata json, string calldata key) external pure returns (uint256[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonUintArray(string,string)", + "selector": "0x522074ab", + "selectorBytes": [ + 82, + 32, + 116, + 171 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJson_0", + "description": "ABI-encodes a JSON object.", + "declaration": "function parseJson(string calldata json) external pure returns (bytes memory abiEncodedData);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJson(string)", + "selector": "0x6a82600a", + "selectorBytes": [ + 106, + 130, + 96, + 10 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJson_1", + "description": "ABI-encodes a JSON object at `key`.", + "declaration": "function parseJson(string calldata json, string calldata key) external pure returns (bytes memory abiEncodedData);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJson(string,string)", + "selector": "0x85940ef1", + "selectorBytes": [ + 133, + 148, + 14, + 241 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlAddress", + "description": "Parses a string of TOML data at `key` and coerces it to `address`.", + "declaration": "function parseTomlAddress(string calldata toml, string calldata key) external pure returns (address);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlAddress(string,string)", + "selector": "0x65e7c844", + "selectorBytes": [ + 101, + 231, + 200, + 68 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlAddressArray", + "description": "Parses a string of TOML data at `key` and coerces it to `address[]`.", + "declaration": "function parseTomlAddressArray(string calldata toml, string calldata key) external pure returns (address[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlAddressArray(string,string)", + "selector": "0x65c428e7", + "selectorBytes": [ + 101, + 196, + 40, + 231 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlBool", + "description": "Parses a string of TOML data at `key` and coerces it to `bool`.", + "declaration": "function parseTomlBool(string calldata toml, string calldata key) external pure returns (bool);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlBool(string,string)", + "selector": "0xd30dced6", + "selectorBytes": [ + 211, + 13, + 206, + 214 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlBoolArray", + "description": "Parses a string of TOML data at `key` and coerces it to `bool[]`.", + "declaration": "function parseTomlBoolArray(string calldata toml, string calldata key) external pure returns (bool[] memory);", "visibility": "external", "mutability": "pure", - "signature": "parseJsonBoolArray(string,string)", - "selector": "0x91f3b94f", + "signature": "parseTomlBoolArray(string,string)", + "selector": "0x127cfe9a", "selectorBytes": [ - 145, - 243, - 185, - 79 + 18, + 124, + 254, + 154 ] }, - "group": "json", + "group": "toml", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJsonBytes", - "description": "Parses a string of JSON data at `key` and coerces it to `bytes`.", - "declaration": "function parseJsonBytes(string calldata json, string calldata key) external pure returns (bytes memory);", + "id": "parseTomlBytes", + "description": "Parses a string of TOML data at `key` and coerces it to `bytes`.", + "declaration": "function parseTomlBytes(string calldata toml, string calldata key) external pure returns (bytes memory);", "visibility": "external", "mutability": "pure", - "signature": "parseJsonBytes(string,string)", - "selector": "0xfd921be8", + "signature": "parseTomlBytes(string,string)", + "selector": "0xd77bfdb9", "selectorBytes": [ + 215, + 123, 253, - 146, - 27, - 232 + 185 ] }, - "group": "json", + "group": "toml", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJsonBytes32", - "description": "Parses a string of JSON data at `key` and coerces it to `bytes32`.", - "declaration": "function parseJsonBytes32(string calldata json, string calldata key) external pure returns (bytes32);", + "id": "parseTomlBytes32", + "description": "Parses a string of TOML data at `key` and coerces it to `bytes32`.", + "declaration": "function parseTomlBytes32(string calldata toml, string calldata key) external pure returns (bytes32);", "visibility": "external", "mutability": "pure", - "signature": "parseJsonBytes32(string,string)", - "selector": "0x1777e59d", + "signature": "parseTomlBytes32(string,string)", + "selector": "0x8e214810", "selectorBytes": [ - 23, - 119, - 229, - 157 + 142, + 33, + 72, + 16 ] }, - "group": "json", + "group": "toml", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJsonBytes32Array", - "description": "Parses a string of JSON data at `key` and coerces it to `bytes32[]`.", - "declaration": "function parseJsonBytes32Array(string calldata json, string calldata key) external pure returns (bytes32[] memory);", + "id": "parseTomlBytes32Array", + "description": "Parses a string of TOML data at `key` and coerces it to `bytes32[]`.", + "declaration": "function parseTomlBytes32Array(string calldata toml, string calldata key) external pure returns (bytes32[] memory);", "visibility": "external", "mutability": "pure", - "signature": "parseJsonBytes32Array(string,string)", - "selector": "0x91c75bc3", + "signature": "parseTomlBytes32Array(string,string)", + "selector": "0x3e716f81", "selectorBytes": [ - 145, - 199, - 91, - 195 + 62, + 113, + 111, + 129 ] }, - "group": "json", + "group": "toml", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJsonBytesArray", - "description": "Parses a string of JSON data at `key` and coerces it to `bytes[]`.", - "declaration": "function parseJsonBytesArray(string calldata json, string calldata key) external pure returns (bytes[] memory);", + "id": "parseTomlBytesArray", + "description": "Parses a string of TOML data at `key` and coerces it to `bytes[]`.", + "declaration": "function parseTomlBytesArray(string calldata toml, string calldata key) external pure returns (bytes[] memory);", "visibility": "external", "mutability": "pure", - "signature": "parseJsonBytesArray(string,string)", - "selector": "0x6631aa99", + "signature": "parseTomlBytesArray(string,string)", + "selector": "0xb197c247", "selectorBytes": [ - 102, - 49, - 170, - 153 + 177, + 151, + 194, + 71 ] }, - "group": "json", + "group": "toml", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJsonInt", - "description": "Parses a string of JSON data at `key` and coerces it to `int256`.", - "declaration": "function parseJsonInt(string calldata json, string calldata key) external pure returns (int256);", + "id": "parseTomlInt", + "description": "Parses a string of TOML data at `key` and coerces it to `int256`.", + "declaration": "function parseTomlInt(string calldata toml, string calldata key) external pure returns (int256);", "visibility": "external", "mutability": "pure", - "signature": "parseJsonInt(string,string)", - "selector": "0x7b048ccd", + "signature": "parseTomlInt(string,string)", + "selector": "0xc1350739", "selectorBytes": [ - 123, - 4, - 140, - 205 + 193, + 53, + 7, + 57 ] }, - "group": "json", + "group": "toml", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJsonIntArray", - "description": "Parses a string of JSON data at `key` and coerces it to `int256[]`.", - "declaration": "function parseJsonIntArray(string calldata json, string calldata key) external pure returns (int256[] memory);", + "id": "parseTomlIntArray", + "description": "Parses a string of TOML data at `key` and coerces it to `int256[]`.", + "declaration": "function parseTomlIntArray(string calldata toml, string calldata key) external pure returns (int256[] memory);", "visibility": "external", "mutability": "pure", - "signature": "parseJsonIntArray(string,string)", - "selector": "0x9983c28a", + "signature": "parseTomlIntArray(string,string)", + "selector": "0xd3522ae6", "selectorBytes": [ - 153, - 131, - 194, - 138 + 211, + 82, + 42, + 230 ] }, - "group": "json", + "group": "toml", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJsonKeys", - "description": "Returns an array of all the keys in a JSON object.", - "declaration": "function parseJsonKeys(string calldata json, string calldata key) external pure returns (string[] memory keys);", + "id": "parseTomlKeys", + "description": "Returns an array of all the keys in a TOML table.", + "declaration": "function parseTomlKeys(string calldata toml, string calldata key) external pure returns (string[] memory keys);", "visibility": "external", "mutability": "pure", - "signature": "parseJsonKeys(string,string)", - "selector": "0x213e4198", + "signature": "parseTomlKeys(string,string)", + "selector": "0x812a44b2", "selectorBytes": [ - 33, - 62, - 65, - 152 + 129, + 42, + 68, + 178 ] }, - "group": "json", + "group": "toml", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJsonString", - "description": "Parses a string of JSON data at `key` and coerces it to `string`.", - "declaration": "function parseJsonString(string calldata json, string calldata key) external pure returns (string memory);", + "id": "parseTomlString", + "description": "Parses a string of TOML data at `key` and coerces it to `string`.", + "declaration": "function parseTomlString(string calldata toml, string calldata key) external pure returns (string memory);", "visibility": "external", "mutability": "pure", - "signature": "parseJsonString(string,string)", - "selector": "0x49c4fac8", + "signature": "parseTomlString(string,string)", + "selector": "0x8bb8dd43", "selectorBytes": [ - 73, - 196, - 250, - 200 + 139, + 184, + 221, + 67 ] }, - "group": "json", + "group": "toml", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJsonStringArray", - "description": "Parses a string of JSON data at `key` and coerces it to `string[]`.", - "declaration": "function parseJsonStringArray(string calldata json, string calldata key) external pure returns (string[] memory);", + "id": "parseTomlStringArray", + "description": "Parses a string of TOML data at `key` and coerces it to `string[]`.", + "declaration": "function parseTomlStringArray(string calldata toml, string calldata key) external pure returns (string[] memory);", "visibility": "external", "mutability": "pure", - "signature": "parseJsonStringArray(string,string)", - "selector": "0x498fdcf4", + "signature": "parseTomlStringArray(string,string)", + "selector": "0x9f629281", "selectorBytes": [ - 73, - 143, - 220, - 244 + 159, + 98, + 146, + 129 ] }, - "group": "json", + "group": "toml", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJsonUint", - "description": "Parses a string of JSON data at `key` and coerces it to `uint256`.", - "declaration": "function parseJsonUint(string calldata json, string calldata key) external pure returns (uint256);", + "id": "parseTomlUint", + "description": "Parses a string of TOML data at `key` and coerces it to `uint256`.", + "declaration": "function parseTomlUint(string calldata toml, string calldata key) external pure returns (uint256);", "visibility": "external", "mutability": "pure", - "signature": "parseJsonUint(string,string)", - "selector": "0xaddde2b6", + "signature": "parseTomlUint(string,string)", + "selector": "0xcc7b0487", "selectorBytes": [ - 173, - 221, - 226, - 182 + 204, + 123, + 4, + 135 ] }, - "group": "json", + "group": "toml", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJsonUintArray", - "description": "Parses a string of JSON data at `key` and coerces it to `uint256[]`.", - "declaration": "function parseJsonUintArray(string calldata json, string calldata key) external pure returns (uint256[] memory);", + "id": "parseTomlUintArray", + "description": "Parses a string of TOML data at `key` and coerces it to `uint256[]`.", + "declaration": "function parseTomlUintArray(string calldata toml, string calldata key) external pure returns (uint256[] memory);", "visibility": "external", "mutability": "pure", - "signature": "parseJsonUintArray(string,string)", - "selector": "0x522074ab", + "signature": "parseTomlUintArray(string,string)", + "selector": "0xb5df27c8", "selectorBytes": [ - 82, - 32, - 116, - 171 + 181, + 223, + 39, + 200 ] }, - "group": "json", + "group": "toml", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJson_0", - "description": "ABI-encodes a JSON object.", - "declaration": "function parseJson(string calldata json) external pure returns (bytes memory abiEncodedData);", + "id": "parseToml_0", + "description": "ABI-encodes a TOML table.", + "declaration": "function parseToml(string calldata toml) external pure returns (bytes memory abiEncodedData);", "visibility": "external", "mutability": "pure", - "signature": "parseJson(string)", - "selector": "0x6a82600a", + "signature": "parseToml(string)", + "selector": "0x592151f0", "selectorBytes": [ - 106, - 130, - 96, - 10 + 89, + 33, + 81, + 240 ] }, - "group": "json", + "group": "toml", "status": "stable", "safety": "safe" }, { "func": { - "id": "parseJson_1", - "description": "ABI-encodes a JSON object at `key`.", - "declaration": "function parseJson(string calldata json, string calldata key) external pure returns (bytes memory abiEncodedData);", + "id": "parseToml_1", + "description": "ABI-encodes a TOML table at `key`.", + "declaration": "function parseToml(string calldata toml, string calldata key) external pure returns (bytes memory abiEncodedData);", "visibility": "external", "mutability": "pure", - "signature": "parseJson(string,string)", - "selector": "0x85940ef1", + "signature": "parseToml(string,string)", + "selector": "0x37736e08", "selectorBytes": [ - 133, - 148, - 14, - 241 + 55, + 115, + 110, + 8 ] }, - "group": "json", + "group": "toml", "status": "stable", "safety": "safe" }, @@ -3119,7 +6233,7 @@ }, { "func": { - "id": "prevrandao", + "id": "prevrandao_0", "description": "Sets `block.prevrandao`.\nNot available on EVM versions before Paris. Use `difficulty` instead.\nIf used on unsupported EVM versions it will revert.", "declaration": "function prevrandao(bytes32 newPrevrandao) external;", "visibility": "external", @@ -3135,22 +6249,122 @@ }, "group": "evm", "status": "stable", - "safety": "unsafe" + "safety": "unsafe" + }, + { + "func": { + "id": "prevrandao_1", + "description": "Sets `block.prevrandao`.\nNot available on EVM versions before Paris. Use `difficulty` instead.\nIf used on unsupported EVM versions it will revert.", + "declaration": "function prevrandao(uint256 newPrevrandao) external;", + "visibility": "external", + "mutability": "", + "signature": "prevrandao(uint256)", + "selector": "0x9cb1c0d4", + "selectorBytes": [ + 156, + 177, + 192, + 212 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "projectRoot", + "description": "Get the path of the current project root.", + "declaration": "function projectRoot() external view returns (string memory path);", + "visibility": "external", + "mutability": "view", + "signature": "projectRoot()", + "selector": "0xd930a0e6", + "selectorBytes": [ + 217, + 48, + 160, + 230 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "prompt", + "description": "Prompts the user for a string value in the terminal.", + "declaration": "function prompt(string calldata promptText) external returns (string memory input);", + "visibility": "external", + "mutability": "", + "signature": "prompt(string)", + "selector": "0x47eaf474", + "selectorBytes": [ + 71, + 234, + 244, + 116 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "promptAddress", + "description": "Prompts the user for an address in the terminal.", + "declaration": "function promptAddress(string calldata promptText) external returns (address);", + "visibility": "external", + "mutability": "", + "signature": "promptAddress(string)", + "selector": "0x62ee05f4", + "selectorBytes": [ + 98, + 238, + 5, + 244 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "promptSecret", + "description": "Prompts the user for a hidden string value in the terminal.", + "declaration": "function promptSecret(string calldata promptText) external returns (string memory input);", + "visibility": "external", + "mutability": "", + "signature": "promptSecret(string)", + "selector": "0x1e279d41", + "selectorBytes": [ + 30, + 39, + 157, + 65 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" }, { "func": { - "id": "projectRoot", - "description": "Get the path of the current project root.", - "declaration": "function projectRoot() external view returns (string memory path);", + "id": "promptUint", + "description": "Prompts the user for uint256 in the terminal.", + "declaration": "function promptUint(string calldata promptText) external returns (uint256);", "visibility": "external", - "mutability": "view", - "signature": "projectRoot()", - "selector": "0xd930a0e6", + "mutability": "", + "signature": "promptUint(string)", + "selector": "0x652fd489", "selectorBytes": [ - 217, - 48, - 160, - 230 + 101, + 47, + 212, + 137 ] }, "group": "filesystem", @@ -3417,6 +6631,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "replace", + "description": "Replaces occurrences of `from` in the given `string` with `to`.", + "declaration": "function replace(string calldata input, string calldata from, string calldata to) external pure returns (string memory output);", + "visibility": "external", + "mutability": "pure", + "signature": "replace(string,string,string)", + "selector": "0xe00ad03e", + "selectorBytes": [ + 224, + 10, + 208, + 62 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "resetNonce", @@ -3460,7 +6694,7 @@ { "func": { "id": "revertTo", - "description": "Revert the state of the EVM to a previous snapshot\nTakes the snapshot ID to revert to.\nThis deletes the snapshot and all snapshots taken after the given snapshot ID.", + "description": "Revert the state of the EVM to a previous snapshot\nTakes the snapshot ID to revert to.\nReturns `true` if the snapshot was successfully reverted.\nReturns `false` if the snapshot does not exist.\n**Note:** This does not automatically delete the snapshot. To delete the snapshot use `deleteSnapshot`.", "declaration": "function revertTo(uint256 snapshotId) external returns (bool success);", "visibility": "external", "mutability": "", @@ -3477,6 +6711,26 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "revertToAndDelete", + "description": "Revert the state of the EVM to a previous snapshot and automatically deletes the snapshots\nTakes the snapshot ID to revert to.\nReturns `true` if the snapshot was successfully reverted and deleted.\nReturns `false` if the snapshot does not exist.", + "declaration": "function revertToAndDelete(uint256 snapshotId) external returns (bool success);", + "visibility": "external", + "mutability": "", + "signature": "revertToAndDelete(uint256)", + "selector": "0x03e0aca9", + "selectorBytes": [ + 3, + 224, + 172, + 169 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "revokePersistent_0", @@ -3977,6 +7231,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "serializeUintToHex", + "description": "See `serializeJson`.", + "declaration": "function serializeUintToHex(string calldata objectKey, string calldata valueKey, uint256 value) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeUintToHex(string,string,uint256)", + "selector": "0xae5a2ae8", + "selectorBytes": [ + 174, + 90, + 42, + 232 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "serializeUint_0", @@ -4077,10 +7351,30 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "signP256", + "description": "Signs `digest` with `privateKey` using the secp256r1 curve.", + "declaration": "function signP256(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 s);", + "visibility": "external", + "mutability": "pure", + "signature": "signP256(uint256,bytes32)", + "selector": "0x83211b40", + "selectorBytes": [ + 131, + 33, + 27, + 64 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "sign_0", - "description": "Signs data.", + "description": "Signs `digest` with `privateKey` using the secp256k1 curve.", "declaration": "function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s);", "visibility": "external", "mutability": "pure", @@ -4100,6 +7394,46 @@ { "func": { "id": "sign_1", + "description": "Signs `digest` with signer provided to script using the secp256k1 curve.\nIf `--sender` is provided, the signer with provided address is used, otherwise,\nif exactly one signer is provided to the script, that signer is used.\nRaises error if signer passed through `--sender` does not match any unlocked signers or\nif `--sender` is not provided and not exactly one signer is passed to the script.", + "declaration": "function sign(bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s);", + "visibility": "external", + "mutability": "pure", + "signature": "sign(bytes32)", + "selector": "0x799cd333", + "selectorBytes": [ + 121, + 156, + 211, + 51 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "sign_2", + "description": "Signs `digest` with signer provided to script using the secp256k1 curve.\nRaises error if none of the signers passed into the script have provided address.", + "declaration": "function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s);", + "visibility": "external", + "mutability": "pure", + "signature": "sign(address,bytes32)", + "selector": "0x8c1aa205", + "selectorBytes": [ + 140, + 26, + 162, + 5 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "sign_3", "description": "Signs data with a `Wallet`.", "declaration": "function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s);", "visibility": "external", @@ -4177,10 +7511,30 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "split", + "description": "Splits the given `string` into an array of strings divided by the `delimiter`.", + "declaration": "function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs);", + "visibility": "external", + "mutability": "pure", + "signature": "split(string,string)", + "selector": "0x8bb75533", + "selectorBytes": [ + 139, + 183, + 85, + 51 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "startBroadcast_0", - "description": "Using the address that calls the test contract, has all subsequent calls\n(at this call depth only) create transactions that can later be signed and sent onchain.", + "description": "Has all subsequent calls (at this call depth only) create transactions that can later be signed and sent onchain.\nBroadcasting address is determined by checking the following in order:\n1. If `--sender` argument was provided, that address is used.\n2. If exactly one signer (e.g. private key, hw wallet, keystore) is set when `forge broadcast` is invoked, that signer is used.\n3. Otherwise, default foundry sender (1804c8AB1F12E6bbf3894d4083f33e07309d1f38) is used.", "declaration": "function startBroadcast() external;", "visibility": "external", "mutability": "", @@ -4321,7 +7675,7 @@ "func": { "id": "stopAndReturnStateDiff", "description": "Returns an ordered array of all account accesses from a `vm.startStateDiffRecording` session.", - "declaration": "function stopAndReturnStateDiff() external returns (AccountAccess[] memory accesses);", + "declaration": "function stopAndReturnStateDiff() external returns (AccountAccess[] memory accountAccesses);", "visibility": "external", "mutability": "", "signature": "stopAndReturnStateDiff()", @@ -4357,6 +7711,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "stopExpectSafeMemory", + "description": "Stops all safe memory expectation in the current subcontext.", + "declaration": "function stopExpectSafeMemory() external;", + "visibility": "external", + "mutability": "", + "signature": "stopExpectSafeMemory()", + "selector": "0x0956441b", + "selectorBytes": [ + 9, + 86, + 68, + 27 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "stopMappingRecording", @@ -4417,6 +7791,106 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "toBase64URL_0", + "description": "Encodes a `bytes` value to a base64url string.", + "declaration": "function toBase64URL(bytes calldata data) external pure returns (string memory);", + "visibility": "external", + "mutability": "pure", + "signature": "toBase64URL(bytes)", + "selector": "0xc8bd0e4a", + "selectorBytes": [ + 200, + 189, + 14, + 74 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "toBase64URL_1", + "description": "Encodes a `string` value to a base64url string.", + "declaration": "function toBase64URL(string calldata data) external pure returns (string memory);", + "visibility": "external", + "mutability": "pure", + "signature": "toBase64URL(string)", + "selector": "0xae3165b3", + "selectorBytes": [ + 174, + 49, + 101, + 179 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "toBase64_0", + "description": "Encodes a `bytes` value to a base64 string.", + "declaration": "function toBase64(bytes calldata data) external pure returns (string memory);", + "visibility": "external", + "mutability": "pure", + "signature": "toBase64(bytes)", + "selector": "0xa5cbfe65", + "selectorBytes": [ + 165, + 203, + 254, + 101 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "toBase64_1", + "description": "Encodes a `string` value to a base64 string.", + "declaration": "function toBase64(string calldata data) external pure returns (string memory);", + "visibility": "external", + "mutability": "pure", + "signature": "toBase64(string)", + "selector": "0x3f8be2c8", + "selectorBytes": [ + 63, + 139, + 226, + 200 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "toLowercase", + "description": "Converts the given `string` value to Lowercase.", + "declaration": "function toLowercase(string calldata input) external pure returns (string memory output);", + "visibility": "external", + "mutability": "pure", + "signature": "toLowercase(string)", + "selector": "0x50bb0884", + "selectorBytes": [ + 80, + 187, + 8, + 132 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "toString_0", @@ -4537,6 +8011,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "toUppercase", + "description": "Converts the given `string` value to Uppercase.", + "declaration": "function toUppercase(string calldata input) external pure returns (string memory output);", + "visibility": "external", + "mutability": "pure", + "signature": "toUppercase(string)", + "selector": "0x074ae3d7", + "selectorBytes": [ + 7, + 74, + 227, + 215 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "transact_0", @@ -4577,6 +8071,26 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "trim", + "description": "Trims leading and trailing whitespace from the given `string` value.", + "declaration": "function trim(string calldata input) external pure returns (string memory output);", + "visibility": "external", + "mutability": "pure", + "signature": "trim(string)", + "selector": "0xb2dad155", + "selectorBytes": [ + 178, + 218, + 209, + 85 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "tryFfi", @@ -4756,6 +8270,46 @@ "group": "filesystem", "status": "stable", "safety": "safe" + }, + { + "func": { + "id": "writeToml_0", + "description": "Takes serialized JSON, converts to TOML and write a serialized TOML to a file.", + "declaration": "function writeToml(string calldata json, string calldata path) external;", + "visibility": "external", + "mutability": "", + "signature": "writeToml(string,string)", + "selector": "0xc0865ba7", + "selectorBytes": [ + 192, + 134, + 91, + 167 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "writeToml_1", + "description": "Takes serialized JSON, converts to TOML and write a serialized TOML table to an **existing** TOML file, replacing a value with key = \nThis is useful to replace a specific value of a TOML file, without having to parse the entire thing.", + "declaration": "function writeToml(string calldata json, string calldata path, string calldata valueKey) external;", + "visibility": "external", + "mutability": "", + "signature": "writeToml(string,string,string)", + "selector": "0x51ac6a33", + "selectorBytes": [ + 81, + 172, + 106, + 51 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" } ] } \ No newline at end of file diff --git a/crates/cheatcodes/assets/cheatcodes.schema.json b/crates/cheatcodes/assets/cheatcodes.schema.json index 7b68420ad63bf..cd66ecdc42ba7 100644 --- a/crates/cheatcodes/assets/cheatcodes.schema.json +++ b/crates/cheatcodes/assets/cheatcodes.schema.json @@ -298,6 +298,13 @@ "json" ] }, + { + "description": "Utility cheatcodes that deal with parsing values from and converting values to TOML.\n\nExamples: `parseToml`, `writeToml`.\n\nSafety: safe.", + "type": "string", + "enum": [ + "toml" + ] + }, { "description": "Generic, uncategorized utilities.\n\nExamples: `toString`, `parse*`, `serialize*`.\n\nSafety: safe.", "type": "string", @@ -382,6 +389,13 @@ "enum": [ "removed" ] + }, + { + "description": "The cheatcode is only used internally for foundry testing and may be changed or removed at any time.\n\nUse of internal cheatcodes is discouraged and will result in a warning.", + "type": "string", + "enum": [ + "internal" + ] } ] }, diff --git a/crates/cheatcodes/spec/src/cheatcode.rs b/crates/cheatcodes/spec/src/cheatcode.rs index f7e0c87b306f2..95aa9aa476578 100644 --- a/crates/cheatcodes/spec/src/cheatcode.rs +++ b/crates/cheatcodes/spec/src/cheatcode.rs @@ -50,6 +50,11 @@ pub enum Status { /// /// Use of removed cheatcodes will result in a hard error. Removed, + /// The cheatcode is only used internally for foundry testing and may be changed or removed at + /// any time. + /// + /// Use of internal cheatcodes is discouraged and will result in a warning. + Internal, } /// Cheatcode groups. @@ -103,6 +108,12 @@ pub enum Group { /// /// Safety: safe. Json, + /// Utility cheatcodes that deal with parsing values from and converting values to TOML. + /// + /// Examples: `parseToml`, `writeToml`. + /// + /// Safety: safe. + Toml, /// Generic, uncategorized utilities. /// /// Examples: `toString`, `parse*`, `serialize*`. @@ -125,6 +136,7 @@ impl Group { Self::Environment | Self::String | Self::Json | + Self::Toml | Self::Utilities => Some(Safety::Safe), } } @@ -140,6 +152,7 @@ impl Group { Self::Environment => "environment", Self::String => "string", Self::Json => "json", + Self::Toml => "toml", Self::Utilities => "utilities", } } diff --git a/crates/cheatcodes/spec/src/lib.rs b/crates/cheatcodes/spec/src/lib.rs index 9e0cf12dde025..b2a267f8d716a 100644 --- a/crates/cheatcodes/spec/src/lib.rs +++ b/crates/cheatcodes/spec/src/lib.rs @@ -83,10 +83,12 @@ impl Cheatcodes<'static> { Vm::ChainInfo::STRUCT.clone(), Vm::AccountAccess::STRUCT.clone(), Vm::StorageAccess::STRUCT.clone(), + Vm::Gas::STRUCT.clone(), ]), enums: Cow::Owned(vec![ Vm::CallerMode::ENUM.clone(), Vm::AccountAccessKind::ENUM.clone(), + Vm::ForgeContext::ENUM.clone(), ]), errors: Vm::VM_ERRORS.iter().map(|&x| x.clone()).collect(), events: Cow::Borrowed(&[]), @@ -120,14 +122,17 @@ mod tests { } fn sol_iface() -> String { - let cheats = Cheatcodes::new().to_string().trim().replace('\n', "\n "); + let mut cheats = Cheatcodes::new(); + cheats.errors = Default::default(); // Skip errors to allow <0.8.4. + let cheats = cheats.to_string().trim().replace('\n', "\n "); format!( "\ // Automatically generated from `foundry-cheatcodes` Vm definitions. Do not modify manually. // This interface is just for internal testing purposes. Use `forge-std` instead. // SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.4; +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; interface Vm {{ {cheats} @@ -164,7 +169,7 @@ interface Vm {{ eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display()); if std::env::var("CI").is_ok() { - eprintln!(" NOTE: run `cargo test` locally and commit the updated files\n"); + eprintln!(" NOTE: run `cargo cheats` locally and commit the updated files\n"); } if let Some(parent) = file.parent() { let _ = fs::create_dir_all(parent); diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 676640a2e3052..4ff0b3e9ff417 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -3,6 +3,7 @@ #![allow(missing_docs)] use super::*; +use crate::Vm::ForgeContext; use alloy_sol_types::sol; use foundry_macros::Cheatcode; @@ -55,6 +56,36 @@ interface Vm { SelfDestruct, /// Synthetic access indicating the current context has resumed after a previous sub-context (AccountAccess). Resume, + /// The account's balance was read. + Balance, + /// The account's codesize was read. + Extcodesize, + /// The account's codehash was read. + Extcodehash, + /// The account's code was copied. + Extcodecopy, + } + + /// Forge execution contexts. + enum ForgeContext { + /// Test group execution context (test, coverage or snapshot). + TestGroup, + /// `forge test` execution context. + Test, + /// `forge coverage` execution context. + Coverage, + /// `forge snapshot` execution context. + Snapshot, + /// Script group execution context (dry run, broadcast or resume). + ScriptGroup, + /// `forge script` execution context. + ScriptDryRun, + /// `forge script --broadcast` execution context. + ScriptBroadcast, + /// `forge script --resume` execution context. + ScriptResume, + /// Unknown `forge` execution context. + Unknown, } /// An Ethereum log. Returned by `getRecordedLogs`. @@ -67,6 +98,20 @@ interface Vm { address emitter; } + /// Gas used. Returned by `lastCallGas`. + struct Gas { + /// The gas limit of the call. + uint64 gasLimit; + /// The total gas used. + uint64 gasTotalUsed; + /// The amount of gas used for memory expansion. + uint64 gasMemoryUsed; + /// The amount of gas refunded. + int64 gasRefunded; + /// The amount of gas remaining. + uint64 gasRemaining; + } + /// An RPC URL and its alias. Returned by `rpcUrlStructs`. struct Rpc { /// The alias of the RPC URL. @@ -163,6 +208,22 @@ interface Vm { uint256 chainId; } + /// The storage accessed during an `AccountAccess`. + struct StorageAccess { + /// The account whose storage was accessed. + address account; + /// The slot that was accessed. + bytes32 slot; + /// If the access was a write. + bool isWrite; + /// The previous value of the slot. + bytes32 previousValue; + /// The new value of the slot. + bytes32 newValue; + /// If the access was reverted. + bool reverted; + } + /// The result of a `stopAndReturnStateDiff` call. struct AccountAccess { /// The chain and fork the access occurred. @@ -197,22 +258,8 @@ interface Vm { bool reverted; /// An ordered list of storage accesses made during an account access operation. StorageAccess[] storageAccesses; - } - - /// The storage accessed during an `AccountAccess`. - struct StorageAccess { - /// The account whose storage was accessed. - address account; - /// The slot that was accessed. - bytes32 slot; - /// If the access was a write. - bool isWrite; - /// The previous value of the slot. - bytes32 previousValue; - /// The new value of the slot. - bytes32 newValue; - /// If the access was reverted. - bool reverted; + /// Call depth traversed during the recording of state differences + uint64 depth; } // ======== EVM ======== @@ -221,6 +268,10 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function addr(uint256 privateKey) external pure returns (address keyAddr); + /// Dump a genesis JSON file's `allocs` to disk. + #[cheatcode(group = Evm, safety = Unsafe)] + function dumpState(string calldata pathToStateJson) external; + /// Gets the nonce of an account. #[cheatcode(group = Evm, safety = Safe)] function getNonce(address account) external view returns (uint64 nonce); @@ -233,10 +284,30 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function loadAllocs(string calldata pathToAllocsJson) external; - /// Signs data. + /// Signs `digest` with `privateKey` using the secp256k1 curve. #[cheatcode(group = Evm, safety = Safe)] function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + /// Signs `digest` with signer provided to script using the secp256k1 curve. + /// + /// If `--sender` is provided, the signer with provided address is used, otherwise, + /// if exactly one signer is provided to the script, that signer is used. + /// + /// Raises error if signer passed through `--sender` does not match any unlocked signers or + /// if `--sender` is not provided and not exactly one signer is passed to the script. + #[cheatcode(group = Evm, safety = Safe)] + function sign(bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + + /// Signs `digest` with signer provided to script using the secp256k1 curve. + /// + /// Raises error if none of the signers passed into the script have provided address. + #[cheatcode(group = Evm, safety = Safe)] + function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + + /// Signs `digest` with `privateKey` using the secp256r1 curve. + #[cheatcode(group = Evm, safety = Safe)] + function signP256(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 s); + // -------- Record Storage -------- /// Records all storage reads and writes. @@ -254,7 +325,7 @@ interface Vm { /// Returns an ordered array of all account accesses from a `vm.startStateDiffRecording` session. #[cheatcode(group = Evm, safety = Safe)] - function stopAndReturnStateDiff() external returns (AccountAccess[] memory accesses); + function stopAndReturnStateDiff() external returns (AccountAccess[] memory accountAccesses); // -------- Recording Map Writes -------- @@ -306,11 +377,35 @@ interface Vm { /// If used on unsupported EVM versions it will revert. #[cheatcode(group = Evm, safety = Unsafe)] function prevrandao(bytes32 newPrevrandao) external; + /// Sets `block.prevrandao`. + /// Not available on EVM versions before Paris. Use `difficulty` instead. + /// If used on unsupported EVM versions it will revert. + #[cheatcode(group = Evm, safety = Unsafe)] + function prevrandao(uint256 newPrevrandao) external; + + /// Sets the blobhashes in the transaction. + /// Not available on EVM versions before Cancun. + /// If used on unsupported EVM versions it will revert. + #[cheatcode(group = Evm, safety = Unsafe)] + function blobhashes(bytes32[] calldata hashes) external; + + /// Gets the blockhashes from the current transaction. + /// Not available on EVM versions before Cancun. + /// If used on unsupported EVM versions it will revert. + #[cheatcode(group = Evm, safety = Unsafe)] + function getBlobhashes() external view returns (bytes32[] memory hashes); /// Sets `block.height`. #[cheatcode(group = Evm, safety = Unsafe)] function roll(uint256 newHeight) external; + /// Gets the current `block.number`. + /// You should use this instead of `block.number` if you use `vm.roll`, as `block.number` is assumed to be constant across a transaction, + /// and as a result will get optimized out by the compiler. + /// See https://github.com/foundry-rs/foundry/issues/6180 + #[cheatcode(group = Evm, safety = Safe)] + function getBlockNumber() external view returns (uint256 height); + /// Sets `tx.gasprice`. #[cheatcode(group = Evm, safety = Unsafe)] function txGasPrice(uint256 newGasPrice) external; @@ -319,6 +414,24 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function warp(uint256 newTimestamp) external; + /// Gets the current `block.timestamp`. + /// You should use this instead of `block.timestamp` if you use `vm.warp`, as `block.timestamp` is assumed to be constant across a transaction, + /// and as a result will get optimized out by the compiler. + /// See https://github.com/foundry-rs/foundry/issues/6180 + #[cheatcode(group = Evm, safety = Safe)] + function getBlockTimestamp() external view returns (uint256 timestamp); + + /// Sets `block.blobbasefee` + #[cheatcode(group = Evm, safety = Unsafe)] + function blobBaseFee(uint256 newBlobBaseFee) external; + + /// Gets the current `block.blobbasefee`. + /// You should use this instead of `block.blobbasefee` if you use `vm.blobBaseFee`, as `block.blobbasefee` is assumed to be constant across a transaction, + /// and as a result will get optimized out by the compiler. + /// See https://github.com/foundry-rs/foundry/issues/6180 + #[cheatcode(group = Evm, safety = Safe)] + function getBlobBaseFee() external view returns (uint256 blobBaseFee); + // -------- Account State -------- /// Sets an address' balance. @@ -346,7 +459,7 @@ interface Vm { function store(address target, bytes32 slot, bytes32 value) external; /// Marks the slots of an account and the account address as cold. - #[cheatcode(group = Evm, safety = Unsafe)] + #[cheatcode(group = Evm, safety = Unsafe, status = Experimental)] function cool(address target) external; // -------- Call Manipulation -------- @@ -413,10 +526,34 @@ interface Vm { /// Revert the state of the EVM to a previous snapshot /// Takes the snapshot ID to revert to. - /// This deletes the snapshot and all snapshots taken after the given snapshot ID. + /// + /// Returns `true` if the snapshot was successfully reverted. + /// Returns `false` if the snapshot does not exist. + /// + /// **Note:** This does not automatically delete the snapshot. To delete the snapshot use `deleteSnapshot`. #[cheatcode(group = Evm, safety = Unsafe)] function revertTo(uint256 snapshotId) external returns (bool success); + /// Revert the state of the EVM to a previous snapshot and automatically deletes the snapshots + /// Takes the snapshot ID to revert to. + /// + /// Returns `true` if the snapshot was successfully reverted and deleted. + /// Returns `false` if the snapshot does not exist. + #[cheatcode(group = Evm, safety = Unsafe)] + function revertToAndDelete(uint256 snapshotId) external returns (bool success); + + /// Removes the snapshot with the given ID created by `snapshot`. + /// Takes the snapshot ID to delete. + /// + /// Returns `true` if the snapshot was successfully deleted. + /// Returns `false` if the snapshot does not exist. + #[cheatcode(group = Evm, safety = Unsafe)] + function deleteSnapshot(uint256 snapshotId) external returns (bool success); + + /// Removes _all_ snapshots previously created by `snapshot`. + #[cheatcode(group = Evm, safety = Unsafe)] + function deleteSnapshots() external; + // -------- Forking -------- // --- Creation and Selection --- @@ -478,7 +615,7 @@ interface Vm { /// Gets all the logs according to specified filter. #[cheatcode(group = Evm, safety = Safe)] - function eth_getLogs(uint256 fromBlock, uint256 toBlock, address addr, bytes32[] memory topics) + function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] memory topics) external returns (EthGetLogs[] memory logs); @@ -524,6 +661,7 @@ interface Vm { function getRecordedLogs() external returns (Log[] memory logs); // -------- Gas Metering -------- + // It's recommend to use the `noGasMetering` modifier included with forge-std, instead of // using these functions directly. @@ -535,6 +673,12 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function resumeGasMetering() external; + // -------- Gas Measurement -------- + + /// Gets the gas used in the last call. + #[cheatcode(group = Evm, safety = Safe)] + function lastCallGas() external view returns (Gas memory gas); + // ======== Test Assertions and Utilities ======== /// If the condition is false, discard this run's fuzz inputs and generate new ones. @@ -632,11 +776,27 @@ interface Vm { #[cheatcode(group = Testing, safety = Unsafe)] function expectRevert(bytes calldata revertData) external; + /// Expects an error on next cheatcode call with any revert data. + #[cheatcode(group = Testing, safety = Unsafe, status = Internal)] + function _expectCheatcodeRevert() external; + + /// Expects an error on next cheatcode call that starts with the revert data. + #[cheatcode(group = Testing, safety = Unsafe, status = Internal)] + function _expectCheatcodeRevert(bytes4 revertData) external; + + /// Expects an error on next cheatcode call that exactly matches the revert data. + #[cheatcode(group = Testing, safety = Unsafe, status = Internal)] + function _expectCheatcodeRevert(bytes calldata revertData) external; + /// Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the current subcontext. If any other /// memory is written to, the test will fail. Can be called multiple times to add more ranges to the set. #[cheatcode(group = Testing, safety = Unsafe)] function expectSafeMemory(uint64 min, uint64 max) external; + /// Stops all safe memory expectation in the current subcontext. + #[cheatcode(group = Testing, safety = Unsafe)] + function stopExpectSafeMemory() external; + /// Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the next created subcontext. /// If any other memory is written to, the test will fail. Can be called multiple times to add more ranges /// to the set. @@ -647,6 +807,552 @@ interface Vm { #[cheatcode(group = Testing, safety = Unsafe)] function skip(bool skipTest) external; + /// Asserts that the given condition is true. + #[cheatcode(group = Testing, safety = Safe)] + function assertTrue(bool condition) external pure; + + /// Asserts that the given condition is true and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertTrue(bool condition, string calldata error) external pure; + + /// Asserts that the given condition is false. + #[cheatcode(group = Testing, safety = Safe)] + function assertFalse(bool condition) external pure; + + /// Asserts that the given condition is false and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertFalse(bool condition, string calldata error) external pure; + + /// Asserts that two `bool` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bool left, bool right) external pure; + + /// Asserts that two `bool` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bool left, bool right, string calldata error) external pure; + + /// Asserts that two `uint256` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(uint256 left, uint256 right) external pure; + + /// Asserts that two `uint256` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(uint256 left, uint256 right, string calldata error) external pure; + + /// Asserts that two `int256` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(int256 left, int256 right) external pure; + + /// Asserts that two `int256` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(int256 left, int256 right, string calldata error) external pure; + + /// Asserts that two `address` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(address left, address right) external pure; + + /// Asserts that two `address` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(address left, address right, string calldata error) external pure; + + /// Asserts that two `bytes32` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bytes32 left, bytes32 right) external pure; + + /// Asserts that two `bytes32` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bytes32 left, bytes32 right, string calldata error) external pure; + + /// Asserts that two `string` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(string calldata left, string calldata right) external pure; + + /// Asserts that two `string` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(string calldata left, string calldata right, string calldata error) external pure; + + /// Asserts that two `bytes` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bytes calldata left, bytes calldata right) external pure; + + /// Asserts that two `bytes` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bytes calldata left, bytes calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `bool` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bool[] calldata left, bool[] calldata right) external pure; + + /// Asserts that two arrays of `bool` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `uint256 values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(uint256[] calldata left, uint256[] calldata right) external pure; + + /// Asserts that two arrays of `uint256` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `int256` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(int256[] calldata left, int256[] calldata right) external pure; + + /// Asserts that two arrays of `int256` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `address` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(address[] calldata left, address[] calldata right) external pure; + + /// Asserts that two arrays of `address` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(address[] calldata left, address[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `bytes32` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bytes32[] calldata left, bytes32[] calldata right) external pure; + + /// Asserts that two arrays of `bytes32` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `string` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(string[] calldata left, string[] calldata right) external pure; + + /// Asserts that two arrays of `string` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(string[] calldata left, string[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `bytes` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bytes[] calldata left, bytes[] calldata right) external pure; + + /// Asserts that two arrays of `bytes` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; + + /// Asserts that two `uint256` values are equal, formatting them with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + + /// Asserts that two `uint256` values are equal, formatting them with decimals in failure message. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + + /// Asserts that two `int256` values are equal, formatting them with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertEqDecimal(int256 left, int256 right, uint256 decimals) external pure; + + /// Asserts that two `int256` values are equal, formatting them with decimals in failure message. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + + /// Asserts that two `bool` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bool left, bool right) external pure; + + /// Asserts that two `bool` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bool left, bool right, string calldata error) external pure; + + /// Asserts that two `uint256` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(uint256 left, uint256 right) external pure; + + /// Asserts that two `uint256` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(uint256 left, uint256 right, string calldata error) external pure; + + /// Asserts that two `int256` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(int256 left, int256 right) external pure; + + /// Asserts that two `int256` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(int256 left, int256 right, string calldata error) external pure; + + /// Asserts that two `address` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(address left, address right) external pure; + + /// Asserts that two `address` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(address left, address right, string calldata error) external pure; + + /// Asserts that two `bytes32` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bytes32 left, bytes32 right) external pure; + + /// Asserts that two `bytes32` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bytes32 left, bytes32 right, string calldata error) external pure; + + /// Asserts that two `string` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(string calldata left, string calldata right) external pure; + + /// Asserts that two `string` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(string calldata left, string calldata right, string calldata error) external pure; + + /// Asserts that two `bytes` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bytes calldata left, bytes calldata right) external pure; + + /// Asserts that two `bytes` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bytes calldata left, bytes calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `bool` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bool[] calldata left, bool[] calldata right) external pure; + + /// Asserts that two arrays of `bool` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `uint256` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(uint256[] calldata left, uint256[] calldata right) external pure; + + /// Asserts that two arrays of `uint256` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `int256` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(int256[] calldata left, int256[] calldata right) external pure; + + /// Asserts that two arrays of `int256` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `address` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(address[] calldata left, address[] calldata right) external pure; + + /// Asserts that two arrays of `address` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(address[] calldata left, address[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `bytes32` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bytes32[] calldata left, bytes32[] calldata right) external pure; + + /// Asserts that two arrays of `bytes32` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `string` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(string[] calldata left, string[] calldata right) external pure; + + /// Asserts that two arrays of `string` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(string[] calldata left, string[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `bytes` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bytes[] calldata left, bytes[] calldata right) external pure; + + /// Asserts that two arrays of `bytes` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; + + /// Asserts that two `uint256` values are not equal, formatting them with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + + /// Asserts that two `uint256` values are not equal, formatting them with decimals in failure message. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + + /// Asserts that two `int256` values are not equal, formatting them with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEqDecimal(int256 left, int256 right, uint256 decimals) external pure; + + /// Asserts that two `int256` values are not equal, formatting them with decimals in failure message. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `uint256` values. Expects first value to be greater than second. + #[cheatcode(group = Testing, safety = Safe)] + function assertGt(uint256 left, uint256 right) external pure; + + /// Compares two `uint256` values. Expects first value to be greater than second. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertGt(uint256 left, uint256 right, string calldata error) external pure; + + /// Compares two `int256` values. Expects first value to be greater than second. + #[cheatcode(group = Testing, safety = Safe)] + function assertGt(int256 left, int256 right) external pure; + + /// Compares two `int256` values. Expects first value to be greater than second. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertGt(int256 left, int256 right, string calldata error) external pure; + + /// Compares two `uint256` values. Expects first value to be greater than second. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertGtDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + + /// Compares two `uint256` values. Expects first value to be greater than second. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `int256` values. Expects first value to be greater than second. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertGtDecimal(int256 left, int256 right, uint256 decimals) external pure; + + /// Compares two `int256` values. Expects first value to be greater than second. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `uint256` values. Expects first value to be greater than or equal to second. + #[cheatcode(group = Testing, safety = Safe)] + function assertGe(uint256 left, uint256 right) external pure; + + /// Compares two `uint256` values. Expects first value to be greater than or equal to second. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertGe(uint256 left, uint256 right, string calldata error) external pure; + + /// Compares two `int256` values. Expects first value to be greater than or equal to second. + #[cheatcode(group = Testing, safety = Safe)] + function assertGe(int256 left, int256 right) external pure; + + /// Compares two `int256` values. Expects first value to be greater than or equal to second. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertGe(int256 left, int256 right, string calldata error) external pure; + + /// Compares two `uint256` values. Expects first value to be greater than or equal to second. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertGeDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + + /// Compares two `uint256` values. Expects first value to be greater than or equal to second. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `int256` values. Expects first value to be greater than or equal to second. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertGeDecimal(int256 left, int256 right, uint256 decimals) external pure; + + /// Compares two `int256` values. Expects first value to be greater than or equal to second. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `uint256` values. Expects first value to be less than second. + #[cheatcode(group = Testing, safety = Safe)] + function assertLt(uint256 left, uint256 right) external pure; + + /// Compares two `uint256` values. Expects first value to be less than second. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertLt(uint256 left, uint256 right, string calldata error) external pure; + + /// Compares two `int256` values. Expects first value to be less than second. + #[cheatcode(group = Testing, safety = Safe)] + function assertLt(int256 left, int256 right) external pure; + + /// Compares two `int256` values. Expects first value to be less than second. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertLt(int256 left, int256 right, string calldata error) external pure; + + /// Compares two `uint256` values. Expects first value to be less than second. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertLtDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + + /// Compares two `uint256` values. Expects first value to be less than second. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `int256` values. Expects first value to be less than second. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertLtDecimal(int256 left, int256 right, uint256 decimals) external pure; + + /// Compares two `int256` values. Expects first value to be less than second. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `uint256` values. Expects first value to be less than or equal to second. + #[cheatcode(group = Testing, safety = Safe)] + function assertLe(uint256 left, uint256 right) external pure; + + /// Compares two `uint256` values. Expects first value to be less than or equal to second. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertLe(uint256 left, uint256 right, string calldata error) external pure; + + /// Compares two `int256` values. Expects first value to be less than or equal to second. + #[cheatcode(group = Testing, safety = Safe)] + function assertLe(int256 left, int256 right) external pure; + + /// Compares two `int256` values. Expects first value to be less than or equal to second. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertLe(int256 left, int256 right, string calldata error) external pure; + + /// Compares two `uint256` values. Expects first value to be less than or equal to second. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertLeDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + + /// Compares two `uint256` values. Expects first value to be less than or equal to second. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `int256` values. Expects first value to be less than or equal to second. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertLeDecimal(int256 left, int256 right, uint256 decimals) external pure; + + /// Compares two `int256` values. Expects first value to be less than or equal to second. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta) external pure; + + /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata error) external pure; + + /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta) external pure; + + /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata error) external pure; + + /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals) external pure; + + /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqAbsDecimal( + uint256 left, + uint256 right, + uint256 maxDelta, + uint256 decimals, + string calldata error + ) external pure; + + /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals) external pure; + + /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqAbsDecimal( + int256 left, + int256 right, + uint256 maxDelta, + uint256 decimals, + string calldata error + ) external pure; + + /// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. + /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta) external pure; + + /// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. + /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata error) external pure; + + /// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. + /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta) external pure; + + /// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. + /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata error) external pure; + + /// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. + /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqRelDecimal( + uint256 left, + uint256 right, + uint256 maxPercentDelta, + uint256 decimals + ) external pure; + + /// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. + /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqRelDecimal( + uint256 left, + uint256 right, + uint256 maxPercentDelta, + uint256 decimals, + string calldata error + ) external pure; + + /// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. + /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqRelDecimal( + int256 left, + int256 right, + uint256 maxPercentDelta, + uint256 decimals + ) external pure; + + /// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. + /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqRelDecimal( + int256 left, + int256 right, + uint256 maxPercentDelta, + uint256 decimals, + string calldata error + ) external pure; + // ======== OS and Filesystem ======== // -------- Metadata -------- @@ -765,11 +1471,13 @@ interface Vm { #[cheatcode(group = Filesystem)] function writeLine(string calldata path, string calldata data) external; - /// Gets the creation bytecode from an artifact file. Takes in the relative path to the json file. + /// Gets the creation bytecode from an artifact file. Takes in the relative path to the json file or the path to the + /// artifact in the form of :: where and parts are optional. #[cheatcode(group = Filesystem)] function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode); - /// Gets the deployed bytecode from an artifact file. Takes in the relative path to the json file. + /// Gets the deployed bytecode from an artifact file. Takes in the relative path to the json file or the path to the + /// artifact in the form of :: where and parts are optional. #[cheatcode(group = Filesystem)] function getDeployedCode(string calldata artifactPath) external view returns (bytes memory runtimeBytecode); @@ -783,12 +1491,34 @@ interface Vm { #[cheatcode(group = Filesystem)] function tryFfi(string[] calldata commandInput) external returns (FfiResult memory result); + // -------- User Interaction -------- + + /// Prompts the user for a string value in the terminal. + #[cheatcode(group = Filesystem)] + function prompt(string calldata promptText) external returns (string memory input); + + /// Prompts the user for a hidden string value in the terminal. + #[cheatcode(group = Filesystem)] + function promptSecret(string calldata promptText) external returns (string memory input); + + /// Prompts the user for an address in the terminal. + #[cheatcode(group = Filesystem)] + function promptAddress(string calldata promptText) external returns (address); + + /// Prompts the user for uint256 in the terminal. + #[cheatcode(group = Filesystem)] + function promptUint(string calldata promptText) external returns (uint256); + // ======== Environment Variables ======== /// Sets environment variables. #[cheatcode(group = Environment)] function setEnv(string calldata name, string calldata value) external; + /// Gets the environment variable `name` and returns true if it exists, else returns false. + #[cheatcode(group = Environment)] + function envExists(string calldata name) external view returns (bool result); + /// Gets the environment variable `name` and parses it as `bool`. /// Reverts if the variable was not found or could not be parsed. #[cheatcode(group = Environment)] @@ -851,94 +1581,102 @@ interface Vm { /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] - function envOr(string calldata name, bool defaultValue) external returns (bool value); + function envOr(string calldata name, bool defaultValue) external view returns (bool value); /// Gets the environment variable `name` and parses it as `uint256`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] - function envOr(string calldata name, uint256 defaultValue) external returns (uint256 value); + function envOr(string calldata name, uint256 defaultValue) external view returns (uint256 value); /// Gets the environment variable `name` and parses it as `int256`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] - function envOr(string calldata name, int256 defaultValue) external returns (int256 value); + function envOr(string calldata name, int256 defaultValue) external view returns (int256 value); /// Gets the environment variable `name` and parses it as `address`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] - function envOr(string calldata name, address defaultValue) external returns (address value); + function envOr(string calldata name, address defaultValue) external view returns (address value); /// Gets the environment variable `name` and parses it as `bytes32`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] - function envOr(string calldata name, bytes32 defaultValue) external returns (bytes32 value); + function envOr(string calldata name, bytes32 defaultValue) external view returns (bytes32 value); /// Gets the environment variable `name` and parses it as `string`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] - function envOr(string calldata name, string calldata defaultValue) external returns (string memory value); + function envOr(string calldata name, string calldata defaultValue) external view returns (string memory value); /// Gets the environment variable `name` and parses it as `bytes`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] - function envOr(string calldata name, bytes calldata defaultValue) external returns (bytes memory value); + function envOr(string calldata name, bytes calldata defaultValue) external view returns (bytes memory value); /// Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, string calldata delim, bool[] calldata defaultValue) - external + external view returns (bool[] memory value); /// Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, string calldata delim, uint256[] calldata defaultValue) - external + external view returns (uint256[] memory value); /// Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, string calldata delim, int256[] calldata defaultValue) - external + external view returns (int256[] memory value); /// Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, string calldata delim, address[] calldata defaultValue) - external + external view returns (address[] memory value); /// Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, string calldata delim, bytes32[] calldata defaultValue) - external + external view returns (bytes32[] memory value); /// Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, string calldata delim, string[] calldata defaultValue) - external + external view returns (string[] memory value); /// Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`. /// Reverts if the variable could not be parsed. /// Returns `defaultValue` if the variable was not found. #[cheatcode(group = Environment)] function envOr(string calldata name, string calldata delim, bytes[] calldata defaultValue) - external + external view returns (bytes[] memory value); + /// Returns true if `forge` command was executed in given context. + #[cheatcode(group = Environment)] + function isContext(ForgeContext context) external view returns (bool result); + // ======== Scripts ======== // -------- Broadcasting Transactions -------- - /// Using the address that calls the test contract, has the next call (at this call depth only) - /// create a transaction that can later be signed and sent onchain. + /// Has the next call (at this call depth only) create transactions that can later be signed and sent onchain. + /// + /// Broadcasting address is determined by checking the following in order: + /// 1. If `--sender` argument was provided, that address is used. + /// 2. If exactly one signer (e.g. private key, hw wallet, keystore) is set when `forge broadcast` is invoked, that signer is used. + /// 3. Otherwise, default foundry sender (1804c8AB1F12E6bbf3894d4083f33e07309d1f38) is used. #[cheatcode(group = Scripting)] function broadcast() external; @@ -952,8 +1690,12 @@ interface Vm { #[cheatcode(group = Scripting)] function broadcast(uint256 privateKey) external; - /// Using the address that calls the test contract, has all subsequent calls - /// (at this call depth only) create transactions that can later be signed and sent onchain. + /// Has all subsequent calls (at this call depth only) create transactions that can later be signed and sent onchain. + /// + /// Broadcasting address is determined by checking the following in order: + /// 1. If `--sender` argument was provided, that address is used. + /// 2. If exactly one signer (e.g. private key, hw wallet, keystore) is set when `forge broadcast` is invoked, that signer is used. + /// 3. Otherwise, default foundry sender (1804c8AB1F12E6bbf3894d4083f33e07309d1f38) is used. #[cheatcode(group = Scripting)] function startBroadcast() external; @@ -1013,6 +1755,27 @@ interface Vm { #[cheatcode(group = String)] function parseBool(string calldata stringifiedValue) external pure returns (bool parsedValue); + /// Converts the given `string` value to Lowercase. + #[cheatcode(group = String)] + function toLowercase(string calldata input) external pure returns (string memory output); + /// Converts the given `string` value to Uppercase. + #[cheatcode(group = String)] + function toUppercase(string calldata input) external pure returns (string memory output); + /// Trims leading and trailing whitespace from the given `string` value. + #[cheatcode(group = String)] + function trim(string calldata input) external pure returns (string memory output); + /// Replaces occurrences of `from` in the given `string` with `to`. + #[cheatcode(group = String)] + function replace(string calldata input, string calldata from, string calldata to) external pure returns (string memory output); + /// Splits the given `string` into an array of strings divided by the `delimiter`. + #[cheatcode(group = String)] + function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs); + /// Returns the index of the first occurrence of a `key` in an `input` string. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `key` is not found. + /// Returns 0 in case of an empty `key`. + #[cheatcode(group = String)] + function indexOf(string calldata input, string calldata key) external pure returns (uint256); + // ======== JSON Parsing and Manipulation ======== // -------- Reading -------- @@ -1020,9 +1783,13 @@ interface Vm { // NOTE: Please read https://book.getfoundry.sh/cheatcodes/parse-json to understand the // limitations and caveats of the JSON parsing cheats. + /// Checks if `key` exists in a JSON object + /// `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions. + #[cheatcode(group = Json, status = Deprecated)] + function keyExists(string calldata json, string calldata key) external view returns (bool); /// Checks if `key` exists in a JSON object. #[cheatcode(group = Json)] - function keyExists(string calldata json, string calldata key) external view returns (bool); + function keyExistsJson(string calldata json, string calldata key) external view returns (bool); /// ABI-encodes a JSON object. #[cheatcode(group = Json)] @@ -1112,6 +1879,11 @@ interface Vm { returns (string memory json); /// See `serializeJson`. #[cheatcode(group = Json)] + function serializeUintToHex(string calldata objectKey, string calldata valueKey, uint256 value) + external + returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] function serializeInt(string calldata objectKey, string calldata valueKey, int256 value) external returns (string memory json); @@ -1184,6 +1956,98 @@ interface Vm { #[cheatcode(group = Json)] function writeJson(string calldata json, string calldata path, string calldata valueKey) external; + // ======== TOML Parsing and Manipulation ======== + + // -------- Reading -------- + + // NOTE: Please read https://book.getfoundry.sh/cheatcodes/parse-toml to understand the + // limitations and caveats of the TOML parsing cheat. + + /// Checks if `key` exists in a TOML table. + #[cheatcode(group = Toml)] + function keyExistsToml(string calldata toml, string calldata key) external view returns (bool); + + /// ABI-encodes a TOML table. + #[cheatcode(group = Toml)] + function parseToml(string calldata toml) external pure returns (bytes memory abiEncodedData); + + /// ABI-encodes a TOML table at `key`. + #[cheatcode(group = Toml)] + function parseToml(string calldata toml, string calldata key) external pure returns (bytes memory abiEncodedData); + + // The following parseToml cheatcodes will do type coercion, for the type that they indicate. + // For example, parseTomlUint will coerce all values to a uint256. That includes stringified numbers '12.' + // and hex numbers '0xEF.'. + // Type coercion works ONLY for discrete values or arrays. That means that the key must return a value or array, not + // a TOML table. + + /// Parses a string of TOML data at `key` and coerces it to `uint256`. + #[cheatcode(group = Toml)] + function parseTomlUint(string calldata toml, string calldata key) external pure returns (uint256); + /// Parses a string of TOML data at `key` and coerces it to `uint256[]`. + #[cheatcode(group = Toml)] + function parseTomlUintArray(string calldata toml, string calldata key) external pure returns (uint256[] memory); + /// Parses a string of TOML data at `key` and coerces it to `int256`. + #[cheatcode(group = Toml)] + function parseTomlInt(string calldata toml, string calldata key) external pure returns (int256); + /// Parses a string of TOML data at `key` and coerces it to `int256[]`. + #[cheatcode(group = Toml)] + function parseTomlIntArray(string calldata toml, string calldata key) external pure returns (int256[] memory); + /// Parses a string of TOML data at `key` and coerces it to `bool`. + #[cheatcode(group = Toml)] + function parseTomlBool(string calldata toml, string calldata key) external pure returns (bool); + /// Parses a string of TOML data at `key` and coerces it to `bool[]`. + #[cheatcode(group = Toml)] + function parseTomlBoolArray(string calldata toml, string calldata key) external pure returns (bool[] memory); + /// Parses a string of TOML data at `key` and coerces it to `address`. + #[cheatcode(group = Toml)] + function parseTomlAddress(string calldata toml, string calldata key) external pure returns (address); + /// Parses a string of TOML data at `key` and coerces it to `address[]`. + #[cheatcode(group = Toml)] + function parseTomlAddressArray(string calldata toml, string calldata key) + external + pure + returns (address[] memory); + /// Parses a string of TOML data at `key` and coerces it to `string`. + #[cheatcode(group = Toml)] + function parseTomlString(string calldata toml, string calldata key) external pure returns (string memory); + /// Parses a string of TOML data at `key` and coerces it to `string[]`. + #[cheatcode(group = Toml)] + function parseTomlStringArray(string calldata toml, string calldata key) external pure returns (string[] memory); + /// Parses a string of TOML data at `key` and coerces it to `bytes`. + #[cheatcode(group = Toml)] + function parseTomlBytes(string calldata toml, string calldata key) external pure returns (bytes memory); + /// Parses a string of TOML data at `key` and coerces it to `bytes[]`. + #[cheatcode(group = Toml)] + function parseTomlBytesArray(string calldata toml, string calldata key) external pure returns (bytes[] memory); + /// Parses a string of TOML data at `key` and coerces it to `bytes32`. + #[cheatcode(group = Toml)] + function parseTomlBytes32(string calldata toml, string calldata key) external pure returns (bytes32); + /// Parses a string of TOML data at `key` and coerces it to `bytes32[]`. + #[cheatcode(group = Toml)] + function parseTomlBytes32Array(string calldata toml, string calldata key) + external + pure + returns (bytes32[] memory); + + /// Returns an array of all the keys in a TOML table. + #[cheatcode(group = Toml)] + function parseTomlKeys(string calldata toml, string calldata key) external pure returns (string[] memory keys); + + // -------- Writing -------- + + // NOTE: Please read https://book.getfoundry.sh/cheatcodes/write-toml to understand how + // to use the TOML writing cheat. + + /// Takes serialized JSON, converts to TOML and write a serialized TOML to a file. + #[cheatcode(group = Toml)] + function writeToml(string calldata json, string calldata path) external; + + /// Takes serialized JSON, converts to TOML and write a serialized TOML table to an **existing** TOML file, replacing a value with key = + /// This is useful to replace a specific value of a TOML file, without having to parse the entire thing. + #[cheatcode(group = Toml)] + function writeToml(string calldata json, string calldata path, string calldata valueKey) external; + // -------- Key Management -------- /// Derives a private key from the name, labels the account with that name, and returns the wallet. @@ -1244,7 +2108,7 @@ interface Vm { /// Gets the label for the specified address. #[cheatcode(group = Utilities)] - function getLabel(address account) external returns (string memory currentLabel); + function getLabel(address account) external view returns (string memory currentLabel); /// Compute the address a contract will be deployed at for a given deployer address and nonce. #[cheatcode(group = Utilities)] @@ -1257,5 +2121,48 @@ interface Vm { /// Compute the address of a contract created with CREATE2 using the default CREATE2 deployer. #[cheatcode(group = Utilities)] function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external pure returns (address); + + /// Encodes a `bytes` value to a base64 string. + #[cheatcode(group = Utilities)] + function toBase64(bytes calldata data) external pure returns (string memory); + + /// Encodes a `string` value to a base64 string. + #[cheatcode(group = Utilities)] + function toBase64(string calldata data) external pure returns (string memory); + + /// Encodes a `bytes` value to a base64url string. + #[cheatcode(group = Utilities)] + function toBase64URL(bytes calldata data) external pure returns (string memory); + + /// Encodes a `string` value to a base64url string. + #[cheatcode(group = Utilities)] + function toBase64URL(string calldata data) external pure returns (string memory); +} } + +impl PartialEq for ForgeContext { + // Handles test group case (any of test, coverage or snapshot) + // and script group case (any of dry run, broadcast or resume). + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (_, &ForgeContext::TestGroup) => { + self == &ForgeContext::Test || + self == &ForgeContext::Snapshot || + self == &ForgeContext::Coverage + } + (_, &ForgeContext::ScriptGroup) => { + self == &ForgeContext::ScriptDryRun || + self == &ForgeContext::ScriptBroadcast || + self == &ForgeContext::ScriptResume + } + (&ForgeContext::Test, &ForgeContext::Test) | + (&ForgeContext::Snapshot, &ForgeContext::Snapshot) | + (&ForgeContext::Coverage, &ForgeContext::Coverage) | + (&ForgeContext::ScriptDryRun, &ForgeContext::ScriptDryRun) | + (&ForgeContext::ScriptBroadcast, &ForgeContext::ScriptBroadcast) | + (&ForgeContext::ScriptResume, &ForgeContext::ScriptResume) | + (&ForgeContext::Unknown, &ForgeContext::Unknown) => true, + _ => false, + } + } } diff --git a/crates/cheatcodes/src/base64.rs b/crates/cheatcodes/src/base64.rs new file mode 100644 index 0000000000000..4aa4ba74a0e4b --- /dev/null +++ b/crates/cheatcodes/src/base64.rs @@ -0,0 +1,31 @@ +use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; +use alloy_sol_types::SolValue; +use base64::prelude::*; + +impl Cheatcode for toBase64_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { data } = self; + Ok(BASE64_STANDARD.encode(data).abi_encode()) + } +} + +impl Cheatcode for toBase64_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { data } = self; + Ok(BASE64_STANDARD.encode(data).abi_encode()) + } +} + +impl Cheatcode for toBase64URL_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { data } = self; + Ok(BASE64_URL_SAFE.encode(data).abi_encode()) + } +} + +impl Cheatcode for toBase64URL_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { data } = self; + Ok(BASE64_URL_SAFE.encode(data).abi_encode()) + } +} diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index b3aff6b62ed82..64e02070ac480 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -1,13 +1,20 @@ use super::Result; -use crate::Vm::Rpc; -use foundry_common::fs::normalize_path; +use crate::{script::ScriptWallets, Vm::Rpc}; +use alloy_primitives::Address; +use foundry_common::{fs::normalize_path, ContractsByArtifact}; use foundry_compilers::{utils::canonicalize, ProjectPathsConfig}; use foundry_config::{ cache::StorageCachingConfig, fs_permissions::FsAccessKind, Config, FsPermissions, ResolvedRpcEndpoints, }; use foundry_evm_core::opts::EvmOpts; -use std::path::{Path, PathBuf}; +use semver::Version; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + sync::Arc, + time::Duration, +}; /// Additional, configurable context the `Cheatcodes` inspector has access to /// @@ -16,6 +23,10 @@ use std::path::{Path, PathBuf}; pub struct CheatsConfig { /// Whether the FFI cheatcode is enabled. pub ffi: bool, + /// Use the create 2 factory in all cases including tests and non-broadcasting scripts. + pub always_use_create_2_factory: bool, + /// Sets a timeout for vm.prompt cheatcodes + pub prompt_timeout: Duration, /// RPC storage caching settings determines what chains and endpoints to cache pub rpc_storage_caching: StorageCachingConfig, /// All known endpoints and their aliases @@ -30,11 +41,27 @@ pub struct CheatsConfig { pub allowed_paths: Vec, /// How the evm was configured by the user pub evm_opts: EvmOpts, + /// Address labels from config + pub labels: HashMap, + /// Script wallets + pub script_wallets: Option, + /// Artifacts which are guaranteed to be fresh (either recompiled or cached). + /// If Some, `vm.getDeployedCode` invocations are validated to be in scope of this list. + /// If None, no validation is performed. + pub available_artifacts: Option>, + /// Version of the script/test contract which is currently running. + pub running_version: Option, } impl CheatsConfig { /// Extracts the necessary settings from the Config - pub fn new(config: &Config, evm_opts: EvmOpts) -> Self { + pub fn new( + config: &Config, + evm_opts: EvmOpts, + available_artifacts: Option>, + script_wallets: Option, + running_version: Option, + ) -> Self { let mut allowed_paths = vec![config.__root.0.clone()]; allowed_paths.extend(config.libs.clone()); allowed_paths.extend(config.allow_paths.clone()); @@ -42,8 +69,14 @@ impl CheatsConfig { let rpc_endpoints = config.rpc_endpoints.clone().resolved(); trace!(?rpc_endpoints, "using resolved rpc endpoints"); + // If user explicitly disabled safety checks, do not set available_artifacts + let available_artifacts = + if config.unchecked_cheatcode_artifacts { None } else { available_artifacts }; + Self { ffi: evm_opts.ffi, + always_use_create_2_factory: evm_opts.always_use_create_2_factory, + prompt_timeout: Duration::from_secs(config.prompt_timeout), rpc_storage_caching: config.rpc_storage_caching.clone(), rpc_endpoints, paths: config.project_paths(), @@ -51,6 +84,10 @@ impl CheatsConfig { root: config.__root.0.clone(), allowed_paths, evm_opts, + labels: config.labels.clone(), + script_wallets, + available_artifacts, + running_version, } } @@ -118,13 +155,15 @@ impl CheatsConfig { /// /// If `url_or_alias` is a known alias in the `ResolvedRpcEndpoints` then it returns the /// corresponding URL of that alias. otherwise this assumes `url_or_alias` is itself a URL - /// if it starts with a `http` or `ws` scheme + /// if it starts with a `http` or `ws` scheme. + /// + /// If the url is a path to an existing file, it is also considered a valid RPC URL, IPC path. /// /// # Errors /// /// - Returns an error if `url_or_alias` is a known alias but references an unresolved env var. /// - Returns an error if `url_or_alias` is not an alias but does not start with a `http` or - /// `scheme` + /// `ws` `scheme` and is not a path to an existing file pub fn rpc_url(&self, url_or_alias: &str) -> Result { match self.rpc_endpoints.get(url_or_alias) { Some(Ok(url)) => Ok(url.clone()), @@ -133,7 +172,12 @@ impl CheatsConfig { err.try_resolve().map_err(Into::into) } None => { - if url_or_alias.starts_with("http") || url_or_alias.starts_with("ws") { + // check if it's a URL or a path to an existing file to an ipc socket + if url_or_alias.starts_with("http") || + url_or_alias.starts_with("ws") || + // check for existing ipc file + Path::new(url_or_alias).exists() + { Ok(url_or_alias.into()) } else { Err(fmt_err!("invalid rpc url: {url_or_alias}")) @@ -157,6 +201,8 @@ impl Default for CheatsConfig { fn default() -> Self { Self { ffi: false, + always_use_create_2_factory: false, + prompt_timeout: Duration::from_secs(120), rpc_storage_caching: Default::default(), rpc_endpoints: Default::default(), paths: ProjectPathsConfig::builder().build_with_root("./"), @@ -164,6 +210,10 @@ impl Default for CheatsConfig { root: Default::default(), allowed_paths: vec![], evm_opts: Default::default(), + labels: Default::default(), + script_wallets: None, + available_artifacts: Default::default(), + running_version: Default::default(), } } } @@ -177,6 +227,9 @@ mod tests { CheatsConfig::new( &Config { __root: PathBuf::from(root).into(), fs_permissions, ..Default::default() }, Default::default(), + None, + None, + None, ) } diff --git a/crates/cheatcodes/src/env.rs b/crates/cheatcodes/src/env.rs index b4a4a9a0c1f96..b8245dda1d00a 100644 --- a/crates/cheatcodes/src/env.rs +++ b/crates/cheatcodes/src/env.rs @@ -2,9 +2,11 @@ use crate::{string, Cheatcode, Cheatcodes, Error, Result, Vm::*}; use alloy_dyn_abi::DynSolType; -use alloy_primitives::Bytes; use alloy_sol_types::SolValue; -use std::env; +use std::{env, sync::OnceLock}; + +/// Stores the forge execution context for the duration of the program. +static FORGE_CONTEXT: OnceLock = OnceLock::new(); impl Cheatcode for setEnvCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { @@ -24,6 +26,13 @@ impl Cheatcode for setEnvCall { } } +impl Cheatcode for envExistsCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name } = self; + Ok(env::var(name).is_ok().abi_encode()) + } +} + impl Cheatcode for envBool_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name } = self; @@ -230,11 +239,24 @@ impl Cheatcode for envOr_12Call { impl Cheatcode for envOr_13Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; - let default = defaultValue.iter().map(|vec| vec.clone().into()).collect::>(); + let default = defaultValue.to_vec(); env_array_default(name, delim, &default, &DynSolType::Bytes) } } +impl Cheatcode for isContextCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { context } = self; + Ok((FORGE_CONTEXT.get() == Some(context)).abi_encode()) + } +} + +/// Set `forge` command current execution context for the duration of the program. +/// Execution context is immutable, subsequent calls of this function won't change the context. +pub fn set_execution_context(context: ForgeContext) { + let _ = FORGE_CONTEXT.set(context); +} + fn env(key: &str, ty: &DynSolType) -> Result { get_env(key).and_then(|val| string::parse(&val, ty).map_err(map_env_err(key, &val))) } @@ -267,20 +289,14 @@ fn get_env(key: &str) -> Result { /// doesn't leak the value. fn map_env_err<'a>(key: &'a str, value: &'a str) -> impl FnOnce(Error) -> Error + 'a { move |e| { - let e = e.to_string(); // failed parsing \"xy(123)\" as type `uint256`: parser error:\nxy(123)\n ^\nexpected at - // least one digit - let mut e = e.as_str(); - // cut off the message to not leak the value - let sep = if let Some(idx) = e.rfind(" as type `") { - e = &e[idx..]; - "" - } else { - ": " - }; - // ensure we're also removing the value from the underlying alloy parser error message, See - // [alloy_dyn_abi::parser::Error::parser] - let e = e.replacen(&format!("\n{value}\n"), &format!("\n${key}\n"), 1); - fmt_err!("failed parsing ${key}{sep}{e}") + // failed parsing as type `uint256`: parser error: + // + // ^ + // expected at least one digit + let mut e = e.to_string(); + e = e.replacen(&format!("\"{value}\""), &format!("${key}"), 1); + e = e.replacen(&format!("\n{value}\n"), &format!("\n${key}\n"), 1); + Error::from(e) } } @@ -291,12 +307,11 @@ mod tests { #[test] fn parse_env_uint() { let key = "parse_env_uint"; - let value = "xy(123)"; + let value = "t"; env::set_var(key, value); let err = env(key, &DynSolType::Uint(256)).unwrap_err().to_string(); - assert!(!err.contains(value)); - assert_eq!(err, "failed parsing $parse_env_uint as type `uint256`: parser error:\n$parse_env_uint\n ^\nexpected at least one digit"); + assert_eq!(err.matches("$parse_env_uint").count(), 2, "{err:?}"); env::remove_var(key); } } diff --git a/crates/cheatcodes/src/error.rs b/crates/cheatcodes/src/error.rs index 80ab7324a793d..508e7173ee770 100644 --- a/crates/cheatcodes/src/error.rs +++ b/crates/cheatcodes/src/error.rs @@ -1,11 +1,14 @@ use crate::Vm; use alloy_primitives::{Address, Bytes}; +use alloy_signer::Error as SignerError; +use alloy_signer_wallet::WalletError; use alloy_sol_types::SolError; -use ethers_core::k256::ecdsa::signature::Error as SignatureError; -use ethers_signers::WalletError; use foundry_common::errors::FsPathError; use foundry_config::UnresolvedEnvVarError; use foundry_evm_core::backend::DatabaseError; +use foundry_wallets::error::WalletSignerError; +use k256::ecdsa::signature::Error as SignatureError; +use revm::primitives::EVMError; use std::{borrow::Cow, fmt}; /// Cheatcode result type. @@ -283,7 +286,7 @@ macro_rules! impl_from { impl_from!( alloy_sol_types::Error, - ethers_core::types::SignatureError, + alloy_primitives::SignatureError, FsPathError, hex::FromHexError, eyre::Error, @@ -297,8 +300,17 @@ impl_from!( std::string::FromUtf8Error, UnresolvedEnvVarError, WalletError, + SignerError, + WalletSignerError, ); +impl From> for Error { + #[inline] + fn from(err: EVMError) -> Self { + Self::display(DatabaseError::from(err)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 136cdb8f80481..6189980716b5a 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -1,17 +1,22 @@ //! Implementations of [`Evm`](crate::Group::Evm) cheatcodes. use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; -use alloy_primitives::{Address, Bytes, U256}; +use alloy_genesis::{Genesis, GenesisAccount}; +use alloy_primitives::{Address, Bytes, B256, U256}; use alloy_sol_types::SolValue; -use ethers_core::utils::{Genesis, GenesisAccount}; -use ethers_signers::Signer; -use foundry_common::{fs::read_json_file, types::ToAlloy}; -use foundry_evm_core::backend::DatabaseExt; +use foundry_common::fs::{read_json_file, write_json_file}; +use foundry_evm_core::{ + backend::{DatabaseExt, RevertSnapshotAction}, + constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS}, +}; use revm::{ primitives::{Account, Bytecode, SpecId, KECCAK_EMPTY}, - EVMData, + InnerEvmContext, +}; +use std::{ + collections::{BTreeMap, HashMap}, + path::Path, }; -use std::{collections::HashMap, path::Path}; mod fork; pub(crate) mod mapping; @@ -28,7 +33,7 @@ pub struct RecordAccess { } /// Records `deal` cheatcodes -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct DealRecord { /// Target of the deal. pub address: Address, @@ -42,7 +47,7 @@ impl Cheatcode for addrCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; let wallet = super::utils::parse_wallet(privateKey)?; - Ok(wallet.address().to_alloy().abi_encode()) + Ok(wallet.address().abi_encode()) } } @@ -57,8 +62,8 @@ impl Cheatcode for loadCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, slot } = *self; ensure_not_precompile!(&target, ccx); - ccx.data.journaled_state.load_account(target, ccx.data.db)?; - let (val, _) = ccx.data.journaled_state.sload(target, slot.into(), ccx.data.db)?; + ccx.ecx.load_account(target)?; + let (val, _) = ccx.ecx.sload(target, slot.into())?; Ok(val.abi_encode()) } } @@ -70,26 +75,96 @@ impl Cheatcode for loadAllocsCall { let path = Path::new(pathToAllocsJson); ensure!(path.exists(), "allocs file does not exist: {pathToAllocsJson}"); - // Let's first assume we're reading a genesis.json file. - let allocs: HashMap = match read_json_file::(path) { - Ok(genesis) => genesis.alloc.into_iter().map(|(k, g)| (k.to_alloy(), g)).collect(), - // If that fails, let's try reading a file with just the genesis accounts. - Err(_) => read_json_file(path)?, + // Let's first assume we're reading a file with only the allocs. + let allocs: BTreeMap = match read_json_file(path) { + Ok(allocs) => allocs, + Err(_) => { + // Let's try and read from a genesis file, and extract allocs. + let genesis = read_json_file::(path)?; + genesis.alloc + } }; // Then, load the allocs into the database. - ccx.data + ccx.ecx .db - .load_allocs(&allocs, &mut ccx.data.journaled_state) + .load_allocs(&allocs, &mut ccx.ecx.journaled_state) .map(|()| Vec::default()) .map_err(|e| fmt_err!("failed to load allocs: {e}")) } } +impl Cheatcode for dumpStateCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { pathToStateJson } = self; + let path = Path::new(pathToStateJson); + + // Do not include system account or empty accounts in the dump. + let skip = |key: &Address, val: &Account| { + key == &CHEATCODE_ADDRESS || + key == &CALLER || + key == &HARDHAT_CONSOLE_ADDRESS || + key == &TEST_CONTRACT_ADDRESS || + key == &ccx.caller || + key == &ccx.state.config.evm_opts.sender || + val.is_empty() + }; + + let alloc = ccx + .ecx + .journaled_state + .state() + .iter_mut() + .filter(|(key, val)| !skip(key, val)) + .map(|(key, val)| { + ( + key, + GenesisAccount { + nonce: Some(val.info.nonce), + balance: val.info.balance, + code: val.info.code.as_ref().map(|o| o.original_bytes()), + storage: Some( + val.storage + .iter() + .map(|(k, v)| (B256::from(*k), B256::from(v.present_value()))) + .collect(), + ), + private_key: None, + }, + ) + }) + .collect::>(); + + write_json_file(path, &alloc)?; + Ok(Default::default()) + } +} + impl Cheatcode for sign_0Call { + fn apply_full(&self, _: &mut CheatsCtxt) -> Result { + let Self { privateKey, digest } = self; + super::utils::sign(privateKey, digest) + } +} + +impl Cheatcode for sign_1Call { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { digest } = self; + super::utils::sign_with_wallet(ccx, None, digest) + } +} + +impl Cheatcode for sign_2Call { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { signer, digest } = self; + super::utils::sign_with_wallet(ccx, Some(*signer), digest) + } +} + +impl Cheatcode for signP256Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { privateKey, digest } = self; - super::utils::sign(privateKey, digest, ccx.data.env.cfg.chain_id) + super::utils::sign_p256(privateKey, digest, ccx.state) } } @@ -151,11 +226,24 @@ impl Cheatcode for resumeGasMeteringCall { } } +impl Cheatcode for lastCallGasCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + ensure!(state.last_call_gas.is_some(), "`lastCallGas` is only available after a call"); + Ok(state + .last_call_gas + .as_ref() + // This should never happen, as we ensure `last_call_gas` is `Some` above. + .expect("`lastCallGas` is only available after a call") + .abi_encode()) + } +} + impl Cheatcode for chainIdCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newChainId } = self; ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64 - 1"); - ccx.data.env.cfg.chain_id = newChainId.to(); + ccx.ecx.env.cfg.chain_id = newChainId.to(); Ok(Default::default()) } } @@ -163,7 +251,7 @@ impl Cheatcode for chainIdCall { impl Cheatcode for coinbaseCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newCoinbase } = self; - ccx.data.env.block.coinbase = *newCoinbase; + ccx.ecx.env.block.coinbase = *newCoinbase; Ok(Default::default()) } } @@ -172,11 +260,11 @@ impl Cheatcode for difficultyCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newDifficulty } = self; ensure!( - ccx.data.env.cfg.spec_id < SpecId::MERGE, + ccx.ecx.spec_id() < SpecId::MERGE, "`difficulty` is not supported after the Paris hard fork, use `prevrandao` instead; \ see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" ); - ccx.data.env.block.difficulty = *newDifficulty; + ccx.ecx.env.block.difficulty = *newDifficulty; Ok(Default::default()) } } @@ -184,36 +272,81 @@ impl Cheatcode for difficultyCall { impl Cheatcode for feeCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newBasefee } = self; - ccx.data.env.block.basefee = *newBasefee; + ccx.ecx.env.block.basefee = *newBasefee; + Ok(Default::default()) + } +} + +impl Cheatcode for prevrandao_0Call { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { newPrevrandao } = self; + ensure!( + ccx.ecx.spec_id() >= SpecId::MERGE, + "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \ + see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" + ); + ccx.ecx.env.block.prevrandao = Some(*newPrevrandao); Ok(Default::default()) } } -impl Cheatcode for prevrandaoCall { +impl Cheatcode for prevrandao_1Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newPrevrandao } = self; ensure!( - ccx.data.env.cfg.spec_id >= SpecId::MERGE, + ccx.ecx.spec_id() >= SpecId::MERGE, "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \ see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" ); - ccx.data.env.block.prevrandao = Some(*newPrevrandao); + ccx.ecx.env.block.prevrandao = Some((*newPrevrandao).into()); + Ok(Default::default()) + } +} + +impl Cheatcode for blobhashesCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { hashes } = self; + ensure!( + ccx.ecx.spec_id() >= SpecId::CANCUN, + "`blobhash` is not supported before the Cancun hard fork; \ + see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" + ); + ccx.ecx.env.tx.blob_hashes.clone_from(hashes); Ok(Default::default()) } } +impl Cheatcode for getBlobhashesCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + ensure!( + ccx.ecx.spec_id() >= SpecId::CANCUN, + "`blobhash` is not supported before the Cancun hard fork; \ + see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" + ); + Ok(ccx.ecx.env.tx.blob_hashes.clone().abi_encode()) + } +} + impl Cheatcode for rollCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newHeight } = self; - ccx.data.env.block.number = *newHeight; + ccx.ecx.env.block.number = *newHeight; Ok(Default::default()) } } +impl Cheatcode for getBlockNumberCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + Ok(ccx.ecx.env.block.number.abi_encode()) + } +} + impl Cheatcode for txGasPriceCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newGasPrice } = self; - ccx.data.env.tx.gas_price = *newGasPrice; + ccx.ecx.env.tx.gas_price = *newGasPrice; Ok(Default::default()) } } @@ -221,15 +354,42 @@ impl Cheatcode for txGasPriceCall { impl Cheatcode for warpCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newTimestamp } = self; - ccx.data.env.block.timestamp = *newTimestamp; + ccx.ecx.env.block.timestamp = *newTimestamp; Ok(Default::default()) } } +impl Cheatcode for getBlockTimestampCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + Ok(ccx.ecx.env.block.timestamp.abi_encode()) + } +} + +impl Cheatcode for blobBaseFeeCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { newBlobBaseFee } = self; + ensure!( + ccx.ecx.spec_id() >= SpecId::CANCUN, + "`blobBaseFee` is not supported before the Cancun hard fork; \ + see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" + ); + ccx.ecx.env.block.set_blob_excess_gas_and_price((*newBlobBaseFee).to()); + Ok(Default::default()) + } +} + +impl Cheatcode for getBlobBaseFeeCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + Ok(ccx.ecx.env.block.get_blob_excess_gas().unwrap_or(0).abi_encode()) + } +} + impl Cheatcode for dealCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account: address, newBalance: new_balance } = *self; - let account = journaled_account(ccx.data, address)?; + let account = journaled_account(ccx.ecx, address)?; let old_balance = std::mem::replace(&mut account.info.balance, new_balance); let record = DealRecord { address, old_balance, new_balance }; ccx.state.eth_deals.push(record); @@ -241,9 +401,9 @@ impl Cheatcode for etchCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, newRuntimeBytecode } = self; ensure_not_precompile!(target, ccx); - ccx.data.journaled_state.load_account(*target, ccx.data.db)?; + ccx.ecx.load_account(*target)?; let bytecode = Bytecode::new_raw(Bytes::copy_from_slice(newRuntimeBytecode)).to_checked(); - ccx.data.journaled_state.set_code(*target, bytecode); + ccx.ecx.journaled_state.set_code(*target, bytecode); Ok(Default::default()) } } @@ -251,7 +411,7 @@ impl Cheatcode for etchCall { impl Cheatcode for resetNonceCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - let account = journaled_account(ccx.data, *account)?; + let account = journaled_account(ccx.ecx, *account)?; // Per EIP-161, EOA nonces start at 0, but contract nonces // start at 1. Comparing by code_hash instead of code // to avoid hitting the case where account's code is None. @@ -266,7 +426,7 @@ impl Cheatcode for resetNonceCall { impl Cheatcode for setNonceCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account, newNonce } = *self; - let account = journaled_account(ccx.data, account)?; + let account = journaled_account(ccx.ecx, account)?; // nonce must increment only let current = account.info.nonce; ensure!( @@ -282,7 +442,7 @@ impl Cheatcode for setNonceCall { impl Cheatcode for setNonceUnsafeCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account, newNonce } = *self; - let account = journaled_account(ccx.data, account)?; + let account = journaled_account(ccx.ecx, account)?; account.info.nonce = newNonce; Ok(Default::default()) } @@ -293,8 +453,8 @@ impl Cheatcode for storeCall { let Self { target, slot, value } = *self; ensure_not_precompile!(&target, ccx); // ensure the account is touched - let _ = journaled_account(ccx.data, target)?; - ccx.data.journaled_state.sstore(target, slot.into(), value.into(), ccx.data.db)?; + let _ = journaled_account(ccx.ecx, target)?; + ccx.ecx.sstore(target, slot.into(), value.into())?; Ok(Default::default()) } } @@ -302,7 +462,7 @@ impl Cheatcode for storeCall { impl Cheatcode for coolCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target } = self; - if let Some(account) = ccx.data.journaled_state.state.get_mut(target) { + if let Some(account) = ccx.ecx.journaled_state.state.get_mut(target) { account.unmark_touch(); account.storage.clear(); } @@ -313,25 +473,47 @@ impl Cheatcode for coolCall { impl Cheatcode for readCallersCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - read_callers(ccx.state, &ccx.data.env.tx.caller) + read_callers(ccx.state, &ccx.ecx.env.tx.caller) } } impl Cheatcode for snapshotCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - Ok(ccx.data.db.snapshot(&ccx.data.journaled_state, ccx.data.env).abi_encode()) + Ok(ccx.ecx.db.snapshot(&ccx.ecx.journaled_state, &ccx.ecx.env).abi_encode()) } } impl Cheatcode for revertToCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; - let result = if let Some(journaled_state) = - ccx.data.db.revert(*snapshotId, &ccx.data.journaled_state, ccx.data.env) - { + let result = if let Some(journaled_state) = ccx.ecx.db.revert( + *snapshotId, + &ccx.ecx.journaled_state, + &mut ccx.ecx.env, + RevertSnapshotAction::RevertKeep, + ) { + // we reset the evm's journaled_state to the state of the snapshot previous state + ccx.ecx.journaled_state = journaled_state; + true + } else { + false + }; + Ok(result.abi_encode()) + } +} + +impl Cheatcode for revertToAndDeleteCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { snapshotId } = self; + let result = if let Some(journaled_state) = ccx.ecx.db.revert( + *snapshotId, + &ccx.ecx.journaled_state, + &mut ccx.ecx.env, + RevertSnapshotAction::RevertRemove, + ) { // we reset the evm's journaled_state to the state of the snapshot previous state - ccx.data.journaled_state = journaled_state; + ccx.ecx.journaled_state = journaled_state; true } else { false @@ -340,6 +522,21 @@ impl Cheatcode for revertToCall { } } +impl Cheatcode for deleteSnapshotCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { snapshotId } = self; + let result = ccx.ecx.db.delete_snapshot(*snapshotId); + Ok(result.abi_encode()) + } +} +impl Cheatcode for deleteSnapshotsCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + ccx.ecx.db.delete_snapshots(); + Ok(Default::default()) + } +} + impl Cheatcode for startStateDiffRecordingCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self {} = self; @@ -357,7 +554,7 @@ impl Cheatcode for stopAndReturnStateDiffCall { pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Address) -> Result { super::script::correct_sender_nonce(ccx)?; - let (account, _) = ccx.data.journaled_state.load_account(*address, ccx.data.db)?; + let (account, _) = ccx.ecx.journaled_state.load_account(*address, &mut ccx.ecx.db)?; Ok(account.info.nonce.abi_encode()) } @@ -410,13 +607,13 @@ fn read_callers(state: &Cheatcodes, default_sender: &Address) -> Result { } /// Ensures the `Account` is loaded and touched. -pub(super) fn journaled_account<'a, DB: DatabaseExt>( - data: &'a mut EVMData<'_, DB>, +pub(super) fn journaled_account( + ecx: &mut InnerEvmContext, addr: Address, -) -> Result<&'a mut Account> { - data.journaled_state.load_account(addr, data.db)?; - data.journaled_state.touch(&addr); - Ok(data.journaled_state.state.get_mut(&addr).expect("account is loaded")) +) -> Result<&mut Account> { + ecx.load_account(addr)?; + ecx.journaled_state.touch(&addr); + Ok(ecx.journaled_state.state.get_mut(&addr).expect("account is loaded")) } /// Consumes recorded account accesses and returns them as an abi encoded @@ -433,7 +630,6 @@ fn get_state_diff(state: &mut Cheatcodes) -> Result { .unwrap_or_default() .into_iter() .flatten() - .map(|record| record.access) .collect::>(); Ok(res.abi_encode()) } diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index f246d264c4dc5..f7db4c0e9774b 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -1,19 +1,17 @@ use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Result, Vm::*}; -use alloy_primitives::B256; +use alloy_primitives::{B256, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::Filter; use alloy_sol_types::SolValue; -use ethers_core::types::Filter; -use ethers_providers::Middleware; -use foundry_common::{ - types::{ToAlloy, ToEthers}, - ProviderBuilder, -}; +use eyre::WrapErr; +use foundry_common::provider::ProviderBuilder; use foundry_compilers::utils::RuntimeOrHandle; use foundry_evm_core::fork::CreateFork; impl Cheatcode for activeForkCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - ccx.data + ccx.ecx .db .active_fork_id() .map(|id| id.abi_encode()) @@ -66,7 +64,12 @@ impl Cheatcode for createSelectFork_2Call { impl Cheatcode for rollFork_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { blockNumber } = self; - ccx.data.db.roll_fork(None, *blockNumber, ccx.data.env, &mut ccx.data.journaled_state)?; + ccx.ecx.db.roll_fork( + None, + (*blockNumber).to(), + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, + )?; Ok(Default::default()) } } @@ -74,11 +77,11 @@ impl Cheatcode for rollFork_0Call { impl Cheatcode for rollFork_1Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { txHash } = self; - ccx.data.db.roll_fork_to_transaction( + ccx.ecx.db.roll_fork_to_transaction( None, *txHash, - ccx.data.env, - &mut ccx.data.journaled_state, + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, )?; Ok(Default::default()) } @@ -87,11 +90,11 @@ impl Cheatcode for rollFork_1Call { impl Cheatcode for rollFork_2Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId, blockNumber } = self; - ccx.data.db.roll_fork( + ccx.ecx.db.roll_fork( Some(*forkId), - *blockNumber, - ccx.data.env, - &mut ccx.data.journaled_state, + (*blockNumber).to(), + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, )?; Ok(Default::default()) } @@ -100,11 +103,11 @@ impl Cheatcode for rollFork_2Call { impl Cheatcode for rollFork_3Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId, txHash } = self; - ccx.data.db.roll_fork_to_transaction( + ccx.ecx.db.roll_fork_to_transaction( Some(*forkId), *txHash, - ccx.data.env, - &mut ccx.data.journaled_state, + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, )?; Ok(Default::default()) } @@ -119,7 +122,7 @@ impl Cheatcode for selectForkCall { // fork. ccx.state.corrected_nonce = true; - ccx.data.db.select_fork(*forkId, ccx.data.env, &mut ccx.data.journaled_state)?; + ccx.ecx.db.select_fork(*forkId, &mut ccx.ecx.env, &mut ccx.ecx.journaled_state)?; Ok(Default::default()) } } @@ -127,11 +130,11 @@ impl Cheatcode for selectForkCall { impl Cheatcode for transact_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { txHash } = *self; - ccx.data.db.transact( + ccx.ecx.db.transact( None, txHash, - ccx.data.env, - &mut ccx.data.journaled_state, + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, ccx.state, )?; Ok(Default::default()) @@ -141,11 +144,11 @@ impl Cheatcode for transact_0Call { impl Cheatcode for transact_1Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId, txHash } = *self; - ccx.data.db.transact( + ccx.ecx.db.transact( Some(forkId), txHash, - ccx.data.env, - &mut ccx.data.journaled_state, + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, ccx.state, )?; Ok(Default::default()) @@ -155,7 +158,7 @@ impl Cheatcode for transact_1Call { impl Cheatcode for allowCheatcodesCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - ccx.data.db.allow_cheatcode_access(*account); + ccx.ecx.db.allow_cheatcode_access(*account); Ok(Default::default()) } } @@ -163,7 +166,7 @@ impl Cheatcode for allowCheatcodesCall { impl Cheatcode for makePersistent_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - ccx.data.db.add_persistent_account(*account); + ccx.ecx.db.add_persistent_account(*account); Ok(Default::default()) } } @@ -171,8 +174,8 @@ impl Cheatcode for makePersistent_0Call { impl Cheatcode for makePersistent_1Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account0, account1 } = self; - ccx.data.db.add_persistent_account(*account0); - ccx.data.db.add_persistent_account(*account1); + ccx.ecx.db.add_persistent_account(*account0); + ccx.ecx.db.add_persistent_account(*account1); Ok(Default::default()) } } @@ -180,9 +183,9 @@ impl Cheatcode for makePersistent_1Call { impl Cheatcode for makePersistent_2Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account0, account1, account2 } = self; - ccx.data.db.add_persistent_account(*account0); - ccx.data.db.add_persistent_account(*account1); - ccx.data.db.add_persistent_account(*account2); + ccx.ecx.db.add_persistent_account(*account0); + ccx.ecx.db.add_persistent_account(*account1); + ccx.ecx.db.add_persistent_account(*account2); Ok(Default::default()) } } @@ -190,7 +193,7 @@ impl Cheatcode for makePersistent_2Call { impl Cheatcode for makePersistent_3Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { accounts } = self; - ccx.data.db.extend_persistent_accounts(accounts.iter().copied()); + ccx.ecx.db.extend_persistent_accounts(accounts.iter().copied()); Ok(Default::default()) } } @@ -198,7 +201,7 @@ impl Cheatcode for makePersistent_3Call { impl Cheatcode for revokePersistent_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - ccx.data.db.remove_persistent_account(account); + ccx.ecx.db.remove_persistent_account(account); Ok(Default::default()) } } @@ -206,7 +209,7 @@ impl Cheatcode for revokePersistent_0Call { impl Cheatcode for revokePersistent_1Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { accounts } = self; - ccx.data.db.remove_persistent_accounts(accounts.iter().copied()); + ccx.ecx.db.remove_persistent_accounts(accounts.iter().copied()); Ok(Default::default()) } } @@ -214,7 +217,7 @@ impl Cheatcode for revokePersistent_1Call { impl Cheatcode for isPersistentCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - Ok(ccx.data.db.is_persistent(account).abi_encode()) + Ok(ccx.ecx.db.is_persistent(account).abi_encode()) } } @@ -222,15 +225,15 @@ impl Cheatcode for rpcCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { method, params } = self; let url = - ccx.data.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; + ccx.ecx.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; let provider = ProviderBuilder::new(&url).build()?; - + let method: &'static str = Box::new(method.clone()).leak(); let params_json: serde_json::Value = serde_json::from_str(params)?; let result = RuntimeOrHandle::new() - .block_on(provider.request(method, params_json)) + .block_on(provider.raw_request(method.into(), params_json)) .map_err(|err| fmt_err!("{method:?}: {err}"))?; - let result_as_tokens = crate::json::value_to_token(&result) + let result_as_tokens = crate::json::json_value_to_token(&result) .map_err(|err| fmt_err!("failed to parse result: {err}"))?; Ok(result_as_tokens.abi_encode()) @@ -239,7 +242,7 @@ impl Cheatcode for rpcCall { impl Cheatcode for eth_getLogsCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { - let Self { fromBlock, toBlock, addr, topics } = self; + let Self { fromBlock, toBlock, target, topics } = self; let (Ok(from_block), Ok(to_block)) = (u64::try_from(fromBlock), u64::try_from(toBlock)) else { bail!("blocks in block range must be less than 2^64 - 1") @@ -250,37 +253,40 @@ impl Cheatcode for eth_getLogsCall { } let url = - ccx.data.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; + ccx.ecx.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; let provider = ProviderBuilder::new(&url).build()?; - let mut filter = - Filter::new().address(addr.to_ethers()).from_block(from_block).to_block(to_block); + let mut filter = Filter::new().address(*target).from_block(from_block).to_block(to_block); for (i, topic) in topics.iter().enumerate() { - let topic = topic.to_ethers(); + // todo: needed because rust wants to convert FixedBytes<32> to U256 to convert it back + // to FixedBytes<32> and then to Topic for some reason removing the + // From impl in alloy does not fix the situation, and it is not possible to impl + // From> either because of a conflicting impl match i { - 0 => filter = filter.topic0(topic), - 1 => filter = filter.topic1(topic), - 2 => filter = filter.topic2(topic), - 3 => filter = filter.topic3(topic), + 0 => filter = filter.event_signature(*topic), + 1 => filter = filter.topic1(*topic), + 2 => filter = filter.topic2(*topic), + 3 => filter = filter.topic3(*topic), _ => unreachable!(), }; } + // todo: handle the errors somehow let logs = RuntimeOrHandle::new() .block_on(provider.get_logs(&filter)) - .map_err(|e| fmt_err!("eth_getLogs: {e}"))?; + .wrap_err("failed to get logs")?; let eth_logs = logs .into_iter() .map(|log| EthGetLogs { - emitter: log.address.to_alloy(), - topics: log.topics.into_iter().map(ToAlloy::to_alloy).collect(), - data: log.data.0.into(), - blockHash: log.block_hash.unwrap_or_default().to_alloy(), - blockNumber: log.block_number.unwrap_or_default().to_alloy().to(), - transactionHash: log.transaction_hash.unwrap_or_default().to_alloy(), - transactionIndex: log.transaction_index.unwrap_or_default().to_alloy().to(), - logIndex: log.log_index.unwrap_or_default().to_alloy(), - removed: log.removed.unwrap_or(false), + emitter: log.address(), + topics: log.topics().to_vec(), + data: log.inner.data.data, + blockHash: log.block_hash.unwrap_or_default(), + blockNumber: log.block_number.unwrap_or_default(), + transactionHash: log.transaction_hash.unwrap_or_default(), + transactionIndex: log.transaction_index.unwrap_or_default(), + logIndex: U256::from(log.log_index.unwrap_or_default()), + removed: log.removed, }) .collect::>(); @@ -300,7 +306,7 @@ fn create_select_fork( ccx.state.corrected_nonce = true; let fork = create_fork_request(ccx, url_or_alias, block)?; - let id = ccx.data.db.create_select_fork(fork, ccx.data.env, &mut ccx.data.journaled_state)?; + let id = ccx.ecx.db.create_select_fork(fork, &mut ccx.ecx.env, &mut ccx.ecx.journaled_state)?; Ok(id.abi_encode()) } @@ -311,7 +317,7 @@ fn create_fork( block: Option, ) -> Result { let fork = create_fork_request(ccx, url_or_alias, block)?; - let id = ccx.data.db.create_fork(fork)?; + let id = ccx.ecx.db.create_fork(fork)?; Ok(id.abi_encode()) } @@ -327,10 +333,10 @@ fn create_select_fork_at_transaction( ccx.state.corrected_nonce = true; let fork = create_fork_request(ccx, url_or_alias, None)?; - let id = ccx.data.db.create_select_fork_at_transaction( + let id = ccx.ecx.db.create_select_fork_at_transaction( fork, - ccx.data.env, - &mut ccx.data.journaled_state, + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, *transaction, )?; Ok(id.abi_encode()) @@ -343,7 +349,7 @@ fn create_fork_at_transaction( transaction: &B256, ) -> Result { let fork = create_fork_request(ccx, url_or_alias, None)?; - let id = ccx.data.db.create_fork_at_transaction(fork, *transaction)?; + let id = ccx.ecx.db.create_fork_at_transaction(fork, *transaction)?; Ok(id.abi_encode()) } @@ -359,7 +365,7 @@ fn create_fork_request( let fork = CreateFork { enable_caching: ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url), url, - env: ccx.data.env.clone(), + env: (*ccx.ecx.env).clone(), evm_opts, }; Ok(fork) diff --git a/crates/cheatcodes/src/evm/mock.rs b/crates/cheatcodes/src/evm/mock.rs index 1fa55539e8bed..6a266a4108fb7 100644 --- a/crates/cheatcodes/src/evm/mock.rs +++ b/crates/cheatcodes/src/evm/mock.rs @@ -49,14 +49,15 @@ impl Cheatcode for clearMockedCallsCall { impl Cheatcode for mockCall_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, returnData } = self; - let (acc, _) = ccx.data.journaled_state.load_account(*callee, ccx.data.db)?; + // TODO: use ecx.load_account + let (acc, _) = ccx.ecx.journaled_state.load_account(*callee, &mut ccx.ecx.db)?; // Etches a single byte onto the account if it is empty to circumvent the `extcodesize` // check Solidity might perform. let empty_bytecode = acc.info.code.as_ref().map_or(true, Bytecode::is_empty); if empty_bytecode { let code = Bytecode::new_raw(Bytes::from_static(&[0u8])).to_checked(); - ccx.data.journaled_state.set_code(*callee, code); + ccx.ecx.journaled_state.set_code(*callee, code); } mock_call(ccx.state, callee, data, None, returnData, InstructionResult::Return); @@ -67,7 +68,7 @@ impl Cheatcode for mockCall_0Call { impl Cheatcode for mockCall_1Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, msgValue, data, returnData } = self; - ccx.data.journaled_state.load_account(*callee, ccx.data.db)?; + ccx.ecx.load_account(*callee)?; mock_call(ccx.state, callee, data, Some(msgValue), returnData, InstructionResult::Return); Ok(Default::default()) } @@ -93,9 +94,9 @@ impl Cheatcode for mockCallRevert_1Call { fn mock_call( state: &mut Cheatcodes, callee: &Address, - cdata: &Vec, + cdata: &Bytes, value: Option<&U256>, - rdata: &Vec, + rdata: &Bytes, ret_type: InstructionResult, ) { state.mocked_calls.entry(*callee).or_default().insert( diff --git a/crates/cheatcodes/src/evm/prank.rs b/crates/cheatcodes/src/evm/prank.rs index 73269b23d11b0..3e1452d4236db 100644 --- a/crates/cheatcodes/src/evm/prank.rs +++ b/crates/cheatcodes/src/evm/prank.rs @@ -96,10 +96,10 @@ fn prank( ) -> Result { let prank = Prank::new( ccx.caller, - ccx.data.env.tx.caller, + ccx.ecx.env.tx.caller, *new_caller, new_origin.copied(), - ccx.data.journaled_state.depth(), + ccx.ecx.journaled_state.depth(), single_call, ); diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index 3e345db94a97b..728358bd63011 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -1,16 +1,22 @@ //! Implementations of [`Filesystem`](crate::Group::Filesystem) cheatcodes. +use super::string::parse; use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; +use alloy_dyn_abi::DynSolType; use alloy_json_abi::ContractObject; -use alloy_primitives::U256; +use alloy_primitives::{Bytes, U256}; use alloy_sol_types::SolValue; -use foundry_common::{fs, get_artifact_path}; +use dialoguer::{Input, Password}; +use foundry_common::fs; use foundry_config::fs_permissions::FsAccessKind; +use semver::Version; use std::{ collections::hash_map::Entry, io::{BufRead, BufReader, Write}, - path::Path, + path::{Path, PathBuf}, process::Command, + sync::mpsc, + thread, time::{SystemTime, UNIX_EPOCH}, }; use walkdir::WalkDir; @@ -245,33 +251,140 @@ impl Cheatcode for writeLineCall { impl Cheatcode for getCodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { artifactPath: path } = self; - let object = read_bytecode(state, path)?; - if let Some(bin) = object.bytecode { - Ok(bin.abi_encode()) - } else { - Err(fmt_err!("No bytecode for contract. Is it abstract or unlinked?")) - } + Ok(get_artifact_code(state, path, false)?.abi_encode()) } } impl Cheatcode for getDeployedCodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { artifactPath: path } = self; - let object = read_bytecode(state, path)?; - if let Some(bin) = object.deployed_bytecode { - Ok(bin.abi_encode()) - } else { - Err(fmt_err!("No deployed bytecode for contract. Is it abstract or unlinked?")) - } + Ok(get_artifact_code(state, path, true)?.abi_encode()) } } -/// Reads the bytecode object(s) from the matching artifact -fn read_bytecode(state: &Cheatcodes, path: &str) -> Result { - let path = get_artifact_path(&state.config.paths, path); +/// Returns the path to the json artifact depending on the input +/// +/// Can parse following input formats: +/// - `path/to/artifact.json` +/// - `path/to/contract.sol` +/// - `path/to/contract.sol:ContractName` +/// - `path/to/contract.sol:ContractName:0.8.23` +/// - `path/to/contract.sol:0.8.23` +/// - `ContractName` +/// - `ContractName:0.8.23` +fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result { + let path = if path.ends_with(".json") { + PathBuf::from(path) + } else { + let mut parts = path.split(':'); + + let mut file = None; + let mut contract_name = None; + let mut version = None; + + let path_or_name = parts.next().unwrap(); + if path_or_name.ends_with(".sol") { + file = Some(PathBuf::from(path_or_name)); + if let Some(name_or_version) = parts.next() { + if name_or_version.contains('.') { + version = Some(name_or_version); + } else { + contract_name = Some(name_or_version); + version = parts.next(); + } + } + } else { + contract_name = Some(path_or_name); + version = parts.next(); + } + + let version = if let Some(version) = version { + Some(Version::parse(version).map_err(|_| fmt_err!("Error parsing version"))?) + } else { + None + }; + + // Use available artifacts list if present + if let Some(artifacts) = &state.config.available_artifacts { + let filtered = artifacts + .iter() + .filter(|(id, _)| { + // name might be in the form of "Counter.0.8.23" + let id_name = id.name.split('.').next().unwrap(); + + if let Some(path) = &file { + if !id.source.ends_with(path) { + return false; + } + } + if let Some(name) = contract_name { + if id_name != name { + return false; + } + } + if let Some(ref version) = version { + if id.version.minor != version.minor || + id.version.major != version.major || + id.version.patch != version.patch + { + return false; + } + } + true + }) + .collect::>(); + + let artifact = match filtered.len() { + 0 => Err(fmt_err!("No matching artifact found")), + 1 => Ok(filtered[0]), + _ => { + // If we know the current script/test contract solc version, try to filter by it + state + .config + .running_version + .as_ref() + .and_then(|version| { + let filtered = filtered + .into_iter() + .filter(|(id, _)| id.version == *version) + .collect::>(); + + (filtered.len() == 1).then_some(filtered[0]) + }) + .ok_or_else(|| fmt_err!("Multiple matching artifacts found")) + } + }?; + + let maybe_bytecode = if deployed { + artifact.1.deployed_bytecode.clone() + } else { + artifact.1.bytecode.clone() + }; + + return maybe_bytecode + .ok_or_else(|| fmt_err!("No bytecode for contract. Is it abstract or unlinked?")); + } else { + match (file.map(|f| f.to_string_lossy().to_string()), contract_name) { + (Some(file), Some(contract_name)) => { + PathBuf::from(format!("{file}/{contract_name}.json")) + } + (None, Some(contract_name)) => { + PathBuf::from(format!("{contract_name}.sol/{contract_name}.json")) + } + (Some(file), None) => { + let name = file.replace(".sol", ""); + PathBuf::from(format!("{file}/{name}.json")) + } + _ => return Err(fmt_err!("Invalid artifact path")), + } + } + }; + let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; let data = fs::read_to_string(path)?; - serde_json::from_str::(&data).map_err(Into::into) + let artifact = serde_json::from_str::(&data)?; + let maybe_bytecode = if deployed { artifact.deployed_bytecode } else { artifact.bytecode }; + maybe_bytecode.ok_or_else(|| fmt_err!("No bytecode for contract. Is it abstract or unlinked?")) } impl Cheatcode for ffiCall { @@ -296,6 +409,34 @@ impl Cheatcode for tryFfiCall { } } +impl Cheatcode for promptCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { promptText: text } = self; + prompt(state, text, prompt_input).map(|res| res.abi_encode()) + } +} + +impl Cheatcode for promptSecretCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { promptText: text } = self; + prompt(state, text, prompt_password).map(|res| res.abi_encode()) + } +} + +impl Cheatcode for promptAddressCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { promptText: text } = self; + parse(&prompt(state, text, prompt_input)?, &DynSolType::Address) + } +} + +impl Cheatcode for promptUintCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { promptText: text } = self; + parse(&prompt(state, text, prompt_input)?, &DynSolType::Uint(256)) + } +} + pub(super) fn write_file(state: &Cheatcodes, path: &Path, contents: &[u8]) -> Result { let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; // write access to foundry.toml is not allowed @@ -365,16 +506,49 @@ fn ffi(state: &Cheatcodes, input: &[String]) -> Result { }; Ok(FfiResult { exitCode: output.status.code().unwrap_or(69), - stdout: encoded_stdout, - stderr: output.stderr, + stdout: encoded_stdout.into(), + stderr: output.stderr.into(), }) } +fn prompt_input(prompt_text: &str) -> Result { + Input::new().allow_empty(true).with_prompt(prompt_text).interact_text() +} + +fn prompt_password(prompt_text: &str) -> Result { + Password::new().with_prompt(prompt_text).interact() +} + +fn prompt( + state: &Cheatcodes, + prompt_text: &str, + input: fn(&str) -> Result, +) -> Result { + let text_clone = prompt_text.to_string(); + let timeout = state.config.prompt_timeout; + let (send, recv) = mpsc::channel(); + + thread::spawn(move || { + send.send(input(&text_clone)).unwrap(); + }); + + match recv.recv_timeout(timeout) { + Ok(res) => res.map_err(|err| { + println!(); + err.to_string().into() + }), + Err(_) => { + println!(); + Err("Prompt timed out".into()) + } + } +} + #[cfg(test)] mod tests { use super::*; use crate::CheatsConfig; - use std::{path::PathBuf, sync::Arc}; + use std::sync::Arc; fn cheats() -> Cheatcodes { let config = CheatsConfig { @@ -391,7 +565,7 @@ mod tests { let cheats = cheats(); let args = ["echo".to_string(), hex::encode(msg)]; let output = ffi(&cheats, &args).unwrap(); - assert_eq!(output.stdout, msg); + assert_eq!(output.stdout, Bytes::from(msg)); } #[test] @@ -400,7 +574,7 @@ mod tests { let cheats = cheats(); let args = ["echo".to_string(), msg.to_string()]; let output = ffi(&cheats, &args).unwrap(); - assert_eq!(output.stdout, msg.as_bytes()); + assert_eq!(output.stdout, Bytes::from(msg.as_bytes())); } #[test] diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 9151c1d8b7064..ee418fd420433 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -7,32 +7,34 @@ use crate::{ prank::Prank, DealRecord, RecordAccess, }, - script::Broadcast, + script::{Broadcast, ScriptWallets}, test::expect::{ - self, ExpectedCallData, ExpectedCallTracker, ExpectedCallType, ExpectedEmit, ExpectedRevert, + self, ExpectedCallData, ExpectedCallTracker, ExpectedCallType, ExpectedEmit, + ExpectedRevert, ExpectedRevertKind, }, - CheatsConfig, CheatsCtxt, Error, Result, Vm, + CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result, Vm, + Vm::AccountAccess, }; -use alloy_primitives::{Address, Bytes, B256, U256}; +use alloy_primitives::{Address, Bytes, Log, TxKind, B256, U256}; +use alloy_rpc_types::request::{TransactionInput, TransactionRequest}; use alloy_sol_types::{SolInterface, SolValue}; -use ethers_core::types::{ - transaction::eip2718::TypedTransaction, NameOrAddress, TransactionRequest, -}; -use ethers_signers::LocalWallet; -use foundry_common::{evm::Breakpoints, types::ToEthers, RpcUrl}; +use foundry_common::{evm::Breakpoints, SELECTOR_LEN}; use foundry_evm_core::{ - backend::{DatabaseError, DatabaseExt, RevertDiagnostic}, - constants::{CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, MAGIC_SKIP}, - utils::get_create_address, + abi::Vm::stopExpectSafeMemoryCall, + backend::{DatabaseExt, RevertDiagnostic}, + constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS}, + InspectorExt, }; use itertools::Itertools; use revm::{ interpreter::{ - opcode, CallInputs, CallScheme, CreateInputs, Gas, InstructionResult, Interpreter, + opcode, CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, Gas, + InstructionResult, Interpreter, InterpreterAction, InterpreterResult, }, primitives::{BlockEnv, CreateScheme, TransactTo}, - EVMData, Inspector, + EvmContext, InnerEvmContext, Inspector, }; +use rustc_hash::FxHashMap; use serde_json::Value; use std::{ collections::{BTreeMap, HashMap, VecDeque}, @@ -75,25 +77,17 @@ impl Context { } /// Helps collecting transactions from different forks. -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct BroadcastableTransaction { /// The optional RPC URL. - pub rpc: Option, + pub rpc: Option, /// The transaction to broadcast. - pub transaction: TypedTransaction, + pub transaction: TransactionRequest, } /// List of transactions that can be broadcasted. pub type BroadcastableTransactions = VecDeque; -#[derive(Debug, Clone)] -pub struct AccountAccess { - /// The account access. - pub access: crate::Vm::AccountAccess, - /// The call depth the account was accessed. - pub depth: u64, -} - /// An EVM inspector that handles calls to various cheatcodes, each with their own behavior. /// /// Cheatcodes can be called by contracts during execution to modify the VM environment, such as @@ -129,10 +123,7 @@ pub struct Cheatcodes { pub labels: HashMap, /// Remembered private keys - pub script_wallets: Vec, - - /// Whether the skip cheatcode was activated - pub skip: bool, + pub script_wallets: Option, /// Prank information pub prank: Option, @@ -156,6 +147,10 @@ pub struct Cheatcodes { /// Recorded logs pub recorded_logs: Option>, + /// Cache of the amount of gas used in previous call. + /// This is used by the `lastCallGas` cheatcode. + pub last_call_gas: Option, + /// Mocked calls // **Note**: inner must a BTreeMap because of special `Ord` impl for `MockCallDataContext` pub mocked_calls: HashMap>, @@ -166,7 +161,7 @@ pub struct Cheatcodes { pub expected_emits: VecDeque, /// Map of context depths to memory offset ranges that may be written to within the call depth. - pub allowed_mem_writes: HashMap>>, + pub allowed_mem_writes: FxHashMap>>, /// Current broadcasting information pub broadcast: Option, @@ -222,23 +217,43 @@ impl Cheatcodes { /// Creates a new `Cheatcodes` with the given settings. #[inline] pub fn new(config: Arc) -> Self { - Self { config, fs_commit: true, ..Default::default() } + let labels = config.labels.clone(); + let script_wallets = config.script_wallets.clone(); + Self { config, fs_commit: true, labels, script_wallets, ..Default::default() } } fn apply_cheatcode( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext, call: &CallInputs, ) -> Result { // decode the cheatcode call - let decoded = Vm::VmCalls::abi_decode(&call.input, false)?; + let decoded = Vm::VmCalls::abi_decode(&call.input, false).map_err(|e| { + if let alloy_sol_types::Error::UnknownSelector { name: _, selector } = e { + let msg = format!( + "unknown cheatcode with selector {selector}; \ + you may have a mismatch between the `Vm` interface (likely in `forge-std`) \ + and the `forge` version" + ); + return alloy_sol_types::Error::Other(std::borrow::Cow::Owned(msg)); + } + e + })?; let caller = call.context.caller; // ensure the caller is allowed to execute cheatcodes, // but only if the backend is in forking mode - data.db.ensure_cheatcode_access_forking_mode(caller)?; - - apply_dispatch(&decoded, &mut CheatsCtxt { state: self, data, caller }) + ecx.db.ensure_cheatcode_access_forking_mode(&caller)?; + + apply_dispatch( + &decoded, + &mut CheatsCtxt { + state: self, + ecx: &mut ecx.inner, + precompiles: &mut ecx.precompiles, + caller, + }, + ) } /// Determines the address of the contract and marks it as allowed @@ -248,24 +263,24 @@ impl Cheatcodes { /// automatically we need to determine the new address fn allow_cheatcodes_on_create( &self, - data: &mut EVMData<'_, DB>, + ecx: &mut InnerEvmContext, inputs: &CreateInputs, ) -> Address { - let old_nonce = data + let old_nonce = ecx .journaled_state .state .get(&inputs.caller) .map(|acc| acc.info.nonce) .unwrap_or_default(); - let created_address = get_create_address(inputs, old_nonce); + let created_address = inputs.created_address(old_nonce); - if data.journaled_state.depth > 1 && !data.db.has_cheatcode_access(inputs.caller) { + if ecx.journaled_state.depth > 1 && !ecx.db.has_cheatcode_access(&inputs.caller) { // we only grant cheat code access for new contracts if the caller also has // cheatcode access and the new contract is created in top most call - return created_address + return created_address; } - data.db.allow_cheatcode_access(created_address); + ecx.db.allow_cheatcode_access(created_address); created_address } @@ -274,24 +289,24 @@ impl Cheatcodes { /// /// Cleanup any previously applied cheatcodes that altered the state in such a way that revm's /// revert would run into issues. - pub fn on_revert(&mut self, data: &mut EVMData<'_, DB>) { + pub fn on_revert(&mut self, ecx: &mut EvmContext) { trace!(deals=?self.eth_deals.len(), "rolling back deals"); // Delay revert clean up until expected revert is handled, if set. if self.expected_revert.is_some() { - return + return; } // we only want to apply cleanup top level - if data.journaled_state.depth() > 0 { - return + if ecx.journaled_state.depth() > 0 { + return; } // Roll back all previously applied deals // This will prevent overflow issues in revm's [`JournaledState::journal_revert`] routine // which rolls back any transfers. while let Some(record) = self.eth_deals.pop() { - if let Some(acc) = data.journaled_state.state.get_mut(&record.address) { + if let Some(acc) = ecx.journaled_state.state.get_mut(&record.address) { acc.info.balance = record.old_balance; } } @@ -300,18 +315,19 @@ impl Cheatcodes { impl Inspector for Cheatcodes { #[inline] - fn initialize_interp(&mut self, _: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { + fn initialize_interp(&mut self, _: &mut Interpreter, ecx: &mut EvmContext) { // When the first interpreter is initialized we've circumvented the balance and gas checks, // so we apply our actual block data with the correct fees and all. if let Some(block) = self.block.take() { - data.env.block = block; + ecx.env.block = block; } if let Some(gas_price) = self.gas_price.take() { - data.env.tx.gas_price = gas_price; + ecx.env.tx.gas_price = gas_price; } } - fn step(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { + let ecx = &mut ecx.inner; self.pc = interpreter.program_counter(); // reset gas if gas metering is turned off @@ -406,26 +422,25 @@ impl Inspector for Cheatcodes { if interpreter.current_opcode() == opcode::SELFDESTRUCT { let target = try_or_continue!(interpreter.stack().peek(0)); // load balance of this account - let value = if let Ok((account, _)) = - data.journaled_state.load_account(interpreter.contract().address, data.db) + let value = ecx + .balance(interpreter.contract().address) + .map(|(b, _)| b) + .unwrap_or(U256::ZERO); + let account = Address::from_word(B256::from(target)); + // get previous balance and initialized status of the target account + // TODO: use load_account_exists + let (initialized, old_balance) = if let Ok((account, _)) = + ecx.journaled_state.load_account(account, &mut ecx.db) { - account.info.balance + (account.info.exists(), account.info.balance) } else { - U256::ZERO + (false, U256::ZERO) }; - let account = Address::from_word(B256::from(target)); - // get previous balance and initialized status of the target account - let (initialized, old_balance) = - if let Ok((account, _)) = data.journaled_state.load_account(account, data.db) { - (account.info.exists(), account.info.balance) - } else { - (false, U256::ZERO) - }; // register access for the target account let access = crate::Vm::AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: data.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(data.env.cfg.chain_id), + forkId: ecx.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.env.cfg.chain_id), }, accessor: interpreter.contract().address, account, @@ -434,14 +449,15 @@ impl Inspector for Cheatcodes { oldBalance: old_balance, newBalance: old_balance + value, value, - data: vec![], + data: Bytes::new(), reverted: false, - deployedCode: vec![], + deployedCode: Bytes::new(), storageAccesses: vec![], + depth: ecx.journaled_state.depth(), }; // Ensure that we're not selfdestructing a context recording was initiated on if let Some(last) = account_accesses.last_mut() { - last.push(AccountAccess { access, depth: data.journaled_state.depth() }); + last.push(access); } } } @@ -457,9 +473,8 @@ impl Inspector for Cheatcodes { // it's not set (zero value) let mut present_value = U256::ZERO; // Try to load the account and the slot's present value - if data.journaled_state.load_account(address, data.db).is_ok() { - if let Ok((previous, _)) = data.journaled_state.sload(address, key, data.db) - { + if ecx.load_account(address).is_ok() { + if let Ok((previous, _)) = ecx.sload(address, key) { present_value = previous; } } @@ -474,7 +489,7 @@ impl Inspector for Cheatcodes { append_storage_access( recorded_account_diffs_stack, access, - data.journaled_state.depth(), + ecx.journaled_state.depth(), ); } opcode::SSTORE => { @@ -484,9 +499,8 @@ impl Inspector for Cheatcodes { // Try to load the account and the slot's previous value, otherwise, assume it's // not set (zero value) let mut previous_value = U256::ZERO; - if data.journaled_state.load_account(address, data.db).is_ok() { - if let Ok((previous, _)) = data.journaled_state.sload(address, key, data.db) - { + if ecx.load_account(address).is_ok() { + if let Ok((previous, _)) = ecx.sload(address, key) { previous_value = previous; } } @@ -502,9 +516,60 @@ impl Inspector for Cheatcodes { append_storage_access( recorded_account_diffs_stack, access, - data.journaled_state.depth(), + ecx.journaled_state.depth(), ); } + // Record account accesses via the EXT family of opcodes + opcode::EXTCODECOPY | + opcode::EXTCODESIZE | + opcode::EXTCODEHASH | + opcode::BALANCE => { + let kind = match interpreter.current_opcode() { + opcode::EXTCODECOPY => crate::Vm::AccountAccessKind::Extcodecopy, + opcode::EXTCODESIZE => crate::Vm::AccountAccessKind::Extcodesize, + opcode::EXTCODEHASH => crate::Vm::AccountAccessKind::Extcodehash, + opcode::BALANCE => crate::Vm::AccountAccessKind::Balance, + _ => unreachable!(), + }; + let address = Address::from_word(B256::from(try_or_continue!(interpreter + .stack() + .peek(0)))); + let balance; + let initialized; + // TODO: use ecx.load_account + if let Ok((acc, _)) = ecx.journaled_state.load_account(address, &mut ecx.db) { + initialized = acc.info.exists(); + balance = acc.info.balance; + } else { + initialized = false; + balance = U256::ZERO; + } + let account_access = crate::Vm::AccountAccess { + chainInfo: crate::Vm::ChainInfo { + forkId: ecx.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.env.cfg.chain_id), + }, + accessor: interpreter.contract().address, + account: address, + kind, + initialized, + oldBalance: balance, + newBalance: balance, + value: U256::ZERO, + data: Bytes::new(), + reverted: false, + deployedCode: Bytes::new(), + storageAccesses: vec![], + depth: ecx.journaled_state.depth(), + }; + // Record the EXT* call as an account access at the current depth + // (future storage accesses will be recorded in a new "Resume" context) + if let Some(last) = recorded_account_diffs_stack.last_mut() { + last.push(account_access); + } else { + recorded_account_diffs_stack.push(vec![account_access]); + } + } _ => (), } } @@ -513,7 +578,7 @@ impl Inspector for Cheatcodes { // if the current opcode can either mutate directly or expand memory. If the opcode at // the current program counter is a match, check if the modified memory lies within the // allowed ranges. If not, revert and fail the test. - if let Some(ranges) = self.allowed_mem_writes.get(&data.journaled_state.depth()) { + if let Some(ranges) = self.allowed_mem_writes.get(&ecx.journaled_state.depth()) { // The `mem_opcode_match` macro is used to match the current opcode against a list of // opcodes that can mutate memory (either directly or expansion via reading). If the // opcode is a match, the memory offsets that are being written to are checked to be @@ -538,8 +603,17 @@ impl Inspector for Cheatcodes { if !ranges.iter().any(|range| { range.contains(&offset) && range.contains(&(offset + 31)) }) { + // SPECIAL CASE: When the compiler attempts to store the selector for + // `stopExpectSafeMemory`, this is allowed. It will do so at the current free memory + // pointer, which could have been updated to the exclusive upper bound during + // execution. + let value = try_or_continue!(interpreter.stack().peek(1)).to_be_bytes::<32>(); + let selector = stopExpectSafeMemoryCall {}.cheatcode().func.selector_bytes; + if value[0..SELECTOR_LEN] == selector { + return + } + disallowed_mem_write(offset, 32, interpreter, ranges); - interpreter.instruction_result = InstructionResult::Revert; return } } @@ -551,7 +625,6 @@ impl Inspector for Cheatcodes { // unexpectedly mutated. if !ranges.iter().any(|range| range.contains(&offset)) { disallowed_mem_write(offset, 1, interpreter, ranges); - interpreter.instruction_result = InstructionResult::Revert; return } } @@ -571,7 +644,6 @@ impl Inspector for Cheatcodes { range.contains(&offset) && range.contains(&(offset + 31)) }) { disallowed_mem_write(offset, 32, interpreter, ranges); - interpreter.instruction_result = InstructionResult::Revert; return } } @@ -580,11 +652,48 @@ impl Inspector for Cheatcodes { // OPERATIONS WITH OFFSET AND SIZE ON STACK // //////////////////////////////////////////////////////////////// + opcode::CALL => { + // The destination offset of the operation is the fifth element on the stack. + let dest_offset = try_or_continue!(interpreter.stack().peek(5)).saturating_to::(); + + // The size of the data that will be copied is the sixth element on the stack. + let size = try_or_continue!(interpreter.stack().peek(6)).saturating_to::(); + + // If none of the allowed ranges contain [dest_offset, dest_offset + size), + // memory outside of the expected ranges has been touched. If the opcode + // only reads from memory, this is okay as long as the memory is not expanded. + let fail_cond = !ranges.iter().any(|range| { + range.contains(&dest_offset) && + range.contains(&(dest_offset + size.saturating_sub(1))) + }); + + // If the failure condition is met, set the output buffer to a revert string + // that gives information about the allowed ranges and revert. + if fail_cond { + // SPECIAL CASE: When a call to `stopExpectSafeMemory` is performed, this is allowed. + // It allocated calldata at the current free memory pointer, and will attempt to read + // from this memory region to perform the call. + let to = Address::from_word(try_or_continue!(interpreter.stack().peek(1)).to_be_bytes::<32>().into()); + if to == CHEATCODE_ADDRESS { + let args_offset = try_or_continue!(interpreter.stack().peek(3)).saturating_to::(); + let args_size = try_or_continue!(interpreter.stack().peek(4)).saturating_to::(); + let selector = stopExpectSafeMemoryCall {}.cheatcode().func.selector_bytes; + let memory_word = interpreter.shared_memory.slice(args_offset, args_size); + if memory_word[0..SELECTOR_LEN] == selector { + return + } + } + + disallowed_mem_write(dest_offset, size, interpreter, ranges); + return + } + } + $(opcode::$opcode => { - // The destination offset of the operation is at the top of the stack. + // The destination offset of the operation. let dest_offset = try_or_continue!(interpreter.stack().peek($offset_depth)).saturating_to::(); - // The size of the data that will be copied is the third item on the stack. + // The size of the data that will be copied. let size = try_or_continue!(interpreter.stack().peek($size_depth)).saturating_to::(); // If none of the allowed ranges contain [dest_offset, dest_offset + size), @@ -603,7 +712,6 @@ impl Inspector for Cheatcodes { // that gives information about the allowed ranges and revert. if fail_cond { disallowed_mem_write(dest_offset, size, interpreter, ranges); - interpreter.instruction_result = InstructionResult::Revert; return } })* @@ -619,7 +727,6 @@ impl Inspector for Cheatcodes { (CODECOPY, 0, 2, true), (RETURNDATACOPY, 0, 2, true), (EXTCODECOPY, 1, 3, true), - (CALL, 5, 6, true), (CALLCODE, 5, 6, true), (STATICCALL, 4, 5, true), (DELEGATECALL, 4, 5, true), @@ -642,37 +749,49 @@ impl Inspector for Cheatcodes { } } - fn log(&mut self, _: &mut EVMData<'_, DB>, address: &Address, topics: &[B256], data: &Bytes) { + fn log(&mut self, _context: &mut EvmContext, log: &Log) { if !self.expected_emits.is_empty() { - expect::handle_expect_emit(self, address, topics, data); + expect::handle_expect_emit(self, log); } // Stores this log if `recordLogs` has been called if let Some(storage_recorded_logs) = &mut self.recorded_logs { storage_recorded_logs.push(Vm::Log { - topics: topics.to_vec(), - data: data.to_vec(), - emitter: *address, + topics: log.data.topics().to_vec(), + data: log.data.data.clone(), + emitter: log.address, }); } } - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { + fn call(&mut self, ecx: &mut EvmContext, call: &mut CallInputs) -> Option { let gas = Gas::new(call.gas_limit); if call.contract == CHEATCODE_ADDRESS { - return match self.apply_cheatcode(data, call) { - Ok(retdata) => (InstructionResult::Return, gas, retdata.into()), - Err(err) => (InstructionResult::Revert, gas, err.abi_encode().into()), - } + return match self.apply_cheatcode(ecx, call) { + Ok(retdata) => Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Return, + output: retdata.into(), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }), + Err(err) => Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: err.abi_encode().into(), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }), + }; } + let ecx = &mut ecx.inner; + if call.contract == HARDHAT_CONSOLE_ADDRESS { - return (InstructionResult::Continue, gas, Bytes::new()) + return None } // Handle expected calls @@ -715,19 +834,26 @@ impl Inspector for Cheatcodes { }) .map(|(_, v)| v) }) { - return (return_data.ret_type, gas, return_data.data.clone()) + return Some(CallOutcome { + result: InterpreterResult { + result: return_data.ret_type, + output: return_data.data.clone(), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }) } } // Apply our prank if let Some(prank) = &self.prank { - if data.journaled_state.depth() >= prank.depth && + if ecx.journaled_state.depth() >= prank.depth && call.context.caller == prank.prank_caller { let mut prank_applied = false; // At the target depth we set `msg.sender` - if data.journaled_state.depth() == prank.depth { + if ecx.journaled_state.depth() == prank.depth { call.context.caller = prank.new_caller; call.transfer.source = prank.new_caller; prank_applied = true; @@ -735,7 +861,7 @@ impl Inspector for Cheatcodes { // At the target depth, or deeper, we set `tx.origin` if let Some(new_origin) = prank.new_origin { - data.env.tx.caller = new_origin; + ecx.env.tx.caller = new_origin; prank_applied = true; } @@ -754,13 +880,13 @@ impl Inspector for Cheatcodes { // // We do this because any subsequent contract calls *must* exist on chain and // we only want to grab *this* call, not internal ones - if data.journaled_state.depth() == broadcast.depth && + if ecx.journaled_state.depth() == broadcast.depth && call.context.caller == broadcast.original_caller { // At the target depth we set `msg.sender` & tx.origin. // We are simulating the caller as being an EOA, so *both* must be set to the // broadcast.origin. - data.env.tx.caller = broadcast.new_origin; + ecx.env.tx.caller = broadcast.new_origin; call.context.caller = broadcast.new_origin; call.transfer.source = broadcast.new_origin; @@ -769,41 +895,56 @@ impl Inspector for Cheatcodes { // into 1559, in the cli package, relatively easily once we // know the target chain supports EIP-1559. if !call.is_static { - if let Err(err) = - data.journaled_state.load_account(broadcast.new_origin, data.db) - { - return (InstructionResult::Revert, gas, Error::encode(err)) + if let Err(err) = ecx.load_account(broadcast.new_origin) { + return Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Error::encode(err), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }) } - let is_fixed_gas_limit = check_if_fixed_gas_limit(data, call.gas_limit); + let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx, call.gas_limit); let account = - data.journaled_state.state().get_mut(&broadcast.new_origin).unwrap(); + ecx.journaled_state.state().get_mut(&broadcast.new_origin).unwrap(); self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: data.db.active_fork_url(), - transaction: TypedTransaction::Legacy(TransactionRequest { - from: Some(broadcast.new_origin.to_ethers()), - to: Some(NameOrAddress::Address(call.contract.to_ethers())), - value: Some(call.transfer.value.to_ethers()), - data: Some(call.input.clone().to_ethers()), - nonce: Some(account.info.nonce.into()), + rpc: ecx.db.active_fork_url(), + transaction: TransactionRequest { + from: Some(broadcast.new_origin), + to: Some(TxKind::from(Some(call.contract))), + value: Some(call.transfer.value), + input: TransactionInput::new(call.input.clone()), + nonce: Some(account.info.nonce), gas: if is_fixed_gas_limit { - Some(call.gas_limit.into()) + Some(call.gas_limit as u128) } else { None }, ..Default::default() - }), + }, }); debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable call"); let prev = account.info.nonce; + + // Touch account to ensure that incremented nonce is committed + account.mark_touch(); account.info.nonce += 1; debug!(target: "cheatcodes", address=%broadcast.new_origin, nonce=prev+1, prev, "incremented nonce"); } else if broadcast.single_call { let msg = "`staticcall`s are not allowed after `broadcast`; use `startBroadcast` instead"; - return (InstructionResult::Revert, Gas::new(0), Error::encode(msg)) + return Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Error::encode(msg), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }) } } } @@ -814,7 +955,8 @@ impl Inspector for Cheatcodes { // nonce, a non-zero KECCAK_EMPTY codehash, or non-empty code let initialized; let old_balance; - if let Ok((acc, _)) = data.journaled_state.load_account(call.contract, data.db) { + // TODO: use ecx.load_account + if let Ok((acc, _)) = ecx.journaled_state.load_account(call.contract, &mut ecx.db) { initialized = acc.info.exists(); old_balance = acc.info.balance; } else { @@ -834,107 +976,148 @@ impl Inspector for Cheatcodes { // updated with the revert status of this call, since the EVM does not mark accounts // as "warm" if the call from which they were accessed is reverted recorded_account_diffs_stack.push(vec![AccountAccess { - access: crate::Vm::AccountAccess { - chainInfo: crate::Vm::ChainInfo { - forkId: data.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(data.env.cfg.chain_id), - }, - accessor: call.context.caller, - account: call.contract, - kind, - initialized, - oldBalance: old_balance, - newBalance: U256::ZERO, // updated on call_end - value: call.transfer.value, - data: call.input.to_vec(), - reverted: false, - deployedCode: vec![], - storageAccesses: vec![], // updated on step + chainInfo: crate::Vm::ChainInfo { + forkId: ecx.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.env.cfg.chain_id), }, - depth: data.journaled_state.depth(), + accessor: call.context.caller, + account: call.contract, + kind, + initialized, + oldBalance: old_balance, + newBalance: U256::ZERO, // updated on call_end + value: call.transfer.value, + data: call.input.clone(), + reverted: false, + deployedCode: Bytes::new(), + storageAccesses: vec![], // updated on step + depth: ecx.journaled_state.depth(), }]); } - (InstructionResult::Continue, gas, Bytes::new()) + None } fn call_end( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext, call: &CallInputs, - remaining_gas: Gas, - status: InstructionResult, - retdata: Bytes, - ) -> (InstructionResult, Gas, Bytes) { - if call.contract == CHEATCODE_ADDRESS || call.contract == HARDHAT_CONSOLE_ADDRESS { - return (status, remaining_gas, retdata) - } - - if data.journaled_state.depth() == 0 && self.skip { - return ( - InstructionResult::Revert, - remaining_gas, - super::Error::from(MAGIC_SKIP).abi_encode().into(), - ) - } - - // Clean up pranks - if let Some(prank) = &self.prank { - if data.journaled_state.depth() == prank.depth { - data.env.tx.caller = prank.prank_origin; - - // Clean single-call prank once we have returned to the original depth - if prank.single_call { - let _ = self.prank.take(); + mut outcome: CallOutcome, + ) -> CallOutcome { + let ecx = &mut ecx.inner; + let cheatcode_call = + call.contract == CHEATCODE_ADDRESS || call.contract == HARDHAT_CONSOLE_ADDRESS; + + // Clean up pranks/broadcasts if it's not a cheatcode call end. We shouldn't do + // it for cheatcode calls because they are not appplied for cheatcodes in the `call` hook. + // This should be placed before the revert handling, because we might exit early there + if !cheatcode_call { + // Clean up pranks + if let Some(prank) = &self.prank { + if ecx.journaled_state.depth() == prank.depth { + ecx.env.tx.caller = prank.prank_origin; + + // Clean single-call prank once we have returned to the original depth + if prank.single_call { + let _ = self.prank.take(); + } } } - } - // Clean up broadcast - if let Some(broadcast) = &self.broadcast { - if data.journaled_state.depth() == broadcast.depth { - data.env.tx.caller = broadcast.original_origin; + // Clean up broadcast + if let Some(broadcast) = &self.broadcast { + if ecx.journaled_state.depth() == broadcast.depth { + ecx.env.tx.caller = broadcast.original_origin; - // Clean single-call broadcast once we have returned to the original depth - if broadcast.single_call { - let _ = self.broadcast.take(); + // Clean single-call broadcast once we have returned to the original depth + if broadcast.single_call { + let _ = self.broadcast.take(); + } } } } // Handle expected reverts if let Some(expected_revert) = &self.expected_revert { - if data.journaled_state.depth() <= expected_revert.depth { - let expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); - return match expect::handle_expect_revert( - false, - expected_revert.reason.as_deref(), - status, - retdata, - ) { - Err(error) => { - trace!(expected=?expected_revert, ?error, ?status, "Expected revert mismatch"); - (InstructionResult::Revert, remaining_gas, error.abi_encode().into()) + if ecx.journaled_state.depth() <= expected_revert.depth { + let needs_processing: bool = match expected_revert.kind { + ExpectedRevertKind::Default => !cheatcode_call, + // `pending_processing` == true means that we're in the `call_end` hook for + // `vm.expectCheatcodeRevert` and shouldn't expect revert here + ExpectedRevertKind::Cheatcode { pending_processing } => { + cheatcode_call && !pending_processing + } + }; + + if needs_processing { + let expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); + return match expect::handle_expect_revert( + false, + expected_revert.reason.as_deref(), + outcome.result.result, + outcome.result.output.clone(), + ) { + Err(error) => { + trace!(expected=?expected_revert, ?error, status=?outcome.result.result, "Expected revert mismatch"); + outcome.result.result = InstructionResult::Revert; + outcome.result.output = error.abi_encode().into(); + outcome + } + Ok((_, retdata)) => { + outcome.result.result = InstructionResult::Return; + outcome.result.output = retdata; + outcome + } + }; + } + + // Flip `pending_processing` flag for cheatcode revert expectations, marking that + // we've exited the `expectCheatcodeRevert` call scope + if let ExpectedRevertKind::Cheatcode { pending_processing } = + &mut self.expected_revert.as_mut().unwrap().kind + { + if *pending_processing { + *pending_processing = false; } - Ok((_, retdata)) => (InstructionResult::Return, remaining_gas, retdata), } } } + // Exit early for calls to cheatcodes as other logic is not relevant for cheatcode + // invocations + if cheatcode_call { + return outcome + } + + // Record the gas usage of the call, this allows the `lastCallGas` cheatcode to + // retrieve the gas usage of the last call. + let gas = outcome.result.gas; + self.last_call_gas = Some(crate::Vm::Gas { + // The gas limit of the call. + gasLimit: gas.limit(), + // The total gas used. + gasTotalUsed: gas.spent(), + // The amount of gas used for memory expansion. + gasMemoryUsed: gas.memory(), + // The amount of gas refunded. + gasRefunded: gas.refunded(), + // The amount of gas remaining. + gasRemaining: gas.remaining(), + }); + // If `startStateDiffRecording` has been called, update the `reverted` status of the // previous call depth's recorded accesses, if any if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { // The root call cannot be recorded. - if data.journaled_state.depth() > 0 { + if ecx.journaled_state.depth() > 0 { let mut last_recorded_depth = recorded_account_diffs_stack.pop().expect("missing CALL account accesses"); // Update the reverted status of all deeper calls if this call reverted, in // accordance with EVM behavior - if status.is_revert() { + if outcome.result.is_revert() { last_recorded_depth.iter_mut().for_each(|element| { - element.access.reverted = true; + element.reverted = true; element - .access .storageAccesses .iter_mut() .for_each(|storage_access| storage_access.reverted = true); @@ -944,11 +1127,13 @@ impl Inspector for Cheatcodes { // Assert that we're at the correct depth before recording post-call state changes. // Depending on the depth the cheat was called at, there may not be any pending // calls to update if execution has percolated up to a higher depth. - if call_access.depth == data.journaled_state.depth() { - if let Ok((acc, _)) = data.journaled_state.load_account(call.contract, data.db) + if call_access.depth == ecx.journaled_state.depth() { + // TODO: use ecx.load_account + if let Ok((acc, _)) = + ecx.journaled_state.load_account(call.contract, &mut ecx.db) { - debug_assert!(access_is_call(call_access.access.kind)); - call_access.access.newBalance = acc.info.balance; + debug_assert!(access_is_call(call_access.kind)); + call_access.newBalance = acc.info.balance; } } // Merge the last depth's AccountAccesses into the AccountAccesses at the current @@ -976,17 +1161,15 @@ impl Inspector for Cheatcodes { let should_check_emits = self .expected_emits .iter() - .any(|expected| expected.depth == data.journaled_state.depth()) && + .any(|expected| expected.depth == ecx.journaled_state.depth()) && // Ignore staticcalls !call.is_static; if should_check_emits { // Not all emits were matched. if self.expected_emits.iter().any(|expected| !expected.found) { - return ( - InstructionResult::Revert, - remaining_gas, - "log != expected log".abi_encode().into(), - ) + outcome.result.result = InstructionResult::Revert; + outcome.result.output = "log != expected log".abi_encode().into(); + return outcome } else { // All emits were found, we're good. // Clear the queue, as we expect the user to declare more events for the next call @@ -1001,33 +1184,34 @@ impl Inspector for Cheatcodes { // if there's a revert and a previous call was diagnosed as fork related revert then we can // return a better error here - if status == InstructionResult::Revert { + if outcome.result.is_revert() { if let Some(err) = diag { - return (status, remaining_gas, Error::encode(err.to_error_msg(&self.labels))) + outcome.result.output = Error::encode(err.to_error_msg(&self.labels)); + return outcome } } // try to diagnose reverts in multi-fork mode where a call is made to an address that does // not exist - if let TransactTo::Call(test_contract) = data.env.tx.transact_to { + if let TransactTo::Call(test_contract) = ecx.env.tx.transact_to { // if a call to a different contract than the original test contract returned with // `Stop` we check if the contract actually exists on the active fork - if data.db.is_forked_mode() && - status == InstructionResult::Stop && + if ecx.db.is_forked_mode() && + outcome.result.result == InstructionResult::Stop && call.contract != test_contract { self.fork_revert_diagnostic = - data.db.diagnose_revert(call.contract, &data.journaled_state); + ecx.db.diagnose_revert(call.contract, &ecx.journaled_state); } } // If the depth is 0, then this is the root call terminating - if data.journaled_state.depth() == 0 { + if ecx.journaled_state.depth() == 0 { // If we already have a revert, we shouldn't run the below logic as it can obfuscate an // earlier error that happened first with unrelated information about // another error when using cheatcodes. - if status == InstructionResult::Revert { - return (status, remaining_gas, retdata) + if outcome.result.is_revert() { + return outcome; } // If there's not a revert, we can continue on to run the last logic for expect* @@ -1059,7 +1243,7 @@ impl Inspector for Cheatcodes { .into_iter() .flatten() .join(", "); - let but = if status.is_ok() { + let but = if outcome.result.is_ok() { let s = if *actual_count == 1 { "" } else { "s" }; format!("was called {actual_count} time{s}") } else { @@ -1072,7 +1256,10 @@ impl Inspector for Cheatcodes { "expected call to {address} with {expected_values} \ to be called {count} time{s}, but {but}" ); - return (InstructionResult::Revert, remaining_gas, Error::encode(msg)) + outcome.result.result = InstructionResult::Revert; + outcome.result.output = Error::encode(msg); + + return outcome; } } } @@ -1082,87 +1269,88 @@ impl Inspector for Cheatcodes { self.expected_emits.retain(|expected| !expected.found); // If not empty, we got mismatched emits if !self.expected_emits.is_empty() { - let msg = if status.is_ok() { + let msg = if outcome.result.is_ok() { "expected an emit, but no logs were emitted afterwards. \ you might have mismatched events or not enough events were emitted" } else { "expected an emit, but the call reverted instead. \ ensure you're testing the happy path when using `expectEmit`" }; - return (InstructionResult::Revert, remaining_gas, Error::encode(msg)) + outcome.result.result = InstructionResult::Revert; + outcome.result.output = Error::encode(msg); + return outcome; } } - (status, remaining_gas, retdata) + outcome } fn create( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext, call: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { + ) -> Option { + let ecx = &mut ecx.inner; let gas = Gas::new(call.gas_limit); - // allow cheatcodes from the address of the new contract - let address = self.allow_cheatcodes_on_create(data, call); - // Apply our prank if let Some(prank) = &self.prank { - if data.journaled_state.depth() >= prank.depth && call.caller == prank.prank_caller { + if ecx.journaled_state.depth() >= prank.depth && call.caller == prank.prank_caller { // At the target depth we set `msg.sender` - if data.journaled_state.depth() == prank.depth { + if ecx.journaled_state.depth() == prank.depth { call.caller = prank.new_caller; } // At the target depth, or deeper, we set `tx.origin` if let Some(new_origin) = prank.new_origin { - data.env.tx.caller = new_origin; + ecx.env.tx.caller = new_origin; } } } // Apply our broadcast if let Some(broadcast) = &self.broadcast { - if data.journaled_state.depth() >= broadcast.depth && + if ecx.journaled_state.depth() >= broadcast.depth && call.caller == broadcast.original_caller { - if let Err(err) = data.journaled_state.load_account(broadcast.new_origin, data.db) { - return (InstructionResult::Revert, None, gas, Error::encode(err)) + if let Err(err) = + ecx.journaled_state.load_account(broadcast.new_origin, &mut ecx.db) + { + return Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Error::encode(err), + gas, + }, + address: None, + }) } - data.env.tx.caller = broadcast.new_origin; + ecx.env.tx.caller = broadcast.new_origin; - if data.journaled_state.depth() == broadcast.depth { - let (bytecode, to, nonce) = match process_create( - broadcast.new_origin, - call.init_code.clone(), - data, - call, - ) { - Ok(val) => val, - Err(err) => { - return (InstructionResult::Revert, None, gas, Error::encode(err)) - } - }; + if ecx.journaled_state.depth() == broadcast.depth { + call.caller = broadcast.new_origin; + let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx, call.gas_limit); - let is_fixed_gas_limit = check_if_fixed_gas_limit(data, call.gas_limit); + let account = &ecx.journaled_state.state()[&broadcast.new_origin]; self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: data.db.active_fork_url(), - transaction: TypedTransaction::Legacy(TransactionRequest { - from: Some(broadcast.new_origin.to_ethers()), - to: to.map(|a| NameOrAddress::Address(a.to_ethers())), - value: Some(call.value.to_ethers()), - data: Some(bytecode.to_ethers()), - nonce: Some(nonce.into()), + rpc: ecx.db.active_fork_url(), + transaction: TransactionRequest { + from: Some(broadcast.new_origin), + to: None, + value: Some(call.value), + input: TransactionInput::new(call.init_code.clone()), + nonce: Some(account.info.nonce), gas: if is_fixed_gas_limit { - Some(call.gas_limit.into()) + Some(call.gas_limit as u128) } else { None }, ..Default::default() - }), + }, }); + let kind = match call.scheme { CreateScheme::Create => "create", CreateScheme::Create2 { .. } => "create2", @@ -1172,48 +1360,49 @@ impl Inspector for Cheatcodes { } } + // allow cheatcodes from the address of the new contract + // Compute the address *after* any possible broadcast updates, so it's based on the updated + // call inputs + let address = self.allow_cheatcodes_on_create(ecx, call); // If `recordAccountAccesses` has been called, record the create if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { // Record the create context as an account access and create a new vector to record all // subsequent account accesses recorded_account_diffs_stack.push(vec![AccountAccess { - access: crate::Vm::AccountAccess { - chainInfo: crate::Vm::ChainInfo { - forkId: data.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(data.env.cfg.chain_id), - }, - accessor: call.caller, - account: address, - kind: crate::Vm::AccountAccessKind::Create, - initialized: true, - oldBalance: U256::ZERO, // updated on create_end - newBalance: U256::ZERO, // updated on create_end - value: call.value, - data: call.init_code.to_vec(), - reverted: false, - deployedCode: vec![], // updated on create_end - storageAccesses: vec![], // updated on create_end + chainInfo: crate::Vm::ChainInfo { + forkId: ecx.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.env.cfg.chain_id), }, - depth: data.journaled_state.depth(), + accessor: call.caller, + account: address, + kind: crate::Vm::AccountAccessKind::Create, + initialized: true, + oldBalance: U256::ZERO, // updated on create_end + newBalance: U256::ZERO, // updated on create_end + value: call.value, + data: call.init_code.clone(), + reverted: false, + deployedCode: Bytes::new(), // updated on create_end + storageAccesses: vec![], // updated on create_end + depth: ecx.journaled_state.depth(), }]); } - (InstructionResult::Continue, None, gas, Bytes::new()) + None } fn create_end( &mut self, - data: &mut EVMData<'_, DB>, - _: &CreateInputs, - status: InstructionResult, - address: Option
, - remaining_gas: Gas, - retdata: Bytes, - ) -> (InstructionResult, Option
, Gas, Bytes) { + ecx: &mut EvmContext, + _call: &CreateInputs, + mut outcome: CreateOutcome, + ) -> CreateOutcome { + let ecx = &mut ecx.inner; + // Clean up pranks if let Some(prank) = &self.prank { - if data.journaled_state.depth() == prank.depth { - data.env.tx.caller = prank.prank_origin; + if ecx.journaled_state.depth() == prank.depth { + ecx.env.tx.caller = prank.prank_origin; // Clean single-call prank once we have returned to the original depth if prank.single_call { @@ -1224,8 +1413,8 @@ impl Inspector for Cheatcodes { // Clean up broadcasts if let Some(broadcast) = &self.broadcast { - if data.journaled_state.depth() == broadcast.depth { - data.env.tx.caller = broadcast.original_origin; + if ecx.journaled_state.depth() == broadcast.depth { + ecx.env.tx.caller = broadcast.original_origin; // Clean single-call broadcast once we have returned to the original depth if broadcast.single_call { @@ -1236,21 +1425,28 @@ impl Inspector for Cheatcodes { // Handle expected reverts if let Some(expected_revert) = &self.expected_revert { - if data.journaled_state.depth() <= expected_revert.depth { + if ecx.journaled_state.depth() <= expected_revert.depth && + matches!(expected_revert.kind, ExpectedRevertKind::Default) + { let expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); return match expect::handle_expect_revert( true, expected_revert.reason.as_deref(), - status, - retdata, + outcome.result.result, + outcome.result.output.clone(), ) { Ok((address, retdata)) => { - (InstructionResult::Return, address, remaining_gas, retdata) + outcome.result.result = InstructionResult::Return; + outcome.result.output = retdata; + outcome.address = address; + outcome } Err(err) => { - (InstructionResult::Revert, None, remaining_gas, err.abi_encode().into()) + outcome.result.result = InstructionResult::Revert; + outcome.result.output = err.abi_encode().into(); + outcome } - } + }; } } @@ -1258,16 +1454,15 @@ impl Inspector for Cheatcodes { // previous call depth's recorded accesses, if any if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { // The root call cannot be recorded. - if data.journaled_state.depth() > 0 { + if ecx.journaled_state.depth() > 0 { let mut last_depth = recorded_account_diffs_stack.pop().expect("missing CREATE account accesses"); // Update the reverted status of all deeper calls if this call reverted, in // accordance with EVM behavior - if status.is_revert() { + if outcome.result.is_revert() { last_depth.iter_mut().for_each(|element| { - element.access.reverted = true; + element.reverted = true; element - .access .storageAccesses .iter_mut() .for_each(|storage_access| storage_access.reverted = true); @@ -1278,23 +1473,18 @@ impl Inspector for Cheatcodes { // changes. Depending on what depth the cheat was called at, there // may not be any pending calls to update if execution has // percolated up to a higher depth. - if create_access.depth == data.journaled_state.depth() { + if create_access.depth == ecx.journaled_state.depth() { debug_assert_eq!( - create_access.access.kind as u8, + create_access.kind as u8, crate::Vm::AccountAccessKind::Create as u8 ); - if let Some(address) = address { + if let Some(address) = outcome.address { if let Ok((created_acc, _)) = - data.journaled_state.load_account(address, data.db) + ecx.journaled_state.load_account(address, &mut ecx.db) { - create_access.access.newBalance = created_acc.info.balance; - create_access.access.deployedCode = created_acc - .info - .code - .clone() - .unwrap_or_default() - .original_bytes() - .into(); + create_access.newBalance = created_acc.info.balance; + create_access.deployedCode = + created_acc.info.code.clone().unwrap_or_default().original_bytes(); } } } @@ -1309,86 +1499,73 @@ impl Inspector for Cheatcodes { } } - (status, address, remaining_gas, retdata) + outcome + } +} + +impl InspectorExt for Cheatcodes { + fn should_use_create2_factory( + &mut self, + ecx: &mut EvmContext, + inputs: &mut CreateInputs, + ) -> bool { + if let CreateScheme::Create2 { .. } = inputs.scheme { + let target_depth = if let Some(prank) = &self.prank { + prank.depth + } else if let Some(broadcast) = &self.broadcast { + broadcast.depth + } else { + 1 + }; + + ecx.journaled_state.depth() == target_depth && + (self.broadcast.is_some() || self.config.always_use_create_2_factory) + } else { + false + } } } /// Helper that expands memory, stores a revert string pertaining to a disallowed memory write, /// and sets the return range to the revert string's location in memory. +/// +/// This will set the interpreter's next action to a return with the revert string as the output. +/// And trigger a revert. fn disallowed_mem_write( dest_offset: u64, size: u64, - interpreter: &mut Interpreter<'_>, + interpreter: &mut Interpreter, ranges: &[Range], ) { let revert_string = format!( "memory write at offset 0x{:02X} of size 0x{:02X} not allowed; safe range: {}", dest_offset, size, - ranges.iter().map(|r| format!("(0x{:02X}, 0x{:02X}]", r.start, r.end)).join(" ∪ ") - ) - .abi_encode(); - mstore_revert_string(interpreter, &revert_string); -} - -/// Expands memory, stores a revert string, and sets the return range to the revert -/// string's location in memory. -fn mstore_revert_string(interpreter: &mut Interpreter<'_>, bytes: &[u8]) { - let starting_offset = interpreter.shared_memory.len(); - interpreter.shared_memory.resize(starting_offset + bytes.len()); - interpreter.shared_memory.set_data(starting_offset, 0, bytes.len(), bytes); - interpreter.return_offset = starting_offset; - interpreter.return_len = interpreter.shared_memory.len() - starting_offset -} - -fn process_create( - broadcast_sender: Address, - bytecode: Bytes, - data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, -) -> Result<(Bytes, Option
, u64), DB::Error> { - match call.scheme { - CreateScheme::Create => { - call.caller = broadcast_sender; - Ok((bytecode, None, data.journaled_state.account(broadcast_sender).info.nonce)) - } - CreateScheme::Create2 { salt } => { - // Sanity checks for our CREATE2 deployer - let info = - &data.journaled_state.load_account(DEFAULT_CREATE2_DEPLOYER, data.db)?.0.info; - match &info.code { - Some(code) if code.is_empty() => return Err(DatabaseError::MissingCreate2Deployer), - None if data.db.code_by_hash(info.code_hash)?.is_empty() => { - return Err(DatabaseError::MissingCreate2Deployer) - } - _ => {} - } - - call.caller = DEFAULT_CREATE2_DEPLOYER; - - // We have to increment the nonce of the user address, since this create2 will be done - // by the create2_deployer - let account = data.journaled_state.state().get_mut(&broadcast_sender).unwrap(); - let prev = account.info.nonce; - account.info.nonce += 1; - debug!(target: "cheatcodes", address=%broadcast_sender, nonce=prev+1, prev, "incremented nonce in create2"); - - // Proxy deployer requires the data to be `salt ++ init_code` - let calldata = [&salt.to_be_bytes::<32>()[..], &bytecode[..]].concat(); - Ok((calldata.into(), Some(DEFAULT_CREATE2_DEPLOYER), prev)) - } - } + ranges.iter().map(|r| format!("(0x{:02X}, 0x{:02X}]", r.start, r.end)).join(" U ") + ); + + interpreter.instruction_result = InstructionResult::Revert; + interpreter.next_action = InterpreterAction::Return { + result: InterpreterResult { + output: Error::encode(revert_string), + gas: interpreter.gas, + result: InstructionResult::Revert, + }, + }; } // Determines if the gas limit on a given call was manually set in the script and should therefore // not be overwritten by later estimations -fn check_if_fixed_gas_limit(data: &EVMData<'_, DB>, call_gas_limit: u64) -> bool { +fn check_if_fixed_gas_limit( + ecx: &InnerEvmContext, + call_gas_limit: u64, +) -> bool { // If the gas limit was not set in the source code it is set to the estimated gas left at the // time of the call, which should be rather close to configured gas limit. // TODO: Find a way to reliably make this determination. // For example by generating it in the compilation or EVM simulation process - U256::from(data.env.tx.gas_limit) > data.env.block.gas_limit && - U256::from(call_gas_limit) <= data.env.block.gas_limit + U256::from(ecx.env.tx.gas_limit) > ecx.env.block.gas_limit && + U256::from(call_gas_limit) <= ecx.env.block.gas_limit // Transfers in forge scripts seem to be estimated at 2300 by revm leading to "Intrinsic // gas too low" failure when simulated on chain && call_gas_limit > 2300 @@ -1432,32 +1609,33 @@ fn append_storage_access( // 2. If there's an existing Resume record, then add the storage access to it. // 3. Otherwise, create a new Resume record based on the current context. if last.len() == 1 { - last.first_mut().unwrap().access.storageAccesses.push(storage_access); + last.first_mut().unwrap().storageAccesses.push(storage_access); } else { let last_record = last.last_mut().unwrap(); - if last_record.access.kind as u8 == crate::Vm::AccountAccessKind::Resume as u8 { - last_record.access.storageAccesses.push(storage_access); + if last_record.kind as u8 == crate::Vm::AccountAccessKind::Resume as u8 { + last_record.storageAccesses.push(storage_access); } else { let entry = last.first().unwrap(); let resume_record = crate::Vm::AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: entry.access.chainInfo.forkId, - chainId: entry.access.chainInfo.chainId, + forkId: entry.chainInfo.forkId, + chainId: entry.chainInfo.chainId, }, - accessor: entry.access.accessor, - account: entry.access.account, + accessor: entry.accessor, + account: entry.account, kind: crate::Vm::AccountAccessKind::Resume, - initialized: entry.access.initialized, + initialized: entry.initialized, storageAccesses: vec![storage_access], - reverted: entry.access.reverted, + reverted: entry.reverted, // The remaining fields are defaults oldBalance: U256::ZERO, newBalance: U256::ZERO, value: U256::ZERO, - data: vec![], - deployedCode: vec![], + data: Bytes::new(), + deployedCode: Bytes::new(), + depth: entry.depth, }; - last.push(AccountAccess { access: resume_record, depth: entry.depth }); + last.push(resume_record); } } } diff --git a/crates/cheatcodes/src/json.rs b/crates/cheatcodes/src/json.rs index aa27a446d0e11..e493078a3262d 100644 --- a/crates/cheatcodes/src/json.rs +++ b/crates/cheatcodes/src/json.rs @@ -12,10 +12,14 @@ use std::{borrow::Cow, collections::BTreeMap, fmt::Write}; impl Cheatcode for keyExistsCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; - let json = parse_json_str(json)?; - let values = select(&json, key)?; - let exists = !values.is_empty(); - Ok(exists.abi_encode()) + check_json_key_exists(json, key) + } +} + +impl Cheatcode for keyExistsJsonCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + check_json_key_exists(json, key) } } @@ -134,16 +138,7 @@ impl Cheatcode for parseJsonBytes32ArrayCall { impl Cheatcode for parseJsonKeysCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; - let json = parse_json_str(json)?; - let values = select(&json, key)?; - let [value] = values[..] else { - bail!("key {key:?} must return exactly one JSON object"); - }; - let Value::Object(object) = value else { - bail!("JSON value at {key:?} is not an object"); - }; - let keys = object.keys().collect::>(); - Ok(keys.abi_encode()) + parse_json_keys(json, key) } } @@ -192,7 +187,7 @@ impl Cheatcode for serializeBytes32_0Call { impl Cheatcode for serializeString_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { objectKey, valueKey, value } = self; - serialize_json(state, objectKey, Some(valueKey), &value.to_string()) + serialize_json(state, objectKey, Some(valueKey), value) } } @@ -253,6 +248,14 @@ impl Cheatcode for serializeBytes_1Call { } } +impl Cheatcode for serializeUintToHexCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { objectKey, valueKey, value } = self; + let hex = format!("0x{:x}", value); + serialize_json(state, objectKey, Some(valueKey), &hex) + } +} + impl Cheatcode for writeJson_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, path } = self; @@ -280,14 +283,21 @@ impl Cheatcode for writeJson_1Call { } } -fn parse_json(json: &str, path: &str) -> Result { +pub(super) fn check_json_key_exists(json: &str, key: &str) -> Result { + let json = parse_json_str(json)?; + let values = select(&json, key)?; + let exists = !values.is_empty(); + Ok(exists.abi_encode()) +} + +pub(super) fn parse_json(json: &str, path: &str) -> Result { let value = parse_json_str(json)?; let selected = select(&value, path)?; let sol = json_to_sol(&selected)?; Ok(encode(sol)) } -fn parse_json_coerce(json: &str, path: &str, ty: &DynSolType) -> Result { +pub(super) fn parse_json_coerce(json: &str, path: &str, ty: &DynSolType) -> Result { let value = parse_json_str(json)?; let values = select(&value, path)?; ensure!(!values.is_empty(), "no matching value found at {path:?}"); @@ -311,6 +321,19 @@ fn parse_json_coerce(json: &str, path: &str, ty: &DynSolType) -> Result { } } +pub(super) fn parse_json_keys(json: &str, key: &str) -> Result { + let json = parse_json_str(json)?; + let values = select(&json, key)?; + let [value] = values[..] else { + bail!("key {key:?} must return exactly one JSON object"); + }; + let Value::Object(object) = value else { + bail!("JSON value at {key:?} is not an object"); + }; + let keys = object.keys().collect::>(); + Ok(keys.abi_encode()) +} + fn parse_json_str(json: &str) -> Result { serde_json::from_str(json).map_err(|e| fmt_err!("failed parsing JSON: {e}")) } @@ -318,7 +341,7 @@ fn parse_json_str(json: &str) -> Result { fn json_to_sol(json: &[&Value]) -> Result> { let mut sol = Vec::with_capacity(json.len()); for value in json { - sol.push(value_to_token(value)?); + sol.push(json_value_to_token(value)?); } Ok(sol) } @@ -345,7 +368,7 @@ fn encode(values: Vec) -> Vec { /// Canonicalize a json path key to always start from the root of the document. /// Read more about json path syntax: -fn canonicalize_json_path(path: &str) -> Cow<'_, str> { +pub(super) fn canonicalize_json_path(path: &str) -> Cow<'_, str> { if !path.starts_with('$') { format!("${path}").into() } else { @@ -359,12 +382,12 @@ fn canonicalize_json_path(path: &str) -> Cow<'_, str> { /// it will call itself to convert each of it's value and encode the whole as a /// Tuple #[instrument(target = "cheatcodes", level = "trace", ret)] -pub(super) fn value_to_token(value: &Value) -> Result { +pub(super) fn json_value_to_token(value: &Value) -> Result { match value { Value::Null => Ok(DynSolValue::FixedBytes(B256::ZERO, 32)), Value::Bool(boolean) => Ok(DynSolValue::Bool(*boolean)), Value::Array(array) => { - array.iter().map(value_to_token).collect::>().map(DynSolValue::Array) + array.iter().map(json_value_to_token).collect::>().map(DynSolValue::Array) } value @ Value::Object(_) => { // See: [#3647](https://github.com/foundry-rs/foundry/pull/3647) @@ -372,7 +395,7 @@ pub(super) fn value_to_token(value: &Value) -> Result { serde_json::from_value(value.clone()).unwrap(); ordered_object .values() - .map(value_to_token) + .map(json_value_to_token) .collect::>() .map(DynSolValue::Tuple) } @@ -400,18 +423,18 @@ pub(super) fn value_to_token(value: &Value) -> Result { // used. let fallback_s = f.to_string(); if let Ok(n) = fallback_s.parse() { - return Ok(DynSolValue::Uint(n, 256)) + return Ok(DynSolValue::Uint(n, 256)); } if let Ok(n) = I256::from_dec_str(&fallback_s) { - return Ok(DynSolValue::Int(n, 256)) + return Ok(DynSolValue::Int(n, 256)); } } if let Ok(n) = s.parse() { - return Ok(DynSolValue::Uint(n, 256)) + return Ok(DynSolValue::Uint(n, 256)); } if let Ok(n) = s.parse() { - return Ok(DynSolValue::Int(n, 256)) + return Ok(DynSolValue::Int(n, 256)); } } } diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index b7a401c074a81..01695fa707006 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -12,33 +12,35 @@ extern crate tracing; use alloy_primitives::Address; use foundry_evm_core::backend::DatabaseExt; -use revm::EVMData; +use revm::{ContextPrecompiles, InnerEvmContext}; +pub use config::CheatsConfig; +pub use error::{Error, ErrorKind, Result}; +pub use inspector::{BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, Context}; pub use spec::{CheatcodeDef, Vm}; #[macro_use] mod error; -pub use error::{Error, ErrorKind, Result}; - +mod base64; mod config; -pub use config::CheatsConfig; - -mod inspector; -pub use inspector::{BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, Context}; - mod env; mod evm; mod fs; +mod inspector; mod json; mod script; mod string; mod test; +mod toml; mod utils; +pub use env::set_execution_context; +pub use script::ScriptWallets; pub use test::expect::ExpectedCallTracker; +pub use Vm::ForgeContext; /// Cheatcode implementation. -pub(crate) trait Cheatcode: CheatcodeDef { +pub(crate) trait Cheatcode: CheatcodeDef + DynCheatcode { /// Applies this cheatcode to the given state. /// /// Implement this function if you don't need access to the EVM data. @@ -57,26 +59,28 @@ pub(crate) trait Cheatcode: CheatcodeDef { #[inline] fn apply_traced(&self, ccx: &mut CheatsCtxt) -> Result { - let span = trace_span(self); - let _enter = span.enter(); - trace_call(); + let _span = trace_span_and_call(self); let result = self.apply_full(ccx); trace_return(&result); return result; - // Separate functions to avoid inline and monomorphization bloat. - fn trace_span(cheat: &T) -> tracing::Span { - if enabled!(tracing::Level::TRACE) { - trace_span!(target: "cheatcodes", "apply", cheat=?cheat) - } else { - debug_span!(target: "cheatcodes", "apply", id=%T::CHEATCODE.func.id) + // Separate and non-generic functions to avoid inline and monomorphization bloat. + #[inline(never)] + fn trace_span_and_call(cheat: &dyn DynCheatcode) -> tracing::span::EnteredSpan { + let span = debug_span!(target: "cheatcodes", "apply"); + if !span.is_disabled() { + if enabled!(tracing::Level::TRACE) { + span.record("cheat", tracing::field::debug(cheat.as_debug())); + } else { + span.record("id", cheat.cheatcode().func.id); + } } - } - - fn trace_call() { + let entered = span.entered(); trace!(target: "cheatcodes", "applying"); + entered } + #[inline(never)] fn trace_return(result: &Result) { trace!( target: "cheatcodes", @@ -89,19 +93,52 @@ pub(crate) trait Cheatcode: CheatcodeDef { } } +pub(crate) trait DynCheatcode { + fn cheatcode(&self) -> &'static foundry_cheatcodes_spec::Cheatcode<'static>; + fn as_debug(&self) -> &dyn std::fmt::Debug; +} + +impl DynCheatcode for T { + fn cheatcode(&self) -> &'static foundry_cheatcodes_spec::Cheatcode<'static> { + T::CHEATCODE + } + + fn as_debug(&self) -> &dyn std::fmt::Debug { + self + } +} + /// The cheatcode context, used in [`Cheatcode`]. -pub(crate) struct CheatsCtxt<'a, 'b, 'c, DB: DatabaseExt> { +pub(crate) struct CheatsCtxt<'cheats, 'evm, DB: DatabaseExt> { /// The cheatcodes inspector state. - pub(crate) state: &'a mut Cheatcodes, + pub(crate) state: &'cheats mut Cheatcodes, /// The EVM data. - pub(crate) data: &'b mut EVMData<'c, DB>, + pub(crate) ecx: &'evm mut InnerEvmContext, + /// The precompiles context. + pub(crate) precompiles: &'evm mut ContextPrecompiles, /// The original `msg.sender`. pub(crate) caller: Address, } -impl CheatsCtxt<'_, '_, '_, DB> { +impl<'cheats, 'evm, DB: DatabaseExt> std::ops::Deref for CheatsCtxt<'cheats, 'evm, DB> { + type Target = InnerEvmContext; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.ecx + } +} + +impl<'cheats, 'evm, DB: DatabaseExt> std::ops::DerefMut for CheatsCtxt<'cheats, 'evm, DB> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *self.ecx + } +} + +impl<'cheats, 'evm, DB: DatabaseExt> CheatsCtxt<'cheats, 'evm, DB> { #[inline] pub(crate) fn is_precompile(&self, address: &Address) -> bool { - self.data.precompiles.contains(address) + self.precompiles.contains_key(address) } } diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index f4530b9fb5af1..a28a8be490ca2 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -2,9 +2,11 @@ use crate::{Cheatcode, CheatsCtxt, DatabaseExt, Result, Vm::*}; use alloy_primitives::{Address, U256}; -use ethers_signers::Signer; -use foundry_common::types::ToAlloy; +use alloy_signer_wallet::LocalWallet; use foundry_config::Config; +use foundry_wallets::{multi_wallet::MultiWallet, WalletSigner}; +use parking_lot::Mutex; +use std::sync::Arc; impl Cheatcode for broadcast_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { @@ -73,6 +75,49 @@ pub struct Broadcast { pub single_call: bool, } +/// Contains context for wallet management. +#[derive(Debug)] +pub struct ScriptWalletsInner { + /// All signers in scope of the script. + pub multi_wallet: MultiWallet, + /// Optional signer provided as `--sender` flag. + pub provided_sender: Option
, +} + +/// Clonable wrapper around [ScriptWalletsInner]. +#[derive(Debug, Clone)] +pub struct ScriptWallets { + /// Inner data. + pub inner: Arc>, +} + +impl ScriptWallets { + #[allow(missing_docs)] + pub fn new(multi_wallet: MultiWallet, provided_sender: Option
) -> Self { + Self { inner: Arc::new(Mutex::new(ScriptWalletsInner { multi_wallet, provided_sender })) } + } + + /// Consumes [ScriptWallets] and returns [MultiWallet]. + /// + /// Panics if [ScriptWallets] is still in use. + pub fn into_multi_wallet(self) -> MultiWallet { + Arc::into_inner(self.inner) + .map(|m| m.into_inner().multi_wallet) + .unwrap_or_else(|| panic!("not all instances were dropped")) + } + + /// Locks inner Mutex and adds a signer to the [MultiWallet]. + pub fn add_signer(&self, private_key: impl AsRef<[u8]>) -> Result { + self.inner.lock().multi_wallet.add_signer(WalletSigner::from_private_key(private_key)?); + Ok(Default::default()) + } + + /// Locks inner Mutex and returns all signer addresses in the [MultiWallet]. + pub fn signers(&self) -> Result> { + Ok(self.inner.lock().multi_wallet.signers()?.keys().cloned().collect()) + } +} + /// Sets up broadcasting from a script using `new_origin` as the sender. fn broadcast( ccx: &mut CheatsCtxt, @@ -87,11 +132,28 @@ fn broadcast( correct_sender_nonce(ccx)?; + let mut new_origin = new_origin.cloned(); + + if new_origin.is_none() { + if let Some(script_wallets) = &ccx.state.script_wallets { + let mut script_wallets = script_wallets.inner.lock(); + if let Some(provided_sender) = script_wallets.provided_sender { + new_origin = Some(provided_sender); + } else { + let signers = script_wallets.multi_wallet.signers()?; + if signers.len() == 1 { + let address = signers.keys().next().unwrap(); + new_origin = Some(*address); + } + } + } + } + let broadcast = Broadcast { - new_origin: *new_origin.unwrap_or(&ccx.data.env.tx.caller), + new_origin: new_origin.unwrap_or(ccx.ecx.env.tx.caller), original_caller: ccx.caller, - original_origin: ccx.data.env.tx.caller, - depth: ccx.data.journaled_state.depth(), + original_origin: ccx.ecx.env.tx.caller, + depth: ccx.ecx.journaled_state.depth(), single_call, }; debug!(target: "cheatcodes", ?broadcast, "started"); @@ -107,12 +169,15 @@ fn broadcast_key( private_key: &U256, single_call: bool, ) -> Result { - let wallet = super::utils::parse_wallet(private_key)?.with_chain_id(ccx.data.env.cfg.chain_id); - let new_origin = &wallet.address().to_alloy(); + let key = super::utils::parse_private_key(private_key)?; + let new_origin = LocalWallet::from(key.clone()).address(); + + let result = broadcast(ccx, Some(&new_origin), single_call); - let result = broadcast(ccx, Some(new_origin), single_call); if result.is_ok() { - ccx.state.script_wallets.push(wallet); + if let Some(script_wallets) = &ccx.state.script_wallets { + script_wallets.add_signer(key.to_bytes())?; + } } result } @@ -121,9 +186,9 @@ fn broadcast_key( /// That leads to its nonce being incremented by `call_raw`. In a `broadcast` scenario this is /// undesirable. Therefore, we make sure to fix the sender's nonce **once**. pub(super) fn correct_sender_nonce(ccx: &mut CheatsCtxt) -> Result<()> { - let sender = ccx.data.env.tx.caller; + let sender = ccx.ecx.env.tx.caller; if !ccx.state.corrected_nonce && sender != Config::DEFAULT_SENDER { - let account = super::evm::journaled_account(ccx.data, sender)?; + let account = super::evm::journaled_account(ccx.ecx, sender)?; let prev = account.info.nonce; account.info.nonce = prev.saturating_sub(1); debug!(target: "cheatcodes", %sender, nonce=account.info.nonce, prev, "corrected nonce"); diff --git a/crates/cheatcodes/src/string.rs b/crates/cheatcodes/src/string.rs index d40e1d8d2f159..98fd1a792b7e5 100644 --- a/crates/cheatcodes/src/string.rs +++ b/crates/cheatcodes/src/string.rs @@ -2,6 +2,7 @@ use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; use alloy_dyn_abi::{DynSolType, DynSolValue}; +use alloy_primitives::U256; use alloy_sol_types::SolValue; // address @@ -94,6 +95,55 @@ impl Cheatcode for parseBoolCall { } } +// toLowercase +impl Cheatcode for toLowercaseCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input } = self; + Ok(input.to_lowercase().abi_encode()) + } +} + +// toUppercase +impl Cheatcode for toUppercaseCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input } = self; + Ok(input.to_uppercase().abi_encode()) + } +} + +// trim +impl Cheatcode for trimCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input } = self; + Ok(input.trim().abi_encode()) + } +} + +// Replace +impl Cheatcode for replaceCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input, from, to } = self; + Ok(input.replace(from, to).abi_encode()) + } +} + +// Split +impl Cheatcode for splitCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input, delimiter } = self; + let parts: Vec<&str> = input.split(delimiter).collect(); + Ok(parts.abi_encode()) + } +} + +// indexOf +impl Cheatcode for indexOfCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input, key } = self; + Ok(input.find(key).map(U256::from).unwrap_or(U256::MAX).abi_encode()) + } +} + pub(super) fn parse(s: &str, ty: &DynSolType) -> Result { parse_value(s, ty).map(|v| v.abi_encode()) } @@ -136,7 +186,9 @@ fn parse_value_fallback(s: &str, ty: &DynSolType) -> Option false, s if s.eq_ignore_ascii_case("true") => true, s if s.eq_ignore_ascii_case("false") => false, - _ => return None, + _ => { + return None; + } }; return Some(Ok(DynSolValue::Bool(b))) } @@ -145,7 +197,7 @@ fn parse_value_fallback(s: &str, ty: &DynSolType) -> Option { if !s.starts_with("0x") && s.chars().all(|c| c.is_ascii_hexdigit()) { - return Some(Err("missing hex prefix (\"0x\") for hex string")) + return Some(Err("missing hex prefix (\"0x\") for hex string")); } } _ => {} diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index 0294b23b73d6a..18487c0aaa82e 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -5,6 +5,7 @@ use alloy_primitives::Address; use alloy_sol_types::SolValue; use foundry_evm_core::constants::{MAGIC_ASSUME, MAGIC_SKIP}; +pub(crate) mod assert; pub(crate) mod expect; impl Cheatcode for assumeCall { @@ -68,8 +69,7 @@ impl Cheatcode for skipCall { if skipTest { // Skip should not work if called deeper than at test level. // Since we're not returning the magic skip bytes, this will cause a test failure. - ensure!(ccx.data.journaled_state.depth() <= 1, "`skip` can only be used at test level"); - ccx.state.skip = true; + ensure!(ccx.ecx.journaled_state.depth() <= 1, "`skip` can only be used at test level"); Err(MAGIC_SKIP.into()) } else { Ok(Default::default()) diff --git a/crates/cheatcodes/src/test/assert.rs b/crates/cheatcodes/src/test/assert.rs new file mode 100644 index 0000000000000..3c1ab22f3c859 --- /dev/null +++ b/crates/cheatcodes/src/test/assert.rs @@ -0,0 +1,1221 @@ +use std::fmt::{Debug, Display}; + +use alloy_primitives::{I256, U256}; +use foundry_evm_core::abi::{format_units_int, format_units_uint}; +use itertools::Itertools; + +use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; + +const EQ_REL_DELTA_RESOLUTION: U256 = U256::from_limbs([18, 0, 0, 0]); + +#[derive(Debug, thiserror::Error)] +#[error("assertion failed")] +struct SimpleAssertionError; + +#[derive(thiserror::Error, Debug)] +enum ComparisonAssertionError<'a, T> { + Ne { left: &'a T, right: &'a T }, + Eq { left: &'a T, right: &'a T }, + Ge { left: &'a T, right: &'a T }, + Gt { left: &'a T, right: &'a T }, + Le { left: &'a T, right: &'a T }, + Lt { left: &'a T, right: &'a T }, +} + +macro_rules! format_values { + ($self:expr, $format_fn:expr) => { + match $self { + Self::Ne { left, right } => format!("{} == {}", $format_fn(left), $format_fn(right)), + Self::Eq { left, right } => format!("{} != {}", $format_fn(left), $format_fn(right)), + Self::Ge { left, right } => format!("{} < {}", $format_fn(left), $format_fn(right)), + Self::Gt { left, right } => format!("{} <= {}", $format_fn(left), $format_fn(right)), + Self::Le { left, right } => format!("{} > {}", $format_fn(left), $format_fn(right)), + Self::Lt { left, right } => format!("{} >= {}", $format_fn(left), $format_fn(right)), + } + }; +} + +impl<'a, T: Display> ComparisonAssertionError<'a, T> { + fn format_for_values(&self) -> String { + format_values!(self, T::to_string) + } +} + +impl<'a, T: Display> ComparisonAssertionError<'a, Vec> { + fn format_for_arrays(&self) -> String { + let formatter = |v: &Vec| format!("[{}]", v.iter().format(", ")); + format_values!(self, formatter) + } +} + +impl<'a> ComparisonAssertionError<'a, U256> { + fn format_with_decimals(&self, decimals: &U256) -> String { + let formatter = |v: &U256| format_units_uint(v, decimals); + format_values!(self, formatter) + } +} + +impl<'a> ComparisonAssertionError<'a, I256> { + fn format_with_decimals(&self, decimals: &U256) -> String { + let formatter = |v: &I256| format_units_int(v, decimals); + format_values!(self, formatter) + } +} + +#[derive(thiserror::Error, Debug)] +#[error("{left} !~= {right} (max delta: {max_delta}, real delta: {real_delta})")] +struct EqAbsAssertionError { + left: T, + right: T, + max_delta: D, + real_delta: D, +} + +impl EqAbsAssertionError { + fn format_with_decimals(&self, decimals: &U256) -> String { + format!( + "{} !~= {} (max delta: {}, real delta: {})", + format_units_uint(&self.left, decimals), + format_units_uint(&self.right, decimals), + format_units_uint(&self.max_delta, decimals), + format_units_uint(&self.real_delta, decimals), + ) + } +} + +impl EqAbsAssertionError { + fn format_with_decimals(&self, decimals: &U256) -> String { + format!( + "{} !~= {} (max delta: {}, real delta: {})", + format_units_int(&self.left, decimals), + format_units_int(&self.right, decimals), + format_units_uint(&self.max_delta, decimals), + format_units_uint(&self.real_delta, decimals), + ) + } +} + +fn format_delta_percent(delta: &U256) -> String { + format!("{}%", format_units_uint(delta, &(EQ_REL_DELTA_RESOLUTION - U256::from(2)))) +} + +#[derive(Debug)] +enum EqRelDelta { + Defined(U256), + Undefined, +} + +impl Display for EqRelDelta { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Defined(delta) => write!(f, "{}", format_delta_percent(delta)), + Self::Undefined => write!(f, "undefined"), + } + } +} + +#[derive(thiserror::Error, Debug)] +#[error( + "{left} !~= {right} (max delta: {}, real delta: {})", + format_delta_percent(max_delta), + real_delta +)] +struct EqRelAssertionFailure { + left: T, + right: T, + max_delta: U256, + real_delta: EqRelDelta, +} + +#[derive(thiserror::Error, Debug)] +enum EqRelAssertionError { + #[error(transparent)] + Failure(Box>), + #[error("overflow in delta calculation")] + Overflow, +} + +impl EqRelAssertionError { + fn format_with_decimals(&self, decimals: &U256) -> String { + match self { + Self::Failure(f) => format!( + "{} !~= {} (max delta: {}, real delta: {})", + format_units_uint(&f.left, decimals), + format_units_uint(&f.right, decimals), + format_delta_percent(&f.max_delta), + &f.real_delta, + ), + Self::Overflow => self.to_string(), + } + } +} + +impl EqRelAssertionError { + fn format_with_decimals(&self, decimals: &U256) -> String { + match self { + Self::Failure(f) => format!( + "{} !~= {} (max delta: {}, real delta: {})", + format_units_int(&f.left, decimals), + format_units_int(&f.right, decimals), + format_delta_percent(&f.max_delta), + &f.real_delta, + ), + Self::Overflow => self.to_string(), + } + } +} + +type ComparisonResult<'a, T> = Result, ComparisonAssertionError<'a, T>>; + +impl Cheatcode for assertTrue_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_true(self.condition).map_err(|e| e.to_string())?) + } +} + +impl Cheatcode for assertTrue_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_true(self.condition).map_err(|_| self.error.to_string())?) + } +} + +impl Cheatcode for assertFalse_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_false(self.condition).map_err(|e| e.to_string())?) + } +} + +impl Cheatcode for assertFalse_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_false(self.condition).map_err(|_| self.error.to_string())?) + } +} + +impl Cheatcode for assertEq_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertEq_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertEq_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertEq_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertEq_4Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertEq_5Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertEq_6Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertEq_7Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertEq_8Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertEq_9Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertEq_10Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertEq_11Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertEq_12Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_eq(&hex::encode_prefixed(left), &hex::encode_prefixed(right)) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertEq_13Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_eq(&hex::encode_prefixed(left), &hex::encode_prefixed(right)) + .map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertEq_14Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertEq_15Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertEq_16Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertEq_17Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertEq_18Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertEq_19Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertEq_20Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertEq_21Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertEq_22Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertEq_23Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertEq_24Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertEq_25Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_eq(left, right).map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertEq_26Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + let left = left.iter().map(hex::encode_prefixed).collect::>(); + let right = right.iter().map(hex::encode_prefixed).collect::>(); + Ok(assert_eq(&left, &right) + .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertEq_27Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + let left = left.iter().map(hex::encode_prefixed).collect::>(); + let right = right.iter().map(hex::encode_prefixed).collect::>(); + Ok(assert_eq(&left, &right).map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertEqDecimal_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_eq(&self.left, &self.right) + .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertEqDecimal_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_eq(&self.left, &self.right) + .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertEqDecimal_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_eq(&self.left, &self.right) + .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertEqDecimal_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_eq(&self.left, &self.right) + .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertNotEq_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertNotEq_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertNotEq_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertNotEq_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertNotEq_4Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertNotEq_5Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertNotEq_6Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertNotEq_7Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertNotEq_8Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertNotEq_9Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertNotEq_10Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertNotEq_11Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertNotEq_12Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_not_eq(&hex::encode_prefixed(left), &hex::encode_prefixed(right)) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertNotEq_13Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_not_eq(&hex::encode_prefixed(left), &hex::encode_prefixed(right)) + .map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertNotEq_14Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertNotEq_15Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertNotEq_16Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertNotEq_17Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertNotEq_18Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertNotEq_19Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertNotEq_20Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertNotEq_21Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertNotEq_22Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertNotEq_23Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertNotEq_24Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertNotEq_25Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_not_eq(left, right) + .map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertNotEq_26Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + let left = left.iter().map(hex::encode_prefixed).collect::>(); + let right = right.iter().map(hex::encode_prefixed).collect::>(); + Ok(assert_not_eq(&left, &right) + .map_err(|e| format!("assertion failed: {}", e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertNotEq_27Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + let left = left.iter().map(hex::encode_prefixed).collect::>(); + let right = right.iter().map(hex::encode_prefixed).collect::>(); + Ok(assert_not_eq(&left, &right) + .map_err(|e| format!("{}: {}", error, e.format_for_arrays()))?) + } +} + +impl Cheatcode for assertNotEqDecimal_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_not_eq(&self.left, &self.right) + .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertNotEqDecimal_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_not_eq(&self.left, &self.right) + .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertNotEqDecimal_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_not_eq(&self.left, &self.right) + .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertNotEqDecimal_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_not_eq(&self.left, &self.right) + .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertGt_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_gt(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertGt_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_gt(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertGt_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_gt(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertGt_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_gt(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertGtDecimal_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_gt(&self.left, &self.right) + .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertGtDecimal_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_gt(&self.left, &self.right) + .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertGtDecimal_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_gt(&self.left, &self.right) + .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertGtDecimal_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_gt(&self.left, &self.right) + .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertGe_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_ge(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertGe_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_ge(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertGe_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_ge(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertGe_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_ge(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertGeDecimal_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_ge(&self.left, &self.right) + .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertGeDecimal_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_ge(&self.left, &self.right) + .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertGeDecimal_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_ge(&self.left, &self.right) + .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertGeDecimal_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_ge(&self.left, &self.right) + .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertLt_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_lt(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertLt_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_lt(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertLt_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_lt(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertLt_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_lt(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertLtDecimal_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_lt(&self.left, &self.right) + .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertLtDecimal_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_lt(&self.left, &self.right) + .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertLtDecimal_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_lt(&self.left, &self.right) + .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertLtDecimal_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_lt(&self.left, &self.right) + .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertLe_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_le(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertLe_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_le(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertLe_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right } = self; + Ok(assert_le(left, right) + .map_err(|e| format!("assertion failed: {}", e.format_for_values()))?) + } +} + +impl Cheatcode for assertLe_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { left, right, error } = self; + Ok(assert_le(left, right).map_err(|e| format!("{}: {}", error, e.format_for_values()))?) + } +} + +impl Cheatcode for assertLeDecimal_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_le(&self.left, &self.right) + .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertLeDecimal_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_le(&self.left, &self.right) + .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertLeDecimal_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_le(&self.left, &self.right) + .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertLeDecimal_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(assert_le(&self.left, &self.right) + .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertApproxEqAbs_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(uint_assert_approx_eq_abs(self.left, self.right, self.maxDelta) + .map_err(|e| format!("assertion failed: {}", e))?) + } +} + +impl Cheatcode for assertApproxEqAbs_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(uint_assert_approx_eq_abs(self.left, self.right, self.maxDelta) + .map_err(|e| format!("{}: {}", self.error, e))?) + } +} + +impl Cheatcode for assertApproxEqAbs_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(int_assert_approx_eq_abs(self.left, self.right, self.maxDelta) + .map_err(|e| format!("assertion failed: {}", e))?) + } +} + +impl Cheatcode for assertApproxEqAbs_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(int_assert_approx_eq_abs(self.left, self.right, self.maxDelta) + .map_err(|e| format!("{}: {}", self.error, e))?) + } +} + +impl Cheatcode for assertApproxEqAbsDecimal_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(uint_assert_approx_eq_abs(self.left, self.right, self.maxDelta) + .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertApproxEqAbsDecimal_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(uint_assert_approx_eq_abs(self.left, self.right, self.maxDelta) + .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertApproxEqAbsDecimal_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(int_assert_approx_eq_abs(self.left, self.right, self.maxDelta) + .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertApproxEqAbsDecimal_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(int_assert_approx_eq_abs(self.left, self.right, self.maxDelta) + .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertApproxEqRel_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(uint_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) + .map_err(|e| format!("assertion failed: {}", e))?) + } +} + +impl Cheatcode for assertApproxEqRel_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(uint_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) + .map_err(|e| format!("{}: {}", self.error, e))?) + } +} + +impl Cheatcode for assertApproxEqRel_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(int_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) + .map_err(|e| format!("assertion failed: {}", e))?) + } +} + +impl Cheatcode for assertApproxEqRel_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(int_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) + .map_err(|e| format!("{}: {}", self.error, e))?) + } +} + +impl Cheatcode for assertApproxEqRelDecimal_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(uint_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) + .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertApproxEqRelDecimal_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(uint_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) + .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertApproxEqRelDecimal_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(int_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) + .map_err(|e| format!("assertion failed: {}", e.format_with_decimals(&self.decimals)))?) + } +} + +impl Cheatcode for assertApproxEqRelDecimal_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + Ok(int_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) + .map_err(|e| format!("{}: {}", self.error, e.format_with_decimals(&self.decimals)))?) + } +} + +fn assert_true(condition: bool) -> Result, SimpleAssertionError> { + if condition { + Ok(Default::default()) + } else { + Err(SimpleAssertionError) + } +} + +fn assert_false(condition: bool) -> Result, SimpleAssertionError> { + if !condition { + Ok(Default::default()) + } else { + Err(SimpleAssertionError) + } +} + +fn assert_eq<'a, T: PartialEq>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { + if left == right { + Ok(Default::default()) + } else { + Err(ComparisonAssertionError::Eq { left, right }) + } +} + +fn assert_not_eq<'a, T: PartialEq>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { + if left != right { + Ok(Default::default()) + } else { + Err(ComparisonAssertionError::Ne { left, right }) + } +} + +fn get_delta_uint(left: U256, right: U256) -> U256 { + if left > right { + left - right + } else { + right - left + } +} + +fn get_delta_int(left: I256, right: I256) -> U256 { + let (left_sign, left_abs) = left.into_sign_and_abs(); + let (right_sign, right_abs) = right.into_sign_and_abs(); + + if left_sign == right_sign { + if left_abs > right_abs { + left_abs - right_abs + } else { + right_abs - left_abs + } + } else { + left_abs + right_abs + } +} + +fn uint_assert_approx_eq_abs( + left: U256, + right: U256, + max_delta: U256, +) -> Result, Box>> { + let delta = get_delta_uint(left, right); + + if delta <= max_delta { + Ok(Default::default()) + } else { + Err(Box::new(EqAbsAssertionError { left, right, max_delta, real_delta: delta })) + } +} + +fn int_assert_approx_eq_abs( + left: I256, + right: I256, + max_delta: U256, +) -> Result, Box>> { + let delta = get_delta_int(left, right); + + if delta <= max_delta { + Ok(Default::default()) + } else { + Err(Box::new(EqAbsAssertionError { left, right, max_delta, real_delta: delta })) + } +} + +fn uint_assert_approx_eq_rel( + left: U256, + right: U256, + max_delta: U256, +) -> Result, EqRelAssertionError> { + if right.is_zero() { + if left.is_zero() { + return Ok(Default::default()) + } else { + return Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { + left, + right, + max_delta, + real_delta: EqRelDelta::Undefined, + }))) + }; + } + + let delta = get_delta_uint(left, right) + .checked_mul(U256::pow(U256::from(10), EQ_REL_DELTA_RESOLUTION)) + .ok_or(EqRelAssertionError::Overflow)? / + right; + + if delta <= max_delta { + Ok(Default::default()) + } else { + Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { + left, + right, + max_delta, + real_delta: EqRelDelta::Defined(delta), + }))) + } +} + +fn int_assert_approx_eq_rel( + left: I256, + right: I256, + max_delta: U256, +) -> Result, EqRelAssertionError> { + if right.is_zero() { + if left.is_zero() { + return Ok(Default::default()) + } else { + return Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { + left, + right, + max_delta, + real_delta: EqRelDelta::Undefined, + }))) + } + } + + let (_, abs_right) = right.into_sign_and_abs(); + let delta = get_delta_int(left, right) + .checked_mul(U256::pow(U256::from(10), EQ_REL_DELTA_RESOLUTION)) + .ok_or(EqRelAssertionError::Overflow)? / + abs_right; + + if delta <= max_delta { + Ok(Default::default()) + } else { + Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { + left, + right, + max_delta, + real_delta: EqRelDelta::Defined(delta), + }))) + } +} + +fn assert_gt<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { + if left > right { + Ok(Default::default()) + } else { + Err(ComparisonAssertionError::Gt { left, right }) + } +} + +fn assert_ge<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { + if left >= right { + Ok(Default::default()) + } else { + Err(ComparisonAssertionError::Ge { left, right }) + } +} + +fn assert_lt<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { + if left < right { + Ok(Default::default()) + } else { + Err(ComparisonAssertionError::Lt { left, right }) + } +} + +fn assert_le<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { + if left <= right { + Ok(Default::default()) + } else { + Err(ComparisonAssertionError::Le { left, right }) + } +} diff --git a/crates/cheatcodes/src/test/expect.rs b/crates/cheatcodes/src/test/expect.rs index 64266ae319ea2..7dc692d4e9952 100644 --- a/crates/cheatcodes/src/test/expect.rs +++ b/crates/cheatcodes/src/test/expect.rs @@ -1,5 +1,5 @@ use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Result, Vm::*}; -use alloy_primitives::{address, Address, Bytes, Log as RawLog, B256, U256}; +use alloy_primitives::{address, Address, Bytes, LogData as RawLog, U256}; use alloy_sol_types::{SolError, SolValue}; use revm::interpreter::{return_ok, InstructionResult}; use spec::Vm; @@ -25,7 +25,7 @@ const DUMMY_CREATE_ADDRESS: Address = address!("00000000000000000000000000000000 /// This then allows us to customize the matching behavior for each call data on the /// `ExpectedCallData` struct and track how many times we've actually seen the call on the second /// element of the tuple. -pub type ExpectedCallTracker = HashMap, (ExpectedCallData, u64)>>; +pub type ExpectedCallTracker = HashMap>; #[derive(Clone, Debug)] pub struct ExpectedCallData { @@ -53,12 +53,27 @@ pub enum ExpectedCallType { Count, } -#[derive(Clone, Debug, Default)] +/// The type of expected revert. +#[derive(Clone, Debug)] +pub enum ExpectedRevertKind { + /// Expects revert from the next non-cheatcode call. + Default, + /// Expects revert from the next cheatcode call. + /// + /// The `pending_processing` flag is used to track whether we have exited + /// `expectCheatcodeRevert` context or not. + /// We have to track it to avoid expecting `expectCheatcodeRevert` call to revert itself. + Cheatcode { pending_processing: bool }, +} + +#[derive(Clone, Debug)] pub struct ExpectedRevert { /// The expected data returned by the revert, None being any pub reason: Option>, /// The depth at which the revert is expected pub depth: u64, + /// The type of expected revert. + pub kind: ExpectedRevertKind, } #[derive(Clone, Debug)] @@ -186,7 +201,7 @@ impl Cheatcode for expectEmit_0Call { let Self { checkTopic1, checkTopic2, checkTopic3, checkData } = *self; expect_emit( ccx.state, - ccx.data.journaled_state.depth(), + ccx.ecx.journaled_state.depth(), [checkTopic1, checkTopic2, checkTopic3, checkData], None, ) @@ -198,7 +213,7 @@ impl Cheatcode for expectEmit_1Call { let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self; expect_emit( ccx.state, - ccx.data.journaled_state.depth(), + ccx.ecx.journaled_state.depth(), [checkTopic1, checkTopic2, checkTopic3, checkData], Some(emitter), ) @@ -208,49 +223,77 @@ impl Cheatcode for expectEmit_1Call { impl Cheatcode for expectEmit_2Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - expect_emit(ccx.state, ccx.data.journaled_state.depth(), [true; 4], None) + expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 4], None) } } impl Cheatcode for expectEmit_3Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { emitter } = *self; - expect_emit(ccx.state, ccx.data.journaled_state.depth(), [true; 4], Some(emitter)) + expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 4], Some(emitter)) } } impl Cheatcode for expectRevert_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - expect_revert(ccx.state, None, ccx.data.journaled_state.depth()) + expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), false) } } impl Cheatcode for expectRevert_1Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; - expect_revert(ccx.state, Some(revertData.as_ref()), ccx.data.journaled_state.depth()) + expect_revert(ccx.state, Some(revertData.as_ref()), ccx.ecx.journaled_state.depth(), false) } } impl Cheatcode for expectRevert_2Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; - expect_revert(ccx.state, Some(revertData), ccx.data.journaled_state.depth()) + expect_revert(ccx.state, Some(revertData), ccx.ecx.journaled_state.depth(), false) + } +} + +impl Cheatcode for _expectCheatcodeRevert_0Call { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), true) + } +} + +impl Cheatcode for _expectCheatcodeRevert_1Call { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { revertData } = self; + expect_revert(ccx.state, Some(revertData.as_ref()), ccx.ecx.journaled_state.depth(), true) + } +} + +impl Cheatcode for _expectCheatcodeRevert_2Call { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { revertData } = self; + expect_revert(ccx.state, Some(revertData), ccx.ecx.journaled_state.depth(), true) } } impl Cheatcode for expectSafeMemoryCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { min, max } = *self; - expect_safe_memory(ccx.state, min, max, ccx.data.journaled_state.depth()) + expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth()) + } +} + +impl Cheatcode for stopExpectSafeMemoryCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + ccx.state.allowed_mem_writes.remove(&ccx.ecx.journaled_state.depth()); + Ok(Default::default()) } } impl Cheatcode for expectSafeMemoryCallCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { min, max } = *self; - expect_safe_memory(ccx.state, min, max, ccx.data.journaled_state.depth() + 1) + expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth() + 1) } } @@ -275,7 +318,7 @@ impl Cheatcode for expectSafeMemoryCallCall { fn expect_call( state: &mut Cheatcodes, target: &Address, - calldata: &Vec, + calldata: &Bytes, value: Option<&U256>, mut gas: Option, mut min_gas: Option, @@ -308,7 +351,7 @@ fn expect_call( "counted expected calls can only bet set once" ); expecteds.insert( - calldata.to_vec(), + calldata.clone(), (ExpectedCallData { value: value.copied(), gas, min_gas, count, call_type }, 0), ); } @@ -355,12 +398,7 @@ fn expect_emit( Ok(Default::default()) } -pub(crate) fn handle_expect_emit( - state: &mut Cheatcodes, - address: &Address, - topics: &[B256], - data: &Bytes, -) { +pub(crate) fn handle_expect_emit(state: &mut Cheatcodes, log: &alloy_primitives::Log) { // Fill or check the expected emits. // We expect for emit checks to be filled as they're declared (from oldest to newest), // so we fill them and push them to the back of the queue. @@ -388,20 +426,21 @@ pub(crate) fn handle_expect_emit( let Some(expected) = &event_to_fill_or_check.log else { // Fill the event. - event_to_fill_or_check.log = Some(RawLog::new_unchecked(topics.to_vec(), data.clone())); + event_to_fill_or_check.log = Some(log.data.clone()); state.expected_emits.push_back(event_to_fill_or_check); return }; let expected_topic_0 = expected.topics().first(); - let log_topic_0 = topics.first(); + let log_topic_0 = log.topics().first(); if expected_topic_0 .zip(log_topic_0) - .map_or(false, |(a, b)| a == b && expected.topics().len() == topics.len()) + .map_or(false, |(a, b)| a == b && expected.topics().len() == log.topics().len()) { // Match topics - event_to_fill_or_check.found = topics + event_to_fill_or_check.found = log + .topics() .iter() .skip(1) .enumerate() @@ -410,12 +449,12 @@ pub(crate) fn handle_expect_emit( // Maybe match source address if let Some(addr) = event_to_fill_or_check.address { - event_to_fill_or_check.found &= addr == *address; + event_to_fill_or_check.found &= addr == log.address; } // Maybe match data if event_to_fill_or_check.checks[3] { - event_to_fill_or_check.found &= expected.data == *data; + event_to_fill_or_check.found &= expected.data.as_ref() == log.data.data.as_ref(); } } @@ -430,12 +469,25 @@ pub(crate) fn handle_expect_emit( } } -fn expect_revert(state: &mut Cheatcodes, reason: Option<&[u8]>, depth: u64) -> Result { +fn expect_revert( + state: &mut Cheatcodes, + reason: Option<&[u8]>, + depth: u64, + cheatcode: bool, +) -> Result { ensure!( state.expected_revert.is_none(), "you must call another function prior to expecting a second revert" ); - state.expected_revert = Some(ExpectedRevert { reason: reason.map(<[_]>::to_vec), depth }); + state.expected_revert = Some(ExpectedRevert { + reason: reason.map(<[_]>::to_vec), + depth, + kind: if cheatcode { + ExpectedRevertKind::Cheatcode { pending_processing: true } + } else { + ExpectedRevertKind::Default + }, + }); Ok(Default::default()) } diff --git a/crates/cheatcodes/src/toml.rs b/crates/cheatcodes/src/toml.rs new file mode 100644 index 0000000000000..886c38a4a5807 --- /dev/null +++ b/crates/cheatcodes/src/toml.rs @@ -0,0 +1,243 @@ +//! Implementations of [`Toml`](crate::Group::Toml) cheatcodes. + +use crate::{ + json::{ + canonicalize_json_path, check_json_key_exists, parse_json, parse_json_coerce, + parse_json_keys, + }, + Cheatcode, Cheatcodes, Result, + Vm::*, +}; +use alloy_dyn_abi::DynSolType; +use foundry_common::fs; +use foundry_config::fs_permissions::FsAccessKind; +use serde_json::Value as JsonValue; +use toml::Value as TomlValue; + +impl Cheatcode for keyExistsTomlCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + check_json_key_exists(&toml_to_json_string(toml)?, key) + } +} + +impl Cheatcode for parseToml_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml } = self; + parse_toml(toml, "$") + } +} + +impl Cheatcode for parseToml_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml(toml, key) + } +} + +impl Cheatcode for parseTomlUintCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Uint(256)) + } +} + +impl Cheatcode for parseTomlUintArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Uint(256)) + } +} + +impl Cheatcode for parseTomlIntCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Int(256)) + } +} + +impl Cheatcode for parseTomlIntArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Int(256)) + } +} + +impl Cheatcode for parseTomlBoolCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Bool) + } +} + +impl Cheatcode for parseTomlBoolArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Bool) + } +} + +impl Cheatcode for parseTomlAddressCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Address) + } +} + +impl Cheatcode for parseTomlAddressArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Address) + } +} + +impl Cheatcode for parseTomlStringCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::String) + } +} + +impl Cheatcode for parseTomlStringArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::String) + } +} + +impl Cheatcode for parseTomlBytesCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Bytes) + } +} + +impl Cheatcode for parseTomlBytesArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Bytes) + } +} + +impl Cheatcode for parseTomlBytes32Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::FixedBytes(32)) + } +} + +impl Cheatcode for parseTomlBytes32ArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::FixedBytes(32)) + } +} + +impl Cheatcode for parseTomlKeysCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_keys(toml, key) + } +} + +impl Cheatcode for writeToml_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { json, path } = self; + let value = + serde_json::from_str(json).unwrap_or_else(|_| JsonValue::String(json.to_owned())); + + let toml_string = format_json_to_toml(value)?; + super::fs::write_file(state, path.as_ref(), toml_string.as_bytes()) + } +} + +impl Cheatcode for writeToml_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { json, path, valueKey } = self; + let json = + serde_json::from_str(json).unwrap_or_else(|_| JsonValue::String(json.to_owned())); + + let data_path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + let toml_data = fs::read_to_string(data_path)?; + let json_data: JsonValue = + toml::from_str(&toml_data).map_err(|e| fmt_err!("failed parsing TOML: {e}"))?; + let value = + jsonpath_lib::replace_with(json_data, &canonicalize_json_path(valueKey), &mut |_| { + Some(json.clone()) + })?; + + let toml_string = format_json_to_toml(value)?; + super::fs::write_file(state, path.as_ref(), toml_string.as_bytes()) + } +} + +/// Parse +fn parse_toml_str(toml: &str) -> Result { + toml::from_str(toml).map_err(|e| fmt_err!("failed parsing TOML: {e}")) +} + +/// Parse a TOML string and return the value at the given path. +fn parse_toml(toml: &str, key: &str) -> Result { + parse_json(&toml_to_json_string(toml)?, key) +} + +/// Parse a TOML string and return the value at the given path, coercing it to the given type. +fn parse_toml_coerce(toml: &str, key: &str, ty: &DynSolType) -> Result { + parse_json_coerce(&toml_to_json_string(toml)?, key, ty) +} + +/// Parse a TOML string and return an array of all keys at the given path. +fn parse_toml_keys(toml: &str, key: &str) -> Result { + parse_json_keys(&toml_to_json_string(toml)?, key) +} + +/// Convert a TOML string to a JSON string. +fn toml_to_json_string(toml: &str) -> Result { + let toml = parse_toml_str(toml)?; + let json = toml_to_json_value(toml); + serde_json::to_string(&json).map_err(|e| fmt_err!("failed to serialize JSON: {e}")) +} + +/// Format a JSON value to a TOML pretty string. +fn format_json_to_toml(json: JsonValue) -> Result { + let toml = json_to_toml_value(json); + toml::to_string_pretty(&toml).map_err(|e| fmt_err!("failed to serialize TOML: {e}")) +} + +/// Convert a TOML value to a JSON value. +fn toml_to_json_value(toml: TomlValue) -> JsonValue { + match toml { + TomlValue::String(s) => match s.as_str() { + "null" => JsonValue::Null, + _ => JsonValue::String(s), + }, + TomlValue::Integer(i) => JsonValue::Number(i.into()), + TomlValue::Float(f) => JsonValue::Number(serde_json::Number::from_f64(f).unwrap()), + TomlValue::Boolean(b) => JsonValue::Bool(b), + TomlValue::Array(a) => JsonValue::Array(a.into_iter().map(toml_to_json_value).collect()), + TomlValue::Table(t) => { + JsonValue::Object(t.into_iter().map(|(k, v)| (k, toml_to_json_value(v))).collect()) + } + TomlValue::Datetime(d) => JsonValue::String(d.to_string()), + } +} + +/// Convert a JSON value to a TOML value. +fn json_to_toml_value(json: JsonValue) -> TomlValue { + match json { + JsonValue::String(s) => TomlValue::String(s), + JsonValue::Number(n) => match n.as_i64() { + Some(i) => TomlValue::Integer(i), + None => match n.as_f64() { + Some(f) => TomlValue::Float(f), + None => TomlValue::String(n.to_string()), + }, + }, + JsonValue::Bool(b) => TomlValue::Boolean(b), + JsonValue::Array(a) => TomlValue::Array(a.into_iter().map(json_to_toml_value).collect()), + JsonValue::Object(o) => { + TomlValue::Table(o.into_iter().map(|(k, v)| (k, json_to_toml_value(v))).collect()) + } + JsonValue::Null => TomlValue::String("null".to_string()), + } +} diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs index 8955bebff4db0..7544abc0a624b 100644 --- a/crates/cheatcodes/src/utils.rs +++ b/crates/cheatcodes/src/utils.rs @@ -1,22 +1,23 @@ //! Implementations of [`Utils`](crate::Group::Utils) cheatcodes. use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Result, Vm::*}; -use alloy_primitives::{keccak256, B256, U256}; -use alloy_sol_types::SolValue; -use ethers_core::k256::{ - ecdsa::SigningKey, - elliptic_curve::{sec1::ToEncodedPoint, Curve}, - Secp256k1, -}; -use ethers_signers::{ +use alloy_primitives::{keccak256, Address, B256, U256}; +use alloy_signer::{Signer, SignerSync}; +use alloy_signer_wallet::{ coins_bip39::{ ChineseSimplified, ChineseTraditional, Czech, English, French, Italian, Japanese, Korean, Portuguese, Spanish, Wordlist, }, - LocalWallet, MnemonicBuilder, Signer, + LocalWallet, MnemonicBuilder, }; -use foundry_common::types::{ToAlloy, ToEthers}; -use foundry_evm_core::constants::DEFAULT_CREATE2_DEPLOYER; +use alloy_sol_types::SolValue; +use foundry_evm_core::{constants::DEFAULT_CREATE2_DEPLOYER, utils::RuntimeOrHandle}; +use k256::{ + ecdsa::SigningKey, + elliptic_curve::{sec1::ToEncodedPoint, Curve}, + Secp256k1, +}; +use p256::ecdsa::{signature::hazmat::PrehashSigner, Signature, SigningKey as P256SigningKey}; /// The BIP32 default derivation path prefix. const DEFAULT_DERIVATION_PATH_PREFIX: &str = "m/44'/60'/0'/0/"; @@ -49,10 +50,10 @@ impl Cheatcode for getNonce_1Call { } } -impl Cheatcode for sign_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { +impl Cheatcode for sign_3Call { + fn apply_full(&self, _: &mut CheatsCtxt) -> Result { let Self { wallet, digest } = self; - sign(&wallet.privateKey, digest, ccx.data.env.cfg.chain_id) + sign(&wallet.privateKey, digest) } } @@ -87,10 +88,12 @@ impl Cheatcode for deriveKey_3Call { impl Cheatcode for rememberKeyCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { privateKey } = self; - let wallet = parse_wallet(privateKey)?.with_chain_id(ccx.data.env.cfg.chain_id); - let address = wallet.address(); - ccx.state.script_wallets.push(wallet); - Ok(address.to_alloy().abi_encode()) + let key = parse_private_key(privateKey)?; + let address = LocalWallet::from(key.clone()).address(); + if let Some(script_wallets) = &ccx.state.script_wallets { + script_wallets.add_signer(key.to_bytes())?; + } + Ok(address.abi_encode()) } } @@ -140,7 +143,7 @@ impl Cheatcode for computeCreate2Address_1Call { /// If 'label' is set to 'Some()', assign that label to the associated ETH address in state fn create_wallet(private_key: &U256, label: Option<&str>, state: &mut Cheatcodes) -> Result { let key = parse_private_key(private_key)?; - let addr = ethers_core::utils::secret_key_to_address(&key).to_alloy(); + let addr = alloy_signer::utils::secret_key_to_address(&key); let pub_key = key.verifying_key().as_affine().to_encoded_point(false); let pub_key_x = U256::from_be_bytes((*pub_key.x().unwrap()).into()); @@ -154,21 +157,75 @@ fn create_wallet(private_key: &U256, label: Option<&str>, state: &mut Cheatcodes .abi_encode()) } -pub(super) fn sign(private_key: &U256, digest: &B256, chain_id: u64) -> Result { - let wallet = parse_wallet(private_key)?.with_chain_id(chain_id); +fn encode_vrs(sig: alloy_primitives::Signature) -> Vec { + let v = sig.v().y_parity_byte_non_eip155().unwrap_or(sig.v().y_parity_byte()); + + (U256::from(v), B256::from(sig.r()), B256::from(sig.s())).abi_encode() +} - // The `ecrecover` precompile does not use EIP-155 - let sig = wallet.sign_hash(digest.to_ethers())?; - let recovered = sig.recover(digest.to_ethers())?.to_alloy(); +pub(super) fn sign(private_key: &U256, digest: &B256) -> Result { + // The `ecrecover` precompile does not use EIP-155. No chain ID is needed. + let wallet = parse_wallet(private_key)?; - assert_eq!(recovered, wallet.address().to_alloy()); + let sig = wallet.sign_hash_sync(digest)?; + let recovered = sig.recover_address_from_prehash(digest)?; - let mut r_bytes = [0u8; 32]; - let mut s_bytes = [0u8; 32]; - sig.r.to_big_endian(&mut r_bytes); - sig.s.to_big_endian(&mut s_bytes); + assert_eq!(recovered, wallet.address()); - Ok((sig.v, r_bytes, s_bytes).abi_encode()) + Ok(encode_vrs(sig)) +} + +pub(super) fn sign_with_wallet( + ccx: &mut CheatsCtxt, + signer: Option
, + digest: &B256, +) -> Result { + let Some(script_wallets) = &ccx.state.script_wallets else { + return Err("no wallets are available".into()); + }; + + let mut script_wallets = script_wallets.inner.lock(); + let maybe_provided_sender = script_wallets.provided_sender; + let signers = script_wallets.multi_wallet.signers()?; + + let signer = if let Some(signer) = signer { + signer + } else if let Some(provided_sender) = maybe_provided_sender { + provided_sender + } else if signers.len() == 1 { + *signers.keys().next().unwrap() + } else { + return Err("could not determine signer".into()); + }; + + let wallet = signers + .get(&signer) + .ok_or_else(|| fmt_err!("signer with address {signer} is not available"))?; + + let sig = RuntimeOrHandle::new() + .block_on(wallet.sign_hash(digest)) + .map_err(|err| fmt_err!("{err}"))?; + + let recovered = sig.recover_address_from_prehash(digest).map_err(|err| fmt_err!("{err}"))?; + assert_eq!(recovered, signer); + + Ok(encode_vrs(sig)) +} + +pub(super) fn sign_p256(private_key: &U256, digest: &B256, _state: &mut Cheatcodes) -> Result { + ensure!(*private_key != U256::ZERO, "private key cannot be 0"); + let n = U256::from_limbs(*p256::NistP256::ORDER.as_words()); + ensure!( + *private_key < n, + format!("private key must be less than the secp256r1 curve order ({})", n), + ); + let bytes = private_key.to_be_bytes(); + let signing_key = P256SigningKey::from_bytes((&bytes).into())?; + let signature: Signature = signing_key.sign_prehash(digest.as_slice())?; + let r_bytes: [u8; 32] = signature.r().to_bytes().into(); + let s_bytes: [u8; 32] = signature.s().to_bytes().into(); + + Ok((r_bytes, s_bytes).abi_encode()) } pub(super) fn parse_private_key(private_key: &U256) -> Result { @@ -214,8 +271,71 @@ fn derive_key(mnemonic: &str, path: &str, index: u32) -> Result { let wallet = MnemonicBuilder::::default() .phrase(mnemonic) - .derivation_path(&derive_key_path(path, index))? + .derivation_path(derive_key_path(path, index))? .build()?; let private_key = U256::from_be_bytes(wallet.signer().to_bytes().into()); Ok(private_key.abi_encode()) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::CheatsConfig; + use alloy_primitives::FixedBytes; + use hex::FromHex; + use p256::ecdsa::signature::hazmat::PrehashVerifier; + use std::{path::PathBuf, sync::Arc}; + + fn cheats() -> Cheatcodes { + let config = CheatsConfig { + ffi: true, + root: PathBuf::from(&env!("CARGO_MANIFEST_DIR")), + ..Default::default() + }; + Cheatcodes { config: Arc::new(config), ..Default::default() } + } + + #[test] + fn test_sign_p256() { + use p256::ecdsa::VerifyingKey; + + let pk_u256: U256 = "1".parse().unwrap(); + let signing_key = P256SigningKey::from_bytes(&pk_u256.to_be_bytes().into()).unwrap(); + let digest = FixedBytes::from_hex( + "0x44acf6b7e36c1342c2c5897204fe09504e1e2efb1a900377dbc4e7a6a133ec56", + ) + .unwrap(); + let mut cheats = cheats(); + + let result = sign_p256(&pk_u256, &digest, &mut cheats).unwrap(); + let result_bytes: [u8; 64] = result.try_into().unwrap(); + let signature = Signature::from_bytes(&result_bytes.into()).unwrap(); + let verifying_key = VerifyingKey::from(&signing_key); + assert!(verifying_key.verify_prehash(digest.as_slice(), &signature).is_ok()); + } + + #[test] + fn test_sign_p256_pk_too_large() { + // max n from https://neuromancer.sk/std/secg/secp256r1 + let pk = + "0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551".parse().unwrap(); + let digest = FixedBytes::from_hex( + "0x54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad", + ) + .unwrap(); + let mut cheats = cheats(); + let result = sign_p256(&pk, &digest, &mut cheats); + assert_eq!(result.err().unwrap().to_string(), "private key must be less than the secp256r1 curve order (115792089210356248762697446949407573529996955224135760342422259061068512044369)"); + } + + #[test] + fn test_sign_p256_pk_0() { + let digest = FixedBytes::from_hex( + "0x54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad", + ) + .unwrap(); + let mut cheats = cheats(); + let result = sign_p256(&U256::ZERO, &digest, &mut cheats); + assert_eq!(result.err().unwrap().to_string(), "private key cannot be 0"); + } +} diff --git a/crates/chisel/Cargo.toml b/crates/chisel/Cargo.toml index 92048950cb1a3..6ae62b970db33 100644 --- a/crates/chisel/Cargo.toml +++ b/crates/chisel/Cargo.toml @@ -15,11 +15,12 @@ name = "chisel" path = "bin/main.rs" [build-dependencies] -vergen = { version = "8", default-features = false, features = ["build", "git", "git2"] } +vergen = { workspace = true, default-features = false, features = ["build", "git", "gitcl"] } [dependencies] # forge forge-fmt.workspace = true +foundry-block-explorers.workspace = true foundry-cli.workspace = true foundry-common.workspace = true foundry-compilers = { workspace = true, features = ["project-util", "full"] } @@ -29,35 +30,40 @@ foundry-evm.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary"] } alloy-primitives = { workspace = true, features = ["serde", "getrandom", "arbitrary", "rlp"] } alloy-json-abi.workspace = true - -ethers-core.workspace = true +alloy-rpc-types.workspace = true clap = { version = "4", features = ["derive", "env", "wrap_help"] } dirs = "5" eyre.workspace = true once_cell = "1.18.0" regex = "1" -reqwest = { version = "0.11", default-features = false } +reqwest.workspace = true revm.workspace = true rustyline = "12" semver = "1" serde_json.workspace = true serde.workspace = true solang-parser.workspace = true -strum = { version = "0.25", features = ["derive"] } +strum = { workspace = true, features = ["derive"] } time = { version = "0.3", features = ["formatting"] } tokio = { version = "1", features = ["full"] } -yansi = "0.5" +yansi.workspace = true +tracing.workspace = true + +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = { workspace = true, optional = true } [dev-dependencies] criterion = { version = "0.5", features = ["async_tokio"] } -once_cell = "1" -serial_test = "2" +serial_test = "3" +tracing-subscriber.workspace = true [features] default = ["rustls"] rustls = ["reqwest/rustls-tls", "reqwest/rustls-tls-native-roots"] openssl = ["foundry-compilers/openssl", "reqwest/default-tls"] +asm-keccak = ["alloy-primitives/asm-keccak"] +jemalloc = ["dep:tikv-jemallocator"] [[bench]] name = "session_source" diff --git a/crates/chisel/benches/session_source.rs b/crates/chisel/benches/session_source.rs index 3088c3efe41ff..3c3196956b950 100644 --- a/crates/chisel/benches/session_source.rs +++ b/crates/chisel/benches/session_source.rs @@ -1,8 +1,6 @@ use chisel::session_source::{SessionSource, SessionSourceConfig}; use criterion::{criterion_group, Criterion}; use foundry_compilers::Solc; -use foundry_config::Config; -use foundry_evm::opts::EvmOpts; use once_cell::sync::Lazy; use std::hint::black_box; use tokio::runtime::Runtime; @@ -66,16 +64,7 @@ fn inspect(c: &mut Criterion) { /// Helper function for getting an empty [SessionSource] with default configuration fn get_empty_session_source() -> SessionSource { - SessionSource::new( - SOLC.clone(), - SessionSourceConfig { - foundry_config: Config::default(), - evm_opts: EvmOpts::default(), - backend: None, - traces: false, - calldata: None, - }, - ) + SessionSource::new(SOLC.clone(), SessionSourceConfig::default()) } fn rt() -> Runtime { diff --git a/crates/chisel/bin/main.rs b/crates/chisel/bin/main.rs index 00cb59ed71874..706c6af99e08b 100644 --- a/crates/chisel/bin/main.rs +++ b/crates/chisel/bin/main.rs @@ -7,9 +7,10 @@ use chisel::{ history::chisel_history_file, prelude::{ChiselCommand, ChiselDispatcher, DispatchResult, SolidityHelper}, }; -use clap::Parser; +use clap::{Parser, Subcommand}; use eyre::Context; use foundry_cli::{ + handler, opts::CoreBuildArgs, utils::{self, LoadConfig}, }; @@ -23,10 +24,15 @@ use foundry_config::{ }; use rustyline::{config::Configurer, error::ReadlineError, Editor}; use std::path::PathBuf; +use tracing::debug; use yansi::Paint; +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + // Loads project's figment and merges the build cli arguments into it -foundry_config::merge_impl_figment_convert!(ChiselParser, opts, evm_opts); +foundry_config::merge_impl_figment_convert!(Chisel, opts, evm_opts); const VERSION_MESSAGE: &str = concat!( env!("CARGO_PKG_VERSION"), @@ -39,28 +45,36 @@ const VERSION_MESSAGE: &str = concat!( /// Fast, utilitarian, and verbose Solidity REPL. #[derive(Debug, Parser)] -#[clap(name = "chisel", version = VERSION_MESSAGE)] -pub struct ChiselParser { +#[command(name = "chisel", version = VERSION_MESSAGE)] +pub struct Chisel { #[command(subcommand)] - pub sub: Option, + pub cmd: Option, /// Path to a directory containing Solidity files to import, or path to a single Solidity file. /// /// These files will be evaluated before the top-level of the /// REPL, therefore functioning as a prelude - #[clap(long, help_heading = "REPL options")] + #[arg(long, help_heading = "REPL options")] pub prelude: Option, - #[clap(flatten)] + /// Disable the default `Vm` import. + #[arg(long, help_heading = "REPL options", long_help = format!( + "Disable the default `Vm` import.\n\n\ + The import is disabled by default if the Solc version is less than {}.", + chisel::session_source::MIN_VM_VERSION + ))] + pub no_vm: bool, + + #[command(flatten)] pub opts: CoreBuildArgs, - #[clap(flatten)] + #[command(flatten)] pub evm_opts: EvmArgs, } /// Chisel binary subcommands -#[derive(clap::Subcommand, Debug)] -pub enum ChiselParserSub { +#[derive(Debug, Subcommand)] +pub enum ChiselSubcommand { /// List all cached sessions List, @@ -82,15 +96,12 @@ pub enum ChiselParserSub { #[tokio::main] async fn main() -> eyre::Result<()> { - #[cfg(windows)] - if !Paint::enable_windows_ascii() { - Paint::disable() - } - + handler::install(); + utils::subscriber(); utils::load_dotenv(); // Parse command args - let args = ChiselParser::parse(); + let args = Chisel::parse(); // Keeps track of whether or not an interrupt was the last input let mut interrupt = false; @@ -103,6 +114,7 @@ async fn main() -> eyre::Result<()> { // Enable traces if any level of verbosity was passed traces: config.verbosity > 0, foundry_config: config, + no_vm: args.no_vm, evm_opts, backend: None, calldata: None, @@ -112,8 +124,8 @@ async fn main() -> eyre::Result<()> { evaluate_prelude(&mut dispatcher, args.prelude).await?; // Check for chisel subcommands - match &args.sub { - Some(ChiselParserSub::List) => { + match &args.cmd { + Some(ChiselSubcommand::List) => { let sessions = dispatcher.dispatch_command(ChiselCommand::ListSessions, &[]).await; match sessions { DispatchResult::CommandSuccess(Some(session_list)) => { @@ -124,7 +136,7 @@ async fn main() -> eyre::Result<()> { } return Ok(()) } - Some(ChiselParserSub::Load { id }) | Some(ChiselParserSub::View { id }) => { + Some(ChiselSubcommand::Load { id }) | Some(ChiselSubcommand::View { id }) => { // For both of these subcommands, we need to attempt to load the session from cache match dispatcher.dispatch_command(ChiselCommand::Load, &[id]).await { DispatchResult::CommandSuccess(_) => { /* Continue */ } @@ -136,7 +148,7 @@ async fn main() -> eyre::Result<()> { } // If the subcommand was `view`, print the source and exit. - if matches!(args.sub, Some(ChiselParserSub::View { .. })) { + if matches!(args.cmd, Some(ChiselSubcommand::View { .. })) { match dispatcher.dispatch_command(ChiselCommand::Source, &[]).await { DispatchResult::CommandSuccess(Some(source)) => { println!("{source}"); @@ -146,9 +158,9 @@ async fn main() -> eyre::Result<()> { return Ok(()) } } - Some(ChiselParserSub::ClearCache) => { + Some(ChiselSubcommand::ClearCache) => { match dispatcher.dispatch_command(ChiselCommand::ClearCache, &[]).await { - DispatchResult::CommandSuccess(Some(msg)) => println!("{}", Paint::green(msg)), + DispatchResult::CommandSuccess(Some(msg)) => println!("{}", msg.green()), DispatchResult::CommandFailed(e) => eprintln!("{e}"), _ => panic!("Unexpected result! Please report this bug."), } @@ -170,14 +182,13 @@ async fn main() -> eyre::Result<()> { } // Print welcome header - println!("Welcome to Chisel! Type `{}` to show available commands.", Paint::green("!help")); + println!("Welcome to Chisel! Type `{}` to show available commands.", "!help".green()); // Begin Rustyline loop loop { // Get the prompt from the dispatcher // Variable based on status of the last entry let prompt = dispatcher.get_prompt(); - rl.helper_mut().unwrap().set_errored(dispatcher.errored); // Read the next line let next_string = rl.readline(prompt.as_ref()); @@ -185,11 +196,13 @@ async fn main() -> eyre::Result<()> { // Try to read the string match next_string { Ok(line) => { + debug!("dispatching next line: {line}"); // Clear interrupt flag interrupt = false; // Dispatch and match results - dispatch_repl_line(&mut dispatcher, &line).await; + let errored = dispatch_repl_line(&mut dispatcher, &line).await; + rl.helper_mut().unwrap().set_errored(errored); } Err(ReadlineError::Interrupted) => { if interrupt { @@ -215,7 +228,7 @@ async fn main() -> eyre::Result<()> { } /// [Provider] impl -impl Provider for ChiselParser { +impl Provider for Chisel { fn metadata(&self) -> Metadata { Metadata::named("Script Args Provider") } @@ -226,20 +239,25 @@ impl Provider for ChiselParser { } /// Evaluate a single Solidity line. -async fn dispatch_repl_line(dispatcher: &mut ChiselDispatcher, line: &str) { - match dispatcher.dispatch(line).await { - DispatchResult::Success(msg) | DispatchResult::CommandSuccess(msg) => if let Some(msg) = msg { - println!("{}", Paint::green(msg)); +async fn dispatch_repl_line(dispatcher: &mut ChiselDispatcher, line: &str) -> bool { + let r = dispatcher.dispatch(line).await; + match &r { + DispatchResult::Success(msg) | DispatchResult::CommandSuccess(msg) => { + debug!(%line, ?msg, "dispatch success"); + if let Some(msg) = msg { + println!("{}", msg.green()); + } }, DispatchResult::UnrecognizedCommand(e) => eprintln!("{e}"), DispatchResult::SolangParserFailed(e) => { - eprintln!("{}", Paint::red("Compilation error")); - eprintln!("{}", Paint::red(format!("{e:?}"))); + eprintln!("{}", "Compilation error".red()); + eprintln!("{}", format!("{e:?}").red()); } - DispatchResult::FileIoError(e) => eprintln!("{}", Paint::red(format!("⚒️ Chisel File IO Error - {e}"))), - DispatchResult::CommandFailed(msg) | DispatchResult::Failure(Some(msg)) => eprintln!("{}", Paint::red(msg)), - DispatchResult::Failure(None) => eprintln!("{}\nPlease Report this bug as a github issue if it persists: https://github.com/foundry-rs/foundry/issues/new/choose", Paint::red("⚒️ Unknown Chisel Error ⚒️")), + DispatchResult::FileIoError(e) => eprintln!("{}", format!("⚒️ Chisel File IO Error - {e}").red()), + DispatchResult::CommandFailed(msg) | DispatchResult::Failure(Some(msg)) => eprintln!("{}", msg.red()), + DispatchResult::Failure(None) => eprintln!("{}\nPlease Report this bug as a github issue if it persists: https://github.com/foundry-rs/foundry/issues/new/choose", "⚒️ Unknown Chisel Error ⚒️".red()), } + r.is_error() } /// Evaluate multiple Solidity source files contained within a @@ -250,19 +268,19 @@ async fn evaluate_prelude( ) -> eyre::Result<()> { let Some(prelude_dir) = maybe_prelude else { return Ok(()) }; if prelude_dir.is_file() { - println!("{} {}", Paint::yellow("Loading prelude source file:"), prelude_dir.display(),); + println!("{} {}", "Loading prelude source file:".yellow(), prelude_dir.display(),); load_prelude_file(dispatcher, prelude_dir).await?; - println!("{}\n", Paint::green("Prelude source file loaded successfully!")); + println!("{}\n", "Prelude source file loaded successfully!".green()); } else { let prelude_sources = fs::files_with_ext(prelude_dir, "sol"); let print_success_msg = !prelude_sources.is_empty(); for source_file in prelude_sources { - println!("{} {}", Paint::yellow("Loading prelude source file:"), source_file.display(),); + println!("{} {}", "Loading prelude source file:".yellow(), source_file.display(),); load_prelude_file(dispatcher, source_file).await?; } if print_success_msg { - println!("{}\n", Paint::green("All prelude source files loaded successfully!")); + println!("{}\n", "All prelude source files loaded successfully!".green()); } } Ok(()) @@ -275,3 +293,14 @@ async fn load_prelude_file(dispatcher: &mut ChiselDispatcher, file: PathBuf) -> dispatch_repl_line(dispatcher, &prelude).await; Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use clap::CommandFactory; + + #[test] + fn verify_cli() { + Chisel::command().debug_assert(); + } +} diff --git a/crates/chisel/src/dispatcher.rs b/crates/chisel/src/dispatcher.rs index b191d8dccad3e..ebf4aeb3765ef 100644 --- a/crates/chisel/src/dispatcher.rs +++ b/crates/chisel/src/dispatcher.rs @@ -3,9 +3,12 @@ //! This module contains the `ChiselDispatcher` struct, which handles the dispatching //! of both builtin commands and Solidity snippets. -use crate::prelude::{ - ChiselCommand, ChiselResult, ChiselSession, CmdCategory, CmdDescriptor, SessionSourceConfig, - SolidityHelper, +use crate::{ + prelude::{ + ChiselCommand, ChiselResult, ChiselSession, CmdCategory, CmdDescriptor, + SessionSourceConfig, SolidityHelper, + }, + session_source::SessionSource, }; use alloy_json_abi::JsonAbi; use alloy_primitives::{hex, Address}; @@ -14,8 +17,8 @@ use foundry_config::{Config, RpcEndpoint}; use foundry_evm::{ decode::decode_console_logs, traces::{ - identifier::{EtherscanIdentifier, SignaturesIdentifier}, - CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, + identifier::{SignaturesIdentifier, TraceIdentifiers}, + render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, }, }; use once_cell::sync::Lazy; @@ -23,8 +26,15 @@ use regex::Regex; use reqwest::Url; use serde::{Deserialize, Serialize}; use solang_parser::diagnostics::Diagnostic; -use std::{borrow::Cow, error::Error, io::Write, path::PathBuf, process::Command}; +use std::{ + borrow::Cow, + error::Error, + io::Write, + path::{Path, PathBuf}, + process::Command, +}; use strum::IntoEnumIterator; +use tracing::debug; use yansi::Paint; /// Prompt arrow character @@ -40,14 +50,14 @@ pub static CHISEL_CHAR: &str = "⚒️"; static COMMENT_RE: Lazy = Lazy::new(|| Regex::new(r"^\s*(?://.*\s*$)|(/*[\s\S]*?\*/\s*$)").unwrap()); -/// Matches Ethereum addresses -static ADDRESS_RE: Lazy = Lazy::new(|| Regex::new(r"0x[a-fA-F0-9]{40}").unwrap()); +/// Matches Ethereum addresses that are not strings +static ADDRESS_RE: Lazy = Lazy::new(|| { + Regex::new(r#"(?m)(([^"']\s*)|^)(?P
0x[a-fA-F0-9]{40})((\s*[^"'\w])|$)"#).unwrap() +}); /// Chisel input dispatcher #[derive(Debug)] pub struct ChiselDispatcher { - /// The status of the previous dispatch - pub errored: bool, /// A Chisel Session pub session: ChiselSession, } @@ -71,6 +81,20 @@ pub enum DispatchResult { FileIoError(Box), } +impl DispatchResult { + /// Returns `true` if the result is an error. + pub fn is_error(&self) -> bool { + matches!( + self, + DispatchResult::Failure(_) | + DispatchResult::CommandFailed(_) | + DispatchResult::UnrecognizedCommand(_) | + DispatchResult::SolangParserFailed(_) | + DispatchResult::FileIoError(_) + ) + } +} + /// A response from the Etherscan API's `getabi` action #[derive(Debug, Serialize, Deserialize)] pub struct EtherscanABIResponse { @@ -112,7 +136,29 @@ pub fn format_source(source: &str, config: FormatterConfig) -> eyre::Result eyre::Result { - ChiselSession::new(config).map(|session| Self { errored: false, session }) + ChiselSession::new(config).map(|session| Self { session }) + } + + /// Returns the optional ID of the current session. + pub fn id(&self) -> Option<&str> { + self.session.id.as_deref() + } + + /// Returns the [`SessionSource`]. + pub fn source(&self) -> &SessionSource { + &self.session.session_source + } + + /// Returns the [`SessionSource`]. + pub fn source_mut(&mut self) -> &mut SessionSource { + &mut self.session.session_source + } + + fn format_source(&self) -> eyre::Result { + format_source( + &self.source().to_repl_source(), + self.source().config.foundry_config.fmt.clone(), + ) } /// Returns the prompt based on the current status of the Dispatcher @@ -149,7 +195,7 @@ impl ChiselDispatcher { ChiselCommand::iter().map(CmdDescriptor::from).collect::>(); DispatchResult::CommandSuccess(Some(format!( "{}\n{}", - Paint::cyan(format!("{CHISEL_CHAR} Chisel help\n=============")), + format!("{CHISEL_CHAR} Chisel help\n=============").cyan(), CmdCategory::iter() .map(|cat| { // Get commands in the current category @@ -163,13 +209,13 @@ impl ChiselDispatcher { // Format the help menu for the current category format!( "{}\n{}\n", - Paint::magenta(cat), + cat.magenta(), cat_cmds .iter() .map(|(cmds, desc, _)| format!( "\t{} - {}", cmds.iter() - .map(|cmd| format!("!{}", Paint::green(cmd))) + .map(|cmd| format!("!{}", cmd.green())) .collect::>() .join(" | "), desc @@ -187,18 +233,10 @@ impl ChiselDispatcher { std::process::exit(0); } ChiselCommand::Clear => { - if let Some(session_source) = self.session.session_source.as_mut() { - // Drain all source sections - session_source.drain_run(); - session_source.drain_global_code(); - session_source.drain_top_level_code(); - - DispatchResult::CommandSuccess(Some(String::from("Cleared session!"))) - } else { - DispatchResult::CommandFailed( - Paint::red("Session source not present!").to_string(), - ) - } + self.source_mut().drain_run(); + self.source_mut().drain_global_code(); + self.source_mut().drain_top_level_code(); + DispatchResult::CommandSuccess(Some(String::from("Cleared session!"))) } ChiselCommand::Save => { if args.len() <= 1 { @@ -231,15 +269,14 @@ impl ChiselDispatcher { // Use args as the name let name = args[0]; // Try to save the current session before loading another - if let Some(session_source) = &self.session.session_source { - // Don't save an empty session - if !session_source.run_code.is_empty() { - if let Err(e) = self.session.write() { - return DispatchResult::FileIoError(e.into()) - } - println!("{}", Paint::green("Saved current session!")); + // Don't save an empty session + if !self.source().run_code.is_empty() { + if let Err(e) = self.session.write() { + return DispatchResult::FileIoError(e.into()) } + println!("{}", "Saved current session!".green()); } + // Parse the arguments let new_session = match name { "latest" => ChiselSession::latest(), @@ -253,7 +290,7 @@ impl ChiselDispatcher { // SAFETY // Should never panic due to the checks performed when the session was created // in the first place. - new_session.session_source.as_mut().unwrap().build().unwrap(); + new_session.session_source.build().unwrap(); self.session = new_session; DispatchResult::CommandSuccess(Some(format!( @@ -267,11 +304,11 @@ impl ChiselDispatcher { ChiselCommand::ListSessions => match ChiselSession::list_sessions() { Ok(sessions) => DispatchResult::CommandSuccess(Some(format!( "{}\n{}", - Paint::cyan(format!("{CHISEL_CHAR} Chisel Sessions")), + format!("{CHISEL_CHAR} Chisel Sessions").cyan(), sessions .iter() .map(|(time, name)| { - format!("{} - {}", Paint::blue(format!("{time:?}")), name) + format!("{} - {}", format!("{time:?}").blue(), name) }) .collect::>() .join("\n") @@ -280,23 +317,14 @@ impl ChiselDispatcher { "No sessions found. Use the `!save` command to save a session.", )), }, - ChiselCommand::Source => { - if let Some(session_source) = self.session.session_source.as_ref() { - match format_source( - &session_source.to_repl_source(), - session_source.config.foundry_config.fmt.clone(), - ) { - Ok(formatted_source) => DispatchResult::CommandSuccess(Some( - SolidityHelper::highlight(&formatted_source).into_owned(), - )), - Err(_) => DispatchResult::CommandFailed(String::from( - "Failed to format session source", - )), - } - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) + ChiselCommand::Source => match self.format_source() { + Ok(formatted_source) => DispatchResult::CommandSuccess(Some( + SolidityHelper::highlight(&formatted_source).into_owned(), + )), + Err(_) => { + DispatchResult::CommandFailed(String::from("Failed to format session source")) } - } + }, ChiselCommand::ClearCache => match ChiselSession::clear_cache() { Ok(_) => { self.session.id = None; @@ -305,191 +333,158 @@ impl ChiselDispatcher { Err(_) => DispatchResult::CommandFailed(Self::make_error("Failed to clear cache!")), }, ChiselCommand::Fork => { - if let Some(session_source) = self.session.session_source.as_mut() { - if args.is_empty() || args[0].trim().is_empty() { - session_source.config.evm_opts.fork_url = None; - return DispatchResult::CommandSuccess(Some( - "Now using local environment.".to_string(), - )) - } - if args.len() != 1 { - return DispatchResult::CommandFailed(Self::make_error( - "Must supply a session ID as the argument.", - )) + if args.is_empty() || args[0].trim().is_empty() { + self.source_mut().config.evm_opts.fork_url = None; + return DispatchResult::CommandSuccess(Some( + "Now using local environment.".to_string(), + )) + } + if args.len() != 1 { + return DispatchResult::CommandFailed(Self::make_error( + "Must supply a session ID as the argument.", + )) + } + let arg = *args.first().unwrap(); + + // If the argument is an RPC alias designated in the + // `[rpc_endpoints]` section of the `foundry.toml` within + // the pwd, use the URL matched to the key. + let endpoint = if let Some(endpoint) = + self.source_mut().config.foundry_config.rpc_endpoints.get(arg) + { + endpoint.clone() + } else { + RpcEndpoint::Env(arg.to_string()).into() + }; + let fork_url = match endpoint.resolve() { + Ok(fork_url) => fork_url, + Err(e) => { + return DispatchResult::CommandFailed(Self::make_error(format!( + "\"{}\" ENV Variable not set!", + e.var + ))) } - let arg = *args.first().unwrap(); - - // If the argument is an RPC alias designated in the - // `[rpc_endpoints]` section of the `foundry.toml` within - // the pwd, use the URL matched to the key. - let endpoint = if let Some(endpoint) = - session_source.config.foundry_config.rpc_endpoints.get(arg) - { - endpoint.clone() - } else { - RpcEndpoint::Env(arg.to_string()) - }; - let fork_url = match endpoint.resolve() { - Ok(fork_url) => fork_url, - Err(e) => { - return DispatchResult::CommandFailed(Self::make_error(format!( - "\"{}\" ENV Variable not set!", - e.var - ))) - } - }; + }; - // Check validity of URL - if Url::parse(&fork_url).is_err() { - return DispatchResult::CommandFailed(Self::make_error("Invalid fork URL!")) - } + // Check validity of URL + if Url::parse(&fork_url).is_err() { + return DispatchResult::CommandFailed(Self::make_error("Invalid fork URL!")) + } - // Create success message before moving the fork_url - let success_msg = format!("Set fork URL to {}", Paint::yellow(&fork_url)); + // Create success message before moving the fork_url + let success_msg = format!("Set fork URL to {}", &fork_url.yellow()); - // Update the fork_url inside of the [SessionSourceConfig]'s [EvmOpts] - // field - session_source.config.evm_opts.fork_url = Some(fork_url); + // Update the fork_url inside of the [SessionSourceConfig]'s [EvmOpts] + // field + self.source_mut().config.evm_opts.fork_url = Some(fork_url); - // Clear the backend so that it is re-instantiated with the new fork - // upon the next execution of the session source. - session_source.config.backend = None; + // Clear the backend so that it is re-instantiated with the new fork + // upon the next execution of the session source. + self.source_mut().config.backend = None; - DispatchResult::CommandSuccess(Some(success_msg)) - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) - } + DispatchResult::CommandSuccess(Some(success_msg)) } ChiselCommand::Traces => { - if let Some(session_source) = self.session.session_source.as_mut() { - session_source.config.traces = !session_source.config.traces; - DispatchResult::CommandSuccess(Some(format!( - "{} traces!", - if session_source.config.traces { "Enabled" } else { "Disabled" } - ))) - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) - } + self.source_mut().config.traces = !self.source_mut().config.traces; + DispatchResult::CommandSuccess(Some(format!( + "{} traces!", + if self.source_mut().config.traces { "Enabled" } else { "Disabled" } + ))) } ChiselCommand::Calldata => { - if let Some(session_source) = self.session.session_source.as_mut() { - // remove empty space, double quotes, and 0x prefix - let arg = args - .first() - .map(|s| { - s.trim_matches(|c: char| c.is_whitespace() || c == '"' || c == '\'') - }) - .map(|s| s.strip_prefix("0x").unwrap_or(s)) - .unwrap_or(""); + // remove empty space, double quotes, and 0x prefix + let arg = args + .first() + .map(|s| s.trim_matches(|c: char| c.is_whitespace() || c == '"' || c == '\'')) + .map(|s| s.strip_prefix("0x").unwrap_or(s)) + .unwrap_or(""); + + if arg.is_empty() { + self.source_mut().config.calldata = None; + return DispatchResult::CommandSuccess(Some("Calldata cleared.".to_string())) + } - if arg.is_empty() { - session_source.config.calldata = None; - return DispatchResult::CommandSuccess(Some("Calldata cleared.".to_string())) + let calldata = hex::decode(arg); + match calldata { + Ok(calldata) => { + self.source_mut().config.calldata = Some(calldata); + DispatchResult::CommandSuccess(Some(format!( + "Set calldata to '{}'", + arg.yellow() + ))) } - - let calldata = hex::decode(arg); - match calldata { - Ok(calldata) => { - session_source.config.calldata = Some(calldata); - DispatchResult::CommandSuccess(Some(format!( - "Set calldata to '{}'", - Paint::yellow(arg) - ))) - } - Err(e) => DispatchResult::CommandFailed(Self::make_error(format!( - "Invalid calldata: {}", - e - ))), - } - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) + Err(e) => DispatchResult::CommandFailed(Self::make_error(format!( + "Invalid calldata: {}", + e + ))), } } ChiselCommand::MemDump | ChiselCommand::StackDump => { - if let Some(session_source) = self.session.session_source.as_mut() { - match session_source.execute().await { - Ok((_, res)) => { - if let Some((stack, mem, _)) = res.state.as_ref() { - if matches!(cmd, ChiselCommand::MemDump) { - // Print memory by word - (0..mem.len()).step_by(32).for_each(|i| { - println!( - "{}: {}", - Paint::yellow(format!( - "[0x{:02x}:0x{:02x}]", - i, - i + 32 - )), - Paint::cyan(hex::encode_prefixed(&mem[i..i + 32])) - ); - }); - } else { - // Print all stack items - (0..stack.len()).rev().for_each(|i| { - println!( - "{}: {}", - Paint::yellow(format!("[{}]", stack.len() - i - 1)), - Paint::cyan(format!("0x{:02x}", stack.data()[i])) - ); - }); - } - DispatchResult::CommandSuccess(None) + match self.source_mut().execute().await { + Ok((_, res)) => { + if let Some((stack, mem, _)) = res.state.as_ref() { + if matches!(cmd, ChiselCommand::MemDump) { + // Print memory by word + (0..mem.len()).step_by(32).for_each(|i| { + println!( + "{}: {}", + format!("[0x{:02x}:0x{:02x}]", i, i + 32).yellow(), + hex::encode_prefixed(&mem[i..i + 32]).cyan() + ); + }); } else { - DispatchResult::CommandFailed(Self::make_error( - "Run function is empty.", - )) + // Print all stack items + (0..stack.len()).rev().for_each(|i| { + println!( + "{}: {}", + format!("[{}]", stack.len() - i - 1).yellow(), + format!("0x{:02x}", stack[i]).cyan() + ); + }); } + DispatchResult::CommandSuccess(None) + } else { + DispatchResult::CommandFailed(Self::make_error( + "Run function is empty.", + )) } - Err(e) => DispatchResult::CommandFailed(Self::make_error(e.to_string())), } - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) + Err(e) => DispatchResult::CommandFailed(Self::make_error(e.to_string())), } } ChiselCommand::Export => { // Check if the current session inherits `Script.sol` before exporting - if let Some(session_source) = self.session.session_source.as_ref() { - // Check if the pwd is a foundry project - if PathBuf::from("foundry.toml").exists() { - // Create "script" dir if it does not already exist. - if !PathBuf::from("script").exists() { - if let Err(e) = std::fs::create_dir_all("script") { - return DispatchResult::CommandFailed(Self::make_error( - e.to_string(), - )) - } - } - match format_source( - &session_source.to_script_source(), - session_source.config.foundry_config.fmt.clone(), - ) { - Ok(formatted_source) => { - // Write session source to `script/REPL.s.sol` - if let Err(e) = std::fs::write( - PathBuf::from("script/REPL.s.sol"), - formatted_source, - ) { - return DispatchResult::CommandFailed(Self::make_error( - e.to_string(), - )) - } + // Check if the pwd is a foundry project + if !Path::new("foundry.toml").exists() { + return DispatchResult::CommandFailed(Self::make_error( + "Must be in a foundry project to export source to script.", + )); + } - DispatchResult::CommandSuccess(Some(String::from( - "Exported session source to script/REPL.s.sol!", - ))) - } - Err(_) => DispatchResult::CommandFailed(String::from( - "Failed to format session source", - )), + // Create "script" dir if it does not already exist. + if !Path::new("script").exists() { + if let Err(e) = std::fs::create_dir_all("script") { + return DispatchResult::CommandFailed(Self::make_error(e.to_string())) + } + } + + match self.format_source() { + Ok(formatted_source) => { + // Write session source to `script/REPL.s.sol` + if let Err(e) = + std::fs::write(PathBuf::from("script/REPL.s.sol"), formatted_source) + { + return DispatchResult::CommandFailed(Self::make_error(e.to_string())) } - } else { - DispatchResult::CommandFailed(Self::make_error( - "Must be in a foundry project to export source to script.", - )) + + DispatchResult::CommandSuccess(Some(String::from( + "Exported session source to script/REPL.s.sol!", + ))) } - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) + Err(_) => DispatchResult::CommandFailed(String::from( + "Failed to format session source", + )), } } ChiselCommand::Fetch => { @@ -502,15 +497,8 @@ impl ChiselDispatcher { let request_url = format!( "https://api.etherscan.io/api?module=contract&action=getabi&address={}{}", args[0], - if let Some(api_key) = self - .session - .session_source - .as_ref() - .unwrap() - .config - .foundry_config - .etherscan_api_key - .as_ref() + if let Some(api_key) = + self.source().config.foundry_config.etherscan_api_key.as_ref() { format!("&apikey={api_key}") } else { @@ -601,11 +589,7 @@ impl ChiselDispatcher { // Add the interface to the source outright - no need to verify // syntax via compilation and/or // parsing. - self.session - .session_source - .as_mut() - .unwrap() - .with_global_code(&interface); + self.source_mut().with_global_code(&interface); DispatchResult::CommandSuccess(Some(format!( "Added {}'s interface to source as `{}`", @@ -652,97 +636,91 @@ impl ChiselDispatcher { } } ChiselCommand::Edit => { - if let Some(session_source) = self.session.session_source.as_mut() { - // create a temp file with the content of the run code - let mut temp_file_path = std::env::temp_dir(); - temp_file_path.push("chisel-tmp.sol"); - let result = std::fs::File::create(&temp_file_path) - .map(|mut file| file.write_all(session_source.run_code.as_bytes())); - if let Err(e) = result { - return DispatchResult::CommandFailed(format!( - "Could not write to a temporary file: {e}" - )) - } + // create a temp file with the content of the run code + let mut temp_file_path = std::env::temp_dir(); + temp_file_path.push("chisel-tmp.sol"); + let result = std::fs::File::create(&temp_file_path) + .map(|mut file| file.write_all(self.source().run_code.as_bytes())); + if let Err(e) = result { + return DispatchResult::CommandFailed(format!( + "Could not write to a temporary file: {e}" + )) + } - // open the temp file with the editor - let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vim".to_string()); - let mut cmd = Command::new(editor); - cmd.arg(&temp_file_path); - - match cmd.status() { - Ok(status) => { - if !status.success() { - if let Some(status_code) = status.code() { - return DispatchResult::CommandFailed(format!( - "Editor exited with status {status_code}" - )) - } else { - return DispatchResult::CommandFailed( - "Editor exited without a status code".to_string(), - ) - } + // open the temp file with the editor + let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vim".to_string()); + let mut cmd = Command::new(editor); + cmd.arg(&temp_file_path); + + match cmd.status() { + Ok(status) => { + if !status.success() { + if let Some(status_code) = status.code() { + return DispatchResult::CommandFailed(format!( + "Editor exited with status {status_code}" + )) + } else { + return DispatchResult::CommandFailed( + "Editor exited without a status code".to_string(), + ) } } - Err(_) => { - return DispatchResult::CommandFailed( - "Editor exited without a status code".to_string(), - ) - } } - - let mut new_session_source = session_source.clone(); - if let Ok(edited_code) = std::fs::read_to_string(temp_file_path) { - new_session_source.drain_run(); - new_session_source.with_run_code(&edited_code); - } else { + Err(_) => { return DispatchResult::CommandFailed( - "Could not read the edited file".to_string(), + "Editor exited without a status code".to_string(), ) } + } - // if the editor exited successfully, try to compile the new code - match new_session_source.execute().await { - Ok((_, mut res)) => { - let failed = !res.success; - if new_session_source.config.traces || failed { - if let Ok(decoder) = - Self::decode_traces(&new_session_source.config, &mut res) - { - if let Err(e) = Self::show_traces(&decoder, &mut res).await { - self.errored = true; - return DispatchResult::CommandFailed(e.to_string()) - }; - - // Show console logs, if there are any - let decoded_logs = decode_console_logs(&res.logs); - if !decoded_logs.is_empty() { - println!("{}", Paint::green("Logs:")); - for log in decoded_logs { - println!(" {log}"); - } + let mut new_session_source = self.source().clone(); + if let Ok(edited_code) = std::fs::read_to_string(temp_file_path) { + new_session_source.drain_run(); + new_session_source.with_run_code(&edited_code); + } else { + return DispatchResult::CommandFailed( + "Could not read the edited file".to_string(), + ) + } + + // if the editor exited successfully, try to compile the new code + match new_session_source.execute().await { + Ok((_, mut res)) => { + let failed = !res.success; + if new_session_source.config.traces || failed { + if let Ok(decoder) = + Self::decode_traces(&new_session_source.config, &mut res) + { + if let Err(e) = Self::show_traces(&decoder, &mut res).await { + return DispatchResult::CommandFailed(e.to_string()) + }; + + // Show console logs, if there are any + let decoded_logs = decode_console_logs(&res.logs); + if !decoded_logs.is_empty() { + println!("{}", "Logs:".green()); + for log in decoded_logs { + println!(" {log}"); } } - - // If the contract execution failed, continue on without - // updating the source. - self.errored = true; - DispatchResult::CommandFailed(Self::make_error( - "Failed to execute edited contract!", - )) - } else { - // the code could be compiled, save it - *session_source = new_session_source; - DispatchResult::CommandSuccess(Some(String::from( - "Successfully edited `run()` function's body!", - ))) } + + // If the contract execution failed, continue on without + // updating the source. + DispatchResult::CommandFailed(Self::make_error( + "Failed to execute edited contract!", + )) + } else { + // the code could be compiled, save it + *self.source_mut() = new_session_source; + DispatchResult::CommandSuccess(Some(String::from( + "Successfully edited `run()` function's body!", + ))) } - Err(_) => DispatchResult::CommandFailed( - "The code could not be compiled".to_string(), - ), } - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) + Err(_) => { + DispatchResult::CommandFailed("The code could not be compiled".to_string()) + } } } ChiselCommand::RawStack => { @@ -759,14 +737,7 @@ impl ChiselDispatcher { let to_inspect = args.first().unwrap(); // Get a mutable reference to the session source - let source = match self.session.session_source.as_mut() { - Some(session_source) => session_source, - _ => { - return DispatchResult::CommandFailed( - "Session source not present".to_string(), - ) - } - }; + let source = self.source_mut(); // Copy the variable's stack contents into a bytes32 variable without updating // the current session source. @@ -795,33 +766,21 @@ impl ChiselDispatcher { let raw_cmd = &split[0][1..]; return match raw_cmd.parse::() { - Ok(cmd) => { - let command_dispatch = self.dispatch_command(cmd, &split[1..]).await; - self.errored = !matches!(command_dispatch, DispatchResult::CommandSuccess(_)); - command_dispatch - } - Err(e) => { - self.errored = true; - DispatchResult::UnrecognizedCommand(e) - } + Ok(cmd) => self.dispatch_command(cmd, &split[1..]).await, + Err(e) => DispatchResult::UnrecognizedCommand(e), } } if input.trim().is_empty() { + debug!("empty dispatch input"); return DispatchResult::Success(None) } // Get a mutable reference to the session source - let source = match self.session.session_source.as_mut().ok_or(DispatchResult::Failure(None)) - { - Ok(project) => project, - Err(e) => { - self.errored = true; - return e - } - }; + let source = self.source_mut(); // If the input is a comment, add it to the run code so we avoid running with empty input if COMMENT_RE.is_match(input) { + debug!(%input, "matched comment"); source.with_run_code(input); return DispatchResult::Success(None) } @@ -829,9 +788,10 @@ impl ChiselDispatcher { // If there is an address (or multiple addresses) in the input, ensure that they are // encoded with a valid checksum per EIP-55. let mut heap_input = input.to_string(); - ADDRESS_RE.find_iter(input).for_each(|m| { + ADDRESS_RE.captures_iter(input).for_each(|m| { // Convert the match to a string slice - let match_str = m.as_str(); + let match_str = m.name("address").expect("exists").as_str(); + // We can always safely unwrap here due to the regex matching. let addr: Address = match_str.parse().expect("Valid address regex"); // Replace all occurrences of the address with a checksummed version @@ -844,7 +804,6 @@ impl ChiselDispatcher { let (mut new_source, do_execute) = match source.clone_with_new_line(input.to_string()) { Ok(new) => new, Err(e) => { - self.errored = true; return DispatchResult::CommandFailed(Self::make_error(format!( "Failed to parse input! {e}" ))) @@ -858,13 +817,13 @@ impl ChiselDispatcher { Ok((true, Some(res))) => println!("{res}"), Ok((true, None)) => {} // Return successfully - Ok((false, res)) => return DispatchResult::Success(res), + Ok((false, res)) => { + debug!(%input, ?res, "inspect success"); + return DispatchResult::Success(res) + } // Return with the error - Err(e) => { - self.errored = true; - return DispatchResult::CommandFailed(Self::make_error(e)) - } + Err(e) => return DispatchResult::CommandFailed(Self::make_error(e)), } if do_execute { @@ -877,14 +836,13 @@ impl ChiselDispatcher { if new_source.config.traces || failed { if let Ok(decoder) = Self::decode_traces(&new_source.config, &mut res) { if let Err(e) = Self::show_traces(&decoder, &mut res).await { - self.errored = true; return DispatchResult::CommandFailed(e.to_string()) }; // Show console logs, if there are any let decoded_logs = decode_console_logs(&res.logs); if !decoded_logs.is_empty() { - println!("{}", Paint::green("Logs:")); + println!("{}", "Logs:".green()); for log in decoded_logs { println!(" {log}"); } @@ -893,7 +851,6 @@ impl ChiselDispatcher { // If the contract execution failed, continue on without adding the new // line to the source. if failed { - self.errored = true; return DispatchResult::Failure(Some(Self::make_error( "Failed to execute REPL contract!", ))) @@ -902,28 +859,20 @@ impl ChiselDispatcher { } // Replace the old session source with the new version - self.session.session_source = Some(new_source); - // Clear any outstanding errors - self.errored = false; + *self.source_mut() = new_source; DispatchResult::Success(None) } - Err(e) => { - self.errored = true; - DispatchResult::Failure(Some(e.to_string())) - } + Err(e) => DispatchResult::Failure(Some(e.to_string())), } } else { match new_source.build() { - Ok(_) => { - self.session.session_source = Some(new_source); - self.errored = false; + Ok(out) => { + debug!(%input, ?out, "skipped execute and rebuild source"); + *self.source_mut() = new_source; DispatchResult::Success(None) } - Err(e) => { - self.errored = true; - DispatchResult::Failure(Some(e.to_string())) - } + Err(e) => DispatchResult::Failure(Some(e.to_string())), } } } @@ -944,11 +893,6 @@ impl ChiselDispatcher { result: &mut ChiselResult, // known_contracts: &ContractsByArtifact, ) -> eyre::Result { - let mut etherscan_identifier = EtherscanIdentifier::new( - &session_config.foundry_config, - session_config.evm_opts.get_remote_chain_id(), - )?; - let mut decoder = CallTraceDecoderBuilder::new() .with_labels(result.labeled_addresses.clone()) .with_signature_identifier(SignaturesIdentifier::new( @@ -957,9 +901,14 @@ impl ChiselDispatcher { )?) .build(); - for (_, trace) in &mut result.traces { - // decoder.identify(trace, &mut local_identifier); - decoder.identify(trace, &mut etherscan_identifier); + let mut identifier = TraceIdentifiers::new().with_etherscan( + &session_config.foundry_config, + session_config.evm_opts.get_remote_chain_id(), + )?; + if !identifier.is_empty() { + for (_, trace) in &mut result.traces { + decoder.identify(trace, &mut identifier); + } } Ok(decoder) } @@ -982,12 +931,11 @@ impl ChiselDispatcher { eyre::bail!("Unexpected error: No traces gathered. Please report this as a bug: https://github.com/foundry-rs/foundry/issues/new?assignees=&labels=T-bug&template=BUG-FORM.yml"); } - println!("{}", Paint::green("Traces:")); - for (kind, trace) in &mut result.traces { + println!("{}", "Traces:".green()); + for (kind, trace) in &result.traces { // Display all Setup + Execution traces. if matches!(kind, TraceKind::Setup | TraceKind::Execution) { - decoder.decode(trace).await; - println!("{trace}"); + println!("{}", render_trace_arena(trace, decoder).await?); } } @@ -1004,7 +952,7 @@ impl ChiselDispatcher { /// /// A formatted error [String]. pub fn make_error(msg: T) -> String { - format!("{} {}", Paint::red(format!("{CHISEL_CHAR} Chisel Error:")), Paint::red(msg)) + format!("{} {}", format!("{CHISEL_CHAR} Chisel Error:").red(), msg.red()) } } @@ -1022,4 +970,16 @@ mod tests { assert!(COMMENT_RE.is_match(" \t\n /* block \n \t comment */\n")); assert!(!COMMENT_RE.is_match("/* block \n \t comment */\nwith \tother")); } + + #[test] + fn test_address_regex() { + assert!(ADDRESS_RE.is_match("0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4")); + assert!(ADDRESS_RE.is_match(" 0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4 ")); + assert!(ADDRESS_RE.is_match("0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4,")); + assert!(ADDRESS_RE.is_match("(0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4)")); + assert!(!ADDRESS_RE.is_match("0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4aaa")); + assert!(!ADDRESS_RE.is_match("'0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4'")); + assert!(!ADDRESS_RE.is_match("' 0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4'")); + assert!(!ADDRESS_RE.is_match("'0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4'")); + } } diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index 9015ce1a10fa3..424bcda901d7f 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -10,7 +10,6 @@ use alloy_json_abi::EventParam; use alloy_primitives::{hex, Address, U256}; use core::fmt::Debug; use eyre::{Result, WrapErr}; -use foundry_common::types::ToEthers; use foundry_compilers::Artifact; use foundry_evm::{ backend::Backend, decode::decode_console_logs, executors::ExecutorBuilder, @@ -18,6 +17,7 @@ use foundry_evm::{ }; use solang_parser::pt::{self, CodeLocation}; use std::str::FromStr; +use tracing::debug; use yansi::Paint; const USIZE_MAX_AS_U256: U256 = U256::from_limbs([usize::MAX as u64, 0, 0, 0]); @@ -30,6 +30,8 @@ impl SessionSource { /// /// Optionally, a tuple containing the [Address] of the deployed REPL contract as well as /// the [ChiselResult]. + /// + /// Returns an error if compilation fails. pub async fn execute(&mut self) -> Result<(Address, ChiselResult)> { // Recompile the project and ensure no errors occurred. let compiled = self.build()?; @@ -125,35 +127,41 @@ impl SessionSource { /// /// ### Returns /// - /// If the input is valid `Ok((formatted_output, continue))` where: + /// If the input is valid `Ok((continue, formatted_output))` where: /// - `continue` is true if the input should be appended to the source - /// - `formatted_output` is the formatted value + /// - `formatted_output` is the formatted value, if any pub async fn inspect(&self, input: &str) -> Result<(bool, Option)> { let line = format!("bytes memory inspectoor = abi.encode({input});"); - let mut source = match self.clone_with_new_line(line) { + let mut source = match self.clone_with_new_line(line.clone()) { Ok((source, _)) => source, - Err(_) => return Ok((true, None)), + Err(err) => { + debug!(%err, "failed to build new source"); + return Ok((true, None)) + } }; let mut source_without_inspector = self.clone(); // Events and tuples fails compilation due to it not being able to be encoded in // `inspectoor`. If that happens, try executing without the inspector. - let (mut res, has_inspector) = match source.execute().await { - Ok((_, res)) => (res, true), - Err(e) => match source_without_inspector.execute().await { - Ok((_, res)) => (res, false), - Err(_) => { - if self.config.foundry_config.verbosity >= 3 { - eprintln!("Could not inspect: {e}"); + let (mut res, err) = match source.execute().await { + Ok((_, res)) => (res, None), + Err(err) => { + debug!(?err, %input, "execution failed"); + match source_without_inspector.execute().await { + Ok((_, res)) => (res, Some(err)), + Err(_) => { + if self.config.foundry_config.verbosity >= 3 { + eprintln!("Could not inspect: {err}"); + } + return Ok((true, None)) } - return Ok((true, None)) } - }, + } }; // If abi-encoding the input failed, check whether it is an event - if !has_inspector { + if let Some(err) = err { let generated_output = source_without_inspector .generated_output .as_ref() @@ -170,6 +178,12 @@ impl SessionSource { return Ok((false, Some(formatted))) } + // we were unable to check the event + if self.config.foundry_config.verbosity >= 3 { + eprintln!("Failed eval: {err}"); + } + + debug!(%err, %input, "failed abi encode input"); return Ok((false, None)) } @@ -180,7 +194,7 @@ impl SessionSource { } let decoded_logs = decode_console_logs(&res.logs); if !decoded_logs.is_empty() { - println!("{}", Paint::green("Logs:")); + println!("{}", "Logs:".green()); for log in decoded_logs { println!(" {log}"); } @@ -222,7 +236,7 @@ impl SessionSource { // the file compiled correctly, thus the last stack item must be the memory offset of // the `bytes memory inspectoor` value - let mut offset = stack.data().last().unwrap().to_ethers().as_usize(); + let mut offset = stack.last().unwrap().to::(); let mem_offset = &memory[offset..offset + 32]; let len = U256::try_from_be_slice(mem_offset).unwrap().to::(); offset += 32; @@ -278,10 +292,8 @@ impl SessionSource { let backend = match self.config.backend.take() { Some(backend) => backend, None => { - let backend = Backend::spawn( - self.config.evm_opts.get_fork(&self.config.foundry_config, env.clone()), - ) - .await; + let fork = self.config.evm_opts.get_fork(&self.config.foundry_config, env.clone()); + let backend = Backend::spawn(fork); self.config.backend = Some(backend.clone()); backend } @@ -291,8 +303,14 @@ impl SessionSource { let executor = ExecutorBuilder::new() .inspectors(|stack| { stack.chisel_state(final_pc).trace(true).cheatcodes( - CheatsConfig::new(&self.config.foundry_config, self.config.evm_opts.clone()) - .into(), + CheatsConfig::new( + &self.config.foundry_config, + self.config.evm_opts.clone(), + None, + None, + self.solc.version().ok(), + ) + .into(), ) }) .gas_limit(self.config.evm_opts.gas_limit()) @@ -319,70 +337,90 @@ impl SessionSource { fn format_token(token: DynSolValue) -> String { match token { DynSolValue::Address(a) => { - format!("Type: {}\n└ Data: {}", Paint::red("address"), Paint::cyan(a.to_string())) + format!("Type: {}\n└ Data: {}", "address".red(), a.cyan()) } - DynSolValue::FixedBytes(b, _) => { + DynSolValue::FixedBytes(b, byte_len) => { format!( "Type: {}\n└ Data: {}", - Paint::red(format!("bytes{}", b.len())), - Paint::cyan(hex::encode_prefixed(b)) + format!("bytes{byte_len}").red(), + hex::encode_prefixed(b).cyan() ) } - DynSolValue::Int(i, _) => { + DynSolValue::Int(i, bit_len) => { format!( - "Type: {}\n├ Hex: {}\n└ Decimal: {}", - Paint::red("int"), - Paint::cyan(format!("0x{i:x}")), - Paint::cyan(i) + "Type: {}\n├ Hex: {}\n├ Hex (full word): {}\n└ Decimal: {}", + format!("int{}", bit_len).red(), + format!( + "0x{}", + format!("{i:x}") + .char_indices() + .skip(64 - bit_len / 4) + .take(bit_len / 4) + .map(|(_, c)| c) + .collect::() + ) + .cyan(), + format!("{i:#x}").cyan(), + i.cyan() ) } - DynSolValue::Uint(i, _) => { + DynSolValue::Uint(i, bit_len) => { format!( - "Type: {}\n├ Hex: {}\n└ Decimal: {}", - Paint::red("uint"), - Paint::cyan(format!("0x{i:x}")), - Paint::cyan(i) + "Type: {}\n├ Hex: {}\n├ Hex (full word): {}\n└ Decimal: {}", + format!("uint{}", bit_len).red(), + format!( + "0x{}", + format!("{i:x}") + .char_indices() + .skip(64 - bit_len / 4) + .take(bit_len / 4) + .map(|(_, c)| c) + .collect::() + ) + .cyan(), + format!("{i:#x}").cyan(), + i.cyan() ) } DynSolValue::Bool(b) => { - format!("Type: {}\n└ Value: {}", Paint::red("bool"), Paint::cyan(b)) + format!("Type: {}\n└ Value: {}", "bool".red(), b.cyan()) } DynSolValue::String(_) | DynSolValue::Bytes(_) => { let hex = hex::encode(token.abi_encode()); let s = token.as_str(); format!( "Type: {}\n{}├ Hex (Memory):\n├─ Length ({}): {}\n├─ Contents ({}): {}\n├ Hex (Tuple Encoded):\n├─ Pointer ({}): {}\n├─ Length ({}): {}\n└─ Contents ({}): {}", - Paint::red(if s.is_some() { "string" } else { "dynamic bytes" }), + if s.is_some() { "string" } else { "dynamic bytes" }.red(), if let Some(s) = s { - format!("├ UTF-8: {}\n", Paint::cyan(s)) + format!("├ UTF-8: {}\n", s.cyan()) } else { String::default() }, - Paint::yellow("[0x00:0x20]"), - Paint::cyan(format!("0x{}", &hex[64..128])), - Paint::yellow("[0x20:..]"), - Paint::cyan(format!("0x{}", &hex[128..])), - Paint::yellow("[0x00:0x20]"), - Paint::cyan(format!("0x{}", &hex[..64])), - Paint::yellow("[0x20:0x40]"), - Paint::cyan(format!("0x{}", &hex[64..128])), - Paint::yellow("[0x40:..]"), - Paint::cyan(format!("0x{}", &hex[128..])), + "[0x00:0x20]".yellow(), + format!("0x{}", &hex[64..128]).cyan(), + "[0x20:..]".yellow(), + format!("0x{}", &hex[128..]).cyan(), + "[0x00:0x20]".yellow(), + format!("0x{}", &hex[..64]).cyan(), + "[0x20:0x40]".yellow(), + format!("0x{}", &hex[64..128]).cyan(), + "[0x40:..]".yellow(), + format!("0x{}", &hex[128..]).cyan(), ) } DynSolValue::FixedArray(tokens) | DynSolValue::Array(tokens) => { let mut out = format!( "{}({}) = {}", - Paint::red("array"), - Paint::yellow(format!("{}", tokens.len())), - Paint::red('[') + "array".red(), + format!("{}", tokens.len()).yellow(), + '['.red() ); for token in tokens { out.push_str("\n ├ "); out.push_str(&format_token(token).replace('\n', "\n ")); out.push('\n'); } - out.push_str(&Paint::red(']').to_string()); + out.push_str(&']'.red().to_string()); out } DynSolValue::Tuple(tokens) => { @@ -392,18 +430,14 @@ fn format_token(token: DynSolValue) -> String { .map(|t| t.unwrap_or_default().into_owned()) .collect::>() .join(", "); - let mut out = format!( - "{}({}) = {}", - Paint::red("tuple"), - Paint::yellow(displayed_types), - Paint::red('(') - ); + let mut out = + format!("{}({}) = {}", "tuple".red(), displayed_types.yellow(), '('.red()); for token in tokens { out.push_str("\n ├ "); out.push_str(&format_token(token).replace('\n', "\n ")); out.push('\n'); } - out.push_str(&Paint::red(')').to_string()); + out.push_str(&')'.red().to_string()); out } _ => { @@ -450,8 +484,8 @@ fn format_event_definition(event_definition: &pt::EventDefinition) -> Result Result>() .join(", ") )), - Paint::cyan(event.signature()), + event.signature().cyan(), + event.selector().cyan(), )) } @@ -480,7 +515,7 @@ fn format_event_definition(event_definition: &pt::EventDefinition) -> Result fn map_special(self) -> Self { if !matches!(self, Self::Function(_, _, _) | Self::Access(_, _) | Self::Custom(_)) { return self @@ -754,7 +791,7 @@ impl Type { // Array / bytes members let ty = Self::Builtin(ty); match access.as_str() { - "length" if ty.is_dynamic() || ty.is_array() => { + "length" if ty.is_dynamic() || ty.is_array() || ty.is_fixed_bytes() => { return Self::Builtin(DynSolType::Uint(256)) } "pop" if ty.is_dynamic_array() => return ty, @@ -781,20 +818,21 @@ impl Type { match name { "block" => match access { "coinbase" => Some(DynSolType::Address), - "basefee" | "chainid" | "difficulty" | "gaslimit" | "number" | - "timestamp" => Some(DynSolType::Uint(256)), + "timestamp" | "difficulty" | "prevrandao" | "number" | "gaslimit" | + "chainid" | "basefee" | "blobbasefee" => Some(DynSolType::Uint(256)), _ => None, }, "msg" => match access { - "data" => Some(DynSolType::Bytes), "sender" => Some(DynSolType::Address), - "sig" => Some(DynSolType::FixedBytes(4)), + "gas" => Some(DynSolType::Uint(256)), "value" => Some(DynSolType::Uint(256)), + "data" => Some(DynSolType::Bytes), + "sig" => Some(DynSolType::FixedBytes(4)), _ => None, }, "tx" => match access { - "gasprice" => Some(DynSolType::Uint(256)), "origin" => Some(DynSolType::Address), + "gasprice" => Some(DynSolType::Uint(256)), _ => None, }, "abi" => match access { @@ -828,10 +866,11 @@ impl Type { "name" => Some(DynSolType::String), "creationCode" | "runtimeCode" => Some(DynSolType::Bytes), "interfaceId" => Some(DynSolType::FixedBytes(4)), - "min" | "max" => { - let arg = args.unwrap().pop().flatten().unwrap(); - Some(arg.into_builtin().unwrap()) - } + "min" | "max" => Some( + // Either a builtin or an enum + (|| args?.pop()??.into_builtin())() + .unwrap_or(DynSolType::Uint(256)), + ), _ => None, }, "string" => match access { @@ -1157,9 +1196,9 @@ impl Type { Some(DynSolType::Array(inner)) | Some(DynSolType::FixedArray(inner, _)) => { Some(*inner) } - Some(DynSolType::Bytes) | Some(DynSolType::String) => { - Some(DynSolType::FixedBytes(1)) - } + Some(DynSolType::Bytes) | + Some(DynSolType::String) | + Some(DynSolType::FixedBytes(_)) => Some(DynSolType::FixedBytes(1)), ty => ty, } } @@ -1199,6 +1238,10 @@ impl Type { fn is_dynamic_array(&self) -> bool { matches!(self, Self::Array(_) | Self::Builtin(DynSolType::Array(_))) } + + fn is_fixed_bytes(&self) -> bool { + matches!(self, Self::Builtin(DynSolType::FixedBytes(_))) + } } /// Returns Some if the custom type is a function member access @@ -1320,7 +1363,7 @@ fn unit_multiplier(unit: &Option) -> Result { } } -#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] struct Instruction { pub pc: usize, pub opcode: u8, @@ -1537,6 +1580,8 @@ mod tests { #[test] fn test_global_vars() { + init_tracing(); + // https://docs.soliditylang.org/en/latest/cheatsheet.html#global-variables let global_variables = { use DynSolType::*; @@ -1616,9 +1661,13 @@ mod tests { ("type(C).runtimeCode", Bytes), ("type(I).interfaceId", FixedBytes(4)), ("type(uint256).min", Uint(256)), + ("type(int128).min", Int(128)), ("type(int256).min", Int(256)), ("type(uint256).max", Uint(256)), + ("type(int128).max", Int(128)), ("type(int256).max", Int(256)), + ("type(Enum1).min", Uint(256)), + ("type(Enum1).max", Uint(256)), // function ("this.run.address", Address), ("this.run.selector", FixedBytes(4)), @@ -1679,14 +1728,16 @@ mod tests { s.drain_global_code(); } - let input = input.trim_end().trim_end_matches(';').to_string() + ";"; + *s = s.clone_with_new_line("enum Enum1 { A }".into()).unwrap().0; + + let input = format!("{};", input.trim_end().trim_end_matches(';')); let (mut _s, _) = s.clone_with_new_line(input).unwrap(); *s = _s.clone(); let s = &mut _s; if let Err(e) = s.parse() { for err in e { - eprintln!("{} @ {}:{}", err.message, err.loc.start(), err.loc.end()); + eprintln!("{}:{}: {}", err.loc.start(), err.loc.end(), err.message); } let source = s.to_repl_source(); panic!("could not parse input:\n{source}") @@ -1717,7 +1768,6 @@ mod tests { ty.and_then(|ty| ty.try_as_ethabi(Some(&intermediate))) } - #[track_caller] fn generic_type_test<'a, T, I>(s: &mut SessionSource, input: I) where T: AsRef + std::fmt::Display + 'a, @@ -1729,4 +1779,10 @@ mod tests { assert_eq!(ty.as_ref(), Some(expected), "\n{input}"); } } + + fn init_tracing() { + let _ = tracing_subscriber::FmtSubscriber::builder() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .try_init(); + } } diff --git a/crates/chisel/src/runner.rs b/crates/chisel/src/runner.rs index 286cc937c9948..6ffcb6770be45 100644 --- a/crates/chisel/src/runner.rs +++ b/crates/chisel/src/runner.rs @@ -3,15 +3,14 @@ //! This module contains the `ChiselRunner` struct, which assists with deploying //! and calling the REPL contract on a in-memory REVM instance. -use alloy_primitives::{Address, Bytes, U256}; -use ethers_core::types::Log; +use alloy_primitives::{Address, Bytes, Log, U256}; use eyre::Result; use foundry_evm::{ executors::{DeployResult, Executor, RawCallResult}, traces::{CallTraceArena, TraceKind}, }; use revm::interpreter::{return_ok, InstructionResult}; -use std::collections::BTreeMap; +use std::collections::HashMap; /// The function selector of the REPL contract's entrypoint, the `run()` function. static RUN_SELECTOR: [u8; 4] = [0xc0, 0x40, 0x62, 0x26]; @@ -44,13 +43,13 @@ pub struct ChiselResult { /// Amount of gas used in the transaction pub gas_used: u64, /// Map of addresses to their labels - pub labeled_addresses: BTreeMap, + pub labeled_addresses: HashMap, /// Return data pub returned: Bytes, /// Called address pub address: Option
, /// EVM State at the final instruction of the `run()` function - pub state: Option<(revm::interpreter::Stack, Vec, InstructionResult)>, + pub state: Option<(Vec, Vec, InstructionResult)>, } /// ChiselRunner implementation @@ -154,7 +153,7 @@ impl ChiselRunner { match res.exit_reason { InstructionResult::Revert | InstructionResult::OutOfGas | - InstructionResult::OutOfFund => { + InstructionResult::OutOfFunds => { lowest_gas_limit = mid_gas_limit; } _ => { @@ -168,7 +167,7 @@ impl ChiselRunner { { // update the gas gas_used = highest_gas_limit; - break + break; } last_highest_gas_limit = highest_gas_limit; } @@ -203,8 +202,7 @@ impl ChiselRunner { traces: traces .map(|traces| { // Manually adjust gas for the trace to add back the stipend/real used gas - // TODO: For chisel, we may not want to perform this adjustment. - // traces.arena[0].trace.gas_cost = gas_used; + vec![(TraceKind::Execution, traces)] }) .unwrap_or_default(), diff --git a/crates/chisel/src/session.rs b/crates/chisel/src/session.rs index f0f1debd5a705..3be34a179cc90 100644 --- a/crates/chisel/src/session.rs +++ b/crates/chisel/src/session.rs @@ -13,7 +13,7 @@ use time::{format_description, OffsetDateTime}; #[derive(Debug, Serialize, Deserialize)] pub struct ChiselSession { /// The `SessionSource` object that houses the REPL session. - pub session_source: Option, + pub session_source: SessionSource, /// The current session's identifier pub id: Option, } @@ -31,9 +31,8 @@ impl ChiselSession { /// A new instance of [ChiselSession] pub fn new(config: SessionSourceConfig) -> Result { let solc = config.solc()?; - // Return initialized ChiselSession with set solc version - Ok(Self { session_source: Some(SessionSource::new(solc, config)), id: None }) + Ok(Self { session_source: SessionSource::new(solc, config), id: None }) } /// Render the full source code for the current session. @@ -47,11 +46,7 @@ impl ChiselSession { /// This function will not panic, but will return a blank string if the /// session's [SessionSource] is None. pub fn contract_source(&self) -> String { - if let Some(source) = &self.session_source { - source.to_repl_source() - } else { - String::default() - } + self.session_source.to_repl_source() } /// Clears the cache directory diff --git a/crates/chisel/src/session_source.rs b/crates/chisel/src/session_source.rs index 4a05d2cde11ee..9deac254b0131 100644 --- a/crates/chisel/src/session_source.rs +++ b/crates/chisel/src/session_source.rs @@ -8,21 +8,24 @@ use eyre::Result; use forge_fmt::solang_ext::SafeUnwrap; use foundry_compilers::{ artifacts::{Source, Sources}, - CompilerInput, CompilerOutput, EvmVersion, Solc, + CompilerInput, CompilerOutput, Solc, }; use foundry_config::{Config, SolcReq}; use foundry_evm::{backend::Backend, opts::EvmOpts}; use semver::Version; use serde::{Deserialize, Serialize}; -use solang_parser::pt; +use solang_parser::{diagnostics::Diagnostic, pt}; use std::{collections::HashMap, fs, path::PathBuf}; use yansi::Paint; +/// The minimum Solidity version of the `Vm` interface. +pub const MIN_VM_VERSION: Version = Version::new(0, 6, 2); + /// Solidity source for the `Vm` interface in [forge-std](https://github.com/foundry-rs/forge-std) static VM_SOURCE: &str = include_str!("../../../testdata/cheats/Vm.sol"); /// Intermediate output for the compiled [SessionSource] -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct IntermediateOutput { /// All expressions within the REPL contract's run function and top level scope. #[serde(skip)] @@ -34,7 +37,7 @@ pub struct IntermediateOutput { /// A refined intermediate parse tree for a contract that enables easy lookups /// of definitions. -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct IntermediateContract { /// All function definitions within the contract #[serde(skip)] @@ -54,7 +57,7 @@ pub struct IntermediateContract { type IntermediateContracts = HashMap; /// Full compilation output for the [SessionSource] -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GeneratedOutput { /// The [IntermediateOutput] component pub intermediate: IntermediateOutput, @@ -63,12 +66,14 @@ pub struct GeneratedOutput { } /// Configuration for the [SessionSource] -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct SessionSourceConfig { /// Foundry configuration pub foundry_config: Config, /// EVM Options pub evm_opts: EvmOpts, + /// Disable the default `Vm` import. + pub no_vm: bool, #[serde(skip)] /// In-memory REVM db for the session's runner. pub backend: Option, @@ -92,7 +97,7 @@ impl SessionSourceConfig { SolcReq::Version(version.into()) } else { if !self.foundry_config.offline { - print!("{}", Paint::green("No solidity versions installed! ")); + print!("{}", "No solidity versions installed! ".green()); } // use default SolcReq::Version("0.8.19".parse().unwrap()) @@ -100,33 +105,25 @@ impl SessionSourceConfig { match solc_req { SolcReq::Version(version) => { - // We now need to verify if the solc version provided is supported by the evm - // version set. If not, we bail and ask the user to provide a newer version. - // 1. Do we need solc 0.8.18 or higher? - let evm_version = self.foundry_config.evm_version; - let needs_post_merge_solc = evm_version >= EvmVersion::Paris; - // 2. Check if the version provided is less than 0.8.18 and bail, - // or leave it as-is if we don't need a post merge solc version or the version we - // have is good enough. - let v = if needs_post_merge_solc && version < Version::new(0, 8, 18) { - eyre::bail!("solc {version} is not supported by the set evm version: {evm_version}. Please install and use a version of solc higher or equal to 0.8.18. -You can also set the solc version in your foundry.toml.") - } else { - version.to_string() - }; + // Validate that the requested evm version is supported by the solc version + let req_evm_version = self.foundry_config.evm_version; + if let Some(compat_evm_version) = req_evm_version.normalize_version(&version) { + if req_evm_version > compat_evm_version { + eyre::bail!( + "The set evm version, {req_evm_version}, is not supported by solc {version}. Upgrade to a newer solc version." + ); + } + } - let mut solc = Solc::find_svm_installed_version(&v)?; + let mut solc = Solc::find_svm_installed_version(version.to_string())?; if solc.is_none() { if self.foundry_config.offline { eyre::bail!("can't install missing solc {version} in offline mode") } - println!( - "{}", - Paint::green(format!("Installing solidity version {version}...")) - ); + println!("{}", format!("Installing solidity version {version}...").green()); Solc::blocking_install(&version)?; - solc = Solc::find_svm_installed_version(&v)?; + solc = Solc::find_svm_installed_version(version.to_string())?; } solc.ok_or_else(|| eyre::eyre!("Failed to install {version}")) } @@ -143,7 +140,7 @@ You can also set the solc version in your foundry.toml.") /// REPL Session Source wrapper /// /// Heavily based on soli's [`ConstructedSource`](https://github.com/jpopesculian/soli/blob/master/src/main.rs#L166) -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct SessionSource { /// The file name pub file_name: PathBuf, @@ -184,9 +181,13 @@ impl SessionSource { /// /// A new instance of [SessionSource] #[track_caller] - pub fn new(solc: Solc, config: SessionSourceConfig) -> Self { - #[cfg(debug_assertions)] - let _ = solc.version().unwrap(); + pub fn new(solc: Solc, mut config: SessionSourceConfig) -> Self { + if let Ok(v) = solc.version_short() { + if v < MIN_VM_VERSION && !config.no_vm { + tracing::info!(version=%v, minimum=%MIN_VM_VERSION, "Disabling VM injection"); + config.no_vm = true; + } + } Self { file_name: PathBuf::from("ReplContract.sol".to_string()), @@ -285,21 +286,21 @@ impl SessionSource { /// Clears global code from the source pub fn drain_global_code(&mut self) -> &mut Self { - self.global_code.clear(); + String::clear(&mut self.global_code); self.generated_output = None; self } /// Clears top-level code from the source pub fn drain_top_level_code(&mut self) -> &mut Self { - self.top_level_code.clear(); + String::clear(&mut self.top_level_code); self.generated_output = None; self } /// Clears the "run()" function's code pub fn drain_run(&mut self) -> &mut Self { - self.run_code.clear(); + String::clear(&mut self.run_code); self.generated_output = None; self } @@ -314,15 +315,11 @@ impl SessionSource { let mut sources = Sources::new(); sources.insert(self.file_name.clone(), Source::new(self.to_repl_source())); + let remappings = self.config.foundry_config.get_all_remappings().collect::>(); + // Include Vm.sol if forge-std remapping is not available - if !self - .config - .foundry_config - .get_all_remappings() - .into_iter() - .any(|r| r.name.starts_with("forge-std")) - { - sources.insert(PathBuf::from("forge-std/Vm.sol"), Source::new(VM_SOURCE.to_owned())); + if !self.config.no_vm && !remappings.iter().any(|r| r.name.starts_with("forge-std")) { + sources.insert(PathBuf::from("forge-std/Vm.sol"), Source::new(VM_SOURCE)); } // we only care about the solidity source, so we can safely unwrap @@ -332,7 +329,7 @@ impl SessionSource { .expect("Solidity source not found"); // get all remappings from the config - compiler_input.settings.remappings = self.config.foundry_config.get_all_remappings(); + compiler_input.settings.remappings = remappings; // We also need to enforce the EVM version that the user has specified. compiler_input.settings.evm_version = Some(self.config.foundry_config.evm_version); @@ -446,24 +443,27 @@ impl SessionSource { /// The [SessionSource] represented as a Forge Script contract. pub fn to_script_source(&self) -> String { let Version { major, minor, patch, .. } = self.solc.version().unwrap(); + let Self { contract_name, global_code, top_level_code, run_code, config, .. } = self; + + let script_import = + if !config.no_vm { "import {Script} from \"forge-std/Script.sol\";\n" } else { "" }; + format!( r#" // SPDX-License-Identifier: UNLICENSED pragma solidity ^{major}.{minor}.{patch}; -import {{Script}} from "forge-std/Script.sol"; -{} +{script_import} +{global_code} -contract {} is Script {{ - {} - +contract {contract_name} is Script {{ + {top_level_code} + /// @notice Script entry point function run() public {{ - {} + {run_code} }} -}} - "#, - self.global_code, self.contract_name, self.top_level_code, self.run_code, +}}"#, ) } @@ -474,25 +474,34 @@ contract {} is Script {{ /// The [SessionSource] represented as a REPL contract. pub fn to_repl_source(&self) -> String { let Version { major, minor, patch, .. } = self.solc.version().unwrap(); + let Self { contract_name, global_code, top_level_code, run_code, config, .. } = self; + + let (vm_import, vm_constant) = if !config.no_vm { + ( + "import {Vm} from \"forge-std/Vm.sol\";\n", + "Vm internal constant vm = Vm(address(uint160(uint256(keccak256(\"hevm cheat code\")))));\n" + ) + } else { + ("", "") + }; + format!( r#" // SPDX-License-Identifier: UNLICENSED pragma solidity ^{major}.{minor}.{patch}; -import {{Vm}} from "forge-std/Vm.sol"; -{} +{vm_import} +{global_code} -contract {} {{ - Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); - {} +contract {contract_name} {{ + {vm_constant} + {top_level_code} /// @notice REPL contract entry point function run() public {{ - {} + {run_code} }} -}} - "#, - self.global_code, self.contract_name, self.top_level_code, self.run_code, +}}"#, ) } @@ -646,15 +655,28 @@ pub fn parse_fragment( ) -> Option { let mut base = SessionSource::new(solc, config); - if base.clone().with_run_code(buffer).parse().is_ok() { - return Some(ParseTreeFragment::Function) + match base.clone().with_run_code(buffer).parse() { + Ok(_) => return Some(ParseTreeFragment::Function), + Err(e) => debug_errors(&e), } - if base.clone().with_top_level_code(buffer).parse().is_ok() { - return Some(ParseTreeFragment::Contract) + match base.clone().with_top_level_code(buffer).parse() { + Ok(_) => return Some(ParseTreeFragment::Contract), + Err(e) => debug_errors(&e), } - if base.with_global_code(buffer).parse().is_ok() { - return Some(ParseTreeFragment::Source) + match base.with_global_code(buffer).parse() { + Ok(_) => return Some(ParseTreeFragment::Source), + Err(e) => debug_errors(&e), } None } + +fn debug_errors(errors: &[Diagnostic]) { + if !tracing::enabled!(tracing::Level::DEBUG) { + return; + } + + for error in errors { + tracing::debug!("error: {}", error.message); + } +} diff --git a/crates/chisel/src/solidity_helper.rs b/crates/chisel/src/solidity_helper.rs index 42641a1f0a8d2..f09ad168356e2 100644 --- a/crates/chisel/src/solidity_helper.rs +++ b/crates/chisel/src/solidity_helper.rs @@ -19,7 +19,7 @@ use solang_parser::{ pt, }; use std::{borrow::Cow, str::FromStr}; -use yansi::{Color, Paint, Style}; +use yansi::{Color, Style}; /// The default pre-allocation for solang parsed comments const DEFAULT_COMMENTS: usize = 5; @@ -71,7 +71,7 @@ impl SolidityHelper { pt::Comment::DocLine(loc, _) | pt::Comment::DocBlock(loc, _) => loc, }; - (loc.start(), Style::default().dimmed(), loc.end()) + (loc.start(), Style::new().dim(), loc.end()) }); out.extend(comments_iter); @@ -102,7 +102,7 @@ impl SolidityHelper { /// Highlights a solidity source string pub fn highlight(input: &str) -> Cow { - if !Paint::is_enabled() { + if !yansi::is_enabled() { return Cow::Borrowed(input) } @@ -119,7 +119,7 @@ impl SolidityHelper { // cmd out.push(COMMAND_LEADER); let cmd_res = ChiselCommand::from_str(cmd); - let style = Style::new(if cmd_res.is_ok() { Color::Green } else { Color::Red }); + let style = (if cmd_res.is_ok() { Color::Green } else { Color::Red }).foreground(); Self::paint_unchecked(cmd, style, &mut out); // rest @@ -186,7 +186,7 @@ impl SolidityHelper { } /// Formats `input` with `style` into `out`, without checking `style.wrapping` or - /// `Paint::is_enabled` + /// `yansi::is_enabled` #[inline] fn paint_unchecked(string: &str, style: Style, out: &mut String) { if style == Style::default() { @@ -220,7 +220,7 @@ impl Highlighter for SolidityHelper { prompt: &'p str, _default: bool, ) -> Cow<'b, str> { - if !Paint::is_enabled() { + if !yansi::is_enabled() { return Cow::Borrowed(prompt) } @@ -231,12 +231,16 @@ impl Highlighter for SolidityHelper { let id_end = prompt.find(')').unwrap(); let id_span = 5..id_end; let id = &prompt[id_span.clone()]; - out.replace_range(id_span, &Self::paint_unchecked_owned(id, Color::Yellow.style())); - out.replace_range(1..=2, &Self::paint_unchecked_owned("ID", Color::Cyan.style())); + out.replace_range( + id_span, + &Self::paint_unchecked_owned(id, Color::Yellow.foreground()), + ); + out.replace_range(1..=2, &Self::paint_unchecked_owned("ID", Color::Cyan.foreground())); } if let Some(i) = out.find(PROMPT_ARROW) { - let style = if self.errored { Color::Red.style() } else { Color::Green.style() }; + let style = + if self.errored { Color::Red.foreground() } else { Color::Green.foreground() }; let mut arrow = String::with_capacity(MAX_ANSI_LEN + 4); @@ -278,7 +282,7 @@ impl<'a> TokenStyle for Token<'a> { fn style(&self) -> Style { use Token::*; match self { - StringLiteral(_, _) => Color::Green.style(), + StringLiteral(_, _) => Color::Green.foreground(), AddressLiteral(_) | HexLiteral(_) | @@ -286,21 +290,21 @@ impl<'a> TokenStyle for Token<'a> { RationalNumber(_, _, _) | HexNumber(_) | True | - False => Color::Yellow.style(), + False => Color::Yellow.foreground(), Memory | Storage | Calldata | Public | Private | Internal | External | Constant | Pure | View | Payable | Anonymous | Indexed | Abstract | Virtual | Override | - Modifier | Immutable | Unchecked => Color::Cyan.style(), + Modifier | Immutable | Unchecked => Color::Cyan.foreground(), Contract | Library | Interface | Function | Pragma | Import | Struct | Event | Enum | Type | Constructor | As | Is | Using | New | Delete | Do | Continue | Break | Throw | Emit | Return | Returns | Revert | For | While | If | Else | Try | Catch | Assembly | Let | Leave | Switch | Case | Default | YulArrow | Arrow => { - Color::Magenta.style() + Color::Magenta.foreground() } Uint(_) | Int(_) | Bytes(_) | Byte | DynamicBytes | Bool | Address | String | - Mapping => Color::Blue.style(), + Mapping => Color::Blue.foreground(), Identifier(_) => Style::default(), diff --git a/crates/chisel/tests/cache.rs b/crates/chisel/tests/cache.rs index dda53cd62382c..c6b9625ebd7c3 100644 --- a/crates/chisel/tests/cache.rs +++ b/crates/chisel/tests/cache.rs @@ -1,7 +1,6 @@ use chisel::session::ChiselSession; use foundry_compilers::EvmVersion; -use foundry_config::Config; -use foundry_evm::opts::EvmOpts; +use foundry_config::{Config, SolcReq}; use serial_test::serial; use std::path::Path; @@ -43,10 +42,7 @@ fn test_write_session() { // Create a new session let mut env = ChiselSession::new(chisel::session_source::SessionSourceConfig { foundry_config, - evm_opts: EvmOpts::default(), - backend: None, - traces: false, - calldata: None, + ..Default::default() }) .unwrap_or_else(|e| panic!("Failed to create ChiselSession!, {}", e)); @@ -163,10 +159,7 @@ fn test_load_cache() { assert!(new_env.is_ok()); let new_env = new_env.unwrap(); assert_eq!(new_env.id.unwrap(), String::from("0")); - assert_eq!( - new_env.session_source.unwrap().to_repl_source(), - env.session_source.unwrap().to_repl_source() - ); + assert_eq!(new_env.session_source.to_repl_source(), env.session_source.to_repl_source()); } #[test] @@ -225,8 +218,29 @@ fn test_load_latest_cache() { // Validate the session assert_eq!(new_env.id.unwrap(), "1"); - assert_eq!( - new_env.session_source.unwrap().to_repl_source(), - env.session_source.unwrap().to_repl_source() - ); + assert_eq!(new_env.session_source.to_repl_source(), env.session_source.to_repl_source()); +} + +#[test] +#[serial] +fn test_solc_evm_configuration_mismatch() { + // Create and clear the cache directory + ChiselSession::create_cache_dir().unwrap(); + ChiselSession::clear_cache().unwrap(); + + // Force the solc version to be 0.8.13 which does not support Paris + let foundry_config = Config { + evm_version: EvmVersion::Paris, + solc: Some(SolcReq::Version("0.8.13".parse().expect("invalid semver"))), + ..Default::default() + }; + + // Create a new session that is expected to fail + let error = ChiselSession::new(chisel::session_source::SessionSourceConfig { + foundry_config, + ..Default::default() + }) + .unwrap_err(); + + assert_eq!(error.to_string(), "The set evm version, paris, is not supported by solc 0.8.13. Upgrade to a newer solc version."); } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 60b80fc799110..dd247ac4802f3 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -15,45 +15,39 @@ foundry-common.workspace = true foundry-config.workspace = true foundry-debugger.workspace = true foundry-evm.workspace = true +foundry-wallets.workspace = true foundry-compilers = { workspace = true, features = ["full"] } alloy-dyn-abi.workspace = true alloy-json-abi.workspace = true alloy-primitives.workspace = true +alloy-provider.workspace = true +alloy-transport.workspace = true +alloy-chains.workspace = true -ethers-core.workspace = true -ethers-providers.workspace = true -ethers-signers = { workspace = true, features = ["aws", "ledger", "trezor"] } - -rusoto_core = { version = "0.48", default-features = false } -rusoto_kms = { version = "0.48", default-features = false } - -async-trait = "0.1" clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } color-eyre.workspace = true dotenvy = "0.15" eyre.workspace = true -hex = { workspace = true, features = ["serde"] } indicatif = "0.17" -itertools.workspace = true once_cell = "1" regex = { version = "1", default-features = false } -rpassword = "7" serde.workspace = true strsim = "0.10" -strum = { version = "0.25", features = ["derive"] } -thiserror = "1" +strum = { workspace = true, features = ["derive"] } tokio = { version = "1", features = ["macros"] } tracing-error = "0.2" tracing-subscriber = { workspace = true, features = ["registry", "env-filter", "fmt"] } tracing.workspace = true -yansi = "0.5" +yansi.workspace = true +hex.workspace = true +futures = "0.3" [dev-dependencies] -tempfile = "3.7" +tempfile.workspace = true [features] default = ["rustls"] -rustls = ["ethers-providers/rustls", "rusoto_core/rustls"] -openssl = ["ethers-providers/openssl", "foundry-compilers/openssl"] +rustls = ["foundry-wallets/rustls"] +openssl = ["foundry-compilers/openssl"] diff --git a/crates/cli/src/handler.rs b/crates/cli/src/handler.rs index f4778c43960d0..d6e34f3b8e1ae 100644 --- a/crates/cli/src/handler.rs +++ b/crates/cli/src/handler.rs @@ -1,4 +1,4 @@ -use eyre::{EyreHandler, Result}; +use eyre::EyreHandler; use std::error::Error; use yansi::Paint; @@ -16,7 +16,7 @@ impl EyreHandler for Handler { return core::fmt::Debug::fmt(error, f) } writeln!(f)?; - write!(f, "{}", Paint::red(error))?; + write!(f, "{}", error.red())?; if let Some(cause) = error.source() { write!(f, "\n\nContext:")?; @@ -47,9 +47,13 @@ impl EyreHandler for Handler { /// verbose debug-centric handler is installed. /// /// Panics are always caught by the more debug-centric handler. -pub fn install() -> Result<()> { - let debug_enabled = std::env::var("FOUNDRY_DEBUG").is_ok(); +pub fn install() { + // If the user has not explicitly overridden "RUST_BACKTRACE", then produce full backtraces. + if std::env::var_os("RUST_BACKTRACE").is_none() { + std::env::set_var("RUST_BACKTRACE", "full"); + } + let debug_enabled = std::env::var("FOUNDRY_DEBUG").is_ok(); if debug_enabled { if let Err(e) = color_eyre::install() { debug!("failed to install color eyre error hook: {e}"); @@ -65,6 +69,4 @@ pub fn install() -> Result<()> { debug!("failed to install eyre error hook: {e}"); } } - - Ok(()) } diff --git a/crates/cli/src/opts/build/core.rs b/crates/cli/src/opts/build/core.rs index aec937c780e6f..99caaea354542 100644 --- a/crates/cli/src/opts/build/core.rs +++ b/crates/cli/src/opts/build/core.rs @@ -18,60 +18,67 @@ use foundry_config::{ use serde::Serialize; use std::path::PathBuf; -#[derive(Debug, Clone, Parser, Serialize, Default)] -#[clap(next_help_heading = "Build options")] +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Build options")] pub struct CoreBuildArgs { /// Clear the cache and artifacts folder and recompile. - #[clap(long, help_heading = "Cache options")] + #[arg(long, help_heading = "Cache options")] #[serde(skip)] pub force: bool, /// Disable the cache. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub no_cache: bool, /// Set pre-linked libraries. - #[clap(long, help_heading = "Linker options", env = "DAPP_LIBRARIES")] + #[arg(long, help_heading = "Linker options", env = "DAPP_LIBRARIES")] #[serde(skip_serializing_if = "Vec::is_empty")] pub libraries: Vec, /// Ignore solc warnings by error code. - #[clap(long, help_heading = "Compiler options", value_name = "ERROR_CODES")] + #[arg(long, help_heading = "Compiler options", value_name = "ERROR_CODES")] #[serde(skip_serializing_if = "Vec::is_empty")] pub ignored_error_codes: Vec, /// Warnings will trigger a compiler error - #[clap(long, help_heading = "Compiler options")] + #[arg(long, help_heading = "Compiler options")] #[serde(skip)] pub deny_warnings: bool, /// Do not auto-detect the `solc` version. - #[clap(long, help_heading = "Compiler options")] + #[arg(long, help_heading = "Compiler options")] #[serde(skip)] pub no_auto_detect: bool, /// Specify the solc version, or a path to a local solc, to build with. /// /// Valid values are in the format `x.y.z`, `solc:x.y.z` or `path/to/solc`. - #[clap(long = "use", help_heading = "Compiler options", value_name = "SOLC_VERSION")] + #[arg(long = "use", help_heading = "Compiler options", value_name = "SOLC_VERSION")] #[serde(skip)] pub use_solc: Option, /// Do not access the network. /// /// Missing solc versions will not be installed. - #[clap(help_heading = "Compiler options", long)] + #[arg(help_heading = "Compiler options", long)] #[serde(skip)] pub offline: bool, /// Use the Yul intermediate representation compilation pipeline. - #[clap(long, help_heading = "Compiler options")] + #[arg(long, help_heading = "Compiler options")] #[serde(skip)] pub via_ir: bool, + /// Do not append any metadata to the bytecode. + /// + /// This is equivalent to setting `bytecode_hash` to `none` and `cbor_metadata` to `false`. + #[arg(long, help_heading = "Compiler options")] + #[serde(skip)] + pub no_metadata: bool, + /// The path to the contract artifacts folder. - #[clap( + #[arg( long = "out", short, help_heading = "Project options", @@ -85,22 +92,22 @@ pub struct CoreBuildArgs { /// /// Possible values are "default", "strip" (remove), /// "debug" (Solidity-generated revert strings) and "verboseDebug" - #[clap(long, help_heading = "Project options", value_name = "REVERT")] + #[arg(long, help_heading = "Project options", value_name = "REVERT")] #[serde(skip)] pub revert_strings: Option, /// Don't print anything on startup. - #[clap(long, help_heading = "Compiler options")] + #[arg(long, help_heading = "Compiler options")] #[serde(skip)] pub silent: bool, /// Generate build info files. - #[clap(long, help_heading = "Project options")] + #[arg(long, help_heading = "Project options")] #[serde(skip)] pub build_info: bool, /// Output path to directory that build info files will be written to. - #[clap( + #[arg( long, help_heading = "Project options", value_hint = ValueHint::DirPath, @@ -110,11 +117,11 @@ pub struct CoreBuildArgs { #[serde(skip_serializing_if = "Option::is_none")] pub build_info_path: Option, - #[clap(flatten)] + #[command(flatten)] #[serde(flatten)] pub compiler: CompilerArgs, - #[clap(flatten)] + #[command(flatten)] #[serde(flatten)] pub project_paths: ProjectPathsArgs, } @@ -204,9 +211,15 @@ impl Provider for CoreBuildArgs { dict.insert("via_ir".to_string(), true.into()); } + if self.no_metadata { + dict.insert("bytecode_hash".to_string(), "none".into()); + dict.insert("cbor_metadata".to_string(), false.into()); + } + if self.force { dict.insert("force".to_string(), self.force.into()); } + // we need to ensure no_cache set accordingly if self.no_cache { dict.insert("cache".to_string(), false.into()); @@ -216,6 +229,10 @@ impl Provider for CoreBuildArgs { dict.insert("build_info".to_string(), self.build_info.into()); } + if self.compiler.ast { + dict.insert("ast".to_string(), true.into()); + } + if self.compiler.optimize { dict.insert("optimizer".to_string(), self.compiler.optimize.into()); } diff --git a/crates/cli/src/opts/build/mod.rs b/crates/cli/src/opts/build/mod.rs index aca7fba75a1c0..e752ae53fec68 100644 --- a/crates/cli/src/opts/build/mod.rs +++ b/crates/cli/src/opts/build/mod.rs @@ -12,21 +12,26 @@ pub use self::paths::ProjectPathsArgs; // to be merged into an existing `foundry_config::Config`. // // See also `BuildArgs`. -#[derive(Default, Debug, Clone, Parser, Serialize)] -#[clap(next_help_heading = "Compiler options")] +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Compiler options")] pub struct CompilerArgs { + /// Includes the AST as JSON in the compiler output. + #[arg(long, help_heading = "Compiler options")] + #[serde(skip)] + pub ast: bool, + /// The target EVM version. - #[clap(long, value_name = "VERSION")] + #[arg(long, value_name = "VERSION")] #[serde(skip_serializing_if = "Option::is_none")] pub evm_version: Option, /// Activate the Solidity optimizer. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub optimize: bool, /// The number of optimizer runs. - #[clap(long, value_name = "RUNS")] + #[arg(long, value_name = "RUNS")] #[serde(skip_serializing_if = "Option::is_none")] pub optimizer_runs: Option, @@ -35,14 +40,14 @@ pub struct CompilerArgs { /// Example keys: evm.assembly, ewasm, ir, irOptimized, metadata /// /// For a full description, see https://docs.soliditylang.org/en/v0.8.13/using-the-compiler.html#input-description - #[clap(long, num_args(1..), value_name = "SELECTOR")] + #[arg(long, num_args(1..), value_name = "SELECTOR")] #[serde(skip_serializing_if = "Vec::is_empty")] pub extra_output: Vec, /// Extra output to write to separate files. /// /// Valid values: metadata, ir, irOptimized, ewasm, evm.assembly - #[clap(long, num_args(1..), value_name = "SELECTOR")] + #[arg(long, num_args(1..), value_name = "SELECTOR")] #[serde(skip_serializing_if = "Vec::is_empty")] pub extra_output_files: Vec, } diff --git a/crates/cli/src/opts/build/paths.rs b/crates/cli/src/opts/build/paths.rs index 4989bf7d911c7..9497427518f34 100644 --- a/crates/cli/src/opts/build/paths.rs +++ b/crates/cli/src/opts/build/paths.rs @@ -14,51 +14,51 @@ use serde::Serialize; use std::path::PathBuf; /// Common arguments for a project's paths. -#[derive(Debug, Clone, Parser, Serialize, Default)] -#[clap(next_help_heading = "Project options")] +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Project options")] pub struct ProjectPathsArgs { /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] #[serde(skip)] pub root: Option, /// The contracts source directory. - #[clap(long, short = 'C', value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, short = 'C', value_hint = ValueHint::DirPath, value_name = "PATH")] #[serde(rename = "src", skip_serializing_if = "Option::is_none")] pub contracts: Option, /// The project's remappings. - #[clap(long, short = 'R')] + #[arg(long, short = 'R')] #[serde(skip)] pub remappings: Vec, /// The project's remappings from the environment. - #[clap(long, value_name = "ENV")] + #[arg(long, value_name = "ENV")] #[serde(skip)] pub remappings_env: Option, /// The path to the compiler cache. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] #[serde(skip_serializing_if = "Option::is_none")] pub cache_path: Option, /// The path to the library folder. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] #[serde(rename = "libs", skip_serializing_if = "Vec::is_empty")] pub lib_paths: Vec, /// Use the Hardhat-style project layout. /// /// This is the same as using: `--contracts contracts --lib-paths node_modules`. - #[clap(long, conflicts_with = "contracts", visible_alias = "hh")] + #[arg(long, conflicts_with = "contracts", visible_alias = "hh")] #[serde(skip)] pub hardhat: bool, /// Path to the config file. - #[clap(long, value_hint = ValueHint::FilePath, value_name = "FILE")] + #[arg(long, value_hint = ValueHint::FilePath, value_name = "FILE")] #[serde(skip)] pub config_path: Option, } @@ -67,8 +67,14 @@ impl ProjectPathsArgs { /// Returns the root directory to use for configuring the [Project] /// /// This will be the `--root` argument if provided, otherwise see [find_project_root_path()] + /// + /// # Panics + /// + /// If the project root directory cannot be found: [find_project_root_path()] pub fn project_root(&self) -> PathBuf { - self.root.clone().unwrap_or_else(|| find_project_root_path(None).unwrap()) + self.root + .clone() + .unwrap_or_else(|| find_project_root_path(None).expect("Failed to find project root")) } /// Returns the remappings to add to the config diff --git a/crates/cli/src/opts/dependency.rs b/crates/cli/src/opts/dependency.rs index a4478f358d7ee..5fa1d851f3cfc 100644 --- a/crates/cli/src/opts/dependency.rs +++ b/crates/cli/src/opts/dependency.rs @@ -35,7 +35,7 @@ const COMMON_ORG_ALIASES: &[(&str, &str); 2] = /// /// Non Github URLs must be provided with an https:// prefix. /// Adding dependencies as local paths is not supported yet. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Dependency { /// The name of the dependency pub name: String, diff --git a/crates/cli/src/opts/ethereum.rs b/crates/cli/src/opts/ethereum.rs index 17319952b01a5..272d3a5a2aede 100644 --- a/crates/cli/src/opts/ethereum.rs +++ b/crates/cli/src/opts/ethereum.rs @@ -1,27 +1,30 @@ -use super::{ChainValueParser, Wallet, WalletSigner}; +use crate::opts::ChainValueParser; use clap::Parser; use eyre::Result; use foundry_config::{ figment::{ self, - value::{Dict, Map, Value}, + value::{Dict, Map}, Metadata, Profile, }, impl_figment_convert_cast, Chain, Config, }; +use foundry_wallets::WalletOpts; use serde::Serialize; use std::borrow::Cow; -const FLASHBOTS_URL: &str = "https://rpc.flashbots.net"; +const FLASHBOTS_URL: &str = "https://rpc.flashbots.net/fast"; #[derive(Clone, Debug, Default, Parser)] pub struct RpcOpts { /// The RPC endpoint. - #[clap(short = 'r', long = "rpc-url", env = "ETH_RPC_URL")] + #[arg(short = 'r', long = "rpc-url", env = "ETH_RPC_URL")] pub url: Option, - /// Use the Flashbots RPC URL (https://rpc.flashbots.net). - #[clap(long)] + /// Use the Flashbots RPC URL with fast mode (https://rpc.flashbots.net/fast). + /// This shares the transaction privately with all registered builders. + /// https://docs.flashbots.net/flashbots-protect/quick-start#faster-transactions + #[arg(long)] pub flashbots: bool, /// JWT Secret for the RPC endpoint. @@ -33,7 +36,7 @@ pub struct RpcOpts { /// '["0x6bb38c26db65749ab6e472080a3d20a2f35776494e72016d1e339593f21c59bc", /// "0x6bb38c26db65749ab6e472080a3d20a2f35776494e72016d1e339593f21c59bc", /// "0x6bb38c26db65749ab6e472080a3d20a2f35776494e72016d1e339593f21c59bc"]' - #[clap(long, env = "ETH_RPC_JWT_SECRET")] + #[arg(long, env = "ETH_RPC_JWT_SECRET")] pub jwt_secret: Option, } @@ -83,15 +86,15 @@ impl RpcOpts { } } -#[derive(Clone, Debug, Default, Parser, Serialize)] +#[derive(Clone, Debug, Default, Serialize, Parser)] pub struct EtherscanOpts { /// The Etherscan (or equivalent) API key. - #[clap(short = 'e', long = "etherscan-api-key", alias = "api-key", env = "ETHERSCAN_API_KEY")] + #[arg(short = 'e', long = "etherscan-api-key", alias = "api-key", env = "ETHERSCAN_API_KEY")] #[serde(rename = "etherscan_api_key", skip_serializing_if = "Option::is_none")] pub key: Option, /// The chain name or EIP-155 chain ID. - #[clap( + #[arg( short, long, alias = "chain-id", @@ -115,40 +118,43 @@ impl figment::Provider for EtherscanOpts { } impl EtherscanOpts { - pub fn key<'a>(&'a self, config: Option<&'a Config>) -> Option> { - match (self.key.as_deref(), config) { - (Some(key), _) => Some(Cow::Borrowed(key)), - (None, Some(config)) => config.get_etherscan_api_key(self.chain).map(Cow::Owned), - (None, None) => None, - } + /// Returns true if the Etherscan API key is set. + pub fn has_key(&self) -> bool { + self.key.as_ref().filter(|key| !key.trim().is_empty()).is_some() + } + + /// Returns the Etherscan API key. + pub fn key(&self) -> Option { + self.key.as_ref().filter(|key| !key.trim().is_empty()).cloned() } pub fn dict(&self) -> Dict { - Value::serialize(self).unwrap().into_dict().unwrap() + let mut dict = Dict::new(); + if let Some(key) = self.key() { + dict.insert("etherscan_api_key".into(), key.into()); + } + if let Some(chain) = self.chain { + dict.insert("chain_id".into(), chain.to_string().into()); + } + dict } } #[derive(Clone, Debug, Default, Parser)] -#[clap(next_help_heading = "Ethereum options")] +#[command(next_help_heading = "Ethereum options")] pub struct EthereumOpts { - #[clap(flatten)] + #[command(flatten)] pub rpc: RpcOpts, - #[clap(flatten)] + #[command(flatten)] pub etherscan: EtherscanOpts, - #[clap(flatten)] - pub wallet: Wallet, + #[command(flatten)] + pub wallet: WalletOpts, } impl_figment_convert_cast!(EthereumOpts); -impl EthereumOpts { - pub async fn signer(&self) -> Result { - self.wallet.signer(self.etherscan.chain.unwrap_or_default().id()).await - } -} - // Make this args a `Figment` so that it can be merged into the `Config` impl figment::Provider for EthereumOpts { fn metadata(&self) -> Metadata { @@ -166,3 +172,19 @@ impl figment::Provider for EthereumOpts { Ok(Map::from([(Config::selected_profile(), dict)])) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_etherscan_opts() { + let args: EtherscanOpts = + EtherscanOpts::parse_from(["foundry-cli", "--etherscan-api-key", "dummykey"]); + assert_eq!(args.key(), Some("dummykey".to_string())); + + let args: EtherscanOpts = + EtherscanOpts::parse_from(["foundry-cli", "--etherscan-api-key", ""]); + assert!(!args.has_key()); + } +} diff --git a/crates/cli/src/opts/mod.rs b/crates/cli/src/opts/mod.rs index 76480f400ed84..7825cba3c8b36 100644 --- a/crates/cli/src/opts/mod.rs +++ b/crates/cli/src/opts/mod.rs @@ -3,11 +3,9 @@ mod chain; mod dependency; mod ethereum; mod transaction; -mod wallet; pub use build::*; pub use chain::*; pub use dependency::*; pub use ethereum::*; pub use transaction::*; -pub use wallet::*; diff --git a/crates/cli/src/opts/transaction.rs b/crates/cli/src/opts/transaction.rs index ec92ce3ef457b..81d6107bdea03 100644 --- a/crates/cli/src/opts/transaction.rs +++ b/crates/cli/src/opts/transaction.rs @@ -1,17 +1,17 @@ use crate::utils::parse_ether_value; -use alloy_primitives::U256; +use alloy_primitives::{U256, U64}; use clap::Parser; use serde::Serialize; -#[derive(Parser, Debug, Clone, Serialize)] -#[clap(next_help_heading = "Transaction options")] +#[derive(Clone, Debug, Serialize, Parser)] +#[command(next_help_heading = "Transaction options")] pub struct TransactionOpts { /// Gas limit for the transaction. - #[clap(long, env = "ETH_GAS_LIMIT")] + #[arg(long, env = "ETH_GAS_LIMIT")] pub gas_limit: Option, /// Gas price for legacy transactions, or max fee per gas for EIP1559 transactions. - #[clap( + #[arg( long, env = "ETH_GAS_PRICE", value_parser = parse_ether_value, @@ -20,7 +20,7 @@ pub struct TransactionOpts { pub gas_price: Option, /// Max priority fee per gas for EIP1559 transactions. - #[clap( + #[arg( long, env = "ETH_PRIORITY_GAS_PRICE", value_parser = parse_ether_value, @@ -33,17 +33,17 @@ pub struct TransactionOpts { /// /// /// Examples: 1ether, 10gwei, 0.01ether - #[clap(long, value_parser = parse_ether_value)] + #[arg(long, value_parser = parse_ether_value)] pub value: Option, /// Nonce for the transaction. - #[clap(long)] - pub nonce: Option, + #[arg(long)] + pub nonce: Option, /// Send a legacy transaction instead of an EIP1559 transaction. /// /// This is automatically enabled for common networks without EIP1559. - #[clap(long)] + #[arg(long)] pub legacy: bool, } diff --git a/crates/cli/src/opts/wallet/error.rs b/crates/cli/src/opts/wallet/error.rs deleted file mode 100644 index e8ad90a891de3..0000000000000 --- a/crates/cli/src/opts/wallet/error.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Errors when working with wallets - -use hex::FromHexError; - -#[derive(Debug, thiserror::Error)] -pub enum PrivateKeyError { - #[error("Failed to create wallet from private key. Private key is invalid hex: {0}")] - InvalidHex(#[from] FromHexError), - #[error("Failed to create wallet from private key. Invalid private key. But env var {0} exists. Is the `$` anchor missing?")] - ExistsAsEnvVar(String), -} diff --git a/crates/cli/src/opts/wallet/mod.rs b/crates/cli/src/opts/wallet/mod.rs deleted file mode 100644 index e7b0db68e02d0..0000000000000 --- a/crates/cli/src/opts/wallet/mod.rs +++ /dev/null @@ -1,616 +0,0 @@ -use crate::opts::error::PrivateKeyError; -use alloy_primitives::Address; -use async_trait::async_trait; -use clap::Parser; -use ethers_core::types::{ - transaction::{eip2718::TypedTransaction, eip712::Eip712}, - Signature, -}; -use ethers_signers::{ - coins_bip39::English, AwsSigner, AwsSignerError, HDPath as LedgerHDPath, Ledger, LedgerError, - LocalWallet, MnemonicBuilder, Signer, Trezor, TrezorError, TrezorHDPath, WalletError, -}; -use eyre::{bail, Result, WrapErr}; -use foundry_common::{fs, types::ToAlloy}; -use foundry_config::Config; -use rusoto_core::{ - credential::ChainProvider as AwsChainProvider, region::Region as AwsRegion, - request::HttpClient as AwsHttpClient, Client as AwsClient, -}; -use rusoto_kms::KmsClient; -use serde::{Deserialize, Serialize}; -use std::{ - path::{Path, PathBuf}, - str::FromStr, -}; - -pub mod multi_wallet; -pub use multi_wallet::*; - -pub mod error; - -/// A wrapper for the raw data options for `Wallet`, extracted to also be used standalone. -/// The raw wallet options can either be: -/// 1. Private Key (cleartext in CLI) -/// 2. Private Key (interactively via secure prompt) -/// 3. Mnemonic (via file path) -#[derive(Parser, Debug, Default, Clone, Serialize)] -#[clap(next_help_heading = "Wallet options - raw", about = None, long_about = None)] -pub struct RawWallet { - /// Open an interactive prompt to enter your private key. - #[clap(long, short)] - pub interactive: bool, - - /// Use the provided private key. - #[clap( - long, - value_name = "RAW_PRIVATE_KEY", - value_parser = foundry_common::clap_helpers::strip_0x_prefix - )] - pub private_key: Option, - - /// Use the mnemonic phrase of mnemonic file at the specified path. - #[clap(long, alias = "mnemonic-path")] - pub mnemonic: Option, - - /// Use a BIP39 passphrase for the mnemonic. - #[clap(long, value_name = "PASSPHRASE")] - pub mnemonic_passphrase: Option, - - /// The wallet derivation path. - /// - /// Works with both --mnemonic-path and hardware wallets. - #[clap(long = "mnemonic-derivation-path", alias = "hd-path", value_name = "PATH")] - pub hd_path: Option, - - /// Use the private key from the given mnemonic index. - /// - /// Used with --mnemonic-path. - #[clap(long, conflicts_with = "hd_path", default_value_t = 0, value_name = "INDEX")] - pub mnemonic_index: u32, -} - -/// The wallet options can either be: -/// 1. Raw (via private key / mnemonic file, see `RawWallet`) -/// 2. Ledger -/// 3. Trezor -/// 4. Keystore (via file path) -/// 5. AWS KMS -#[derive(Parser, Debug, Default, Clone, Serialize)] -#[clap(next_help_heading = "Wallet options", about = None, long_about = None)] -pub struct Wallet { - /// The sender account. - #[clap( - long, - short, - value_name = "ADDRESS", - help_heading = "Wallet options - raw", - env = "ETH_FROM" - )] - pub from: Option
, - - #[clap(flatten)] - pub raw: RawWallet, - - /// Use the keystore in the given folder or file. - #[clap( - long = "keystore", - help_heading = "Wallet options - keystore", - value_name = "PATH", - env = "ETH_KEYSTORE" - )] - pub keystore_path: Option, - - /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename - #[clap( - long = "account", - help_heading = "Wallet options - keystore", - value_name = "ACCOUNT_NAME", - env = "ETH_KEYSTORE_ACCOUNT", - conflicts_with = "keystore_path" - )] - pub keystore_account_name: Option, - - /// The keystore password. - /// - /// Used with --keystore. - #[clap( - long = "password", - help_heading = "Wallet options - keystore", - requires = "keystore_path", - value_name = "PASSWORD" - )] - pub keystore_password: Option, - - /// The keystore password file path. - /// - /// Used with --keystore. - #[clap( - long = "password-file", - help_heading = "Wallet options - keystore", - requires = "keystore_path", - value_name = "PASSWORD_FILE", - env = "ETH_PASSWORD" - )] - pub keystore_password_file: Option, - - /// Use a Ledger hardware wallet. - #[clap(long, short, help_heading = "Wallet options - hardware wallet")] - pub ledger: bool, - - /// Use a Trezor hardware wallet. - #[clap(long, short, help_heading = "Wallet options - hardware wallet")] - pub trezor: bool, - - /// Use AWS Key Management Service. - #[clap(long, help_heading = "Wallet options - AWS KMS")] - pub aws: bool, -} - -impl From for Wallet { - fn from(options: RawWallet) -> Self { - Self { raw: options, ..Default::default() } - } -} - -impl Wallet { - pub fn interactive(&self) -> Result> { - Ok(if self.raw.interactive { Some(self.get_from_interactive()?) } else { None }) - } - - pub fn private_key(&self) -> Result> { - Ok(if let Some(ref private_key) = self.raw.private_key { - Some(self.get_from_private_key(private_key)?) - } else { - None - }) - } - - pub fn keystore(&self) -> Result> { - let default_keystore_dir = Config::foundry_keystores_dir() - .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; - // If keystore path is provided, use it, otherwise use default path + keystore account name - let keystore_path: Option = self.keystore_path.clone().or_else(|| { - self.keystore_account_name.as_ref().map(|keystore_name| { - default_keystore_dir.join(keystore_name).to_string_lossy().into_owned() - }) - }); - - self.get_from_keystore( - keystore_path.as_ref(), - self.keystore_password.as_ref(), - self.keystore_password_file.as_ref(), - ) - } - - pub fn mnemonic(&self) -> Result> { - Ok(if let Some(ref mnemonic) = self.raw.mnemonic { - Some(self.get_from_mnemonic( - mnemonic, - self.raw.mnemonic_passphrase.as_ref(), - self.raw.hd_path.as_ref(), - self.raw.mnemonic_index, - )?) - } else { - None - }) - } - - /// Returns the sender address of the signer or `from`. - pub async fn sender(&self) -> Address { - if let Ok(signer) = self.signer(0).await { - signer.address().to_alloy() - } else { - self.from.unwrap_or(Address::ZERO) - } - } - - /// Tries to resolve a local wallet from the provided options. - #[track_caller] - pub fn try_resolve_local_wallet(&self) -> Result> { - self.private_key() - .transpose() - .or_else(|| self.interactive().transpose()) - .or_else(|| self.mnemonic().transpose()) - .or_else(|| self.keystore().transpose()) - .transpose() - } - /// Returns a [Signer] corresponding to the provided private key, mnemonic or hardware signer. - #[instrument(skip(self), level = "trace")] - pub async fn signer(&self, chain_id: u64) -> Result { - trace!("start finding signer"); - - if self.ledger { - let derivation = match self.raw.hd_path.as_ref() { - Some(hd_path) => LedgerHDPath::Other(hd_path.clone()), - None => LedgerHDPath::LedgerLive(self.raw.mnemonic_index as usize), - }; - let ledger = Ledger::new(derivation, chain_id).await.wrap_err_with(|| { - "\ -Could not connect to Ledger device. -Make sure it's connected and unlocked, with no other desktop wallet apps open." - })?; - - Ok(WalletSigner::Ledger(ledger)) - } else if self.trezor { - let derivation = match self.raw.hd_path.as_ref() { - Some(hd_path) => TrezorHDPath::Other(hd_path.clone()), - None => TrezorHDPath::TrezorLive(self.raw.mnemonic_index as usize), - }; - - // cached to ~/.ethers-rs/trezor/cache/trezor.session - let trezor = Trezor::new(derivation, chain_id, None).await.wrap_err_with(|| { - "\ -Could not connect to Trezor device. -Make sure it's connected and unlocked, with no other conflicting desktop wallet apps open." - })?; - - Ok(WalletSigner::Trezor(trezor)) - } else if self.aws { - let client = - AwsClient::new_with(AwsChainProvider::default(), AwsHttpClient::new().unwrap()); - - let kms = KmsClient::new_with_client(client, AwsRegion::default()); - - let key_id = std::env::var("AWS_KMS_KEY_ID")?; - - let aws_signer = AwsSigner::new(kms, key_id, chain_id).await?; - - Ok(WalletSigner::Aws(aws_signer)) - } else { - trace!("finding local key"); - - let maybe_local = self.try_resolve_local_wallet()?; - - let local = maybe_local.ok_or_else(|| { - eyre::eyre!( - "\ -Error accessing local wallet. Did you set a private key, mnemonic or keystore? -Run `cast send --help` or `forge create --help` and use the corresponding CLI -flag to set your key via: ---private-key, --mnemonic-path, --aws, --interactive, --trezor or --ledger. -Alternatively, if you're using a local node with unlocked accounts, -use the --unlocked flag and either set the `ETH_FROM` environment variable to the address -of the unlocked account you want to use, or provide the --from flag with the address directly." - ) - })?; - - Ok(WalletSigner::Local(local.with_chain_id(chain_id))) - } - } -} - -pub trait WalletTrait { - /// Returns the configured sender. - fn sender(&self) -> Option
; - - fn get_from_interactive(&self) -> Result { - let private_key = rpassword::prompt_password("Enter private key: ")?; - let private_key = private_key.strip_prefix("0x").unwrap_or(&private_key); - Ok(LocalWallet::from_str(private_key)?) - } - - #[track_caller] - fn get_from_private_key(&self, private_key: &str) -> Result { - let privk = private_key.trim().strip_prefix("0x").unwrap_or(private_key); - match LocalWallet::from_str(privk) { - Ok(pk) => Ok(pk), - Err(err) => { - // helper closure to check if pk was meant to be an env var, this usually happens if - // `$` is missing - let ensure_not_env = |pk: &str| { - // check if pk was meant to be an env var - if !pk.starts_with("0x") && std::env::var(pk).is_ok() { - // SAFETY: at this point we know the user actually wanted to use an env var - // and most likely forgot the `$` anchor, so the - // `private_key` here is an unresolved env var - return Err(PrivateKeyError::ExistsAsEnvVar(pk.to_string())) - } - Ok(()) - }; - match err { - WalletError::HexError(err) => { - ensure_not_env(private_key)?; - return Err(PrivateKeyError::InvalidHex(err).into()) - } - WalletError::EcdsaError(_) => { - ensure_not_env(private_key)?; - } - _ => {} - }; - bail!("Failed to create wallet from private key: {err}") - } - } - } - - fn get_from_mnemonic( - &self, - mnemonic: &String, - passphrase: Option<&String>, - derivation_path: Option<&String>, - index: u32, - ) -> Result { - let mnemonic = if Path::new(mnemonic).is_file() { - fs::read_to_string(mnemonic)?.replace('\n', "") - } else { - mnemonic.to_owned() - }; - let builder = MnemonicBuilder::::default().phrase(mnemonic.as_str()); - let builder = if let Some(passphrase) = passphrase { - builder.password(passphrase.as_str()) - } else { - builder - }; - let builder = if let Some(hd_path) = derivation_path { - builder.derivation_path(hd_path.as_str())? - } else { - builder.index(index)? - }; - Ok(builder.build()?) - } - - /// Ensures the path to the keystore exists. - /// - /// if the path is a directory, it bails and asks the user to specify the keystore file - /// directly. - fn find_keystore_file(&self, path: impl AsRef) -> Result { - let path = path.as_ref(); - if !path.exists() { - bail!("Keystore file `{path:?}` does not exist") - } - - if path.is_dir() { - bail!("Keystore path `{path:?}` is a directory. Please specify the keystore file directly.") - } - - Ok(path.to_path_buf()) - } - - fn get_from_keystore( - &self, - keystore_path: Option<&String>, - keystore_password: Option<&String>, - keystore_password_file: Option<&String>, - ) -> Result> { - Ok(match (keystore_path, keystore_password, keystore_password_file) { - // Path and password provided - (Some(path), Some(password), _) => { - let path = self.find_keystore_file(path)?; - Some( - LocalWallet::decrypt_keystore(&path, password) - .wrap_err_with(|| format!("Failed to decrypt keystore {path:?}"))?, - ) - } - // Path and password file provided - (Some(path), _, Some(password_file)) => { - let path = self.find_keystore_file(path)?; - Some( - LocalWallet::decrypt_keystore(&path, self.password_from_file(password_file)?) - .wrap_err_with(|| format!("Failed to decrypt keystore {path:?} with password file {password_file:?}"))?, - ) - } - // Only Path provided -> interactive - (Some(path), None, None) => { - let path = self.find_keystore_file(path)?; - let password = rpassword::prompt_password("Enter keystore password:")?; - Some(LocalWallet::decrypt_keystore(path, password)?) - } - // Nothing provided - (None, _, _) => None, - }) - } - - /// Attempts to read the keystore password from the password file. - fn password_from_file(&self, password_file: impl AsRef) -> Result { - let password_file = password_file.as_ref(); - if !password_file.is_file() { - bail!("Keystore password file `{password_file:?}` does not exist") - } - - Ok(fs::read_to_string(password_file)?.trim_end().to_string()) - } -} - -impl WalletTrait for Wallet { - fn sender(&self) -> Option
{ - self.from - } -} - -#[derive(Debug, thiserror::Error)] -pub enum WalletSignerError { - #[error(transparent)] - Local(#[from] WalletError), - #[error(transparent)] - Ledger(#[from] LedgerError), - #[error(transparent)] - Trezor(#[from] TrezorError), - #[error(transparent)] - Aws(#[from] AwsSignerError), -} - -#[derive(Debug)] -pub enum WalletSigner { - Local(LocalWallet), - Ledger(Ledger), - Trezor(Trezor), - Aws(AwsSigner), -} - -impl From for WalletSigner { - fn from(wallet: LocalWallet) -> Self { - Self::Local(wallet) - } -} - -impl From for WalletSigner { - fn from(hw: Ledger) -> Self { - Self::Ledger(hw) - } -} - -impl From for WalletSigner { - fn from(hw: Trezor) -> Self { - Self::Trezor(hw) - } -} - -impl From for WalletSigner { - fn from(wallet: AwsSigner) -> Self { - Self::Aws(wallet) - } -} - -macro_rules! delegate { - ($s:ident, $inner:ident => $e:expr) => { - match $s { - Self::Local($inner) => $e, - Self::Ledger($inner) => $e, - Self::Trezor($inner) => $e, - Self::Aws($inner) => $e, - } - }; -} - -#[async_trait] -impl Signer for WalletSigner { - type Error = WalletSignerError; - - async fn sign_message>( - &self, - message: S, - ) -> Result { - delegate!(self, inner => inner.sign_message(message).await.map_err(Into::into)) - } - - async fn sign_transaction(&self, message: &TypedTransaction) -> Result { - delegate!(self, inner => inner.sign_transaction(message).await.map_err(Into::into)) - } - - async fn sign_typed_data( - &self, - payload: &T, - ) -> Result { - delegate!(self, inner => inner.sign_typed_data(payload).await.map_err(Into::into)) - } - - fn address(&self) -> ethers_core::types::Address { - delegate!(self, inner => inner.address()) - } - - fn chain_id(&self) -> u64 { - delegate!(self, inner => inner.chain_id()) - } - - fn with_chain_id>(self, chain_id: T) -> Self { - match self { - Self::Local(inner) => Self::Local(inner.with_chain_id(chain_id)), - Self::Ledger(inner) => Self::Ledger(inner.with_chain_id(chain_id)), - Self::Trezor(inner) => Self::Trezor(inner.with_chain_id(chain_id)), - Self::Aws(inner) => Self::Aws(inner.with_chain_id(chain_id)), - } - } -} - -#[async_trait] -impl Signer for &WalletSigner { - type Error = WalletSignerError; - - async fn sign_message>( - &self, - message: S, - ) -> Result { - (*self).sign_message(message).await - } - - async fn sign_transaction(&self, message: &TypedTransaction) -> Result { - (*self).sign_transaction(message).await - } - - async fn sign_typed_data( - &self, - payload: &T, - ) -> Result { - (*self).sign_typed_data(payload).await - } - - fn address(&self) -> ethers_core::types::Address { - (*self).address() - } - - fn chain_id(&self) -> u64 { - (*self).chain_id() - } - - fn with_chain_id>(self, chain_id: T) -> Self { - let _ = chain_id; - self - } -} - -/// Excerpt of a keystore file. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct KeystoreFile { - pub address: Address, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn find_keystore() { - let keystore = - Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../cast/tests/fixtures/keystore")); - let keystore_file = keystore - .join("UTC--2022-10-30T06-51-20.130356000Z--560d246fcddc9ea98a8b032c9a2f474efb493c28"); - let wallet: Wallet = Wallet::parse_from([ - "foundry-cli", - "--from", - "560d246fcddc9ea98a8b032c9a2f474efb493c28", - ]); - let file = wallet.find_keystore_file(&keystore_file).unwrap(); - assert_eq!(file, keystore_file); - } - - #[test] - fn illformed_private_key_generates_user_friendly_error() { - let wallet = Wallet { - raw: RawWallet { - interactive: false, - private_key: Some("123".to_string()), - mnemonic: None, - mnemonic_passphrase: None, - hd_path: None, - mnemonic_index: 0, - }, - from: None, - keystore_path: None, - keystore_account_name: None, - keystore_password: None, - keystore_password_file: None, - ledger: false, - trezor: false, - aws: false, - }; - match wallet.private_key() { - Ok(_) => { - panic!("illformed private key shouldn't decode") - } - Err(x) => { - assert!( - x.to_string().contains("Failed to create wallet"), - "Error message is not user-friendly" - ); - } - } - } - - #[test] - fn gets_password_from_file() { - let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../cast/tests/fixtures/keystore/password"); - let wallet: Wallet = Wallet::parse_from(["foundry-cli"]); - let password = wallet.password_from_file(path).unwrap(); - assert_eq!(password, "this is keystore password") - } -} diff --git a/crates/cli/src/opts/wallet/multi_wallet.rs b/crates/cli/src/opts/wallet/multi_wallet.rs deleted file mode 100644 index 1114d24fa65ad..0000000000000 --- a/crates/cli/src/opts/wallet/multi_wallet.rs +++ /dev/null @@ -1,520 +0,0 @@ -use super::{WalletSigner, WalletTrait}; -use alloy_primitives::Address; -use clap::Parser; -use ethers_providers::Middleware; -use ethers_signers::{ - AwsSigner, HDPath as LedgerHDPath, Ledger, LocalWallet, Signer, Trezor, TrezorHDPath, -}; -use eyre::{Context, ContextCompat, Result}; -use foundry_common::{types::ToAlloy, RetryProvider}; -use foundry_config::Config; -use itertools::izip; -use rusoto_core::{ - credential::ChainProvider as AwsChainProvider, region::Region as AwsRegion, - request::HttpClient as AwsHttpClient, Client as AwsClient, -}; -use rusoto_kms::KmsClient; -use serde::Serialize; -use std::{ - collections::{HashMap, HashSet}, - iter::repeat, - sync::Arc, -}; - -macro_rules! get_wallets { - ($id:ident, [ $($wallets:expr),+ ], $call:expr) => { - $( - if let Some($id) = $wallets { - $call; - } - )+ - }; -} - -/// A macro that initializes multiple wallets -/// -/// Should be used with a [`MultiWallet`] instance -macro_rules! create_hw_wallets { - ($self:ident, $chain_id:ident ,$get_wallet:ident, $wallets:ident) => { - let mut $wallets = vec![]; - - if let Some(hd_paths) = &$self.hd_paths { - for path in hd_paths { - if let Some(hw) = $self.$get_wallet($chain_id, Some(path), None).await? { - $wallets.push(hw); - } - } - } - - if let Some(mnemonic_indexes) = &$self.mnemonic_indexes { - for index in mnemonic_indexes { - if let Some(hw) = $self.$get_wallet($chain_id, None, Some(*index as usize)).await? { - $wallets.push(hw); - } - } - } - - if $wallets.is_empty() { - if let Some(hw) = $self.$get_wallet($chain_id, None, Some(0)).await? { - $wallets.push(hw); - } - } - }; -} - -/// The wallet options can either be: -/// 1. Ledger -/// 2. Trezor -/// 3. Mnemonics (via file path) -/// 4. Keystores (via file path) -/// 5. Private Keys (cleartext in CLI) -/// 6. Private Keys (interactively via secure prompt) -/// 7. AWS KMS -#[derive(Parser, Debug, Clone, Serialize, Default)] -#[clap(next_help_heading = "Wallet options", about = None, long_about = None)] -pub struct MultiWallet { - /// The sender accounts. - #[clap( - long, - short = 'a', - help_heading = "Wallet options - raw", - value_name = "ADDRESSES", - env = "ETH_FROM", - num_args(0..), - )] - pub froms: Option>, - - /// Open an interactive prompt to enter your private key. - /// - /// Takes a value for the number of keys to enter. - #[clap( - long, - short, - help_heading = "Wallet options - raw", - default_value = "0", - value_name = "NUM" - )] - pub interactives: u32, - - /// Use the provided private keys. - #[clap( - long, - help_heading = "Wallet options - raw", - value_name = "RAW_PRIVATE_KEYS", - value_parser = foundry_common::clap_helpers::strip_0x_prefix, - )] - pub private_keys: Option>, - - /// Use the provided private key. - #[clap( - long, - help_heading = "Wallet options - raw", - conflicts_with = "private_keys", - value_name = "RAW_PRIVATE_KEY", - value_parser = foundry_common::clap_helpers::strip_0x_prefix, - )] - pub private_key: Option, - - /// Use the mnemonic phrases of mnemonic files at the specified paths. - #[clap(long, alias = "mnemonic-paths", help_heading = "Wallet options - raw")] - pub mnemonics: Option>, - - /// Use a BIP39 passphrases for the mnemonic. - #[clap(long, help_heading = "Wallet options - raw", value_name = "PASSPHRASE")] - pub mnemonic_passphrases: Option>, - - /// The wallet derivation path. - /// - /// Works with both --mnemonic-path and hardware wallets. - #[clap( - long = "mnemonic-derivation-paths", - alias = "hd-paths", - help_heading = "Wallet options - raw", - value_name = "PATH" - )] - pub hd_paths: Option>, - - /// Use the private key from the given mnemonic index. - /// - /// Can be used with --mnemonics, --ledger, --aws and --trezor. - #[clap( - long, - conflicts_with = "hd_paths", - help_heading = "Wallet options - raw", - default_value = "0", - value_name = "INDEXES" - )] - pub mnemonic_indexes: Option>, - - /// Use the keystore in the given folder or file. - #[clap( - long = "keystore", - visible_alias = "keystores", - help_heading = "Wallet options - keystore", - value_name = "PATHS", - env = "ETH_KEYSTORE" - )] - pub keystore_paths: Option>, - - /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename - #[clap( - long = "account", - visible_alias = "accounts", - help_heading = "Wallet options - keystore", - value_name = "ACCOUNT_NAMES", - env = "ETH_KEYSTORE_ACCOUNT", - conflicts_with = "keystore_paths" - )] - pub keystore_account_names: Option>, - - /// The keystore password. - /// - /// Used with --keystore. - #[clap( - long = "password", - help_heading = "Wallet options - keystore", - requires = "keystore_paths", - value_name = "PASSWORDS" - )] - pub keystore_passwords: Option>, - - /// The keystore password file path. - /// - /// Used with --keystore. - #[clap( - long = "password-file", - help_heading = "Wallet options - keystore", - requires = "keystore_paths", - value_name = "PATHS", - env = "ETH_PASSWORD" - )] - pub keystore_password_files: Option>, - - /// Use a Ledger hardware wallet. - #[clap(long, short, help_heading = "Wallet options - hardware wallet")] - pub ledger: bool, - - /// Use a Trezor hardware wallet. - #[clap(long, short, help_heading = "Wallet options - hardware wallet")] - pub trezor: bool, - - /// Use AWS Key Management Service. - #[clap(long, help_heading = "Wallet options - remote")] - pub aws: bool, -} - -impl WalletTrait for MultiWallet { - fn sender(&self) -> Option
{ - self.froms.as_ref()?.first().copied() - } -} - -impl MultiWallet { - /// Given a list of addresses, it finds all the associated wallets if they exist. Throws an - /// error, if it can't find all. - pub async fn find_all( - &self, - provider: Arc, - mut addresses: HashSet
, - script_wallets: &[LocalWallet], - ) -> Result> { - println!("\n###\nFinding wallets for all the necessary addresses..."); - let chain = provider.get_chainid().await?.as_u64(); - - let mut local_wallets = HashMap::new(); - let mut unused_wallets = vec![]; - - get_wallets!( - wallets, - [ - self.trezors(chain).await?, - self.ledgers(chain).await?, - self.private_keys()?, - self.interactives()?, - self.mnemonics()?, - self.keystores()?, - self.aws_signers(chain).await?, - (!script_wallets.is_empty()).then(|| script_wallets.to_vec()) - ], - for wallet in wallets.into_iter() { - let address = wallet.address(); - if addresses.contains(&address.to_alloy()) { - addresses.remove(&address.to_alloy()); - - let signer = WalletSigner::from(wallet.with_chain_id(chain)); - local_wallets.insert(address.to_alloy(), signer); - - if addresses.is_empty() { - return Ok(local_wallets) - } - } else { - // Just to show on error. - unused_wallets.push(address.to_alloy()); - } - } - ); - - let mut error_msg = String::new(); - - // This is an actual used address - if addresses.contains(&Config::DEFAULT_SENDER) { - error_msg += "\nYou seem to be using Foundry's default sender. Be sure to set your own --sender.\n"; - } - - unused_wallets.extend(local_wallets.into_keys()); - eyre::bail!( - "{}No associated wallet for addresses: {:?}. Unlocked wallets: {:?}", - error_msg, - addresses, - unused_wallets - ) - } - - pub fn interactives(&self) -> Result>> { - if self.interactives != 0 { - let mut wallets = vec![]; - for _ in 0..self.interactives { - wallets.push(self.get_from_interactive()?); - } - return Ok(Some(wallets)) - } - Ok(None) - } - - pub fn private_keys(&self) -> Result>> { - if let Some(private_keys) = &self.private_keys { - let mut wallets = vec![]; - for private_key in private_keys.iter() { - wallets.push(self.get_from_private_key(private_key.trim())?); - } - return Ok(Some(wallets)) - } - Ok(None) - } - - /// Returns all wallets read from the provided keystores arguments - /// - /// Returns `Ok(None)` if no keystore provided. - pub fn keystores(&self) -> Result>> { - let default_keystore_dir = Config::foundry_keystores_dir() - .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; - // If keystore paths are provided, use them, else, use default path + keystore account names - let keystore_paths = self.keystore_paths.clone().or_else(|| { - self.keystore_account_names.as_ref().map(|keystore_names| { - keystore_names - .iter() - .map(|keystore_name| { - default_keystore_dir.join(keystore_name).to_string_lossy().into_owned() - }) - .collect() - }) - }); - - if let Some(keystore_paths) = keystore_paths { - let mut wallets = Vec::with_capacity(keystore_paths.len()); - - let mut passwords_iter = - self.keystore_passwords.clone().unwrap_or_default().into_iter(); - - let mut password_files_iter = - self.keystore_password_files.clone().unwrap_or_default().into_iter(); - - for path in keystore_paths { - let wallet = self.get_from_keystore(Some(&path), passwords_iter.next().as_ref(), password_files_iter.next().as_ref())?.wrap_err("Keystore paths do not have the same length as provided passwords or password files.")?; - wallets.push(wallet); - } - return Ok(Some(wallets)) - } - Ok(None) - } - - pub fn mnemonics(&self) -> Result>> { - if let Some(ref mnemonics) = self.mnemonics { - let mut wallets = vec![]; - let hd_paths: Vec<_> = if let Some(ref hd_paths) = self.hd_paths { - hd_paths.iter().map(Some).collect() - } else { - repeat(None).take(mnemonics.len()).collect() - }; - let mnemonic_passphrases: Vec<_> = - if let Some(ref mnemonic_passphrases) = self.mnemonic_passphrases { - mnemonic_passphrases.iter().map(Some).collect() - } else { - repeat(None).take(mnemonics.len()).collect() - }; - let mnemonic_indexes: Vec<_> = if let Some(ref mnemonic_indexes) = self.mnemonic_indexes - { - mnemonic_indexes.to_vec() - } else { - repeat(0).take(mnemonics.len()).collect() - }; - for (mnemonic, mnemonic_passphrase, hd_path, mnemonic_index) in - izip!(mnemonics, mnemonic_passphrases, hd_paths, mnemonic_indexes) - { - wallets.push(self.get_from_mnemonic( - mnemonic, - mnemonic_passphrase, - hd_path, - mnemonic_index, - )?) - } - return Ok(Some(wallets)) - } - Ok(None) - } - - pub async fn ledgers(&self, chain_id: u64) -> Result>> { - if self.ledger { - let mut args = self.clone(); - - if let Some(paths) = &args.hd_paths { - if paths.len() > 1 { - eyre::bail!("Ledger only supports one signer."); - } - args.mnemonic_indexes = None; - } - - create_hw_wallets!(args, chain_id, get_from_ledger, wallets); - return Ok(Some(wallets)) - } - Ok(None) - } - - pub async fn trezors(&self, chain_id: u64) -> Result>> { - if self.trezor { - create_hw_wallets!(self, chain_id, get_from_trezor, wallets); - return Ok(Some(wallets)) - } - Ok(None) - } - - pub async fn aws_signers(&self, chain_id: u64) -> Result>> { - if self.aws { - let mut wallets = vec![]; - let client = - AwsClient::new_with(AwsChainProvider::default(), AwsHttpClient::new().unwrap()); - - let kms = KmsClient::new_with_client(client, AwsRegion::default()); - - let env_key_ids = std::env::var("AWS_KMS_KEY_IDS"); - let key_ids = - if env_key_ids.is_ok() { env_key_ids? } else { std::env::var("AWS_KMS_KEY_ID")? }; - - for key in key_ids.split(',') { - let aws_signer = AwsSigner::new(kms.clone(), key, chain_id).await?; - wallets.push(aws_signer) - } - - return Ok(Some(wallets)) - } - Ok(None) - } - - async fn get_from_trezor( - &self, - chain_id: u64, - hd_path: Option<&str>, - mnemonic_index: Option, - ) -> Result> { - let derivation = match &hd_path { - Some(hd_path) => TrezorHDPath::Other(hd_path.to_string()), - None => TrezorHDPath::TrezorLive(mnemonic_index.unwrap_or(0)), - }; - - Ok(Some(Trezor::new(derivation, chain_id, None).await?)) - } - - async fn get_from_ledger( - &self, - chain_id: u64, - hd_path: Option<&str>, - mnemonic_index: Option, - ) -> Result> { - let derivation = match hd_path { - Some(hd_path) => LedgerHDPath::Other(hd_path.to_string()), - None => LedgerHDPath::LedgerLive(mnemonic_index.unwrap_or(0)), - }; - - trace!(?chain_id, "Creating new ledger signer"); - Ok(Some(Ledger::new(derivation, chain_id).await.wrap_err("Ledger device not available.")?)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::path::Path; - - #[test] - fn parse_keystore_args() { - let args: MultiWallet = - MultiWallet::parse_from(["foundry-cli", "--keystores", "my/keystore/path"]); - assert_eq!(args.keystore_paths, Some(vec!["my/keystore/path".to_string()])); - - std::env::set_var("ETH_KEYSTORE", "MY_KEYSTORE"); - let args: MultiWallet = MultiWallet::parse_from(["foundry-cli"]); - assert_eq!(args.keystore_paths, Some(vec!["MY_KEYSTORE".to_string()])); - - std::env::remove_var("ETH_KEYSTORE"); - } - - #[test] - fn parse_keystore_password_file() { - let keystore = - Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../cast/tests/fixtures/keystore")); - let keystore_file = keystore - .join("UTC--2022-12-20T10-30-43.591916000Z--ec554aeafe75601aaab43bd4621a22284db566c2"); - - let keystore_password_file = keystore.join("password-ec554").into_os_string(); - - let args: MultiWallet = MultiWallet::parse_from([ - "foundry-cli", - "--keystores", - keystore_file.to_str().unwrap(), - "--password-file", - keystore_password_file.to_str().unwrap(), - ]); - assert_eq!( - args.keystore_password_files, - Some(vec![keystore_password_file.to_str().unwrap().to_string()]) - ); - - let wallets = args.keystores().unwrap().unwrap(); - assert_eq!(wallets.len(), 1); - assert_eq!( - wallets[0].address(), - "ec554aeafe75601aaab43bd4621a22284db566c2".parse().unwrap() - ); - } - - // https://github.com/foundry-rs/foundry/issues/5179 - #[test] - fn should_not_require_the_mnemonics_flag_with_mnemonic_indexes() { - let wallet_options = vec![ - ("ledger", "--mnemonic-indexes", 1), - ("trezor", "--mnemonic-indexes", 2), - ("aws", "--mnemonic-indexes", 10), - ]; - - for test_case in wallet_options { - let args: MultiWallet = MultiWallet::parse_from([ - "foundry-cli", - &format!("--{}", test_case.0), - test_case.1, - &test_case.2.to_string(), - ]); - - match test_case.0 { - "ledger" => assert!(args.ledger), - "trezor" => assert!(args.trezor), - "aws" => assert!(args.aws), - _ => panic!("Should have matched one of the previous wallet options"), - } - - assert_eq!( - args.mnemonic_indexes.expect("--mnemonic-indexes should have been set")[0], - test_case.2 - ) - } - } -} diff --git a/crates/cli/src/utils/abi.rs b/crates/cli/src/utils/abi.rs new file mode 100644 index 0000000000000..634b889ca7110 --- /dev/null +++ b/crates/cli/src/utils/abi.rs @@ -0,0 +1,61 @@ +use alloy_chains::Chain; +use alloy_json_abi::Function; +use alloy_primitives::Address; +use alloy_provider::{network::AnyNetwork, Provider}; +use alloy_transport::Transport; +use eyre::{OptionExt, Result}; +use foundry_common::{ + abi::{encode_function_args, get_func, get_func_etherscan}, + ens::NameOrAddress, +}; +use futures::future::join_all; + +async fn resolve_name_args>( + args: &[String], + provider: &P, +) -> Vec { + join_all(args.iter().map(|arg| async { + if arg.contains('.') { + let addr = NameOrAddress::Name(arg.to_string()).resolve(provider).await; + match addr { + Ok(addr) => addr.to_string(), + Err(_) => arg.to_string(), + } + } else { + arg.to_string() + } + })) + .await +} + +pub async fn parse_function_args>( + sig: &str, + args: Vec, + to: Option
, + chain: Chain, + provider: &P, + etherscan_api_key: Option<&str>, +) -> Result<(Vec, Option)> { + if sig.trim().is_empty() { + eyre::bail!("Function signature or calldata must be provided.") + } + + let args = resolve_name_args(&args, provider).await; + + if sig.starts_with("0x") { + return Ok((hex::decode(sig)?, None)); + } + + let func = if sig.contains('(') { + // a regular function signature with parentheses + get_func(sig)? + } else { + let etherscan_api_key = etherscan_api_key.ok_or_eyre( + "If you wish to fetch function data from EtherScan, please provide an API key.", + )?; + let to = to.ok_or_eyre("A 'to' address must be provided to fetch function data.")?; + get_func_etherscan(sig, to, &args, chain, etherscan_api_key).await? + }; + + Ok((encode_function_args(&func, &args)?, Some(func))) +} diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 0631ec974a639..d8c5ab2227b81 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -1,26 +1,29 @@ -use alloy_json_abi::JsonAbi as Abi; +use alloy_json_abi::JsonAbi; use alloy_primitives::Address; use eyre::{Result, WrapErr}; use foundry_common::{cli_warn, fs, TestFunctionExt}; use foundry_compilers::{ artifacts::{CompactBytecode, CompactDeployedBytecode}, cache::{CacheEntry, SolFilesCache}, - info::ContractInfo, utils::read_json_file, Artifact, ProjectCompileOutput, }; use foundry_config::{error::ExtractConfigError, figment::Figment, Chain, Config, NamedChain}; -use foundry_debugger::DebuggerBuilder; +use foundry_debugger::Debugger; use foundry_evm::{ debug::DebugArena, - executors::{DeployResult, EvmError, ExecutionErr, RawCallResult}, + executors::{DeployResult, EvmError, RawCallResult}, opts::EvmOpts, traces::{ identifier::{EtherscanIdentifier, SignaturesIdentifier}, - CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces, + render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces, }, }; -use std::{fmt::Write, path::PathBuf, str::FromStr}; +use std::{ + fmt::Write, + path::{Path, PathBuf}, + str::FromStr, +}; use yansi::Paint; /// Given a `Project`'s output, removes the matching ABI, Bytecode and @@ -28,16 +31,17 @@ use yansi::Paint; #[track_caller] pub fn remove_contract( output: &mut ProjectCompileOutput, - info: &ContractInfo, -) -> Result<(Abi, CompactBytecode, CompactDeployedBytecode)> { - let contract = if let Some(contract) = output.remove_contract(info) { + path: &Path, + name: &str, +) -> Result<(JsonAbi, CompactBytecode, CompactDeployedBytecode)> { + let contract = if let Some(contract) = output.remove(path.to_string_lossy(), name) { contract } else { - let mut err = format!("could not find artifact: `{}`", info.name); + let mut err = format!("could not find artifact: `{}`", name); if let Some(suggestion) = - super::did_you_mean(&info.name, output.artifacts().map(|(name, _)| name)).pop() + super::did_you_mean(name, output.artifacts().map(|(name, _)| name)).pop() { - if suggestion != info.name { + if suggestion != name { err = format!( r#"{err} @@ -50,17 +54,17 @@ pub fn remove_contract( let abi = contract .get_abi() - .ok_or_else(|| eyre::eyre!("contract {} does not contain abi", info))? + .ok_or_else(|| eyre::eyre!("contract {} does not contain abi", name))? .into_owned(); let bin = contract .get_bytecode() - .ok_or_else(|| eyre::eyre!("contract {} does not contain bytecode", info))? + .ok_or_else(|| eyre::eyre!("contract {} does not contain bytecode", name))? .into_owned(); let runtime = contract .get_deployed_bytecode() - .ok_or_else(|| eyre::eyre!("contract {} does not contain deployed bytecode", info))? + .ok_or_else(|| eyre::eyre!("contract {} does not contain deployed bytecode", name))? .into_owned(); Ok((abi, bin, runtime)) @@ -93,7 +97,7 @@ pub fn get_cached_entry_by_name( } if let Some(entry) = cached_entry { - return Ok(entry) + return Ok(entry); } let mut err = format!("could not find artifact: `{name}`"); @@ -108,7 +112,7 @@ pub fn get_cached_entry_by_name( } /// Returns error if constructor has arguments. -pub fn ensure_clean_constructor(abi: &Abi) -> Result<()> { +pub fn ensure_clean_constructor(abi: &JsonAbi) -> Result<()> { if let Some(constructor) = &abi.constructor { if !constructor.inputs.is_empty() { eyre::bail!("Contract constructor should have no arguments. Add those arguments to `run(...)` instead, and call it with `--sig run(...)`."); @@ -117,14 +121,14 @@ pub fn ensure_clean_constructor(abi: &Abi) -> Result<()> { Ok(()) } -pub fn needs_setup(abi: &Abi) -> bool { +pub fn needs_setup(abi: &JsonAbi) -> bool { let setup_fns: Vec<_> = abi.functions().filter(|func| func.name.is_setup()).collect(); for setup_fn in setup_fns.iter() { if setup_fn.name != "setUp" { println!( "{} Found invalid setup function \"{}\" did you mean \"setUp()\"?", - Paint::yellow("Warning:").bold(), + "Warning:".yellow().bold(), setup_fn.signature() ); } @@ -175,7 +179,7 @@ pub fn has_different_gas_calc(chain_id: u64) -> bool { NamedChain::Moonriver | NamedChain::Moonbase | NamedChain::MoonbeamDev - ) + ); } false } @@ -189,7 +193,7 @@ pub fn has_batch_support(chain_id: u64) -> bool { NamedChain::ArbitrumTestnet | NamedChain::ArbitrumGoerli | NamedChain::ArbitrumSepolia - ) + ); } true } @@ -316,6 +320,7 @@ pub fn read_constructor_args_file(constructor_args_path: PathBuf) -> Result for TraceResult { impl From for TraceResult { fn from(result: DeployResult) -> Self { - let DeployResult { gas_used, traces, debug, .. } = result; - + let RawCallResult { gas_used, traces, debug, .. } = result.raw; Self { success: true, traces: vec![(TraceKind::Execution, traces.expect("traces is None"))], @@ -355,7 +359,7 @@ impl TryFrom for TraceResult { fn try_from(err: EvmError) -> Result { match err { EvmError::Execution(err) => { - let ExecutionErr { reverted, gas_used, traces, debug: run_debug, .. } = *err; + let RawCallResult { reverted, gas_used, traces, debug: run_debug, .. } = err.raw; Ok(TraceResult { success: !reverted, traces: vec![(TraceKind::Execution, traces.expect("traces is None"))], @@ -374,73 +378,68 @@ pub async fn handle_traces( config: &Config, chain: Option, labels: Vec, - verbose: bool, debug: bool, ) -> Result<()> { - let mut etherscan_identifier = EtherscanIdentifier::new(config, chain)?; - - let labeled_addresses = labels.iter().filter_map(|label_str| { + let labels = labels.iter().filter_map(|label_str| { let mut iter = label_str.split(':'); if let Some(addr) = iter.next() { if let (Ok(address), Some(label)) = (Address::from_str(addr), iter.next()) { - return Some((address, label.to_string())) + return Some((address, label.to_string())); } } None }); - + let config_labels = config.labels.clone().into_iter(); let mut decoder = CallTraceDecoderBuilder::new() - .with_labels(labeled_addresses) + .with_labels(labels.chain(config_labels)) .with_signature_identifier(SignaturesIdentifier::new( Config::foundry_cache_dir(), config.offline, )?) .build(); - for (_, trace) in &mut result.traces { - decoder.identify(trace, &mut etherscan_identifier); + let mut etherscan_identifier = EtherscanIdentifier::new(config, chain)?; + if let Some(etherscan_identifier) = &mut etherscan_identifier { + for (_, trace) in &mut result.traces { + decoder.identify(trace, etherscan_identifier); + } } if debug { - let sources = etherscan_identifier.get_compiled_contracts().await?; - let mut debugger = DebuggerBuilder::new() + let sources = if let Some(etherscan_identifier) = etherscan_identifier { + etherscan_identifier.get_compiled_contracts().await? + } else { + Default::default() + }; + let mut debugger = Debugger::builder() .debug_arena(&result.debug) .decoder(&decoder) .sources(sources) - .build()?; + .build(); debugger.try_run()?; } else { - print_traces(&mut result, &decoder, verbose).await?; + print_traces(&mut result, &decoder).await?; } Ok(()) } -pub async fn print_traces( - result: &mut TraceResult, - decoder: &CallTraceDecoder, - verbose: bool, -) -> Result<()> { +pub async fn print_traces(result: &mut TraceResult, decoder: &CallTraceDecoder) -> Result<()> { if result.traces.is_empty() { panic!("No traces found") } println!("Traces:"); - for (_, trace) in &mut result.traces { - decoder.decode(trace).await; - if !verbose { - println!("{trace}"); - } else { - println!("{trace:#}"); - } + for (_, arena) in &result.traces { + println!("{}", render_trace_arena(arena, decoder).await?); } println!(); if result.success { - println!("{}", Paint::green("Transaction successfully executed.")); + println!("{}", "Transaction successfully executed.".green()); } else { - println!("{}", Paint::red("Transaction failed.")); + println!("{}", "Transaction failed.".red()); } println!("Gas used: {}", result.gas_used); diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 8a0e51ad9b78d..8f751728dee3d 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -1,21 +1,18 @@ use alloy_json_abi::JsonAbi; use alloy_primitives::U256; -use ethers_core::types::TransactionReceipt; -use ethers_providers::Middleware; +use alloy_provider::{network::AnyNetwork, Provider}; +use alloy_transport::Transport; use eyre::{ContextCompat, Result}; -use foundry_common::{types::ToAlloy, units::format_units}; use foundry_config::{Chain, Config}; use std::{ ffi::OsStr, future::Future, - ops::Mul, path::{Path, PathBuf}, process::{Command, Output, Stdio}, time::{Duration, SystemTime, UNIX_EPOCH}, }; use tracing_error::ErrorLayer; use tracing_subscriber::prelude::*; -use yansi::Paint; mod cmd; pub use cmd::*; @@ -23,6 +20,9 @@ pub use cmd::*; mod suggestions; pub use suggestions::*; +mod abi; +pub use abi::*; + // reexport all `foundry_config::utils` #[doc(hidden)] pub use foundry_config::utils::*; @@ -30,7 +30,7 @@ pub use foundry_config::utils::*; /// Deterministic fuzzer seed used for gas snapshots and coverage reports. /// /// The keccak256 hash of "foundry rulez" -pub static STATIC_FUZZ_SEED: [u8; 32] = [ +pub const STATIC_FUZZ_SEED: [u8; 32] = [ 0x01, 0x00, 0xfa, 0x69, 0xa5, 0xf1, 0x71, 0x0a, 0x95, 0xcd, 0xef, 0x94, 0x88, 0x9b, 0x02, 0x84, 0x5d, 0x64, 0x0b, 0x19, 0xad, 0xf0, 0xe3, 0x57, 0xb8, 0xd4, 0xbe, 0x7d, 0x49, 0xee, 0x70, 0xe6, ]; @@ -76,26 +76,24 @@ pub fn subscriber() { } pub fn abi_to_solidity(abi: &JsonAbi, name: &str) -> Result { - let s = abi.to_sol(name); + let s = abi.to_sol(name, None); let s = forge_fmt::format(&s)?; Ok(s) } -/// Returns a [RetryProvider](foundry_common::RetryProvider) instantiated using [Config]'s RPC URL -/// and chain. -/// -/// Defaults to `http://localhost:8545` and `Mainnet`. -pub fn get_provider(config: &Config) -> Result { +/// Returns a [RetryProvider](foundry_common::RetryProvider) instantiated using [Config]'s +/// RPC +pub fn get_provider(config: &Config) -> Result { get_provider_builder(config)?.build() } -/// Returns a [ProviderBuilder](foundry_common::ProviderBuilder) instantiated using [Config]'s RPC -/// URL and chain. +/// Returns a [ProviderBuilder](foundry_common::provider::ProviderBuilder) instantiated using +/// [Config] values. /// /// Defaults to `http://localhost:8545` and `Mainnet`. -pub fn get_provider_builder(config: &Config) -> Result { +pub fn get_provider_builder(config: &Config) -> Result { let url = config.get_rpc_url_or_localhost_http()?; - let mut builder = foundry_common::ProviderBuilder::new(url.as_ref()); + let mut builder = foundry_common::provider::ProviderBuilder::new(url.as_ref()); if let Ok(chain) = config.chain.unwrap_or_default().try_into() { builder = builder.chain(chain); @@ -109,14 +107,14 @@ pub fn get_provider_builder(config: &Config) -> Result(chain: Option, provider: M) -> Result +pub async fn get_chain(chain: Option, provider: P) -> Result where - M: Middleware, - M::Error: 'static, + P: Provider, + T: Transport + Clone, { match chain { Some(chain) => Ok(chain), - None => Ok(Chain::from_id(provider.get_chainid().await?.as_u64())), + None => Ok(Chain::from_id(provider.get_chain_id().await?)), } } @@ -207,50 +205,10 @@ pub fn load_dotenv() { }; } -/// Disables terminal colours if either: -/// - Running windows and the terminal does not support colour codes. -/// - Colour has been disabled by some environment variable. -/// - We are running inside a test +/// Sets the default [`yansi`] color output condition. pub fn enable_paint() { - let is_windows = cfg!(windows) && !Paint::enable_windows_ascii(); - let env_colour_disabled = std::env::var("NO_COLOR").is_ok(); - if is_windows || env_colour_disabled { - Paint::disable(); - } -} - -/// Prints parts of the receipt to stdout -pub fn print_receipt(chain: Chain, receipt: &TransactionReceipt) { - let gas_used = receipt.gas_used.unwrap_or_default(); - let gas_price = receipt.effective_gas_price.unwrap_or_default(); - foundry_common::shell::println(format!( - "\n##### {chain}\n{status}Hash: {tx_hash:?}{caddr}\nBlock: {bn}\n{gas}\n", - status = if receipt.status.map_or(true, |s| s.is_zero()) { - "❌ [Failed]" - } else { - "✅ [Success]" - }, - tx_hash = receipt.transaction_hash, - caddr = if let Some(addr) = &receipt.contract_address { - format!("\nContract Address: {}", addr.to_alloy().to_checksum(None)) - } else { - String::new() - }, - bn = receipt.block_number.unwrap_or_default(), - gas = if gas_price.is_zero() { - format!("Gas Used: {gas_used}") - } else { - let paid = format_units(gas_used.mul(gas_price).to_alloy(), 18) - .unwrap_or_else(|_| "N/A".into()); - let gas_price = format_units(gas_price.to_alloy(), 9).unwrap_or_else(|_| "N/A".into()); - format!( - "Paid: {} ETH ({gas_used} gas * {} gwei)", - paid.trim_end_matches('0'), - gas_price.trim_end_matches('0').trim_end_matches('.') - ) - }, - )) - .expect("could not print receipt"); + let enable = yansi::Condition::os_support() && yansi::Condition::tty_and_color_live(); + yansi::whenever(yansi::Condition::cached(enable)); } /// Useful extensions to [`std::process::Command`]. @@ -475,7 +433,7 @@ impl<'a> Git<'a> { output.status.code(), stdout.trim(), stderr.trim() - )) + )); } } Ok(()) @@ -540,6 +498,19 @@ https://github.com/foundry-rs/foundry/issues/new/choose" .map(|stdout| stdout.lines().any(|line| line.starts_with('-'))) } + /// Returns true if the given path has no submodules by checking `git submodule status` + pub fn has_submodules(self, paths: I) -> Result + where + I: IntoIterator, + S: AsRef, + { + self.cmd() + .args(["submodule", "status"]) + .args(paths) + .get_stdout_lossy() + .map(|stdout| stdout.trim().lines().next().is_some()) + } + pub fn submodule_add( self, force: bool, diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index ff7472d61dbaf..10f3cf6aab934 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -13,39 +13,56 @@ repository.workspace = true foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } foundry-compilers.workspace = true foundry-config.workspace = true - -ethers-core.workspace = true -ethers-middleware.workspace = true -ethers-providers = { workspace = true, features = ["ws", "ipc"] } +foundry-linking.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-json-abi.workspace = true -alloy-primitives = { workspace = true, features = ["serde", "getrandom", "arbitrary", "rlp"] } +alloy-primitives = { workspace = true, features = [ + "serde", + "getrandom", + "arbitrary", + "rlp", +] } +alloy-rpc-types-engine.workspace = true +alloy-rpc-types.workspace = true +alloy-rpc-client.workspace = true +alloy-provider.workspace = true +alloy-transport.workspace = true +alloy-transport-http = { workspace = true, features = [ + "reqwest", + "reqwest-rustls-tls", +] } +alloy-transport-ws.workspace = true +alloy-transport-ipc.workspace = true +alloy-json-rpc.workspace = true +alloy-pubsub.workspace = true alloy-sol-types.workspace = true +alloy-contract.workspace = true +alloy-consensus.workspace = true + +tower.workspace = true async-trait = "0.1" -auto_impl = "1.1.0" clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } comfy-table = "7" dunce = "1" eyre.workspace = true glob = "0.3" globset = "0.4" -hex.workspace = true once_cell = "1" -rand.workspace = true -regex = "1" -reqwest = { version = "0.11", default-features = false } +reqwest.workspace = true semver = "1" serde_json.workspace = true serde.workspace = true -tempfile = "3" +tempfile.workspace = true thiserror = "1" tokio = "1" tracing.workspace = true url = "2" walkdir = "2" -yansi = "0.5" +yansi.workspace = true +rustc-hash.workspace = true +num-format.workspace = true [dev-dependencies] foundry-macros.workspace = true diff --git a/crates/common/src/abi.rs b/crates/common/src/abi.rs index 5a166a20f09e0..6b7615b39c9f2 100644 --- a/crates/common/src/abi.rs +++ b/crates/common/src/abi.rs @@ -1,9 +1,9 @@ //! ABI related helper functions. use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt, JsonAbiExt}; -use alloy_json_abi::{AbiItem, Event, Function}; -use alloy_primitives::{hex, Address, Log}; -use eyre::{ContextCompat, Result}; +use alloy_json_abi::{Event, Function}; +use alloy_primitives::{hex, Address, LogData}; +use eyre::{Context, ContextCompat, Result}; use foundry_block_explorers::{contract::ContractMetadata, errors::EtherscanError, Client}; use foundry_config::Chain; use std::{future::Future, pin::Pin}; @@ -21,6 +21,23 @@ where func.abi_encode_input(params.as_slice()).map_err(Into::into) } +/// Given a function and a vector of string arguments, it proceeds to convert the args to alloy +/// [DynSolValue]s and encode them using the packed encoding. +pub fn encode_function_args_packed(func: &Function, args: I) -> Result> +where + I: IntoIterator, + S: AsRef, +{ + let params: Vec> = std::iter::zip(&func.inputs, args) + .map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref())) + .collect::>>()? + .into_iter() + .map(|v| v.abi_encode_packed()) + .collect(); + + Ok(params.concat()) +} + /// Decodes the calldata of the function pub fn abi_decode_calldata( sig: &str, @@ -28,7 +45,7 @@ pub fn abi_decode_calldata( input: bool, fn_selector: bool, ) -> Result> { - let func = Function::parse(sig)?; + let func = get_func(sig)?; let calldata = hex::decode(calldata)?; let mut calldata = calldata.as_slice(); @@ -51,62 +68,19 @@ pub fn abi_decode_calldata( Ok(res) } -/// Helper trait for converting types to Functions. Helpful for allowing the `call` -/// function on the EVM to be generic over `String`, `&str` and `Function`. -pub trait IntoFunction { - /// Consumes self and produces a function - /// - /// # Panic - /// - /// This function does not return a Result, so it is expected that the consumer - /// uses it correctly so that it does not panic. - fn into(self) -> Function; -} - -impl IntoFunction for Function { - fn into(self) -> Function { - self - } -} - -impl IntoFunction for String { - fn into(self) -> Function { - IntoFunction::into(self.as_str()) - } -} - -impl<'a> IntoFunction for &'a str { - fn into(self) -> Function { - Function::parse(self).expect("could not parse function") - } -} - /// Given a function signature string, it tries to parse it as a `Function` pub fn get_func(sig: &str) -> Result { - if let Ok(func) = Function::parse(sig) { - Ok(func) - } else { - // Try to parse as human readable ABI. - let item = match AbiItem::parse(sig) { - Ok(item) => match item { - AbiItem::Function(func) => func, - _ => return Err(eyre::eyre!("Expected function, got {:?}", item)), - }, - Err(e) => return Err(e.into()), - }; - Ok(item.into_owned().to_owned()) - } + Function::parse(sig).wrap_err("could not parse function signature") } /// Given an event signature string, it tries to parse it as a `Event` pub fn get_event(sig: &str) -> Result { - let sig = sig.strip_prefix("event").unwrap_or(sig).trim(); - Ok(Event::parse(sig)?) + Event::parse(sig).wrap_err("could not parse event signature") } /// Given an event without indexed parameters and a rawlog, it tries to return the event with the /// proper indexed parameters. Otherwise, it returns the original event. -pub fn get_indexed_event(mut event: Event, raw_log: &Log) -> Event { +pub fn get_indexed_event(mut event: Event, raw_log: &LogData) -> Event { if !event.anonymous && raw_log.topics().len() > 1 { let indexed_params = raw_log.topics().len() - 1; let num_inputs = event.inputs.len(); @@ -185,7 +159,7 @@ pub fn find_source( } /// Helper function to coerce a value to a [DynSolValue] given a type string -fn coerce_value(ty: &str, arg: &str) -> Result { +pub fn coerce_value(ty: &str, arg: &str) -> Result { let ty = DynSolType::parse(ty)?; Ok(DynSolType::coerce_str(&ty, arg)?) } @@ -224,7 +198,8 @@ mod tests { let param0 = B256::random(); let param1 = vec![3; 32]; let param2 = B256::random(); - let log = Log::new_unchecked(vec![event.selector(), param0, param2], param1.clone().into()); + let log = + LogData::new_unchecked(vec![event.selector(), param0, param2], param1.clone().into()); let event = get_indexed_event(event, &log); assert_eq!(event.inputs.len(), 3); @@ -245,7 +220,7 @@ mod tests { let param0 = B256::random(); let param1 = vec![3; 32]; let param2 = B256::random(); - let log = Log::new_unchecked( + let log = LogData::new_unchecked( vec![event.selector(), param0, B256::from_slice(¶m1), param2], vec![].into(), ); diff --git a/crates/common/src/calc.rs b/crates/common/src/calc.rs index e404c45201669..bde75635ce7fe 100644 --- a/crates/common/src/calc.rs +++ b/crates/common/src/calc.rs @@ -1,29 +1,28 @@ //! Commonly used calculations. use alloy_primitives::{Sign, U256}; -use std::ops::Div; -/// Returns the mean of the slice +/// Returns the mean of the slice. #[inline] -pub fn mean(values: &[U256]) -> U256 { +pub fn mean(values: &[u64]) -> u64 { if values.is_empty() { - return U256::ZERO + return 0; } - values.iter().copied().fold(U256::ZERO, |sum, val| sum + val).div(U256::from(values.len())) + (values.iter().map(|x| *x as u128).sum::() / values.len() as u128) as u64 } -/// Returns the median of a _sorted_ slice +/// Returns the median of a _sorted_ slice. #[inline] -pub fn median_sorted(values: &[U256]) -> U256 { +pub fn median_sorted(values: &[u64]) -> u64 { if values.is_empty() { - return U256::ZERO + return 0; } let len = values.len(); let mid = len / 2; if len % 2 == 0 { - (values[mid - 1] + values[mid]) / U256::from(2u64) + (values[mid - 1] + values[mid]) / 2 } else { values[mid] } @@ -70,49 +69,42 @@ mod tests { #[test] fn calc_mean_empty() { - let values: [U256; 0] = []; - let m = mean(&values); - assert_eq!(m, U256::ZERO); + let m = mean(&[]); + assert_eq!(m, 0); } #[test] fn calc_mean() { - let values = [ - U256::ZERO, - U256::from(1), - U256::from(2u64), - U256::from(3u64), - U256::from(4u64), - U256::from(5u64), - U256::from(6u64), - ]; - let m = mean(&values); - assert_eq!(m, U256::from(3u64)); + let m = mean(&[0, 1, 2, 3, 4, 5, 6]); + assert_eq!(m, 3); + } + + #[test] + fn calc_mean_overflow() { + let m = mean(&[0, 1, 2, u32::MAX as u64, 3, u16::MAX as u64, u64::MAX, 6]); + assert_eq!(m, 2305843009750573057); } #[test] fn calc_median_empty() { - let values: Vec = vec![]; - let m = median_sorted(&values); - assert_eq!(m, U256::from(0)); + let m = median_sorted(&[]); + assert_eq!(m, 0); } #[test] fn calc_median() { - let mut values = - vec![29, 30, 31, 40, 59, 61, 71].into_iter().map(U256::from).collect::>(); + let mut values = vec![29, 30, 31, 40, 59, 61, 71]; values.sort(); let m = median_sorted(&values); - assert_eq!(m, U256::from(40)); + assert_eq!(m, 40); } #[test] fn calc_median_even() { - let mut values = - vec![80, 90, 30, 40, 50, 60, 10, 20].into_iter().map(U256::from).collect::>(); + let mut values = vec![80, 90, 30, 40, 50, 60, 10, 20]; values.sort(); let m = median_sorted(&values); - assert_eq!(m, U256::from(45)); + assert_eq!(m, 45); } #[test] diff --git a/crates/common/src/clap_helpers.rs b/crates/common/src/clap_helpers.rs deleted file mode 100644 index f26455b764148..0000000000000 --- a/crates/common/src/clap_helpers.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Additional utils for clap - -/// A `clap` `value_parser` that removes a `0x` prefix if it exists -pub fn strip_0x_prefix(s: &str) -> Result { - Ok(s.strip_prefix("0x").unwrap_or(s).to_string()) -} diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index eb11f0ac167c2..3b1d83c182502 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -1,118 +1,216 @@ //! Support for compiling [foundry_compilers::Project] -use crate::{compact_to_contract, glob::GlobMatcher, term, TestFunctionExt}; -use comfy_table::{presets::ASCII_MARKDOWN, *}; -use eyre::Result; + +use crate::{compact_to_contract, glob::GlobMatcher, term::SpinnerReporter, TestFunctionExt}; +use comfy_table::{presets::ASCII_MARKDOWN, Attribute, Cell, CellAlignment, Color, Table}; +use eyre::{Context, Result}; use foundry_block_explorers::contract::Metadata; use foundry_compilers::{ - artifacts::{BytecodeObject, ContractBytecodeSome}, + artifacts::{BytecodeObject, ContractBytecodeSome, Libraries}, remappings::Remapping, - report::NoReporter, - Artifact, ArtifactId, FileFilter, Graph, Project, ProjectCompileOutput, ProjectPathsConfig, - Solc, SolcConfig, + report::{BasicStdoutReporter, NoReporter, Report}, + Artifact, ArtifactId, FileFilter, Project, ProjectCompileOutput, ProjectPathsConfig, Solc, + SolcConfig, }; +use foundry_linking::Linker; +use num_format::{Locale, ToFormattedString}; +use rustc_hash::FxHashMap; use std::{ collections::{BTreeMap, HashMap}, convert::Infallible, fmt::Display, + io::IsTerminal, path::{Path, PathBuf}, result, str::FromStr, + time::Instant, }; -/// Helper type to configure how to compile a project +/// Builder type to configure how to compile a project. /// -/// This is merely a wrapper for [Project::compile()] which also prints to stdout dependent on its -/// settings -#[derive(Debug, Clone, Default)] +/// This is merely a wrapper for [`Project::compile()`] which also prints to stdout depending on its +/// settings. +#[must_use = "ProjectCompiler does nothing unless you call a `compile*` method"] pub struct ProjectCompiler { - /// whether to also print the contract names - print_names: bool, - /// whether to also print the contract sizes - print_sizes: bool, - /// files to exclude - filters: Vec, + /// Whether we are going to verify the contracts after compilation. + verify: Option, + + /// Whether to also print contract names. + print_names: Option, + + /// Whether to also print contract sizes. + print_sizes: Option, + + /// Whether to print anything at all. Overrides other `print` options. + quiet: Option, + + /// Whether to bail on compiler errors. + bail: Option, + + /// Files to exclude. + filter: Option>, + + /// Extra files to include, that are not necessarily in the project's source dir. + files: Vec, +} + +impl Default for ProjectCompiler { + #[inline] + fn default() -> Self { + Self::new() + } } impl ProjectCompiler { - /// Create a new instance with the settings - pub fn new(print_names: bool, print_sizes: bool) -> Self { - Self::with_filter(print_names, print_sizes, Vec::new()) - } - - /// Create a new instance with all settings - pub fn with_filter( - print_names: bool, - print_sizes: bool, - filters: Vec, - ) -> Self { - Self { print_names, print_sizes, filters } - } - - /// Compiles the project with [`Project::compile()`] - pub fn compile(self, project: &Project) -> Result { - let filters = self.filters.clone(); - self.compile_with(project, |prj| { - let output = if filters.is_empty() { - prj.compile() - } else { - prj.compile_sparse(SkipBuildFilters(filters)) - }?; - Ok(output) - }) + /// Create a new builder with the default settings. + #[inline] + pub fn new() -> Self { + Self { + verify: None, + print_names: None, + print_sizes: None, + quiet: Some(crate::shell::verbosity().is_silent()), + bail: None, + filter: None, + files: Vec::new(), + } } - /// Compiles the project with [`Project::compile_parse()`] and the given filter. - /// - /// This will emit artifacts only for files that match the given filter. - /// Files that do _not_ match the filter are given a pruned output selection and do not generate - /// artifacts. - pub fn compile_sparse( - self, - project: &Project, - filter: F, - ) -> Result { - self.compile_with(project, |prj| Ok(prj.compile_sparse(filter)?)) + /// Sets whether we are going to verify the contracts after compilation. + #[inline] + pub fn verify(mut self, yes: bool) -> Self { + self.verify = Some(yes); + self + } + + /// Sets whether to print contract names. + #[inline] + pub fn print_names(mut self, yes: bool) -> Self { + self.print_names = Some(yes); + self + } + + /// Sets whether to print contract sizes. + #[inline] + pub fn print_sizes(mut self, yes: bool) -> Self { + self.print_sizes = Some(yes); + self + } + + /// Sets whether to print anything at all. Overrides other `print` options. + #[inline] + #[doc(alias = "silent")] + pub fn quiet(mut self, yes: bool) -> Self { + self.quiet = Some(yes); + self + } + + /// Do not print anything at all if true. Overrides other `print` options. + #[inline] + pub fn quiet_if(mut self, maybe: bool) -> Self { + if maybe { + self.quiet = Some(true); + } + self + } + + /// Sets whether to bail on compiler errors. + #[inline] + pub fn bail(mut self, yes: bool) -> Self { + self.bail = Some(yes); + self + } + + /// Sets the filter to use. + #[inline] + pub fn filter(mut self, filter: Box) -> Self { + self.filter = Some(filter); + self + } + + /// Sets extra files to include, that are not necessarily in the project's source dir. + #[inline] + pub fn files(mut self, files: impl IntoIterator) -> Self { + self.files.extend(files); + self + } + + /// Compiles the project. + pub fn compile(mut self, project: &Project) -> Result { + // TODO: Avoid process::exit + if !project.paths.has_input_files() && self.files.is_empty() { + println!("Nothing to compile"); + // nothing to do here + std::process::exit(0); + } + + // Taking is fine since we don't need these in `compile_with`. + let filter = std::mem::take(&mut self.filter); + let files = std::mem::take(&mut self.files); + self.compile_with(|| { + if !files.is_empty() { + project.compile_files(files) + } else if let Some(filter) = filter { + project.compile_sparse(filter) + } else { + project.compile() + } + .map_err(Into::into) + }) } /// Compiles the project with the given closure /// /// # Example /// - /// ```no_run + /// ```ignore /// use foundry_common::compile::ProjectCompiler; /// let config = foundry_config::Config::load(); - /// ProjectCompiler::default() - /// .compile_with(&config.project().unwrap(), |prj| Ok(prj.compile()?)) - /// .unwrap(); + /// let prj = config.project().unwrap(); + /// ProjectCompiler::new().compile_with(|| Ok(prj.compile()?)).unwrap(); /// ``` #[instrument(target = "forge::compile", skip_all)] - pub fn compile_with(self, project: &Project, f: F) -> Result + fn compile_with(self, f: F) -> Result where - F: FnOnce(&Project) -> Result, + F: FnOnce() -> Result, { - if !project.paths.has_input_files() { - println!("Nothing to compile"); - // nothing to do here - std::process::exit(0); - } + let quiet = self.quiet.unwrap_or(false); + let bail = self.bail.unwrap_or(true); + #[allow(clippy::collapsible_else_if)] + let reporter = if quiet { + Report::new(NoReporter::default()) + } else { + if std::io::stdout().is_terminal() { + Report::new(SpinnerReporter::spawn()) + } else { + Report::new(BasicStdoutReporter::default()) + } + }; - let now = std::time::Instant::now(); - trace!("start compiling project"); + let output = foundry_compilers::report::with_scoped(&reporter, || { + tracing::debug!("compiling project"); - let output = term::with_spinner_reporter(|| f(project))?; + let timer = Instant::now(); + let r = f(); + let elapsed = timer.elapsed(); - let elapsed = now.elapsed(); - trace!(?elapsed, "finished compiling"); + tracing::debug!("finished compiling in {:.3}s", elapsed.as_secs_f64()); + r + })?; - if output.has_compiler_errors() { - warn!("compiled with errors"); - eyre::bail!(output.to_string()) - } else if output.is_unchanged() { - println!("No files changed, compilation skipped"); - self.handle_output(&output); - } else { - // print the compiler output / warnings - println!("{output}"); + // need to drop the reporter here, so that the spinner terminates + drop(reporter); + + if bail && output.has_compiler_errors() { + eyre::bail!("{output}") + } + + if !quiet { + if output.is_unchanged() { + println!("No files changed, compilation skipped"); + } else { + // print the compiler output / warnings + println!("{output}"); + } self.handle_output(&output); } @@ -122,8 +220,11 @@ impl ProjectCompiler { /// If configured, this will print sizes or names fn handle_output(&self, output: &ProjectCompileOutput) { + let print_names = self.print_names.unwrap_or(false); + let print_sizes = self.print_sizes.unwrap_or(false); + // print any sizes or names - if self.print_names { + if print_names { let mut artifacts: BTreeMap<_, Vec<_>> = BTreeMap::new(); for (name, (_, version)) in output.versioned_artifacts() { artifacts.entry(version).or_default().push(name); @@ -138,22 +239,33 @@ impl ProjectCompiler { } } } - if self.print_sizes { + + if print_sizes { // add extra newline if names were already printed - if self.print_names { + if print_names { println!(); } + let mut size_report = SizeReport { contracts: BTreeMap::new() }; - let artifacts: BTreeMap<_, _> = output.artifacts().collect(); + + let artifacts: BTreeMap<_, _> = output + .artifact_ids() + .filter(|(id, _)| { + // filter out forge-std specific contracts + !id.source.to_string_lossy().contains("/forge-std/src/") + }) + .map(|(id, artifact)| (id.name, artifact)) + .collect(); + for (name, artifact) in artifacts { let size = deployed_contract_size(artifact).unwrap_or_default(); let dev_functions = artifact.abi.as_ref().map(|abi| abi.functions()).into_iter().flatten().filter( - |&func| { + |func| { func.name.is_test() || - func.name == "IS_TEST" || - func.name == "IS_SCRIPT" + func.name.eq("IS_TEST") || + func.name.eq("IS_SCRIPT") }, ); @@ -163,6 +275,7 @@ impl ProjectCompiler { println!("{size_report}"); + // TODO: avoid process::exit // exit with error if any contract exceeds the size limit, excluding test contracts. if size_report.exceeds_size_limit() { std::process::exit(1); @@ -171,16 +284,99 @@ impl ProjectCompiler { } } -/// Map over artifacts contract sources name -> file_id -> (source, contract) -#[derive(Default, Debug, Clone)] -pub struct ContractSources(pub HashMap>); +/// Contract source code and bytecode. +#[derive(Clone, Debug, Default)] +pub struct ContractSources { + /// Map over artifacts' contract names -> vector of file IDs + pub ids_by_name: HashMap>, + /// Map over file_id -> source code + pub sources_by_id: FxHashMap, + /// Map over file_id -> contract name -> bytecode + pub artifacts_by_id: FxHashMap>, +} + +impl ContractSources { + /// Collects the contract sources and artifacts from the project compile output. + pub fn from_project_output( + output: &ProjectCompileOutput, + root: &Path, + libraries: &Libraries, + ) -> Result { + let linker = Linker::new(root, output.artifact_ids().collect()); + + let mut sources = ContractSources::default(); + for (id, artifact) in output.artifact_ids() { + if let Some(file_id) = artifact.id { + let abs_path = root.join(&id.source); + let source_code = std::fs::read_to_string(abs_path).wrap_err_with(|| { + format!("failed to read artifact source file for `{}`", id.identifier()) + })?; + let linked = linker.link(&id, libraries)?; + let contract = compact_to_contract(linked)?; + sources.insert(&id, file_id, source_code, contract); + } else { + warn!(id = id.identifier(), "source not found"); + } + } + Ok(sources) + } + + /// Inserts a contract into the sources. + pub fn insert( + &mut self, + artifact_id: &ArtifactId, + file_id: u32, + source: String, + bytecode: ContractBytecodeSome, + ) { + self.ids_by_name.entry(artifact_id.name.clone()).or_default().push(file_id); + self.sources_by_id.insert(file_id, source); + self.artifacts_by_id.entry(file_id).or_default().insert(artifact_id.name.clone(), bytecode); + } + + /// Returns the source for a contract by file ID. + pub fn get(&self, id: u32) -> Option<&String> { + self.sources_by_id.get(&id) + } + + /// Returns all sources for a contract by name. + pub fn get_sources<'a>( + &'a self, + name: &'a str, + ) -> Option> { + self.ids_by_name.get(name).map(|ids| { + ids.iter().filter_map(|id| { + Some(( + *id, + self.sources_by_id.get(id)?.as_ref(), + self.artifacts_by_id.get(id)?.get(name)?, + )) + }) + }) + } + + /// Returns all (name, source, bytecode) sets. + pub fn entries(&self) -> impl Iterator { + self.artifacts_by_id + .iter() + .filter_map(|(id, artifacts)| { + let source = self.sources_by_id.get(id)?; + Some( + artifacts + .iter() + .map(move |(name, bytecode)| (name.as_ref(), source.as_ref(), bytecode)), + ) + }) + .flatten() + } +} // https://eips.ethereum.org/EIPS/eip-170 const CONTRACT_SIZE_LIMIT: usize = 24576; /// Contracts with info about their size pub struct SizeReport { - /// `:info>` + /// `contract name -> info` pub contracts: BTreeMap, } @@ -208,10 +404,11 @@ impl Display for SizeReport { table.load_preset(ASCII_MARKDOWN); table.set_header([ Cell::new("Contract").add_attribute(Attribute::Bold).fg(Color::Blue), - Cell::new("Size (kB)").add_attribute(Attribute::Bold).fg(Color::Blue), - Cell::new("Margin (kB)").add_attribute(Attribute::Bold).fg(Color::Blue), + Cell::new("Size (B)").add_attribute(Attribute::Bold).fg(Color::Blue), + Cell::new("Margin (B)").add_attribute(Attribute::Bold).fg(Color::Blue), ]); + // filters out non dev contracts (Test or Script) let contracts = self.contracts.iter().filter(|(_, c)| !c.is_dev_contract && c.size > 0); for (name, contract) in contracts { let margin = CONTRACT_SIZE_LIMIT as isize - contract.size as isize; @@ -221,10 +418,15 @@ impl Display for SizeReport { _ => Color::Red, }; + let locale = &Locale::en; table.add_row([ Cell::new(name).fg(color), - Cell::new(contract.size as f64 / 1000.0).fg(color), - Cell::new(margin as f64 / 1000.0).fg(color), + Cell::new(contract.size.to_formatted_string(locale)) + .set_alignment(CellAlignment::Right) + .fg(color), + Cell::new(margin.to_formatted_string(locale)) + .set_alignment(CellAlignment::Right) + .fg(color), ]); } @@ -253,7 +455,7 @@ pub fn deployed_contract_size(artifact: &T) -> Option { } /// How big the contract is and whether it is a dev contract where size limits can be neglected -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct ContractInfo { /// size of the contract in bytes pub size: usize, @@ -261,139 +463,9 @@ pub struct ContractInfo { pub is_dev_contract: bool, } -/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether -/// compilation was successful or if there was a cache hit. -pub fn compile( - project: &Project, - print_names: bool, - print_sizes: bool, -) -> Result { - ProjectCompiler::new(print_names, print_sizes).compile(project) -} - -/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether -/// compilation was successful or if there was a cache hit. -/// -/// Takes a list of [`SkipBuildFilter`] for files to exclude from the build. -pub fn compile_with_filter( - project: &Project, - print_names: bool, - print_sizes: bool, - skip: Vec, -) -> Result { - ProjectCompiler::with_filter(print_names, print_sizes, skip).compile(project) -} - -/// Compiles the provided [`Project`] and does not throw if there's any compiler error -/// Doesn't print anything to stdout, thus is "suppressed". -pub fn try_suppress_compile(project: &Project) -> Result { - Ok(foundry_compilers::report::with_scoped( - &foundry_compilers::report::Report::new(NoReporter::default()), - || project.compile(), - )?) -} - -/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether -/// compilation was successful or if there was a cache hit. -/// Doesn't print anything to stdout, thus is "suppressed". -pub fn suppress_compile(project: &Project) -> Result { - let output = try_suppress_compile(project)?; - - if output.has_compiler_errors() { - eyre::bail!(output.to_string()) - } - - Ok(output) -} - -/// Depending on whether the `skip` is empty this will [`suppress_compile_sparse`] or -/// [`suppress_compile`] and throw if there's any compiler error -pub fn suppress_compile_with_filter( - project: &Project, - skip: Vec, -) -> Result { - if skip.is_empty() { - suppress_compile(project) - } else { - suppress_compile_sparse(project, SkipBuildFilters(skip)) - } -} - -/// Depending on whether the `skip` is empty this will [`suppress_compile_sparse`] or -/// [`suppress_compile`] and does not throw if there's any compiler error -pub fn suppress_compile_with_filter_json( - project: &Project, - skip: Vec, -) -> Result { - if skip.is_empty() { - try_suppress_compile(project) - } else { - try_suppress_compile_sparse(project, SkipBuildFilters(skip)) - } -} - -/// Compiles the provided [`Project`], -/// Doesn't print anything to stdout, thus is "suppressed". -/// -/// See [`Project::compile_sparse`] -pub fn try_suppress_compile_sparse( - project: &Project, - filter: F, -) -> Result { - Ok(foundry_compilers::report::with_scoped( - &foundry_compilers::report::Report::new(NoReporter::default()), - || project.compile_sparse(filter), - )?) -} - -/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether -/// compilation was successful or if there was a cache hit. -/// Doesn't print anything to stdout, thus is "suppressed". -/// -/// See [`Project::compile_sparse`] -pub fn suppress_compile_sparse( - project: &Project, - filter: F, -) -> Result { - let output = try_suppress_compile_sparse(project, filter)?; - - if output.has_compiler_errors() { - eyre::bail!(output.to_string()) - } - - Ok(output) -} - -/// Compile a set of files not necessarily included in the `project`'s source dir -/// -/// If `silent` no solc related output will be emitted to stdout -pub fn compile_files( - project: &Project, - files: Vec, - silent: bool, -) -> Result { - let output = if silent { - foundry_compilers::report::with_scoped( - &foundry_compilers::report::Report::new(NoReporter::default()), - || project.compile_files(files), - ) - } else { - term::with_spinner_reporter(|| project.compile_files(files)) - }?; - - if output.has_compiler_errors() { - eyre::bail!(output.to_string()) - } - if !silent { - println!("{output}"); - } - - Ok(output) -} - /// Compiles target file path. /// -/// If `silent` no solc related output will be emitted to stdout. +/// If `quiet` no solc related output will be emitted to stdout. /// /// If `verify` and it's a standalone script, throw error. Only allowed for projects. /// @@ -401,35 +473,9 @@ pub fn compile_files( pub fn compile_target( target_path: &Path, project: &Project, - silent: bool, - verify: bool, -) -> Result { - compile_target_with_filter(target_path, project, silent, verify, Vec::new()) -} - -/// Compiles target file path. -pub fn compile_target_with_filter( - target_path: &Path, - project: &Project, - silent: bool, - verify: bool, - skip: Vec, + quiet: bool, ) -> Result { - let graph = Graph::resolve(&project.paths)?; - - // Checking if it's a standalone script, or part of a project. - if graph.files().get(target_path).is_none() { - if verify { - eyre::bail!("You can only verify deployments from inside a project! Make sure it exists with `forge tree`."); - } - return compile_files(project, vec![target_path.to_path_buf()], silent) - } - - if silent { - suppress_compile_with_filter(project, skip) - } else { - compile_with_filter(project, false, false, skip) - } + ProjectCompiler::new().quiet(quiet).files([target_path.into()]).compile(project) } /// Compiles an Etherscan source from metadata by creating a project. @@ -444,7 +490,7 @@ pub async fn compile_from_source( let project_output = project.compile()?; if project_output.has_compiler_errors() { - eyre::bail!(project_output.to_string()) + eyre::bail!("{project_output}") } let (artifact_id, file_id, contract) = project_output @@ -453,7 +499,12 @@ pub async fn compile_from_source( .map(|(aid, art)| { (aid, art.source_file().expect("no source file").id, art.into_contract_bytecode()) }) - .expect("there should be a contract with bytecode"); + .ok_or_else(|| { + eyre::eyre!( + "Unable to find bytecode in compiled output for contract: {}", + metadata.contract_name + ) + })?; let bytecode = compact_to_contract(contract)?; root.close()?; @@ -508,18 +559,41 @@ pub fn etherscan_project(metadata: &Metadata, target_path: impl AsRef) -> } /// Bundles multiple `SkipBuildFilter` into a single `FileFilter` -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct SkipBuildFilters(pub Vec); +#[derive(Clone, Debug)] +pub struct SkipBuildFilters { + /// All provided filters. + pub matchers: Vec, + /// Root of the project. + pub project_root: PathBuf, +} impl FileFilter for SkipBuildFilters { /// Only returns a match if _no_ exclusion filter matches fn is_match(&self, file: &Path) -> bool { - self.0.iter().all(|filter| filter.is_match(file)) + self.matchers.iter().all(|matcher| { + if !is_match_exclude(matcher, file) { + false + } else { + file.strip_prefix(&self.project_root) + .map_or(true, |stripped| is_match_exclude(matcher, stripped)) + } + }) + } +} + +impl SkipBuildFilters { + /// Creates a new `SkipBuildFilters` from multiple `SkipBuildFilter`. + pub fn new( + filters: impl IntoIterator, + project_root: PathBuf, + ) -> Result { + let matchers = filters.into_iter().map(|m| m.compile()).collect::>(); + matchers.map(|filters| Self { matchers: filters, project_root }) } } /// A filter that excludes matching contracts from the build -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum SkipBuildFilter { /// Exclude all `.t.sol` contracts Tests, @@ -530,6 +604,14 @@ pub enum SkipBuildFilter { } impl SkipBuildFilter { + fn new(s: &str) -> Self { + match s { + "test" | "tests" => SkipBuildFilter::Tests, + "script" | "scripts" => SkipBuildFilter::Scripts, + s => SkipBuildFilter::Custom(s.to_string()), + } + } + /// Returns the pattern to match against a file fn file_pattern(&self) -> &str { match self { @@ -538,15 +620,9 @@ impl SkipBuildFilter { SkipBuildFilter::Custom(s) => s.as_str(), } } -} -impl> From for SkipBuildFilter { - fn from(s: T) -> Self { - match s.as_ref() { - "test" | "tests" => SkipBuildFilter::Tests, - "script" | "scripts" => SkipBuildFilter::Scripts, - s => SkipBuildFilter::Custom(s.to_string()), - } + fn compile(&self) -> Result { + self.file_pattern().parse().map_err(Into::into) } } @@ -554,23 +630,20 @@ impl FromStr for SkipBuildFilter { type Err = Infallible; fn from_str(s: &str) -> result::Result { - Ok(s.into()) + Ok(Self::new(s)) } } -impl FileFilter for SkipBuildFilter { - /// Matches file only if the filter does not apply - /// - /// This is returns the inverse of `file.name.contains(pattern) || matcher.is_match(file)` - fn is_match(&self, file: &Path) -> bool { - fn exclude(file: &Path, pattern: &str) -> Option { - let matcher: GlobMatcher = pattern.parse().unwrap(); - let file_name = file.file_name()?.to_str()?; - Some(file_name.contains(pattern) || matcher.is_match(file.as_os_str().to_str()?)) - } - - !exclude(file, self.file_pattern()).unwrap_or_default() +/// Matches file only if the filter does not apply. +/// +/// This returns the inverse of `file.name.contains(pattern) || matcher.is_match(file)`. +fn is_match_exclude(matcher: &GlobMatcher, path: &Path) -> bool { + fn is_match(matcher: &GlobMatcher, path: &Path) -> Option { + let file_name = path.file_name()?.to_str()?; + Some(file_name.contains(matcher.as_str()) || matcher.is_match(path)) } + + !is_match(matcher, path).unwrap_or_default() } #[cfg(test)] @@ -579,19 +652,24 @@ mod tests { #[test] fn test_build_filter() { + let tests = SkipBuildFilter::Tests.compile().unwrap(); + let scripts = SkipBuildFilter::Scripts.compile().unwrap(); + let custom = |s: &str| SkipBuildFilter::Custom(s.to_string()).compile().unwrap(); + let file = Path::new("A.t.sol"); - assert!(!SkipBuildFilter::Tests.is_match(file)); - assert!(SkipBuildFilter::Scripts.is_match(file)); - assert!(!SkipBuildFilter::Custom("A.t".to_string()).is_match(file)); + assert!(!is_match_exclude(&tests, file)); + assert!(is_match_exclude(&scripts, file)); + assert!(!is_match_exclude(&custom("A.t"), file)); let file = Path::new("A.s.sol"); - assert!(SkipBuildFilter::Tests.is_match(file)); - assert!(!SkipBuildFilter::Scripts.is_match(file)); - assert!(!SkipBuildFilter::Custom("A.s".to_string()).is_match(file)); + assert!(is_match_exclude(&tests, file)); + assert!(!is_match_exclude(&scripts, file)); + assert!(!is_match_exclude(&custom("A.s"), file)); let file = Path::new("/home/test/Foo.sol"); - assert!(!SkipBuildFilter::Custom("*/test/**".to_string()).is_match(file)); + assert!(!is_match_exclude(&custom("*/test/**"), file)); + let file = Path::new("/home/script/Contract.sol"); - assert!(!SkipBuildFilter::Custom("*/script/**".to_string()).is_match(file)); + assert!(!is_match_exclude(&custom("*/script/**"), file)); } } diff --git a/crates/common/src/constants.rs b/crates/common/src/constants.rs index 23ca0abab43f2..0ba0514c2b87b 100644 --- a/crates/common/src/constants.rs +++ b/crates/common/src/constants.rs @@ -38,7 +38,7 @@ pub const ARBITRUM_SENDER: Address = address!("000000000000000000000000000000000 pub const OPTIMISM_SYSTEM_ADDRESS: Address = address!("deaddeaddeaddeaddeaddeaddeaddeaddead0001"); /// Transaction identifier of System transaction types -pub const SYSTEM_TRANSACTION_TYPE: u64 = 126u64; +pub const SYSTEM_TRANSACTION_TYPE: u8 = 126; /// Returns whether the sender is a known L2 system sender that is the first tx in every block. /// diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 652db9531d1cc..1627e79587c91 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -1,36 +1,89 @@ //! Commonly used contract types and functions. -use alloy_json_abi::{Event, Function, JsonAbi as Abi}; -use alloy_primitives::{hex, Address, B256}; +use alloy_json_abi::{Event, Function, JsonAbi}; +use alloy_primitives::{Address, Bytes, Selector, B256}; +use eyre::Result; use foundry_compilers::{ artifacts::{CompactContractBytecode, ContractBytecodeSome}, - ArtifactId, ProjectPathsConfig, + ArtifactId, }; -use once_cell::sync::Lazy; -use regex::Regex; use std::{ collections::BTreeMap, ops::{Deref, DerefMut}, - path::PathBuf, }; -type ArtifactWithContractRef<'a> = (&'a ArtifactId, &'a (Abi, Vec)); +/// Container for commonly used contract data. +#[derive(Debug, Clone)] +pub struct ContractData { + /// Contract name. + pub name: String, + /// Contract ABI. + pub abi: JsonAbi, + /// Contract creation code. + pub bytecode: Option, + /// Contract runtime code. + pub deployed_bytecode: Option, +} + +type ArtifactWithContractRef<'a> = (&'a ArtifactId, &'a ContractData); /// Wrapper type that maps an artifact to a contract ABI and bytecode. -#[derive(Default, Clone)] -pub struct ContractsByArtifact(pub BTreeMap)>); +#[derive(Clone, Default, Debug)] +pub struct ContractsByArtifact(pub BTreeMap); impl ContractsByArtifact { + /// Creates a new instance by collecting all artifacts with present bytecode from an iterator. + /// + /// It is recommended to use this method with an output of + /// [foundry_linking::Linker::get_linked_artifacts]. + pub fn new(artifacts: impl IntoIterator) -> Self { + Self( + artifacts + .into_iter() + .filter_map(|(id, artifact)| { + let name = id.name.clone(); + let bytecode = artifact.bytecode.and_then(|b| b.into_bytes())?; + let deployed_bytecode = + artifact.deployed_bytecode.and_then(|b| b.into_bytes())?; + + // Exclude artifacts with present but empty bytecode. Such artifacts are usually + // interfaces and abstract contracts. + let bytecode = (bytecode.len() > 0).then_some(bytecode); + let deployed_bytecode = + (deployed_bytecode.len() > 0).then_some(deployed_bytecode); + let abi = artifact.abi?; + + Some((id, ContractData { name, abi, bytecode, deployed_bytecode })) + }) + .collect(), + ) + } + /// Finds a contract which has a similar bytecode as `code`. - pub fn find_by_code(&self, code: &[u8]) -> Option { - self.iter().find(|(_, (_, known_code))| diff_score(known_code, code) < 0.1) + pub fn find_by_creation_code(&self, code: &[u8]) -> Option { + self.iter().find(|(_, contract)| { + if let Some(bytecode) = &contract.bytecode { + bytecode_diff_score(bytecode.as_ref(), code) <= 0.1 + } else { + false + } + }) } + + /// Finds a contract which has a similar deployed bytecode as `code`. + pub fn find_by_deployed_code(&self, code: &[u8]) -> Option { + self.iter().find(|(_, contract)| { + if let Some(deployed_bytecode) = &contract.deployed_bytecode { + bytecode_diff_score(deployed_bytecode.as_ref(), code) <= 0.1 + } else { + false + } + }) + } + /// Finds a contract which has the same contract name or identifier as `id`. If more than one is /// found, return error. - pub fn find_by_name_or_identifier( - &self, - id: &str, - ) -> eyre::Result> { + pub fn find_by_name_or_identifier(&self, id: &str) -> Result> { let contracts = self .iter() .filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id) @@ -43,41 +96,28 @@ impl ContractsByArtifact { Ok(contracts.first().cloned()) } - /// Flattens a group of contracts into maps of all events and functions - pub fn flatten(&self) -> (BTreeMap<[u8; 4], Function>, BTreeMap, Abi) { - let flattened_funcs: BTreeMap<[u8; 4], Function> = self - .iter() - .flat_map(|(_name, (abi, _code))| { - abi.functions() - .map(|func| (func.selector().into(), func.clone())) - .collect::>() - }) - .collect(); - - let flattened_events: BTreeMap = self - .iter() - .flat_map(|(_name, (abi, _code))| { - abi.events() - .map(|event| (event.selector(), event.clone())) - .collect::>() - }) - .collect(); - - // We need this for better revert decoding, and want it in abi form - let mut errors_abi = Abi::default(); - self.iter().for_each(|(_name, (abi, _code))| { - abi.errors().for_each(|error| { - let entry = - errors_abi.errors.entry(error.name.clone()).or_insert_with(Default::default); - entry.push(error.clone()); - }); - }); - (flattened_funcs, flattened_events, errors_abi) + /// Flattens the contracts into functions, events and errors. + pub fn flatten(&self) -> (BTreeMap, BTreeMap, JsonAbi) { + let mut funcs = BTreeMap::new(); + let mut events = BTreeMap::new(); + let mut errors_abi = JsonAbi::new(); + for (_name, contract) in self.iter() { + for func in contract.abi.functions() { + funcs.insert(func.selector(), func.clone()); + } + for event in contract.abi.events() { + events.insert(event.selector(), event.clone()); + } + for error in contract.abi.errors() { + errors_abi.errors.entry(error.name.clone()).or_default().push(error.clone()); + } + } + (funcs, events, errors_abi) } } impl Deref for ContractsByArtifact { - type Target = BTreeMap)>; + type Target = BTreeMap; fn deref(&self) -> &Self::Target { &self.0 @@ -91,50 +131,57 @@ impl DerefMut for ContractsByArtifact { } /// Wrapper type that maps an address to a contract identifier and contract ABI. -pub type ContractsByAddress = BTreeMap; +pub type ContractsByAddress = BTreeMap; /// Very simple fuzzy matching of contract bytecode. /// -/// Will fail for small contracts that are essentially all immutable variables. -pub fn diff_score(a: &[u8], b: &[u8]) -> f64 { - let cutoff_len = usize::min(a.len(), b.len()); - if cutoff_len == 0 { - return 1.0 +/// Returns a value between `0.0` (identical) and `1.0` (completely different). +pub fn bytecode_diff_score<'a>(mut a: &'a [u8], mut b: &'a [u8]) -> f64 { + // Make sure `a` is the longer one. + if a.len() < b.len() { + std::mem::swap(&mut a, &mut b); } - let a = &a[..cutoff_len]; - let b = &b[..cutoff_len]; - let mut diff_chars = 0; - for i in 0..cutoff_len { - if a[i] != b[i] { - diff_chars += 1; - } + // Account for different lengths. + let mut n_different_bytes = a.len() - b.len(); + + // If the difference is more than 32 bytes and more than 10% of the total length, + // we assume the bytecodes are completely different. + // This is a simple heuristic to avoid checking every byte when the lengths are very different. + // 32 is chosen to be a reasonable minimum as it's the size of metadata hashes and one EVM word. + if n_different_bytes > 32 && n_different_bytes * 10 > a.len() { + return 1.0; } - diff_chars as f64 / cutoff_len as f64 + + // Count different bytes. + // SAFETY: `a` is longer than `b`. + n_different_bytes += unsafe { count_different_bytes(a, b) }; + + n_different_bytes as f64 / a.len() as f64 } -/// Flattens the contracts into (`id` -> (`Abi`, `Vec`)) pairs -pub fn flatten_contracts( - contracts: &BTreeMap, - deployed_code: bool, -) -> ContractsByArtifact { - ContractsByArtifact( - contracts - .iter() - .filter_map(|(id, c)| { - let bytecode = if deployed_code { - c.deployed_bytecode.clone().into_bytes() - } else { - c.bytecode.clone().object.into_bytes() - }; - - if let Some(bytecode) = bytecode { - return Some((id.clone(), (c.abi.clone(), bytecode.to_vec()))) - } - None - }) - .collect(), - ) +/// Returns the amount of different bytes between two slices. +/// +/// # Safety +/// +/// `a` must be at least as long as `b`. +unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize { + // This could've been written as `std::iter::zip(a, b).filter(|(x, y)| x != y).count()`, + // however this function is very hot, and has been written to be as primitive as + // possible for lower optimization levels. + + let a_ptr = a.as_ptr(); + let b_ptr = b.as_ptr(); + let len = b.len(); + + let mut sum = 0; + let mut i = 0; + while i < len { + // SAFETY: `a` is at least as long as `b`, and `i` is in bound of `b`. + sum += unsafe { *a_ptr.add(i) != *b_ptr.add(i) } as usize; + i += 1; + } + sum } /// Artifact/Contract identifier can take the following form: @@ -172,57 +219,8 @@ pub fn get_file_name(id: &str) -> &str { id.split(':').next().unwrap_or(id) } -/// Returns the path to the json artifact depending on the input -pub fn get_artifact_path(paths: &ProjectPathsConfig, path: &str) -> PathBuf { - if path.ends_with(".json") { - PathBuf::from(path) - } else { - let parts: Vec<&str> = path.split(':').collect(); - let file = parts[0]; - let contract_name = - if parts.len() == 1 { parts[0].replace(".sol", "") } else { parts[1].to_string() }; - paths.artifacts.join(format!("{file}/{contract_name}.json")) - } -} - -/// Given the transaction data tries to identify the constructor arguments -/// The constructor data is encoded as: Constructor Code + Contract Code + Constructor arguments -/// decoding the arguments here with only the transaction data is not trivial here, we try to find -/// the beginning of the constructor arguments by finding the length of the code, which is PUSH op -/// code which holds the code size and the code starts after the invalid op code (0xfe) -/// -/// finding the `0xfe` (invalid opcode) in the data which should mark the beginning of constructor -/// arguments -pub fn find_constructor_args(data: &[u8]) -> Option<&[u8]> { - // ref - static CONSTRUCTOR_CODE_RE: Lazy = Lazy::new(|| { - Regex::new(r"(?m)(?:5b)?(?:60([a-z0-9]{2})|61([a-z0-9_]{4})|62([a-z0-9_]{6}))80(?:60([a-z0-9]{2})|61([a-z0-9_]{4})|62([a-z0-9_]{6}))(6000396000f3fe)").unwrap() - }); - let s = hex::encode(data); - - // we're only interested in the last occurrence which skips additional CREATE inside the - // constructor itself - let caps = CONSTRUCTOR_CODE_RE.captures_iter(&s).last()?; - - let contract_len = u64::from_str_radix( - caps.get(1).or_else(|| caps.get(2)).or_else(|| caps.get(3))?.as_str(), - 16, - ) - .unwrap(); - - // the end position of the constructor code, we use this instead of the contract offset , since - // there could be multiple CREATE inside the data we need to divide by 2 for hex conversion - let constructor_end = (caps.get(7)?.end() / 2) as u64; - let start = (contract_len + constructor_end) as usize; - let args = &data[start..]; - - Some(args) -} - /// Helper function to convert CompactContractBytecode ~> ContractBytecodeSome -pub fn compact_to_contract( - contract: CompactContractBytecode, -) -> eyre::Result { +pub fn compact_to_contract(contract: CompactContractBytecode) -> Result { Ok(ContractBytecodeSome { abi: contract.abi.ok_or_else(|| eyre::eyre!("No contract abi"))?, bytecode: contract.bytecode.ok_or_else(|| eyre::eyre!("No contract bytecode"))?.into(), @@ -236,44 +234,20 @@ pub fn compact_to_contract( #[cfg(test)] mod tests { use super::*; - use alloy_dyn_abi::DynSolType; - - // - #[test] - fn test_find_constructor_args() { - let code = "6080604052348015600f57600080fd5b50604051610121380380610121833981016040819052602c91606e565b600080546001600160a01b0319166001600160a01b0396909616959095179094556001929092556002556003556004805460ff191691151591909117905560d4565b600080600080600060a08688031215608557600080fd5b85516001600160a01b0381168114609b57600080fd5b809550506020860151935060408601519250606086015191506080860151801515811460c657600080fd5b809150509295509295909350565b603f806100e26000396000f3fe6080604052600080fdfea264697066735822122089f2c61beace50d105ec1b6a56a1204301b5595e850e7576f6f3aa8e76f12d0b64736f6c6343000810003300000000000000000000000000a329c0648769a73afac7f9381e08fb43dbea720000000000000000000000000000000000000000000000000000000100000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60000000000000000000000000000000000000000000000000000000000000001"; - - let code = hex::decode(code).unwrap(); - - let args = find_constructor_args(&code).unwrap(); - - let params = DynSolType::Tuple(vec![ - DynSolType::Address, - DynSolType::Uint(256), - DynSolType::Int(256), - DynSolType::FixedBytes(32), - DynSolType::Bool, - ]); - let _decoded = params.abi_decode_params(args).unwrap(); - } #[test] - fn test_find_constructor_args_nested_deploy() { - let code = "608060405234801561001057600080fd5b5060405161066d38038061066d83398101604081905261002f9161014a565b868686868686866040516100429061007c565b610052979695949392919061022f565b604051809103906000f08015801561006e573d6000803e3d6000fd5b50505050505050505061028a565b610396806102d783390190565b634e487b7160e01b600052604160045260246000fd5b60005b838110156100ba5781810151838201526020016100a2565b50506000910152565b600082601f8301126100d457600080fd5b81516001600160401b03808211156100ee576100ee610089565b604051601f8301601f19908116603f0116810190828211818310171561011657610116610089565b8160405283815286602085880101111561012f57600080fd5b61014084602083016020890161009f565b9695505050505050565b600080600080600080600060e0888a03121561016557600080fd5b87516001600160a01b038116811461017c57600080fd5b80975050602088015195506040880151945060608801519350608088015180151581146101a857600080fd5b60a08901519093506001600160401b03808211156101c557600080fd5b6101d18b838c016100c3565b935060c08a01519150808211156101e757600080fd5b506101f48a828b016100c3565b91505092959891949750929550565b6000815180845261021b81602086016020860161009f565b601f01601f19169290920160200192915050565b60018060a01b0388168152866020820152856040820152846060820152831515608082015260e060a0820152600061026a60e0830185610203565b82810360c084015261027c8185610203565b9a9950505050505050505050565b603f806102986000396000f3fe6080604052600080fdfea264697066735822122072aeef1567521008007b956bd7c6e9101a9b49fbce1f45210fa929c79d28bd9364736f6c63430008110033608060405234801561001057600080fd5b5060405161039638038061039683398101604081905261002f91610148565b600080546001600160a01b0319166001600160a01b0389161790556001869055600285905560038490556004805460ff19168415151790556005610073838261028a565b506006610080828261028a565b5050505050505050610349565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126100b457600080fd5b81516001600160401b03808211156100ce576100ce61008d565b604051601f8301601f19908116603f011681019082821181831017156100f6576100f661008d565b8160405283815260209250868385880101111561011257600080fd5b600091505b838210156101345785820183015181830184015290820190610117565b600093810190920192909252949350505050565b600080600080600080600060e0888a03121561016357600080fd5b87516001600160a01b038116811461017a57600080fd5b80975050602088015195506040880151945060608801519350608088015180151581146101a657600080fd5b60a08901519093506001600160401b03808211156101c357600080fd5b6101cf8b838c016100a3565b935060c08a01519150808211156101e557600080fd5b506101f28a828b016100a3565b91505092959891949750929550565b600181811c9082168061021557607f821691505b60208210810361023557634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111561028557600081815260208120601f850160051c810160208610156102625750805b601f850160051c820191505b818110156102815782815560010161026e565b5050505b505050565b81516001600160401b038111156102a3576102a361008d565b6102b7816102b18454610201565b8461023b565b602080601f8311600181146102ec57600084156102d45750858301515b600019600386901b1c1916600185901b178555610281565b600085815260208120601f198616915b8281101561031b578886015182559484019460019091019084016102fc565b50858210156103395787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b603f806103576000396000f3fe6080604052600080fdfea2646970667358221220a468ac913d3ecf191b6559ae7dca58e05ba048434318f393b86640b25cbbf1ed64736f6c6343000811003300000000000000000000000000a329c0648769a73afac7f9381e08fb43dbea720000000000000000000000000000000000000000000000000000000100000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000066162636465660000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000568656c6c6f000000000000000000000000000000000000000000000000000000"; - - let code = hex::decode(code).unwrap(); - - let args = find_constructor_args(&code).unwrap(); - - let params = DynSolType::Tuple(vec![ - DynSolType::Address, - DynSolType::Uint(256), - DynSolType::Int(256), - DynSolType::FixedBytes(32), - DynSolType::Bool, - DynSolType::Bytes, - DynSolType::String, - ]); - let _decoded = params.abi_decode_params(args).unwrap(); + fn bytecode_diffing() { + assert_eq!(bytecode_diff_score(b"a", b"a"), 0.0); + assert_eq!(bytecode_diff_score(b"a", b"b"), 1.0); + + let a_100 = &b"a".repeat(100)[..]; + assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(100)), 1.0); + assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(99)), 1.0); + assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(101)), 1.0); + assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(120)), 1.0); + assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(1000)), 1.0); + + let a_99 = &b"a".repeat(99)[..]; + assert!(bytecode_diff_score(a_100, a_99) <= 0.01); } } diff --git a/crates/common/src/ens.rs b/crates/common/src/ens.rs new file mode 100644 index 0000000000000..b96ecba8d8742 --- /dev/null +++ b/crates/common/src/ens.rs @@ -0,0 +1,237 @@ +//! ENS Name resolving utilities. + +#![allow(missing_docs)] + +use self::EnsResolver::EnsResolverInstance; +use alloy_primitives::{address, Address, Keccak256, B256}; +use alloy_provider::{Network, Provider}; +use alloy_sol_types::sol; +use alloy_transport::Transport; +use async_trait::async_trait; +use std::{borrow::Cow, str::FromStr}; + +// ENS Registry and Resolver contracts. +sol! { + /// ENS Registry contract. + #[sol(rpc)] + contract EnsRegistry { + /// Returns the resolver for the specified node. + function resolver(bytes32 node) view returns (address); + } + + /// ENS Resolver interface. + #[sol(rpc)] + contract EnsResolver { + /// Returns the address associated with the specified node. + function addr(bytes32 node) view returns (address); + + /// Returns the name associated with an ENS node, for reverse records. + function name(bytes32 node) view returns (string); + } +} + +/// ENS registry address (`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`) +pub const ENS_ADDRESS: Address = address!("00000000000C2E074eC69A0dFb2997BA6C7d2e1e"); + +pub const ENS_REVERSE_REGISTRAR_DOMAIN: &str = "addr.reverse"; + +/// Error type for ENS resolution. +#[derive(Debug, thiserror::Error)] +pub enum EnsError { + /// Failed to get resolver from the ENS registry. + #[error("Failed to get resolver from the ENS registry: {0}")] + Resolver(alloy_contract::Error), + /// Failed to get resolver from the ENS registry. + #[error("ENS resolver not found for name {0:?}")] + ResolverNotFound(String), + /// Failed to lookup ENS name from an address. + #[error("Failed to lookup ENS name from an address: {0}")] + Lookup(alloy_contract::Error), + /// Failed to resolve ENS name to an address. + #[error("Failed to resolve ENS name to an address: {0}")] + Resolve(alloy_contract::Error), +} + +/// ENS name or Ethereum Address. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum NameOrAddress { + /// An ENS Name (format does not get checked) + Name(String), + /// An Ethereum Address + Address(Address), +} + +impl NameOrAddress { + /// Resolves the name to an Ethereum Address. + pub async fn resolve>( + &self, + provider: &P, + ) -> Result { + match self { + NameOrAddress::Name(name) => provider.resolve_name(name).await, + NameOrAddress::Address(addr) => Ok(*addr), + } + } +} + +impl From for NameOrAddress { + fn from(name: String) -> Self { + NameOrAddress::Name(name) + } +} + +impl From<&String> for NameOrAddress { + fn from(name: &String) -> Self { + NameOrAddress::Name(name.clone()) + } +} + +impl From
for NameOrAddress { + fn from(addr: Address) -> Self { + NameOrAddress::Address(addr) + } +} + +impl FromStr for NameOrAddress { + type Err =
::Err; + + fn from_str(s: &str) -> Result { + if let Ok(addr) = Address::from_str(s) { + Ok(NameOrAddress::Address(addr)) + } else { + Ok(NameOrAddress::Name(s.to_string())) + } + } +} + +/// Extension trait for ENS contract calls. +#[async_trait] +pub trait ProviderEnsExt> { + /// Returns the resolver for the specified node. The `&str` is only used for error messages. + async fn get_resolver( + &self, + node: B256, + error_name: &str, + ) -> Result, EnsError>; + + /// Performs a forward lookup of an ENS name to an address. + async fn resolve_name(&self, name: &str) -> Result { + let node = namehash(name); + let resolver = self.get_resolver(node, name).await?; + let addr = resolver + .addr(node) + .call() + .await + .map_err(EnsError::Resolve) + .inspect_err(|e| eprintln!("{e:?}"))? + ._0; + Ok(addr) + } + + /// Performs a reverse lookup of an address to an ENS name. + async fn lookup_address(&self, address: &Address) -> Result { + let name = reverse_address(address); + let node = namehash(&name); + let resolver = self.get_resolver(node, &name).await?; + let name = resolver.name(node).call().await.map_err(EnsError::Lookup)?._0; + Ok(name) + } +} + +#[async_trait] +impl ProviderEnsExt for P +where + P: Provider, + N: Network, + T: Transport + Clone, +{ + async fn get_resolver( + &self, + node: B256, + error_name: &str, + ) -> Result, EnsError> { + let registry = EnsRegistry::new(ENS_ADDRESS, self); + let address = registry.resolver(node).call().await.map_err(EnsError::Resolver)?._0; + if address == Address::ZERO { + return Err(EnsError::ResolverNotFound(error_name.to_string())); + } + Ok(EnsResolverInstance::new(address, self)) + } +} + +/// Returns the ENS namehash as specified in [EIP-137](https://eips.ethereum.org/EIPS/eip-137) +pub fn namehash(name: &str) -> B256 { + if name.is_empty() { + return B256::ZERO + } + + // Remove the variation selector `U+FE0F` if present. + const VARIATION_SELECTOR: char = '\u{fe0f}'; + let name = if name.contains(VARIATION_SELECTOR) { + Cow::Owned(name.replace(VARIATION_SELECTOR, "")) + } else { + Cow::Borrowed(name) + }; + + // Generate the node starting from the right. + // This buffer is `[node @ [u8; 32], label_hash @ [u8; 32]]`. + let mut buffer = [0u8; 64]; + for label in name.rsplit('.') { + // node = keccak256([node, keccak256(label)]) + + // Hash the label. + let mut label_hasher = Keccak256::new(); + label_hasher.update(label.as_bytes()); + label_hasher.finalize_into(&mut buffer[32..]); + + // Hash both the node and the label hash, writing into the node. + let mut buffer_hasher = Keccak256::new(); + buffer_hasher.update(buffer.as_slice()); + buffer_hasher.finalize_into(&mut buffer[..32]); + } + buffer[..32].try_into().unwrap() +} + +/// Returns the reverse-registrar name of an address. +pub fn reverse_address(addr: &Address) -> String { + format!("{addr:x}.{ENS_REVERSE_REGISTRAR_DOMAIN}") +} + +#[cfg(test)] +mod test { + use super::*; + use alloy_primitives::hex; + + fn assert_hex(hash: B256, val: &str) { + assert_eq!(hash.0[..], hex::decode(val).unwrap()[..]); + } + + #[test] + fn test_namehash() { + for (name, expected) in &[ + ("", "0x0000000000000000000000000000000000000000000000000000000000000000"), + ("eth", "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae"), + ("foo.eth", "0xde9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f"), + ("alice.eth", "0x787192fc5378cc32aa956ddfdedbf26b24e8d78e40109add0eea2c1a012c3dec"), + ("ret↩️rn.eth", "0x3de5f4c02db61b221e7de7f1c40e29b6e2f07eb48d65bf7e304715cd9ed33b24"), + ] { + assert_hex(namehash(name), expected); + } + } + + #[test] + fn test_reverse_address() { + for (addr, expected) in [ + ( + "0x314159265dd8dbb310642f98f50c066173c1259b", + "314159265dd8dbb310642f98f50c066173c1259b.addr.reverse", + ), + ( + "0x28679A1a632125fbBf7A68d850E50623194A709E", + "28679a1a632125fbbf7a68d850e50623194a709e.addr.reverse", + ), + ] { + assert_eq!(reverse_address(&addr.parse().unwrap()), expected, "{addr}"); + } + } +} diff --git a/crates/common/src/evm.rs b/crates/common/src/evm.rs index 830ae35da3d45..a0cc4d4b802f3 100644 --- a/crates/common/src/evm.rs +++ b/crates/common/src/evm.rs @@ -11,11 +11,11 @@ use foundry_config::{ }, Chain, Config, }; +use rustc_hash::FxHashMap; use serde::Serialize; -use std::collections::HashMap; /// Map keyed by breakpoints char to their location (contract address, pc) -pub type Breakpoints = HashMap; +pub type Breakpoints = FxHashMap; /// `EvmArgs` and `EnvArgs` take the highest precedence in the Config/Figment hierarchy. /// All vars are opt-in, their default values are expected to be set by the @@ -38,34 +38,34 @@ pub type Breakpoints = HashMap; /// let opts = figment.extract::().unwrap(); /// # } /// ``` -#[derive(Debug, Clone, Default, Parser, Serialize)] -#[clap(next_help_heading = "EVM options", about = None, long_about = None)] // override doc +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "EVM options", about = None, long_about = None)] // override doc pub struct EvmArgs { /// Fetch state over a remote endpoint instead of starting from an empty state. /// /// If you want to fetch state from a specific block number, see --fork-block-number. - #[clap(long, short, visible_alias = "rpc-url", value_name = "URL")] + #[arg(long, short, visible_alias = "rpc-url", value_name = "URL")] #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")] pub fork_url: Option, /// Fetch state from a specific block number over a remote endpoint. /// /// See --fork-url. - #[clap(long, requires = "fork_url", value_name = "BLOCK")] + #[arg(long, requires = "fork_url", value_name = "BLOCK")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_block_number: Option, /// Number of retries. /// /// See --fork-url. - #[clap(long, requires = "fork_url", value_name = "RETRIES")] + #[arg(long, requires = "fork_url", value_name = "RETRIES")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_retries: Option, /// Initial retry backoff on encountering errors. /// /// See --fork-url. - #[clap(long, requires = "fork_url", value_name = "BACKOFF")] + #[arg(long, requires = "fork_url", value_name = "BACKOFF")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_retry_backoff: Option, @@ -76,25 +76,30 @@ pub struct EvmArgs { /// This flag overrides the project's configuration file. /// /// See --fork-url. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub no_storage_caching: bool, /// The initial balance of deployed test contracts. - #[clap(long, value_name = "BALANCE")] + #[arg(long, value_name = "BALANCE")] #[serde(skip_serializing_if = "Option::is_none")] pub initial_balance: Option, /// The address which will be executing tests. - #[clap(long, value_name = "ADDRESS")] + #[arg(long, value_name = "ADDRESS")] #[serde(skip_serializing_if = "Option::is_none")] pub sender: Option
, /// Enable the FFI cheatcode. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub ffi: bool, + /// Use the create 2 factory in all cases including tests and non-broadcasting scripts. + #[arg(long)] + #[serde(skip)] + pub always_use_create_2_factory: bool, + /// Verbosity of the EVM. /// /// Pass multiple times to increase the verbosity (e.g. -v, -vv, -vvv). @@ -104,7 +109,7 @@ pub struct EvmArgs { /// - 3: Print execution traces for failing tests /// - 4: Print execution traces for all tests, and setup traces for failing tests /// - 5: Print execution and setup traces for all tests - #[clap(long, short, verbatim_doc_comment, action = ArgAction::Count)] + #[arg(long, short, verbatim_doc_comment, action = ArgAction::Count)] #[serde(skip)] pub verbosity: u8, @@ -113,21 +118,14 @@ pub struct EvmArgs { /// default value: 330 /// /// See also --fork-url and https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second - #[clap( - long, - requires = "fork_url", - alias = "cups", - value_name = "CUPS", - help_heading = "Fork config" - )] + #[arg(long, alias = "cups", value_name = "CUPS", help_heading = "Fork config")] pub compute_units_per_second: Option, /// Disables rate limiting for this node's provider. /// /// See also --fork-url and https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second - #[clap( + #[arg( long, - requires = "fork_url", value_name = "NO_RATE_LIMITS", help_heading = "Fork config", visible_alias = "no-rate-limit" @@ -136,9 +134,16 @@ pub struct EvmArgs { pub no_rpc_rate_limit: bool, /// All ethereum environment related arguments - #[clap(flatten)] + #[command(flatten)] #[serde(flatten)] pub env: EnvArgs, + + /// Whether to enable isolation of calls. + /// In isolation mode all top-level calls are executed as a separate transaction in a separate + /// EVM context, enabling more precise gas accounting and transaction state changes. + #[arg(long)] + #[serde(skip)] + pub isolate: bool, } // Make this set of options a `figment::Provider` so that it can be merged into the `Config` @@ -161,6 +166,17 @@ impl Provider for EvmArgs { dict.insert("ffi".to_string(), self.ffi.into()); } + if self.isolate { + dict.insert("isolate".to_string(), self.isolate.into()); + } + + if self.always_use_create_2_factory { + dict.insert( + "always_use_create_2_factory".to_string(), + self.always_use_create_2_factory.into(), + ); + } + if self.no_storage_caching { dict.insert("no_storage_caching".to_string(), self.no_storage_caching.into()); } @@ -178,67 +194,67 @@ impl Provider for EvmArgs { } /// Configures the executor environment during tests. -#[derive(Debug, Clone, Default, Parser, Serialize)] -#[clap(next_help_heading = "Executor environment config")] +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Executor environment config")] pub struct EnvArgs { /// The block gas limit. - #[clap(long, value_name = "GAS_LIMIT")] + #[arg(long, value_name = "GAS_LIMIT")] #[serde(skip_serializing_if = "Option::is_none")] pub gas_limit: Option, /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. By /// default, it is 0x6000 (~25kb). - #[clap(long, value_name = "CODE_SIZE")] + #[arg(long, value_name = "CODE_SIZE")] #[serde(skip_serializing_if = "Option::is_none")] pub code_size_limit: Option, /// The chain name or EIP-155 chain ID. - #[clap(long, visible_alias = "chain-id", value_name = "CHAIN")] + #[arg(long, visible_alias = "chain-id", value_name = "CHAIN")] #[serde(rename = "chain_id", skip_serializing_if = "Option::is_none", serialize_with = "id")] pub chain: Option, /// The gas price. - #[clap(long, value_name = "GAS_PRICE")] + #[arg(long, value_name = "GAS_PRICE")] #[serde(skip_serializing_if = "Option::is_none")] pub gas_price: Option, /// The base fee in a block. - #[clap(long, visible_alias = "base-fee", value_name = "FEE")] + #[arg(long, visible_alias = "base-fee", value_name = "FEE")] #[serde(skip_serializing_if = "Option::is_none")] pub block_base_fee_per_gas: Option, /// The transaction origin. - #[clap(long, value_name = "ADDRESS")] + #[arg(long, value_name = "ADDRESS")] #[serde(skip_serializing_if = "Option::is_none")] pub tx_origin: Option
, /// The coinbase of the block. - #[clap(long, value_name = "ADDRESS")] + #[arg(long, value_name = "ADDRESS")] #[serde(skip_serializing_if = "Option::is_none")] pub block_coinbase: Option
, /// The timestamp of the block. - #[clap(long, value_name = "TIMESTAMP")] + #[arg(long, value_name = "TIMESTAMP")] #[serde(skip_serializing_if = "Option::is_none")] pub block_timestamp: Option, /// The block number. - #[clap(long, value_name = "BLOCK")] + #[arg(long, value_name = "BLOCK")] #[serde(skip_serializing_if = "Option::is_none")] pub block_number: Option, /// The block difficulty. - #[clap(long, value_name = "DIFFICULTY")] + #[arg(long, value_name = "DIFFICULTY")] #[serde(skip_serializing_if = "Option::is_none")] pub block_difficulty: Option, /// The block prevrandao value. NOTE: Before merge this field was mix_hash. - #[clap(long, value_name = "PREVRANDAO")] + #[arg(long, value_name = "PREVRANDAO")] #[serde(skip_serializing_if = "Option::is_none")] pub block_prevrandao: Option, /// The block gas limit. - #[clap(long, value_name = "GAS_LIMIT")] + #[arg(long, value_name = "GAS_LIMIT")] #[serde(skip_serializing_if = "Option::is_none")] pub block_gas_limit: Option, @@ -246,9 +262,13 @@ pub struct EnvArgs { /// If this limit is exceeded, a `MemoryLimitOOG` result is thrown. /// /// The default is 128MiB. - #[clap(long, value_name = "MEMORY_LIMIT")] + #[arg(long, value_name = "MEMORY_LIMIT")] #[serde(skip_serializing_if = "Option::is_none")] pub memory_limit: Option, + + /// Whether to disable the block gas limit checks. + #[arg(long, visible_alias = "no-gas-limit")] + pub disable_block_gas_limit: bool, } impl EvmArgs { diff --git a/crates/common/src/fmt/mod.rs b/crates/common/src/fmt/mod.rs index ea03a98274d9a..e6a4a73211ec0 100644 --- a/crates/common/src/fmt/mod.rs +++ b/crates/common/src/fmt/mod.rs @@ -23,7 +23,7 @@ pub use ui::{get_pretty_block_attr, get_pretty_tx_attr, get_pretty_tx_receipt_at /// use alloy_primitives::U256; /// use foundry_common::fmt::format_uint_exp as f; /// -/// # yansi::Paint::disable(); +/// # yansi::disable(); /// assert_eq!(f(U256::from(0)), "0"); /// assert_eq!(f(U256::from(1234)), "1234"); /// assert_eq!(f(U256::from(1234567890)), "1234567890 [1.234e9]"); @@ -36,7 +36,7 @@ pub fn format_uint_exp(num: U256) -> String { } let exp = to_exp_notation(num, 4, true, Sign::Positive); - format!("{num} {}", Paint::default(format!("[{exp}]")).dimmed()) + format!("{num} {}", format!("[{exp}]").dim()) } /// Formats a U256 number to string, adding an exponential notation _hint_. @@ -49,7 +49,7 @@ pub fn format_uint_exp(num: U256) -> String { /// use alloy_primitives::I256; /// use foundry_common::fmt::format_int_exp as f; /// -/// # yansi::Paint::disable(); +/// # yansi::disable(); /// assert_eq!(f(I256::try_from(0).unwrap()), "0"); /// assert_eq!(f(I256::try_from(-1).unwrap()), "-1"); /// assert_eq!(f(I256::try_from(1234).unwrap()), "1234"); @@ -72,5 +72,5 @@ pub fn format_int_exp(num: I256) -> String { } let exp = to_exp_notation(abs, 4, true, sign); - format!("{sign}{abs} {}", Paint::default(format!("[{exp}]")).dimmed()) + format!("{sign}{abs} {}", format!("[{exp}]").dim()) } diff --git a/crates/common/src/fmt/ui.rs b/crates/common/src/fmt/ui.rs index 98f6968686d68..3893fb4c9d40f 100644 --- a/crates/common/src/fmt/ui.rs +++ b/crates/common/src/fmt/ui.rs @@ -1,8 +1,12 @@ //! Helper trait and functions to format Ethereum types. use crate::TransactionReceiptWithRevertReason; +use alloy_consensus::{AnyReceiptEnvelope, Receipt, ReceiptWithBloom, TxType}; use alloy_primitives::*; -use ethers_core::types::{Block, Log, OtherFields, Transaction, TransactionReceipt, TxHash}; +use alloy_rpc_types::{ + other::OtherFields, AnyTransactionReceipt, Block, BlockTransactions, Log, Transaction, + TransactionReceipt, +}; use serde::Deserialize; /// length of the name column for pretty formatting `{:>20}{value}` @@ -23,6 +27,12 @@ pub trait UIfmt { fn pretty(&self) -> String; } +impl UIfmt for &T { + fn pretty(&self) -> String { + (*self).pretty() + } +} + impl UIfmt for Option { fn pretty(&self) -> String { if let Some(ref inner) = self { @@ -33,7 +43,7 @@ impl UIfmt for Option { } } -impl UIfmt for Vec { +impl UIfmt for [T] { fn pretty(&self) -> String { if !self.is_empty() { let mut s = String::with_capacity(self.len() * 64); @@ -59,13 +69,25 @@ impl UIfmt for String { } } +impl UIfmt for u64 { + fn pretty(&self) -> String { + self.to_string() + } +} + +impl UIfmt for u128 { + fn pretty(&self) -> String { + self.to_string() + } +} + impl UIfmt for bool { fn pretty(&self) -> String { self.to_string() } } -impl UIfmt for U256 { +impl UIfmt for Uint { fn pretty(&self) -> String { self.to_string() } @@ -89,6 +111,12 @@ impl UIfmt for Bloom { } } +impl UIfmt for TxType { + fn pretty(&self) -> String { + (*self as u8).to_string() + } +} + impl UIfmt for Vec { fn pretty(&self) -> String { self[..].pretty() @@ -119,21 +147,48 @@ impl UIfmt for [u8] { } } -impl UIfmt for U64 { - fn pretty(&self) -> String { - self.to_string() - } +pub fn pretty_status(status: bool) -> String { + if status { "1 (success)" } else { "0 (failed)" }.to_string() } -impl UIfmt for TransactionReceipt { +impl UIfmt for AnyTransactionReceipt { fn pretty(&self) -> String { - format!( + let Self { + inner: + TransactionReceipt { + transaction_hash, + transaction_index, + block_hash, + block_number, + from, + to, + gas_used, + contract_address, + state_root, + effective_gas_price, + inner: + AnyReceiptEnvelope { + r#type: transaction_type, + inner: + ReceiptWithBloom { + receipt: Receipt { status, cumulative_gas_used, logs }, + logs_bloom, + }, + }, + blob_gas_price, + blob_gas_used, + }, + other, + } = self; + + let mut pretty = format!( " blockHash {} blockNumber {} contractAddress {} cumulativeGasUsed {} effectiveGasPrice {} +from {} gasUsed {} logs {} logsBloom {} @@ -141,21 +196,37 @@ root {} status {} transactionHash {} transactionIndex {} -type {}", - self.block_hash.pretty(), - self.block_number.pretty(), - self.contract_address.pretty(), - self.cumulative_gas_used.pretty(), - self.effective_gas_price.pretty(), - self.gas_used.pretty(), - serde_json::to_string(&self.logs).unwrap(), - self.logs_bloom.pretty(), - self.root.pretty(), - self.status.pretty(), - self.transaction_hash.pretty(), - self.transaction_index.pretty(), - self.transaction_type.pretty() - ) +type {} +blobGasPrice {} +blobGasUsed {}", + block_hash.pretty(), + block_number.pretty(), + contract_address.pretty(), + cumulative_gas_used.pretty(), + effective_gas_price.pretty(), + from.pretty(), + gas_used.pretty(), + serde_json::to_string(&logs).unwrap(), + logs_bloom.pretty(), + state_root.pretty(), + pretty_status(*status), + transaction_hash.pretty(), + transaction_index.pretty(), + transaction_type, + blob_gas_price.pretty(), + blob_gas_used.pretty(), + ); + + if let Some(to) = to { + pretty.push_str(&format!("\nto {}", to.pretty())); + } + + // additional captured fields + for (key, val) in other.iter() { + pretty.push_str(&format!("\n{} {}", key, val)); + } + + pretty } } @@ -172,40 +243,38 @@ removed: {} topics: {} transactionHash: {} transactionIndex: {}", - self.address.pretty(), + self.address().pretty(), self.block_hash.pretty(), self.block_number.pretty(), - self.data.pretty(), + self.data().data.pretty(), self.log_index.pretty(), self.removed.pretty(), - self.topics.pretty(), + self.topics().pretty(), self.transaction_hash.pretty(), self.transaction_index.pretty(), ) } } -impl UIfmt for Block { +impl UIfmt for Block { fn pretty(&self) -> String { format!( " {} -transactions {}", +transactions: {}", pretty_block_basics(self), self.transactions.pretty() ) } } -impl UIfmt for Block { +impl UIfmt for BlockTransactions { fn pretty(&self) -> String { - format!( - " -{} -transactions: {}", - pretty_block_basics(self), - self.transactions.pretty() - ) + match self { + BlockTransactions::Hashes(hashes) => hashes.pretty(), + BlockTransactions::Full(transactions) => transactions.pretty(), + BlockTransactions::Uncle => String::new(), + } } } @@ -252,12 +321,12 @@ value {}{}", self.gas_price.pretty(), self.hash.pretty(), self.input.pretty(), - self.nonce.pretty(), - to_bytes(self.r).pretty(), - to_bytes(self.s).pretty(), + self.nonce, + self.signature.map(|s| s.r.to_be_bytes_vec()).pretty(), + self.signature.map(|s| s.s.to_be_bytes_vec()).pretty(), self.to.pretty(), self.transaction_index.pretty(), - self.v.pretty(), + self.signature.map(|s| s.v).pretty(), self.value.pretty(), self.other.pretty() ) @@ -280,7 +349,7 @@ revertReason {}", } /// Various numerical ethereum types used for pretty printing -#[derive(Debug, Clone, Deserialize)] +#[derive(Clone, Debug, Deserialize)] #[serde(untagged)] #[allow(missing_docs)] pub enum EthValue { @@ -309,38 +378,6 @@ impl UIfmt for EthValue { } } -// TODO: replace these above and remove this module once types are converted -mod temp_ethers { - use super::UIfmt; - use ethers_core::types::{Address, Bloom, Bytes, H256, H64, I256, U256, U64}; - use foundry_common::types::ToAlloy; - - macro_rules! with_alloy { - ($($t:ty),*) => {$( - impl UIfmt for $t { - fn pretty(&self) -> String { - self.to_alloy().pretty() - } - } - )*}; - } - - impl UIfmt for Bytes { - fn pretty(&self) -> String { - self.clone().to_alloy().pretty() - } - } - - with_alloy!(Address, Bloom, H64, H256, I256, U256, U64); -} - -/// Convert a U256 to bytes -pub fn to_bytes(uint: ethers_core::types::U256) -> [u8; 32] { - let mut buffer: [u8; 32] = [0; 32]; - uint.to_big_endian(&mut buffer); - buffer -} - /// Returns the `UiFmt::pretty()` formatted attribute of the transactions pub fn get_pretty_tx_attr(transaction: &Transaction, attr: &str) -> Option { match attr { @@ -351,12 +388,12 @@ pub fn get_pretty_tx_attr(transaction: &Transaction, attr: &str) -> Option Some(transaction.gas_price.pretty()), "hash" => Some(transaction.hash.pretty()), "input" => Some(transaction.input.pretty()), - "nonce" => Some(transaction.nonce.pretty()), - "s" => Some(to_bytes(transaction.s).pretty()), - "r" => Some(to_bytes(transaction.r).pretty()), + "nonce" => Some(transaction.nonce.to_string()), + "s" => transaction.signature.map(|s| B256::from(s.s).pretty()), + "r" => transaction.signature.map(|s| B256::from(s.r).pretty()), "to" => Some(transaction.to.pretty()), "transactionIndex" | "transaction_index" => Some(transaction.transaction_index.pretty()), - "v" => Some(transaction.v.pretty()), + "v" => transaction.signature.map(|s| s.v.pretty()), "value" => Some(transaction.value.pretty()), other => { if let Some(value) = transaction.other.get(other) { @@ -377,48 +414,50 @@ pub fn get_pretty_tx_receipt_attr( "blockNumber" | "block_number" => Some(receipt.receipt.block_number.pretty()), "contractAddress" | "contract_address" => Some(receipt.receipt.contract_address.pretty()), "cumulativeGasUsed" | "cumulative_gas_used" => { - Some(receipt.receipt.cumulative_gas_used.pretty()) + Some(receipt.receipt.inner.inner.inner.receipt.cumulative_gas_used.pretty()) } "effectiveGasPrice" | "effective_gas_price" => { - Some(receipt.receipt.effective_gas_price.pretty()) + Some(receipt.receipt.effective_gas_price.to_string()) + } + "gasUsed" | "gas_used" => Some(receipt.receipt.gas_used.to_string()), + "logs" => Some(receipt.receipt.inner.inner.inner.receipt.logs.as_slice().pretty()), + "logsBloom" | "logs_bloom" => Some(receipt.receipt.inner.inner.inner.logs_bloom.pretty()), + "root" | "stateRoot" | "state_root " => Some(receipt.receipt.state_root.pretty()), + "status" | "statusCode" | "status_code" => { + Some(pretty_status(receipt.receipt.inner.inner.inner.receipt.status)) } - "gasUsed" | "gas_used" => Some(receipt.receipt.gas_used.pretty()), - "logs" => Some(receipt.receipt.logs.pretty()), - "logsBloom" | "logs_bloom" => Some(receipt.receipt.logs_bloom.pretty()), - "root" => Some(receipt.receipt.root.pretty()), - "status" => Some(receipt.receipt.status.pretty()), "transactionHash" | "transaction_hash" => Some(receipt.receipt.transaction_hash.pretty()), "transactionIndex" | "transaction_index" => { Some(receipt.receipt.transaction_index.pretty()) } - "type" | "transaction_type" => Some(receipt.receipt.transaction_type.pretty()), + "type" | "transaction_type" => Some(receipt.receipt.inner.inner.r#type.to_string()), "revertReason" | "revert_reason" => Some(receipt.revert_reason.pretty()), _ => None, } } /// Returns the `UiFmt::pretty()` formatted attribute of the given block -pub fn get_pretty_block_attr(block: &Block, attr: &str) -> Option { +pub fn get_pretty_block_attr(block: &Block, attr: &str) -> Option { match attr { - "baseFeePerGas" | "base_fee_per_gas" => Some(block.base_fee_per_gas.pretty()), - "difficulty" => Some(block.difficulty.pretty()), - "extraData" | "extra_data" => Some(block.extra_data.pretty()), - "gasLimit" | "gas_limit" => Some(block.gas_limit.pretty()), - "gasUsed" | "gas_used" => Some(block.gas_used.pretty()), - "hash" => Some(block.hash.pretty()), - "logsBloom" | "logs_bloom" => Some(block.logs_bloom.pretty()), - "miner" | "author" => Some(block.author.pretty()), - "mixHash" | "mix_hash" => Some(block.mix_hash.pretty()), - "nonce" => Some(block.nonce.pretty()), - "number" => Some(block.number.pretty()), - "parentHash" | "parent_hash" => Some(block.parent_hash.pretty()), - "receiptsRoot" | "receipts_root" => Some(block.receipts_root.pretty()), - "sealFields" | "seal_fields" => Some(block.seal_fields.pretty()), - "sha3Uncles" | "sha_3_uncles" => Some(block.uncles_hash.pretty()), + "baseFeePerGas" | "base_fee_per_gas" => Some(block.header.base_fee_per_gas.pretty()), + "difficulty" => Some(block.header.difficulty.pretty()), + "extraData" | "extra_data" => Some(block.header.extra_data.pretty()), + "gasLimit" | "gas_limit" => Some(block.header.gas_limit.pretty()), + "gasUsed" | "gas_used" => Some(block.header.gas_used.pretty()), + "hash" => Some(block.header.hash.pretty()), + "logsBloom" | "logs_bloom" => Some(block.header.logs_bloom.pretty()), + "miner" | "author" => Some(block.header.miner.pretty()), + "mixHash" | "mix_hash" => Some(block.header.mix_hash.pretty()), + "nonce" => Some(block.header.nonce.pretty()), + "number" => Some(block.header.number.pretty()), + "parentHash" | "parent_hash" => Some(block.header.parent_hash.pretty()), + "transactionsRoot" | "transactions_root" => Some(block.header.transactions_root.pretty()), + "receiptsRoot" | "receipts_root" => Some(block.header.receipts_root.pretty()), + "sha3Uncles" | "sha_3_uncles" => Some(block.header.uncles_hash.pretty()), "size" => Some(block.size.pretty()), - "stateRoot" | "state_root" => Some(block.state_root.pretty()), - "timestamp" => Some(block.timestamp.pretty()), - "totalDifficulty" | "total_difficult" => Some(block.total_difficulty.pretty()), + "stateRoot" | "state_root" => Some(block.header.state_root.pretty()), + "timestamp" => Some(block.header.timestamp.pretty()), + "totalDifficulty" | "total_difficult" => Some(block.header.total_difficulty.pretty()), other => { if let Some(value) = block.other.get(other) { let val = EthValue::from(value.clone()); @@ -429,7 +468,7 @@ pub fn get_pretty_block_attr(block: &Block, attr: &str) -> Option(block: &Block) -> String { +fn pretty_block_basics(block: &Block) -> String { format!( " baseFeePerGas {} @@ -444,34 +483,34 @@ mixHash {} nonce {} number {} parentHash {} +transactionsRoot {} receiptsRoot {} -sealFields {} sha3Uncles {} size {} stateRoot {} timestamp {} withdrawalsRoot {} totalDifficulty {}{}", - block.base_fee_per_gas.pretty(), - block.difficulty.pretty(), - block.extra_data.pretty(), - block.gas_limit.pretty(), - block.gas_used.pretty(), - block.hash.pretty(), - block.logs_bloom.pretty(), - block.author.pretty(), - block.mix_hash.pretty(), - block.nonce.pretty(), - block.number.pretty(), - block.parent_hash.pretty(), - block.receipts_root.pretty(), - block.seal_fields.pretty(), - block.uncles_hash.pretty(), + block.header.base_fee_per_gas.pretty(), + block.header.difficulty.pretty(), + block.header.extra_data.pretty(), + block.header.gas_limit.pretty(), + block.header.gas_used.pretty(), + block.header.hash.pretty(), + block.header.logs_bloom.pretty(), + block.header.miner.pretty(), + block.header.mix_hash.pretty(), + block.header.nonce.pretty(), + block.header.number.pretty(), + block.header.parent_hash.pretty(), + block.header.transactions_root.pretty(), + block.header.receipts_root.pretty(), + block.header.uncles_hash.pretty(), block.size.pretty(), - block.state_root.pretty(), - block.timestamp.pretty(), - block.withdrawals_root.pretty(), - block.total_difficulty.pretty(), + block.header.state_root.pretty(), + block.header.timestamp.pretty(), + block.header.withdrawals_root.pretty(), + block.header.total_difficulty.pretty(), block.other.pretty() ) } @@ -558,7 +597,7 @@ txType 0 #[test] fn print_block_w_txs() { let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#; - let block: Block = serde_json::from_str(block).unwrap(); + let block: Block = serde_json::from_str(block).unwrap(); let output ="\nblockHash 0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972 blockNumber 3 from 0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a @@ -573,7 +612,11 @@ to 0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e transactionIndex 0 v 37 value 0".to_string(); - let generated = block.transactions[0].pretty(); + let txs = match block.transactions { + BlockTransactions::Full(txs) => txs, + _ => panic!("not full transactions"), + }; + let generated = txs[0].pretty(); assert_eq!(generated.as_str(), output.as_str()); } @@ -621,45 +664,40 @@ value 0".to_string(); #[test] fn test_pretty_tx_attr() { let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#; - let block: Block = serde_json::from_str(block).unwrap(); - assert_eq!(None, get_pretty_tx_attr(&block.transactions[0], "")); - assert_eq!( - Some("3".to_string()), - get_pretty_tx_attr(&block.transactions[0], "blockNumber") - ); + let block: Block = serde_json::from_str(block).unwrap(); + let txs = match block.transactions { + BlockTransactions::Full(txes) => txes, + _ => panic!("not full transactions"), + }; + assert_eq!(None, get_pretty_tx_attr(&txs[0], "")); + assert_eq!(Some("3".to_string()), get_pretty_tx_attr(&txs[0], "blockNumber")); assert_eq!( Some("0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a".to_string()), - get_pretty_tx_attr(&block.transactions[0], "from") - ); - assert_eq!(Some("90000".to_string()), get_pretty_tx_attr(&block.transactions[0], "gas")); - assert_eq!( - Some("20000000000".to_string()), - get_pretty_tx_attr(&block.transactions[0], "gasPrice") + get_pretty_tx_attr(&txs[0], "from") ); + assert_eq!(Some("90000".to_string()), get_pretty_tx_attr(&txs[0], "gas")); + assert_eq!(Some("20000000000".to_string()), get_pretty_tx_attr(&txs[0], "gasPrice")); assert_eq!( Some("0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067".to_string()), - get_pretty_tx_attr(&block.transactions[0], "hash") + get_pretty_tx_attr(&txs[0], "hash") ); - assert_eq!(Some("0x".to_string()), get_pretty_tx_attr(&block.transactions[0], "input")); - assert_eq!(Some("2".to_string()), get_pretty_tx_attr(&block.transactions[0], "nonce")); + assert_eq!(Some("0x".to_string()), get_pretty_tx_attr(&txs[0], "input")); + assert_eq!(Some("2".to_string()), get_pretty_tx_attr(&txs[0], "nonce")); assert_eq!( Some("0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88".to_string()), - get_pretty_tx_attr(&block.transactions[0], "r") + get_pretty_tx_attr(&txs[0], "r") ); assert_eq!( Some("0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e".to_string()), - get_pretty_tx_attr(&block.transactions[0], "s") + get_pretty_tx_attr(&txs[0], "s") ); assert_eq!( Some("0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e".into()), - get_pretty_tx_attr(&block.transactions[0], "to") - ); - assert_eq!( - Some("0".to_string()), - get_pretty_tx_attr(&block.transactions[0], "transactionIndex") + get_pretty_tx_attr(&txs[0], "to") ); - assert_eq!(Some("37".to_string()), get_pretty_tx_attr(&block.transactions[0], "v")); - assert_eq!(Some("0".to_string()), get_pretty_tx_attr(&block.transactions[0], "value")); + assert_eq!(Some("0".to_string()), get_pretty_tx_attr(&txs[0], "transactionIndex")); + assert_eq!(Some("37".to_string()), get_pretty_tx_attr(&txs[0], "v")); + assert_eq!(Some("0".to_string()), get_pretty_tx_attr(&txs[0], "value")); } #[test] @@ -695,7 +733,7 @@ value 0".to_string(); } ); - let block: Block<()> = serde_json::from_value(json).unwrap(); + let block: Block = serde_json::from_value(json).unwrap(); assert_eq!(None, get_pretty_block_attr(&block, "")); assert_eq!(Some("7".to_string()), get_pretty_block_attr(&block, "baseFeePerGas")); @@ -725,6 +763,10 @@ value 0".to_string(); Some("0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5".to_string()), get_pretty_block_attr(&block, "parentHash") ); + assert_eq!( + Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()), + get_pretty_block_attr(&block, "transactionsRoot") + ); assert_eq!( Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()), get_pretty_block_attr(&block, "receiptsRoot") diff --git a/crates/common/src/glob.rs b/crates/common/src/glob.rs index 8557c35b5985a..070f703675fd8 100644 --- a/crates/common/src/glob.rs +++ b/crates/common/src/glob.rs @@ -22,36 +22,48 @@ pub fn expand_globs( /// A `globset::Glob` that creates its `globset::GlobMatcher` when its created, so it doesn't need /// to be compiled when the filter functions `TestFilter` functions are called. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct GlobMatcher { - /// The parsed glob - pub glob: globset::Glob, /// The compiled glob pub matcher: globset::GlobMatcher, } impl GlobMatcher { + /// Creates a new `GlobMatcher` from a `globset::Glob`. + pub fn new(glob: globset::Glob) -> Self { + Self { matcher: glob.compile_matcher() } + } + /// Tests whether the given path matches this pattern or not. /// /// The glob `./test/*` won't match absolute paths like `test/Contract.sol`, which is common /// format here, so we also handle this case here - pub fn is_match(&self, path: &str) -> bool { - let mut matches = self.matcher.is_match(path); - if !matches && !path.starts_with("./") && self.as_str().starts_with("./") { - matches = self.matcher.is_match(format!("./{path}")); + pub fn is_match(&self, path: &Path) -> bool { + if self.matcher.is_match(path) { + return true; + } + + if !path.starts_with("./") && self.as_str().starts_with("./") { + return self.matcher.is_match(format!("./{}", path.display())); } - matches + + false + } + + /// Returns the `globset::Glob`. + pub fn glob(&self) -> &globset::Glob { + self.matcher.glob() } /// Returns the `Glob` string used to compile this matcher. pub fn as_str(&self) -> &str { - self.glob.glob() + self.glob().glob() } } impl fmt::Display for GlobMatcher { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.glob.fmt(f) + self.glob().fmt(f) } } @@ -59,16 +71,16 @@ impl FromStr for GlobMatcher { type Err = globset::Error; fn from_str(s: &str) -> Result { - s.parse::().map(Into::into) + s.parse::().map(Self::new) } } impl From for GlobMatcher { fn from(glob: globset::Glob) -> Self { - let matcher = glob.compile_matcher(); - Self { glob, matcher } + Self::new(glob) } } + #[cfg(test)] mod tests { use super::*; @@ -76,7 +88,7 @@ mod tests { #[test] fn can_match_glob_paths() { let matcher: GlobMatcher = "./test/*".parse().unwrap(); - assert!(matcher.is_match("test/Contract.sol")); - assert!(matcher.is_match("./test/Contract.sol")); + assert!(matcher.is_match(Path::new("test/Contract.sol"))); + assert!(matcher.is_match(Path::new("./test/Contract.sol"))); } } diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 02d11bb43590d..6febaa3787751 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -9,10 +9,10 @@ extern crate tracing; pub mod abi; pub mod calc; -pub mod clap_helpers; pub mod compile; pub mod constants; pub mod contracts; +pub mod ens; pub mod errors; pub mod evm; pub mod fmt; @@ -20,18 +20,14 @@ pub mod fs; pub mod glob; pub mod provider; pub mod retry; -pub mod rpc; -pub mod runtime_client; pub mod selectors; +pub mod serde_helpers; pub mod shell; pub mod term; pub mod traits; pub mod transactions; -pub mod types; -pub mod units; pub use constants::*; pub use contracts::*; -pub use provider::*; pub use traits::*; pub use transactions::*; diff --git a/crates/common/src/provider.rs b/crates/common/src/provider/mod.rs similarity index 68% rename from crates/common/src/provider.rs rename to crates/common/src/provider/mod.rs index bea5e18bc58b6..7c91575fed4f4 100644 --- a/crates/common/src/provider.rs +++ b/crates/common/src/provider/mod.rs @@ -1,26 +1,44 @@ -//! Commonly used helpers to construct `Provider`s +//! Provider-related instantiation and usage utilities. + +pub mod retry; +pub mod runtime_transport; +pub mod tower; use crate::{ - runtime_client::{RuntimeClient, RuntimeClientBuilder}, - ALCHEMY_FREE_TIER_CUPS, REQUEST_TIMEOUT, + provider::runtime_transport::RuntimeTransportBuilder, ALCHEMY_FREE_TIER_CUPS, REQUEST_TIMEOUT, +}; +use alloy_provider::{ + fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, SignerFiller}, + network::{AnyNetwork, EthereumSigner}, + Identity, ProviderBuilder as AlloyProviderBuilder, RootProvider, }; -use ethers_core::types::U256; -use ethers_middleware::gas_oracle::{GasCategory, GasOracle, Polygon}; -use ethers_providers::{is_local_endpoint, Middleware, Provider, DEFAULT_LOCAL_POLL_INTERVAL}; +use alloy_rpc_client::ClientBuilder; use eyre::{Result, WrapErr}; use foundry_config::NamedChain; use reqwest::Url; +use runtime_transport::RuntimeTransport; use std::{ + net::SocketAddr, path::{Path, PathBuf}, + str::FromStr, time::Duration, }; +use tower::{RetryBackoffLayer, RetryBackoffService}; use url::ParseError; /// Helper type alias for a retry provider -pub type RetryProvider = Provider; - -/// Helper type alias for a rpc url -pub type RpcUrl = String; +pub type RetryProvider = RootProvider, N>; + +/// Helper type alias for a retry provider with a signer +pub type RetryProviderWithSigner = FillProvider< + JoinFill< + JoinFill, NonceFiller>, ChainIdFiller>, + SignerFiller, + >, + RootProvider, N>, + RetryBackoffService, + N, +>; /// Constructs a provider with a 100 millisecond interval poll if it's a localhost URL (most likely /// an anvil or other dev node) and with the default, or 7 second otherwise. @@ -34,7 +52,7 @@ pub type RpcUrl = String; /// # Examples /// /// ``` -/// use foundry_common::get_http_provider; +/// use foundry_common::provider::get_http_provider; /// /// let retry_provider = get_http_provider("http://localhost:8545"); /// ``` @@ -79,7 +97,7 @@ impl ProviderBuilder { // invalid url: non-prefixed URL scheme is not allowed, so we prepend the default http // prefix let storage; - if url_str.starts_with("localhost:") || url_str.starts_with("127.0.0.1:") { + if url_str.starts_with("localhost:") { storage = format!("http://{url_str}"); url_str = storage.as_str(); } @@ -87,12 +105,16 @@ impl ProviderBuilder { let url = Url::parse(url_str) .or_else(|err| match err { ParseError::RelativeUrlWithoutBase => { - let path = Path::new(url_str); - - if let Ok(path) = resolve_path(path) { - Url::parse(&format!("file://{}", path.display())) + if SocketAddr::from_str(url_str).is_ok() { + Url::parse(&format!("http://{}", url_str)) } else { - Err(err) + let path = Path::new(url_str); + + if let Ok(path) = resolve_path(path) { + Url::parse(&format!("file://{}", path.display())) + } else { + Err(err) + } } } _ => Err(err), @@ -199,23 +221,11 @@ impl ProviderBuilder { self } - /// Same as [`Self:build()`] but also retrieves the `chainId` in order to derive an appropriate - /// interval. - pub async fn connect(self) -> Result { - let mut provider = self.build()?; - if let Some(blocktime) = provider.get_chainid().await.ok().and_then(|id| { - NamedChain::try_from(id.as_u64()).ok().and_then(|chain| chain.average_blocktime_hint()) - }) { - provider = provider.interval(blocktime / 2); - } - Ok(provider) - } - /// Constructs the `RetryProvider` taking all configs into account. pub fn build(self) -> Result { let ProviderBuilder { url, - chain, + chain: _, max_retry, timeout_retry, initial_backoff, @@ -226,69 +236,64 @@ impl ProviderBuilder { } = self; let url = url?; - let client_builder = RuntimeClientBuilder::new( - url.clone(), + let retry_layer = RetryBackoffLayer::new( + max_retry, + timeout_retry, + initial_backoff, + compute_units_per_second, + ); + let transport = RuntimeTransportBuilder::new(url.clone()) + .with_timeout(timeout) + .with_headers(headers) + .with_jwt(jwt) + .build(); + let client = ClientBuilder::default().layer(retry_layer).transport(transport, false); + + let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() + .on_provider(RootProvider::new(client)); + + Ok(provider) + } + + /// Constructs the `RetryProvider` with a signer + pub fn build_with_signer(self, signer: EthereumSigner) -> Result { + let ProviderBuilder { + url, + chain: _, max_retry, timeout_retry, initial_backoff, timeout, compute_units_per_second, - ) - .with_headers(headers) - .with_jwt(jwt); + jwt, + headers, + } = self; + let url = url?; + + let retry_layer = RetryBackoffLayer::new( + max_retry, + timeout_retry, + initial_backoff, + compute_units_per_second, + ); - let mut provider = Provider::new(client_builder.build()); + let transport = RuntimeTransportBuilder::new(url.clone()) + .with_timeout(timeout) + .with_headers(headers) + .with_jwt(jwt) + .build(); - let is_local = is_local_endpoint(url.as_str()); + let client = ClientBuilder::default().layer(retry_layer).transport(transport, false); - if is_local { - provider = provider.interval(DEFAULT_LOCAL_POLL_INTERVAL); - } else if let Some(blocktime) = chain.average_blocktime_hint() { - provider = provider.interval(blocktime / 2); - } + let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() + .with_recommended_fillers() + .signer(signer) + .on_provider(RootProvider::new(client)); Ok(provider) } } -/// Estimates EIP1559 fees depending on the chain -/// -/// Uses custom gas oracles for -/// - polygon -/// -/// Fallback is the default [`Provider::estimate_eip1559_fees`] implementation -pub async fn estimate_eip1559_fees( - provider: &M, - chain: Option, -) -> Result<(U256, U256)> -where - M::Error: 'static, -{ - let chain = if let Some(chain) = chain { - chain - } else { - provider.get_chainid().await.wrap_err("Failed to get chain id")?.as_u64() - }; - - if let Ok(chain) = NamedChain::try_from(chain) { - // handle chains that deviate from `eth_feeHistory` and have their own oracle - match chain { - NamedChain::Polygon | NamedChain::PolygonMumbai => { - // TODO: phase this out somehow - let chain = match chain { - NamedChain::Polygon => ethers_core::types::Chain::Polygon, - NamedChain::PolygonMumbai => ethers_core::types::Chain::PolygonMumbai, - _ => unreachable!(), - }; - let estimator = Polygon::new(chain)?.category(GasCategory::Standard); - return Ok(estimator.estimate_eip1559_fees().await?) - } - _ => {} - } - } - provider.estimate_eip1559_fees(None).await.wrap_err("Failed fetch EIP1559 fees") -} - #[cfg(not(windows))] fn resolve_path(path: &Path) -> Result { if path.is_absolute() { diff --git a/crates/common/src/provider/retry.rs b/crates/common/src/provider/retry.rs new file mode 100644 index 0000000000000..b6adfb646be86 --- /dev/null +++ b/crates/common/src/provider/retry.rs @@ -0,0 +1,141 @@ +//! An utility trait for retrying requests based on the error type. See [TransportError]. +use alloy_json_rpc::ErrorPayload; +use alloy_transport::{TransportError, TransportErrorKind}; +use serde::Deserialize; + +/// [RetryPolicy] defines logic for which [TransportError] instances should +/// the client retry the request and try to recover from. +pub trait RetryPolicy: Send + Sync + std::fmt::Debug { + /// Whether to retry the request based on the given `error` + fn should_retry(&self, error: &TransportError) -> bool; + + /// Providers may include the `backoff` in the error response directly + fn backoff_hint(&self, error: &TransportError) -> Option; +} + +/// Implements [RetryPolicy] that will retry requests that errored with +/// status code 429 i.e. TOO_MANY_REQUESTS +/// +/// Infura often fails with a `"header not found"` rpc error which is apparently linked to load +/// balancing, which are retried as well. +#[derive(Clone, Debug, Default)] +pub struct RateLimitRetryPolicy; + +impl RetryPolicy for RateLimitRetryPolicy { + fn should_retry(&self, error: &TransportError) -> bool { + match error { + // There was a transport-level error. This is either a non-retryable error, + // or a server error that should be retried. + TransportError::Transport(err) => should_retry_transport_level_error(err), + // The transport could not serialize the error itself. The request was malformed from + // the start. + TransportError::SerError(_) => false, + TransportError::DeserError { text, .. } => { + if let Ok(resp) = serde_json::from_str::(text) { + return should_retry_json_rpc_error(&resp) + } + + // some providers send invalid JSON RPC in the error case (no `id:u64`), but the + // text should be a `JsonRpcError` + #[derive(Deserialize)] + struct Resp { + error: ErrorPayload, + } + + if let Ok(resp) = serde_json::from_str::(text) { + return should_retry_json_rpc_error(&resp.error) + } + + false + } + TransportError::ErrorResp(err) => should_retry_json_rpc_error(err), + TransportError::NullResp => true, + TransportError::UnsupportedFeature(_) => false, + TransportError::LocalUsageError(_) => false, + } + } + + /// Provides a backoff hint if the error response contains it + fn backoff_hint(&self, error: &TransportError) -> Option { + if let TransportError::ErrorResp(resp) = error { + let data = resp.try_data_as::(); + if let Some(Ok(data)) = data { + // if daily rate limit exceeded, infura returns the requested backoff in the error + // response + let backoff_seconds = &data["rate"]["backoff_seconds"]; + // infura rate limit error + if let Some(seconds) = backoff_seconds.as_u64() { + return Some(std::time::Duration::from_secs(seconds)) + } + if let Some(seconds) = backoff_seconds.as_f64() { + return Some(std::time::Duration::from_secs(seconds as u64 + 1)) + } + } + } + None + } +} + +/// Analyzes the [TransportErrorKind] and decides if the request should be retried based on the +/// variant. +fn should_retry_transport_level_error(error: &TransportErrorKind) -> bool { + match error { + // Missing batch response errors can be retried. + TransportErrorKind::MissingBatchResponse(_) => true, + TransportErrorKind::Custom(err) => { + // currently http error responses are not standard in alloy + let msg = err.to_string(); + msg.contains("429 Too Many Requests") + } + + // If the backend is gone, or there's a completely custom error, we should assume it's not + // retryable. + _ => false, + } +} + +/// Analyzes the [ErrorPayload] and decides if the request should be retried based on the +/// error code or the message. +fn should_retry_json_rpc_error(error: &ErrorPayload) -> bool { + let ErrorPayload { code, message, .. } = error; + // alchemy throws it this way + if *code == 429 { + return true + } + + // This is an infura error code for `exceeded project rate limit` + if *code == -32005 { + return true + } + + // alternative alchemy error for specific IPs + if *code == -32016 && message.contains("rate limit") { + return true + } + + // quick node error `"credits limited to 6000/sec"` + // + if *code == -32012 && message.contains("credits") { + return true + } + + // quick node rate limit error: `100/second request limit reached - reduce calls per second or + // upgrade your account at quicknode.com` + if *code == -32007 && message.contains("request limit reached") { + return true + } + + match message.as_str() { + // this is commonly thrown by infura and is apparently a load balancer issue, see also + "header not found" => true, + // also thrown by infura if out of budget for the day and ratelimited + "daily request count exceeded, request rate limited" => true, + msg => { + msg.contains("rate limit") || + msg.contains("rate exceeded") || + msg.contains("too many requests") || + msg.contains("credits limited") || + msg.contains("request limit") + } + } +} diff --git a/crates/common/src/provider/runtime_transport.rs b/crates/common/src/provider/runtime_transport.rs new file mode 100644 index 0000000000000..5402148918ddf --- /dev/null +++ b/crates/common/src/provider/runtime_transport.rs @@ -0,0 +1,323 @@ +//! Runtime transport that connects on first request, which can take either of an HTTP, +//! WebSocket, or IPC transport and supports retries based on CUPS logic. +use crate::REQUEST_TIMEOUT; +use alloy_json_rpc::{RequestPacket, ResponsePacket}; +use alloy_pubsub::{PubSubConnect, PubSubFrontend}; +use alloy_rpc_types_engine::{Claims, JwtSecret}; +use alloy_transport::{ + Authorization, BoxTransport, TransportError, TransportErrorKind, TransportFut, +}; +use alloy_transport_http::Http; +use alloy_transport_ipc::IpcConnect; +use alloy_transport_ws::WsConnect; +use reqwest::header::{HeaderName, HeaderValue}; +use std::{path::PathBuf, str::FromStr, sync::Arc}; +use thiserror::Error; +use tokio::sync::RwLock; +use tower::Service; +use url::Url; + +/// An enum representing the different transports that can be used to connect to a runtime. +/// Only meant to be used internally by [RuntimeTransport]. +#[derive(Clone, Debug)] +pub enum InnerTransport { + /// HTTP transport + Http(Http), + /// WebSocket transport + Ws(PubSubFrontend), + /// IPC transport + Ipc(PubSubFrontend), +} + +/// Error type for the runtime transport. +#[derive(Error, Debug)] +pub enum RuntimeTransportError { + /// Internal transport error + #[error("Internal transport error: {0} with {1}")] + TransportError(TransportError, String), + + /// Failed to lock the transport + #[error("Failed to lock the transport")] + LockError, + + /// Invalid URL scheme + #[error("URL scheme is not supported: {0}")] + BadScheme(String), + + /// Invalid HTTP header + #[error("Invalid HTTP header: {0}")] + BadHeader(String), + + /// Invalid file path + #[error("Invalid IPC file path: {0}")] + BadPath(String), + + /// Invalid construction of Http provider + #[error(transparent)] + HttpConstructionError(#[from] reqwest::Error), + + /// Invalid JWT + #[error("Invalid JWT: {0}")] + InvalidJwt(String), +} + +/// A runtime transport is a custom [alloy_transport::Transport] that only connects when the *first* +/// request is made. When the first request is made, it will connect to the runtime using either an +/// HTTP WebSocket, or IPC transport depending on the URL used. +/// It also supports retries for rate-limiting and timeout-related errors. +#[derive(Clone, Debug, Error)] +pub struct RuntimeTransport { + /// The inner actual transport used. + inner: Arc>>, + /// The URL to connect to. + url: Url, + /// The headers to use for requests. + headers: Vec, + /// The JWT to use for requests. + jwt: Option, + /// The timeout for requests. + timeout: std::time::Duration, +} + +/// A builder for [RuntimeTransport]. +#[derive(Debug)] +pub struct RuntimeTransportBuilder { + url: Url, + headers: Vec, + jwt: Option, + timeout: std::time::Duration, +} + +impl RuntimeTransportBuilder { + /// Create a new builder with the given URL. + pub fn new(url: Url) -> Self { + Self { url, headers: vec![], jwt: None, timeout: REQUEST_TIMEOUT } + } + + /// Set the URL for the transport. + pub fn with_headers(mut self, headers: Vec) -> Self { + self.headers = headers; + self + } + + /// Set the JWT for the transport. + pub fn with_jwt(mut self, jwt: Option) -> Self { + self.jwt = jwt; + self + } + + /// Set the timeout for the transport. + pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self { + self.timeout = timeout; + self + } + + /// Builds the [RuntimeTransport] and returns it in a disconnected state. + /// The runtime transport will then connect when the first request happens. + pub fn build(self) -> RuntimeTransport { + RuntimeTransport { + inner: Arc::new(RwLock::new(None)), + url: self.url, + headers: self.headers, + jwt: self.jwt, + timeout: self.timeout, + } + } +} + +impl ::core::fmt::Display for RuntimeTransport { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "RuntimeTransport {}", self.url) + } +} + +impl RuntimeTransport { + /// Connects the underlying transport, depending on the URL scheme. + pub async fn connect(&self) -> Result { + match self.url.scheme() { + "http" | "https" => self.connect_http().await, + "ws" | "wss" => self.connect_ws().await, + "file" => self.connect_ipc().await, + _ => Err(RuntimeTransportError::BadScheme(self.url.scheme().to_string())), + } + } + + /// Connects to an HTTP [alloy_transport_http::Http] transport. + async fn connect_http(&self) -> Result { + let mut client_builder = reqwest::Client::builder() + .timeout(self.timeout) + .tls_built_in_root_certs(self.url.scheme() == "https"); + let mut headers = reqwest::header::HeaderMap::new(); + + // If there's a JWT, add it to the headers if we can decode it. + if let Some(jwt) = self.jwt.clone() { + let auth = + build_auth(jwt).map_err(|e| RuntimeTransportError::InvalidJwt(e.to_string()))?; + + let mut auth_value: HeaderValue = + HeaderValue::from_str(&auth.to_string()).expect("Header should be valid string"); + auth_value.set_sensitive(true); + + headers.insert(reqwest::header::AUTHORIZATION, auth_value); + }; + + // Add any custom headers. + for header in self.headers.iter() { + let make_err = || RuntimeTransportError::BadHeader(header.to_string()); + + let (key, val) = header.split_once(':').ok_or_else(make_err)?; + + headers.insert( + HeaderName::from_str(key.trim()).map_err(|_| make_err())?, + HeaderValue::from_str(val.trim()).map_err(|_| make_err())?, + ); + } + + client_builder = client_builder.default_headers(headers); + + let client = + client_builder.build().map_err(RuntimeTransportError::HttpConstructionError)?; + + Ok(InnerTransport::Http(Http::with_client(client, self.url.clone()))) + } + + /// Connects to a WS transport. + async fn connect_ws(&self) -> Result { + let auth = self.jwt.as_ref().and_then(|jwt| build_auth(jwt.clone()).ok()); + let ws = WsConnect { url: self.url.to_string(), auth } + .into_service() + .await + .map_err(|e| RuntimeTransportError::TransportError(e, self.url.to_string()))?; + Ok(InnerTransport::Ws(ws)) + } + + /// Connects to an IPC transport. + async fn connect_ipc(&self) -> Result { + let path = url_to_file_path(&self.url) + .map_err(|_| RuntimeTransportError::BadPath(self.url.to_string()))?; + let ipc_connector: IpcConnect = path.clone().into(); + let ipc = ipc_connector.into_service().await.map_err(|e| { + RuntimeTransportError::TransportError(e, path.clone().display().to_string()) + })?; + Ok(InnerTransport::Ipc(ipc)) + } + + /// Sends a request using the underlying transport. + /// If this is the first request, it will connect to the appropriate transport depending on the + /// URL scheme. When sending the request, retries will be automatically handled depending + /// on the parameters set on the [RuntimeTransport]. + /// For sending the actual request, this action is delegated down to the + /// underlying transport through Tower's [tower::Service::call]. See tower's [tower::Service] + /// trait for more information. + pub fn request(&self, req: RequestPacket) -> TransportFut<'static> { + let this = self.clone(); + Box::pin(async move { + let mut inner = this.inner.read().await; + if inner.is_none() { + drop(inner); + { + let mut inner_mut = this.inner.write().await; + if inner_mut.is_none() { + *inner_mut = + Some(this.connect().await.map_err(TransportErrorKind::custom)?); + } + } + inner = this.inner.read().await; + } + + // SAFETY: We just checked that the inner transport exists. + match inner.as_ref().expect("must've been initialized") { + InnerTransport::Http(http) => { + let mut http = http; + http.call(req) + } + InnerTransport::Ws(ws) => { + let mut ws = ws; + ws.call(req) + } + InnerTransport::Ipc(ipc) => { + let mut ipc = ipc; + ipc.call(req) + } + } + .await + }) + } + + /// Convert this transport into a boxed trait object. + pub fn boxed(self) -> BoxTransport + where + Self: Sized + Clone + Send + Sync + 'static, + { + BoxTransport::new(self) + } +} + +impl tower::Service for RuntimeTransport { + type Response = ResponsePacket; + type Error = TransportError; + type Future = TransportFut<'static>; + + #[inline] + fn poll_ready( + &mut self, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + + #[inline] + fn call(&mut self, req: RequestPacket) -> Self::Future { + self.request(req) + } +} + +impl tower::Service for &RuntimeTransport { + type Response = ResponsePacket; + type Error = TransportError; + type Future = TransportFut<'static>; + + #[inline] + fn poll_ready( + &mut self, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + + #[inline] + fn call(&mut self, req: RequestPacket) -> Self::Future { + self.request(req) + } +} + +fn build_auth(jwt: String) -> eyre::Result { + // Decode jwt from hex, then generate claims (iat with current timestamp) + let secret = JwtSecret::from_hex(jwt)?; + let claims = Claims::default(); + let token = secret.encode(&claims)?; + + let auth = Authorization::Bearer(token); + + Ok(auth) +} + +#[cfg(windows)] +fn url_to_file_path(url: &Url) -> Result { + const PREFIX: &str = "file:///pipe/"; + + let url_str = url.as_str(); + + if url_str.starts_with(PREFIX) { + let pipe_name = &url_str[PREFIX.len()..]; + let pipe_path = format!(r"\\.\pipe\{}", pipe_name); + return Ok(PathBuf::from(pipe_path)); + } + + url.to_file_path() +} + +#[cfg(not(windows))] +fn url_to_file_path(url: &Url) -> Result { + url.to_file_path() +} diff --git a/crates/common/src/provider/tower.rs b/crates/common/src/provider/tower.rs new file mode 100644 index 0000000000000..2f97d2f8fbd11 --- /dev/null +++ b/crates/common/src/provider/tower.rs @@ -0,0 +1,196 @@ +//! Alloy-related tower middleware for retrying rate-limited requests +//! and applying backoff. +use std::{ + sync::{ + atomic::{AtomicU32, Ordering}, + Arc, + }, + task::{Context, Poll}, +}; + +use alloy_json_rpc::{RequestPacket, ResponsePacket}; +use alloy_transport::{TransportError, TransportErrorKind, TransportFut}; + +use super::{ + retry::{RateLimitRetryPolicy, RetryPolicy}, + runtime_transport::RuntimeTransport, +}; + +/// An Alloy Tower Layer that is responsible for retrying requests based on the +/// error type. See [TransportError]. +#[derive(Debug, Clone)] +pub struct RetryBackoffLayer { + /// The maximum number of retries for rate limit errors + max_rate_limit_retries: u32, + /// The maximum number of retries for timeout errors + max_timeout_retries: u32, + /// The initial backoff in milliseconds + initial_backoff: u64, + /// The number of compute units per second for this provider + compute_units_per_second: u64, +} + +impl RetryBackoffLayer { + /// Creates a new [RetryWithPolicyLayer] with the given parameters + pub fn new( + max_rate_limit_retries: u32, + max_timeout_retries: u32, + initial_backoff: u64, + compute_units_per_second: u64, + ) -> Self { + Self { + max_rate_limit_retries, + max_timeout_retries, + initial_backoff, + compute_units_per_second, + } + } +} + +impl tower::layer::Layer for RetryBackoffLayer { + type Service = RetryBackoffService; + + fn layer(&self, inner: S) -> Self::Service { + RetryBackoffService { + inner, + policy: RateLimitRetryPolicy, + max_rate_limit_retries: self.max_rate_limit_retries, + max_timeout_retries: self.max_timeout_retries, + initial_backoff: self.initial_backoff, + compute_units_per_second: self.compute_units_per_second, + requests_enqueued: Arc::new(AtomicU32::new(0)), + } + } +} + +/// An Alloy Tower Service that is responsible for retrying requests based on the +/// error type. See [TransportError] and [RateLimitRetryPolicy]. +#[derive(Debug, Clone)] +pub struct RetryBackoffService { + /// The inner service + inner: S, + /// The retry policy + policy: RateLimitRetryPolicy, + /// The maximum number of retries for rate limit errors + max_rate_limit_retries: u32, + /// The maximum number of retries for timeout errors + max_timeout_retries: u32, + /// The initial backoff in milliseconds + initial_backoff: u64, + /// The number of compute units per second for this service + compute_units_per_second: u64, + /// The number of requests currently enqueued + requests_enqueued: Arc, +} + +// impl tower service +impl tower::Service for RetryBackoffService { + type Response = ResponsePacket; + type Error = TransportError; + type Future = TransportFut<'static>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + // Our middleware doesn't care about backpressure, so it's ready as long + // as the inner service is ready. + self.inner.poll_ready(cx) + } + + fn call(&mut self, request: RequestPacket) -> Self::Future { + let mut this = self.clone(); + Box::pin(async move { + let ahead_in_queue = this.requests_enqueued.fetch_add(1, Ordering::SeqCst) as u64; + let mut rate_limit_retry_number: u32 = 0; + let mut timeout_retries: u32 = 0; + loop { + let err; + let fut = this.inner.call(request.clone()).await; + + match fut { + Ok(res) => { + if let Some(e) = res.as_error() { + err = TransportError::ErrorResp(e.clone()) + } else { + this.requests_enqueued.fetch_sub(1, Ordering::SeqCst); + return Ok(res) + } + } + Err(e) => err = e, + } + + let should_retry = this.policy.should_retry(&err); + if should_retry { + rate_limit_retry_number += 1; + if rate_limit_retry_number > this.max_rate_limit_retries { + return Err(TransportErrorKind::custom_str("Max retries exceeded")) + } + trace!("retrying request due to {:?}", err); + + let current_queued_reqs = this.requests_enqueued.load(Ordering::SeqCst) as u64; + + // try to extract the requested backoff from the error or compute the next + // backoff based on retry count + let backoff_hint = this.policy.backoff_hint(&err); + let next_backoff = backoff_hint + .unwrap_or_else(|| std::time::Duration::from_millis(this.initial_backoff)); + + // requests are usually weighted and can vary from 10 CU to several 100 CU, + // cheaper requests are more common some example alchemy + // weights: + // - `eth_getStorageAt`: 17 + // - `eth_getBlockByNumber`: 16 + // - `eth_newFilter`: 20 + // + // (coming from forking mode) assuming here that storage request will be the + // driver for Rate limits we choose `17` as the average cost + // of any request + const AVG_COST: u64 = 17u64; + let seconds_to_wait_for_compute_budget = compute_unit_offset_in_secs( + AVG_COST, + this.compute_units_per_second, + current_queued_reqs, + ahead_in_queue, + ); + let total_backoff = next_backoff + + std::time::Duration::from_secs(seconds_to_wait_for_compute_budget); + + trace!(?total_backoff, budget_backoff = ?seconds_to_wait_for_compute_budget, default_backoff = ?next_backoff, ?backoff_hint, "backing off due to rate limit"); + + tokio::time::sleep(total_backoff).await; + } else { + if timeout_retries < this.max_timeout_retries { + timeout_retries += 1; + continue; + } + + this.requests_enqueued.fetch_sub(1, Ordering::SeqCst); + return Err(err) + } + } + }) + } +} + +/// Calculates an offset in seconds by taking into account the number of currently queued requests, +/// number of requests that were ahead in the queue when the request was first issued, the average +/// cost a weighted request (heuristic), and the number of available compute units per seconds. +/// +/// Returns the number of seconds (the unit the remote endpoint measures compute budget) a request +/// is supposed to wait to not get rate limited. The budget per second is +/// `compute_units_per_second`, assuming an average cost of `avg_cost` this allows (in theory) +/// `compute_units_per_second / avg_cost` requests per seconds without getting rate limited. +/// By taking into account the number of concurrent request and the position in queue when the +/// request was first issued and determine the number of seconds a request is supposed to wait, if +/// at all +fn compute_unit_offset_in_secs( + avg_cost: u64, + compute_units_per_second: u64, + current_queued_requests: u64, + ahead_in_queue: u64, +) -> u64 { + let request_capacity_per_second = compute_units_per_second.saturating_div(avg_cost); + if current_queued_requests > request_capacity_per_second { + current_queued_requests.min(ahead_in_queue).saturating_div(request_capacity_per_second) + } else { + 0 + } +} diff --git a/crates/common/src/retry.rs b/crates/common/src/retry.rs index 9ace81a289e2a..1f8949aa023ed 100644 --- a/crates/common/src/retry.rs +++ b/crates/common/src/retry.rs @@ -4,7 +4,7 @@ use eyre::{Error, Result}; use std::{future::Future, time::Duration}; /// A type that keeps track of attempts. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct Retry { retries: u32, delay: Option, diff --git a/crates/common/src/runtime_client.rs b/crates/common/src/runtime_client.rs deleted file mode 100644 index 972ee9029540a..0000000000000 --- a/crates/common/src/runtime_client.rs +++ /dev/null @@ -1,338 +0,0 @@ -//! Wrap different providers - -use async_trait::async_trait; -use ethers_core::types::U256; -use ethers_providers::{ - Authorization, ConnectionDetails, Http, HttpRateLimitRetryPolicy, Ipc, JsonRpcClient, - JsonRpcError, JwtAuth, JwtKey, ProviderError, PubsubClient, RetryClient, RetryClientBuilder, - RpcError, Ws, -}; -use reqwest::{ - header::{HeaderName, HeaderValue}, - Url, -}; -use serde::{de::DeserializeOwned, Serialize}; -use std::{fmt::Debug, path::PathBuf, str::FromStr, sync::Arc, time::Duration}; -use thiserror::Error; -use tokio::sync::RwLock; - -/// Enum representing a the client types supported by the runtime provider -#[derive(Debug)] -enum InnerClient { - /// HTTP client - Http(RetryClient), - /// WebSocket client - Ws(Ws), - /// IPC client - Ipc(Ipc), -} - -/// Error type for the runtime provider -#[derive(Error, Debug)] -pub enum RuntimeClientError { - /// Internal provider error - #[error(transparent)] - ProviderError(ProviderError), - - /// Failed to lock the client - #[error("Failed to lock the client")] - LockError, - - /// Invalid URL scheme - #[error("URL scheme is not supported: {0}")] - BadScheme(String), - - /// Invalid HTTP header - #[error("Invalid HTTP header: {0}")] - BadHeader(String), - - /// Invalid file path - #[error("Invalid IPC file path: {0}")] - BadPath(String), -} - -impl RpcError for RuntimeClientError { - fn as_error_response(&self) -> Option<&JsonRpcError> { - match self { - RuntimeClientError::ProviderError(err) => err.as_error_response(), - _ => None, - } - } - - fn as_serde_error(&self) -> Option<&serde_json::Error> { - match self { - RuntimeClientError::ProviderError(e) => e.as_serde_error(), - _ => None, - } - } -} - -impl From for ProviderError { - fn from(src: RuntimeClientError) -> Self { - match src { - RuntimeClientError::ProviderError(err) => err, - _ => ProviderError::JsonRpcClientError(Box::new(src)), - } - } -} - -/// A provider that connects on first request allowing handling of different provider types at -/// runtime -#[derive(Clone, Debug, Error)] -pub struct RuntimeClient { - client: Arc>>, - url: Url, - max_retry: u32, - timeout_retry: u32, - initial_backoff: u64, - timeout: Duration, - /// available CUPS - compute_units_per_second: u64, - jwt: Option, - headers: Vec, -} - -/// Builder for RuntimeClient -pub struct RuntimeClientBuilder { - url: Url, - max_retry: u32, - timeout_retry: u32, - initial_backoff: u64, - timeout: Duration, - /// available CUPS - compute_units_per_second: u64, - jwt: Option, - headers: Vec, -} - -impl ::core::fmt::Display for RuntimeClient { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - write!(f, "RuntimeClient") - } -} - -fn build_auth(jwt: String) -> eyre::Result { - // Decode jwt from hex, then generate claims (iat with current timestamp) - let jwt = hex::decode(jwt)?; - let secret = JwtKey::from_slice(&jwt).map_err(|err| eyre::eyre!("Invalid JWT: {}", err))?; - let auth = JwtAuth::new(secret, None, None); - let token = auth.generate_token()?; - - // Essentially unrolled ethers-rs new_with_auth to accommodate the custom timeout - let auth = Authorization::Bearer(token); - - Ok(auth) -} - -impl RuntimeClient { - async fn connect(&self) -> Result { - match self.url.scheme() { - "http" | "https" => { - let mut client_builder = reqwest::Client::builder() - .timeout(self.timeout) - .tls_built_in_root_certs(self.url.scheme() == "https"); - let mut headers = reqwest::header::HeaderMap::new(); - - if let Some(jwt) = self.jwt.as_ref() { - let auth = build_auth(jwt.clone()).map_err(|err| { - RuntimeClientError::ProviderError(ProviderError::CustomError( - err.to_string(), - )) - })?; - - let mut auth_value: HeaderValue = HeaderValue::from_str(&auth.to_string()) - .expect("Header should be valid string"); - auth_value.set_sensitive(true); - - headers.insert(reqwest::header::AUTHORIZATION, auth_value); - }; - - for header in self.headers.iter() { - let make_err = || RuntimeClientError::BadHeader(header.to_string()); - - let (key, val) = header.split_once(':').ok_or_else(make_err)?; - - headers.insert( - HeaderName::from_str(key.trim()).map_err(|_| make_err())?, - HeaderValue::from_str(val.trim()).map_err(|_| make_err())?, - ); - } - - client_builder = client_builder.default_headers(headers); - - let client = client_builder - .build() - .map_err(|e| RuntimeClientError::ProviderError(e.into()))?; - let provider = Http::new_with_client(self.url.clone(), client); - - #[allow(clippy::box_default)] - let provider = RetryClientBuilder::default() - .initial_backoff(Duration::from_millis(self.initial_backoff)) - .rate_limit_retries(self.max_retry) - .timeout_retries(self.timeout_retry) - .compute_units_per_second(self.compute_units_per_second) - .build(provider, Box::new(HttpRateLimitRetryPolicy)); - Ok(InnerClient::Http(provider)) - } - "ws" | "wss" => { - let auth: Option = - self.jwt.as_ref().and_then(|jwt| build_auth(jwt.clone()).ok()); - let connection_details = ConnectionDetails::new(self.url.as_str(), auth); - - let client = - Ws::connect_with_reconnects(connection_details, self.max_retry as usize) - .await - .map_err(|e| RuntimeClientError::ProviderError(e.into()))?; - - Ok(InnerClient::Ws(client)) - } - "file" => { - let path = url_to_file_path(&self.url) - .map_err(|_| RuntimeClientError::BadPath(self.url.to_string()))?; - - let client = Ipc::connect(path) - .await - .map_err(|e| RuntimeClientError::ProviderError(e.into()))?; - - Ok(InnerClient::Ipc(client)) - } - _ => Err(RuntimeClientError::BadScheme(self.url.to_string())), - } - } -} - -impl RuntimeClientBuilder { - /// Create new RuntimeClientBuilder - pub fn new( - url: Url, - max_retry: u32, - timeout_retry: u32, - initial_backoff: u64, - timeout: Duration, - compute_units_per_second: u64, - ) -> Self { - Self { - url, - max_retry, - timeout, - timeout_retry, - initial_backoff, - compute_units_per_second, - jwt: None, - headers: vec![], - } - } - - /// Set jwt to use with RuntimeClient - pub fn with_jwt(mut self, jwt: Option) -> Self { - self.jwt = jwt; - self - } - - /// Set http headers to use with RuntimeClient - /// Only works with http/https schemas - pub fn with_headers(mut self, headers: Vec) -> Self { - self.headers = headers; - self - } - - /// Builds RuntimeClient instance - pub fn build(self) -> RuntimeClient { - RuntimeClient { - client: Arc::new(RwLock::new(None)), - url: self.url, - max_retry: self.max_retry, - timeout_retry: self.timeout_retry, - initial_backoff: self.initial_backoff, - timeout: self.timeout, - compute_units_per_second: self.compute_units_per_second, - jwt: self.jwt, - headers: self.headers, - } - } -} - -#[cfg(windows)] -fn url_to_file_path(url: &Url) -> Result { - const PREFIX: &str = "file:///pipe/"; - - let url_str = url.as_str(); - - if url_str.starts_with(PREFIX) { - let pipe_name = &url_str[PREFIX.len()..]; - let pipe_path = format!(r"\\.\pipe\{}", pipe_name); - return Ok(PathBuf::from(pipe_path)) - } - - url.to_file_path() -} - -#[cfg(not(windows))] -fn url_to_file_path(url: &Url) -> Result { - url.to_file_path() -} - -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl JsonRpcClient for RuntimeClient { - type Error = RuntimeClientError; - - #[allow(implied_bounds_entailment)] - async fn request(&self, method: &str, params: T) -> Result - where - T: Debug + Serialize + Send + Sync, - R: DeserializeOwned + Send, - { - if self.client.read().await.is_none() { - let mut w = self.client.write().await; - *w = Some( - self.connect().await.map_err(|e| RuntimeClientError::ProviderError(e.into()))?, - ); - } - - let res = match self.client.read().await.as_ref().unwrap() { - InnerClient::Http(http) => RetryClient::request(http, method, params) - .await - .map_err(|e| RuntimeClientError::ProviderError(e.into())), - InnerClient::Ws(ws) => JsonRpcClient::request(ws, method, params) - .await - .map_err(|e| RuntimeClientError::ProviderError(e.into())), - InnerClient::Ipc(ipc) => JsonRpcClient::request(ipc, method, params) - .await - .map_err(|e| RuntimeClientError::ProviderError(e.into())), - }?; - Ok(res) - } -} - -// We can also implement [`PubsubClient`] for our dynamic provider. -impl PubsubClient for RuntimeClient { - // Since both `Ws` and `Ipc`'s `NotificationStream` associated type is the same, - // we can simply return one of them. - type NotificationStream = ::NotificationStream; - - fn subscribe>(&self, id: T) -> Result { - match self.client.try_read().map_err(|_| RuntimeClientError::LockError)?.as_ref().unwrap() { - InnerClient::Http(_) => { - Err(RuntimeClientError::ProviderError(ProviderError::UnsupportedRPC)) - } - InnerClient::Ws(client) => Ok(PubsubClient::subscribe(client, id) - .map_err(|e| RuntimeClientError::ProviderError(e.into()))?), - InnerClient::Ipc(client) => Ok(PubsubClient::subscribe(client, id) - .map_err(|e| RuntimeClientError::ProviderError(e.into()))?), - } - } - - fn unsubscribe>(&self, id: T) -> Result<(), Self::Error> { - match self.client.try_read().map_err(|_| (RuntimeClientError::LockError))?.as_ref().unwrap() - { - InnerClient::Http(_) => { - Err(RuntimeClientError::ProviderError(ProviderError::UnsupportedRPC)) - } - InnerClient::Ws(client) => Ok(PubsubClient::unsubscribe(client, id) - .map_err(|e| RuntimeClientError::ProviderError(e.into()))?), - InnerClient::Ipc(client) => Ok(PubsubClient::unsubscribe(client, id) - .map_err(|e| RuntimeClientError::ProviderError(e.into()))?), - } - } -} diff --git a/crates/common/src/selectors.rs b/crates/common/src/selectors.rs index da4884deb1b81..2537b2c0cdecd 100644 --- a/crates/common/src/selectors.rs +++ b/crates/common/src/selectors.rs @@ -1,5 +1,7 @@ +//! Support for handling/identifying selectors. + #![allow(missing_docs)] -//! Support for handling/identifying selectors + use crate::abi::abi_decode_calldata; use alloy_json_abi::JsonAbi; use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; @@ -14,8 +16,8 @@ use std::{ time::Duration, }; -static SELECTOR_DATABASE_URL: &str = "https://api.openchain.xyz/signature-database/v1/"; -static SELECTOR_IMPORT_URL: &str = "https://api.openchain.xyz/signature-database/v1/import"; +const SELECTOR_LOOKUP_URL: &str = "https://api.openchain.xyz/signature-database/v1/lookup"; +const SELECTOR_IMPORT_URL: &str = "https://api.openchain.xyz/signature-database/v1/import"; /// The standard request timeout for API requests const REQ_TIMEOUT: Duration = Duration::from_secs(15); @@ -24,7 +26,7 @@ const REQ_TIMEOUT: Duration = Duration::from_secs(15); const MAX_TIMEDOUT_REQ: usize = 4usize; /// A client that can request API data from `https://api.openchain.xyz` -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct SignEthClient { inner: reqwest::Client, /// Whether the connection is spurious, or API is down @@ -54,6 +56,7 @@ impl SignEthClient { } async fn get_text(&self, url: &str) -> reqwest::Result { + trace!(%url, "GET"); self.inner .get(url) .send() @@ -71,11 +74,12 @@ impl SignEthClient { } /// Sends a new post request - async fn post_json( + async fn post_json( &self, url: &str, body: &T, ) -> reqwest::Result { + trace!(%url, body=?serde_json::to_string(body), "POST"); self.inner .post(url) .json(body) @@ -96,13 +100,13 @@ impl SignEthClient { fn on_reqwest_err(&self, err: &reqwest::Error) { fn is_connectivity_err(err: &reqwest::Error) -> bool { if err.is_timeout() || err.is_connect() { - return true + return true; } // Error HTTP codes (5xx) are considered connectivity issues and will prompt retry if let Some(status) = err.status() { let code = status.as_u16(); if (500..600).contains(&code) { - return true + return true; } } false @@ -140,19 +144,55 @@ impl SignEthClient { selector: &str, selector_type: SelectorType, ) -> eyre::Result> { + self.decode_selectors(selector_type, std::iter::once(selector)) + .await? + .pop() // Not returning on the previous line ensures a vector with exactly 1 element + .unwrap() + .ok_or_else(|| eyre::eyre!("No signature found")) + } + + /// Decodes the given function or event selectors using https://api.openchain.xyz + pub async fn decode_selectors( + &self, + selector_type: SelectorType, + selectors: impl IntoIterator>, + ) -> eyre::Result>>> { + let selectors: Vec = selectors + .into_iter() + .map(Into::into) + .map(|s| s.to_lowercase()) + .map(|s| if s.starts_with("0x") { s } else { format!("0x{s}") }) + .collect(); + + if selectors.is_empty() { + return Ok(vec![]); + } + + debug!(len = selectors.len(), "decoding selectors"); + trace!(?selectors, "decoding selectors"); + // exit early if spurious connection self.ensure_not_spurious()?; + let expected_len = match selector_type { + SelectorType::Function => 10, // 0x + hex(4bytes) + SelectorType::Event => 66, // 0x + hex(32bytes) + }; + if let Some(s) = selectors.iter().find(|s| s.len() != expected_len) { + eyre::bail!( + "Invalid selector {s}: expected {expected_len} characters (including 0x prefix)." + ) + } + #[derive(Deserialize)] struct Decoded { name: String, - filtered: bool, } #[derive(Deserialize)] struct ApiResult { - event: HashMap>, - function: HashMap>, + event: HashMap>>, + function: HashMap>>, } #[derive(Deserialize)] @@ -163,10 +203,14 @@ impl SignEthClient { // using openchain.xyz signature database over 4byte // see https://github.com/foundry-rs/foundry/issues/1672 - let url = match selector_type { - SelectorType::Function => format!("{SELECTOR_DATABASE_URL}lookup?function={selector}"), - SelectorType::Event => format!("{SELECTOR_DATABASE_URL}lookup?event={selector}"), - }; + let url = format!( + "{SELECTOR_LOOKUP_URL}?{ltype}={selectors_str}", + ltype = match selector_type { + SelectorType::Function => "function", + SelectorType::Event => "event", + }, + selectors_str = selectors.join(",") + ); let res = self.get_text(&url).await?; let api_response = match serde_json::from_str::(&res) { @@ -185,27 +229,18 @@ impl SignEthClient { SelectorType::Event => api_response.result.event, }; - Ok(decoded - .get(selector) - .ok_or_else(|| eyre::eyre!("No signature found"))? - .iter() - .filter(|&d| !d.filtered) - .map(|d| d.name.clone()) - .collect::>()) + Ok(selectors + .into_iter() + .map(|selector| match decoded.get(&selector) { + Some(Some(r)) => Some(r.iter().map(|d| d.name.clone()).collect()), + _ => None, + }) + .collect()) } /// Fetches a function signature given the selector using https://api.openchain.xyz pub async fn decode_function_selector(&self, selector: &str) -> eyre::Result> { - let stripped_selector = selector.strip_prefix("0x").unwrap_or(selector); - let prefixed_selector = format!("0x{}", stripped_selector); - if prefixed_selector.len() != 10 { - eyre::bail!( - "Invalid selector: expected 8 characters (excluding 0x prefix), got {}.", - stripped_selector.len() - ) - } - - self.decode_selector(&prefixed_selector[..10], SelectorType::Function).await + self.decode_selector(selector, SelectorType::Function).await } /// Fetches all possible signatures and attempts to abi decode the calldata @@ -230,11 +265,7 @@ impl SignEthClient { /// Fetches an event signature given the 32 byte topic using https://api.openchain.xyz pub async fn decode_event_topic(&self, topic: &str) -> eyre::Result> { - let prefixed_topic = format!("0x{}", topic.strip_prefix("0x").unwrap_or(topic)); - if prefixed_topic.len() != 66 { - eyre::bail!("Invalid topic: expected 64 characters (excluding 0x prefix), got {} characters (including 0x prefix).", prefixed_topic.len()) - } - self.decode_selector(&prefixed_topic[..66], SelectorType::Event).await + self.decode_selector(topic, SelectorType::Event).await } /// Pretty print calldata and if available, fetch possible function signatures @@ -374,12 +405,20 @@ pub enum SelectorType { /// Decodes the given function or event selector using https://api.openchain.xyz pub async fn decode_selector( - selector: &str, selector_type: SelectorType, + selector: &str, ) -> eyre::Result> { SignEthClient::new()?.decode_selector(selector, selector_type).await } +/// Decodes the given function or event selectors using https://api.openchain.xyz +pub async fn decode_selectors( + selector_type: SelectorType, + selectors: impl IntoIterator>, +) -> eyre::Result>>> { + SignEthClient::new()?.decode_selectors(selector_type, selectors).await +} + /// Fetches a function signature given the selector https://api.openchain.xyz pub async fn decode_function_selector(selector: &str) -> eyre::Result> { SignEthClient::new()?.decode_function_selector(selector).await @@ -410,7 +449,6 @@ pub async fn decode_event_topic(topic: &str) -> eyre::Result> { /// # Ok(()) /// # } /// ``` - pub async fn pretty_calldata( calldata: impl AsRef, offline: bool, @@ -418,7 +456,7 @@ pub async fn pretty_calldata( SignEthClient::new()?.pretty_calldata(calldata, offline).await } -#[derive(Default, Serialize, PartialEq, Debug, Eq)] +#[derive(Debug, Default, PartialEq, Eq, Serialize)] pub struct RawSelectorImportData { pub function: Vec, pub event: Vec, @@ -490,7 +528,7 @@ pub async fn import_selectors(data: SelectorImportData) -> eyre::Result, @@ -568,7 +606,7 @@ mod tests { .map_err(|e| { assert_eq!( e.to_string(), - "Invalid selector: expected 8 characters (excluding 0x prefix), got 6." + "Invalid selector 0xa9059c: expected 10 characters (including 0x prefix)." ) }) .map(|_| panic!("Expected fourbyte error")) @@ -684,4 +722,33 @@ mod tests { .await; assert_eq!(decoded.unwrap()[0], "canCall(address,address,bytes4)".to_string()); } + + #[tokio::test(flavor = "multi_thread")] + async fn test_decode_selectors() { + let event_topics = vec![ + "7e1db2a1cd12f0506ecd806dba508035b290666b84b096a87af2fd2a1516ede6", + "0xb7009613e63fb13fd59a2fa4c206a992c1f090a44e5d530be255aa17fed0b3dd", + ]; + let decoded = decode_selectors(SelectorType::Event, event_topics).await; + let decoded = decoded.unwrap(); + assert_eq!( + decoded, + vec![ + Some(vec!["updateAuthority(address,uint8)".to_string()]), + Some(vec!["canCall(address,address,bytes4)".to_string()]), + ] + ); + + let function_selectors = vec!["0xa9059cbb", "0x70a08231", "313ce567"]; + let decoded = decode_selectors(SelectorType::Function, function_selectors).await; + let decoded = decoded.unwrap(); + assert_eq!( + decoded, + vec![ + Some(vec!["transfer(address,uint256)".to_string()]), + Some(vec!["balanceOf(address)".to_string()]), + Some(vec!["decimals()".to_string()]), + ] + ); + } } diff --git a/crates/common/src/serde_helpers.rs b/crates/common/src/serde_helpers.rs new file mode 100644 index 0000000000000..c1b61b6e24241 --- /dev/null +++ b/crates/common/src/serde_helpers.rs @@ -0,0 +1,129 @@ +//! Misc Serde helpers for foundry crates. + +use alloy_primitives::U256; +use serde::{de, Deserialize, Deserializer}; +use std::str::FromStr; + +/// Helper type to parse both `u64` and `U256` +#[derive(Copy, Clone, Deserialize)] +#[serde(untagged)] +pub enum Numeric { + /// A [U256] value. + U256(U256), + /// A `u64` value. + Num(u64), +} + +impl From for U256 { + fn from(n: Numeric) -> U256 { + match n { + Numeric::U256(n) => n, + Numeric::Num(n) => U256::from(n), + } + } +} + +impl FromStr for Numeric { + type Err = String; + + fn from_str(s: &str) -> Result { + if let Ok(val) = s.parse::() { + Ok(Numeric::U256(U256::from(val))) + } else if s.starts_with("0x") { + U256::from_str_radix(s, 16).map(Numeric::U256).map_err(|err| err.to_string()) + } else { + U256::from_str(s).map(Numeric::U256).map_err(|err| err.to_string()) + } + } +} + +/// Deserializes the input into an `Option`, using [`from_int_or_hex`] to deserialize the +/// inner value. +pub fn from_int_or_hex_opt<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + match Option::::deserialize(deserializer)? { + Some(val) => val.try_into_u256().map(Some), + None => Ok(None), + } +} + +/// An enum that represents either a [serde_json::Number] integer, or a hex [U256]. +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum NumberOrHexU256 { + /// An integer + Int(serde_json::Number), + /// A hex U256 + Hex(U256), +} + +impl NumberOrHexU256 { + /// Tries to convert this into a [U256]]. + pub fn try_into_u256(self) -> Result { + match self { + NumberOrHexU256::Int(num) => { + U256::from_str(num.to_string().as_str()).map_err(E::custom) + } + NumberOrHexU256::Hex(val) => Ok(val), + } + } +} + +/// Deserializes the input into a U256, accepting both 0x-prefixed hex and decimal strings with +/// arbitrary precision, defined by serde_json's [`Number`](serde_json::Number). +pub fn from_int_or_hex<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + NumberOrHexU256::deserialize(deserializer)?.try_into_u256() +} + +/// Helper type to deserialize sequence of numbers +#[derive(Deserialize)] +#[serde(untagged)] +pub enum NumericSeq { + /// Single parameter sequence (e.g [1]) + Seq([Numeric; 1]), + /// U256 + U256(U256), + /// Native u64 + Num(u64), +} + +/// Deserializes a number from hex or int +pub fn deserialize_number<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + Numeric::deserialize(deserializer).map(Into::into) +} + +/// Deserializes a number from hex or int, but optionally +pub fn deserialize_number_opt<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let num = match Option::::deserialize(deserializer)? { + Some(Numeric::U256(n)) => Some(n), + Some(Numeric::Num(n)) => Some(U256::from(n)), + _ => None, + }; + + Ok(num) +} + +/// Deserializes single integer params: `1, [1], ["0x01"]` +pub fn deserialize_number_seq<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let num = match NumericSeq::deserialize(deserializer)? { + NumericSeq::Seq(seq) => seq[0].into(), + NumericSeq::U256(n) => n, + NumericSeq::Num(n) => U256::from(n), + }; + + Ok(num) +} diff --git a/crates/common/src/shell.rs b/crates/common/src/shell.rs index 9b359fbc49350..9955269ee38aa 100644 --- a/crates/common/src/shell.rs +++ b/crates/common/src/shell.rs @@ -13,7 +13,7 @@ use std::{ static SHELL: OnceCell = OnceCell::new(); /// Error indicating that `set_hook` was unable to install the provided ErrorHook -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct InstallError; impl fmt::Display for InstallError { @@ -283,7 +283,7 @@ impl ShellOut { } /// The requested verbosity of output. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum Verbosity { /// only allow json output Json, diff --git a/crates/common/src/term.rs b/crates/common/src/term.rs index 573bfb2f5e618..5cfb5ef37fdda 100644 --- a/crates/common/src/term.rs +++ b/crates/common/src/term.rs @@ -69,7 +69,7 @@ impl Spinner { return } - let indicator = Paint::green(self.indicator[self.idx % self.indicator.len()]); + let indicator = self.indicator[self.idx % self.indicator.len()].green(); let indicator = Paint::new(format!("[{indicator}]")).bold(); print!("\r\x33[2K\r{indicator} {}", self.message); io::stdout().flush().unwrap(); @@ -86,6 +86,7 @@ impl Spinner { /// /// This reporter will prefix messages with a spinning cursor #[derive(Debug)] +#[must_use = "Terminates the spinner on drop"] pub struct SpinnerReporter { /// The sender to the spinner thread. sender: mpsc::Sender, @@ -193,7 +194,7 @@ impl Reporter for SpinnerReporter { } fn on_solc_installation_error(&self, version: &Version, error: &str) { - self.send_msg(Paint::red(format!("Failed to install Solc {version}: {error}")).to_string()); + self.send_msg(format!("Failed to install Solc {version}: {error}").red().to_string()); } fn on_unresolved_imports(&self, imports: &[(&Path, &Path)], remappings: &[Remapping]) { @@ -220,8 +221,8 @@ macro_rules! cli_warn { ($($arg:tt)*) => { eprintln!( "{}{} {}", - yansi::Paint::yellow("warning").bold(), - yansi::Paint::new(":").bold(), + yansi::Painted::new("warning").yellow().bold(), + yansi::Painted::new(":").bold(), format_args!($($arg)*) ) } diff --git a/crates/common/src/traits.rs b/crates/common/src/traits.rs index c5e8f96a278c6..8ed1edbec304d 100644 --- a/crates/common/src/traits.rs +++ b/crates/common/src/traits.rs @@ -3,32 +3,39 @@ use alloy_json_abi::Function; use alloy_primitives::Bytes; use alloy_sol_types::SolError; -use auto_impl::auto_impl; +use std::path::Path; -/// Extension trait for matching tests -#[auto_impl(&)] +/// Test filter. pub trait TestFilter: Send + Sync { - /// Returns whether the test should be included - fn matches_test(&self, test_name: impl AsRef) -> bool; - /// Returns whether the contract should be included - fn matches_contract(&self, contract_name: impl AsRef) -> bool; - /// Returns a contract with the given path should be included - fn matches_path(&self, path: impl AsRef) -> bool; + /// Returns whether the test should be included. + fn matches_test(&self, test_name: &str) -> bool; + + /// Returns whether the contract should be included. + fn matches_contract(&self, contract_name: &str) -> bool; + + /// Returns a contract with the given path should be included. + fn matches_path(&self, path: &Path) -> bool; } -/// Extension trait for `Function` -#[auto_impl(&)] +/// Extension trait for `Function`. pub trait TestFunctionExt { - /// Whether this function should be executed as invariant test + /// Returns whether this function should be executed as invariant test. fn is_invariant_test(&self) -> bool; - /// Whether this function should be executed as fuzz test + + /// Returns whether this function should be executed as fuzz test. fn is_fuzz_test(&self) -> bool; - /// Whether this function is a test + + /// Returns whether this function is a test. fn is_test(&self) -> bool; - /// Whether this function is a test that should fail + + /// Returns whether this function is a test that should fail. fn is_test_fail(&self) -> bool; - /// Whether this function is a `setUp` function + + /// Returns whether this function is a `setUp` function. fn is_setup(&self) -> bool; + + /// Returns whether this function is a fixture function. + fn is_fixture(&self) -> bool; } impl TestFunctionExt for Function { @@ -52,6 +59,10 @@ impl TestFunctionExt for Function { fn is_setup(&self) -> bool { self.name.is_setup() } + + fn is_fixture(&self) -> bool { + self.name.is_fixture() + } } impl TestFunctionExt for String { @@ -74,6 +85,10 @@ impl TestFunctionExt for String { fn is_setup(&self) -> bool { self.as_str().is_setup() } + + fn is_fixture(&self) -> bool { + self.as_str().is_fixture() + } } impl TestFunctionExt for str { @@ -82,7 +97,7 @@ impl TestFunctionExt for str { } fn is_fuzz_test(&self) -> bool { - unimplemented!("no naming convention for fuzz tests.") + unimplemented!("no naming convention for fuzz tests") } fn is_test(&self) -> bool { @@ -96,6 +111,10 @@ impl TestFunctionExt for str { fn is_setup(&self) -> bool { self.eq_ignore_ascii_case("setup") } + + fn is_fixture(&self) -> bool { + self.starts_with("fixture") + } } /// An extension trait for `std::error::Error` for ABI encoding. diff --git a/crates/common/src/transactions.rs b/crates/common/src/transactions.rs index 78838bb957b49..1f7d6228eef3c 100644 --- a/crates/common/src/transactions.rs +++ b/crates/common/src/transactions.rs @@ -1,15 +1,16 @@ //! wrappers for transactions -use ethers_core::types::{BlockId, TransactionReceipt}; -use ethers_providers::Middleware; +use alloy_provider::{network::AnyNetwork, Provider}; +use alloy_rpc_types::{AnyTransactionReceipt, BlockId, WithOtherFields}; +use alloy_transport::Transport; use eyre::Result; use serde::{Deserialize, Serialize}; /// Helper type to carry a transaction along with an optional revert reason -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TransactionReceiptWithRevertReason { /// The underlying transaction receipt #[serde(flatten)] - pub receipt: TransactionReceipt, + pub receipt: AnyTransactionReceipt, /// The revert reason string if the transaction status is failed #[serde(skip_serializing_if = "Option::is_none", rename = "revertReason")] @@ -18,68 +19,67 @@ pub struct TransactionReceiptWithRevertReason { impl TransactionReceiptWithRevertReason { /// Returns if the status of the transaction is 0 (failure) - pub fn is_failure(&self) -> Option { - self.receipt.status.map(|status| status.as_u64() == 0) + pub fn is_failure(&self) -> bool { + !self.receipt.inner.inner.inner.receipt.status } /// Updates the revert reason field using `eth_call` and returns an Err variant if the revert /// reason was not successfully updated - pub async fn update_revert_reason(&mut self, provider: &M) -> Result<()> { + pub async fn update_revert_reason>( + &mut self, + provider: &P, + ) -> Result<()> { self.revert_reason = self.fetch_revert_reason(provider).await?; Ok(()) } - async fn fetch_revert_reason(&self, provider: &M) -> Result> { - if let Some(false) | None = self.is_failure() { + async fn fetch_revert_reason>( + &self, + provider: &P, + ) -> Result> { + if !self.is_failure() { return Ok(None) } - if let Some(ref transaction) = provider - .get_transaction(self.receipt.transaction_hash) + let transaction = provider + .get_transaction_by_hash(self.receipt.transaction_hash) .await - .map_err(|_| eyre::eyre!("unable to fetch transaction"))? - { - if let Some(block_hash) = self.receipt.block_hash { - match provider.call(&transaction.into(), Some(BlockId::Hash(block_hash))).await { - Err(e) => return Ok(extract_revert_reason(e.to_string())), - Ok(_) => eyre::bail!("no revert reason as transaction succeeded"), - } + .map_err(|_| eyre::eyre!("unable to fetch transaction"))?; + + if let Some(block_hash) = self.receipt.block_hash { + match provider + .call( + &WithOtherFields::new(transaction.inner.into()), + BlockId::Hash(block_hash.into()), + ) + .await + { + Err(e) => return Ok(extract_revert_reason(e.to_string())), + Ok(_) => eyre::bail!("no revert reason as transaction succeeded"), } - eyre::bail!("unable to fetch block_hash") } - Err(eyre::eyre!("transaction does not exist")) + eyre::bail!("unable to fetch block_hash") } } -impl From for TransactionReceiptWithRevertReason { - fn from(receipt: TransactionReceipt) -> Self { +impl From for TransactionReceiptWithRevertReason { + fn from(receipt: AnyTransactionReceipt) -> Self { Self { receipt, revert_reason: None } } } -impl From for TransactionReceipt { +impl From for AnyTransactionReceipt { fn from(receipt_with_reason: TransactionReceiptWithRevertReason) -> Self { receipt_with_reason.receipt } } fn extract_revert_reason>(error_string: S) -> Option { - let message_substr = "message: execution reverted: "; - - let mut temp = ""; - + let message_substr = "execution reverted: "; error_string .as_ref() .find(message_substr) - .and_then(|index| { - let (_, rest) = error_string.as_ref().split_at(index + message_substr.len()); - temp = rest; - rest.rfind(", ") - }) - .map(|index| { - let (reason, _) = temp.split_at(index); - reason.to_string() - }) + .map(|index| error_string.as_ref().split_at(index + message_substr.len()).1.to_string()) } #[cfg(test)] @@ -88,16 +88,10 @@ mod tests { #[test] fn test_extract_revert_reason() { - let error_string_1 = "(code: 3, message: execution reverted: Transaction too old, data: Some(String(\"0x08c379a0\")))"; - let error_string_2 = "(code: 3, message: execution reverted: missing data: amountIn, amountOut, data: Some(String(\"0x08c379a0\")))"; - let error_string_3 = - "(code: 4, message: invalid signature, data: Some(String(\"0x08c379a0\")))"; + let error_string_1 = "server returned an error response: error code 3: execution reverted: Transaction too old"; + let error_string_2 = "server returned an error response: error code 3: Invalid signature"; assert_eq!(extract_revert_reason(error_string_1), Some("Transaction too old".to_string())); - assert_eq!( - extract_revert_reason(error_string_2), - Some("missing data: amountIn, amountOut".to_string()) - ); - assert_eq!(extract_revert_reason(error_string_3), None); + assert_eq!(extract_revert_reason(error_string_2), None); } } diff --git a/crates/common/src/types.rs b/crates/common/src/types.rs deleted file mode 100644 index 0413805a16aa0..0000000000000 --- a/crates/common/src/types.rs +++ /dev/null @@ -1,250 +0,0 @@ -//! Temporary utility conversion traits between ethers-rs and alloy types. - -use alloy_json_abi::{Event, EventParam, Function, InternalType, Param, StateMutability}; -use alloy_primitives::{Address, Bloom, Bytes, B256, B64, I256, U256, U64}; -use ethers_core::{ - abi as ethabi, - types::{ - Bloom as EthersBloom, Bytes as EthersBytes, H160, H256, H64, I256 as EthersI256, - U256 as EthersU256, U64 as EthersU64, - }, -}; - -/// Conversion trait to easily convert from Ethers types to Alloy types. -pub trait ToAlloy { - /// The corresponding Alloy type. - type To; - - /// Converts the Ethers type to the corresponding Alloy type. - fn to_alloy(self) -> Self::To; -} - -impl ToAlloy for EthersBytes { - type To = Bytes; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - Bytes(self.0) - } -} - -impl ToAlloy for H64 { - type To = B64; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - B64::new(self.0) - } -} - -impl ToAlloy for H160 { - type To = Address; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - Address::new(self.0) - } -} - -impl ToAlloy for H256 { - type To = B256; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - B256::new(self.0) - } -} - -impl ToAlloy for EthersBloom { - type To = Bloom; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - Bloom::new(self.0) - } -} - -impl ToAlloy for EthersU256 { - type To = U256; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - U256::from_limbs(self.0) - } -} - -impl ToAlloy for EthersI256 { - type To = I256; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - I256::from_raw(self.into_raw().to_alloy()) - } -} - -impl ToAlloy for EthersU64 { - type To = U64; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - U64::from_limbs(self.0) - } -} - -impl ToAlloy for u64 { - type To = U256; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - U256::from(self) - } -} - -impl ToAlloy for ethabi::Event { - type To = Event; - - fn to_alloy(self) -> Self::To { - Event { - name: self.name, - inputs: self.inputs.into_iter().map(ToAlloy::to_alloy).collect(), - anonymous: self.anonymous, - } - } -} - -impl ToAlloy for ethabi::Function { - type To = Function; - - fn to_alloy(self) -> Self::To { - Function { - name: self.name, - inputs: self.inputs.into_iter().map(ToAlloy::to_alloy).collect(), - outputs: self.outputs.into_iter().map(ToAlloy::to_alloy).collect(), - state_mutability: self.state_mutability.to_alloy(), - } - } -} - -impl ToAlloy for ethabi::Param { - type To = Param; - - fn to_alloy(self) -> Self::To { - let (ty, components) = self.kind.to_alloy(); - Param { - name: self.name, - ty, - internal_type: self.internal_type.as_deref().and_then(InternalType::parse), - components, - } - } -} - -impl ToAlloy for ethabi::EventParam { - type To = EventParam; - - fn to_alloy(self) -> Self::To { - let (ty, components) = self.kind.to_alloy(); - EventParam { name: self.name, ty, internal_type: None, components, indexed: self.indexed } - } -} - -impl ToAlloy for ethabi::ParamType { - type To = (String, Vec); - - fn to_alloy(self) -> Self::To { - let (s, t) = split_pt(self); - (s, t.into_iter().map(pt_to_param).collect()) - } -} - -fn split_pt(x: ethabi::ParamType) -> (String, Vec) { - let s = ethabi::ethabi::param_type::Writer::write_for_abi(&x, false); - let t = get_tuple(x); - (s, t) -} - -fn get_tuple(x: ethabi::ParamType) -> Vec { - match x { - ethabi::ParamType::FixedArray(x, _) | ethabi::ParamType::Array(x) => get_tuple(*x), - ethabi::ParamType::Tuple(t) => t, - _ => Default::default(), - } -} - -fn pt_to_param(x: ethabi::ParamType) -> Param { - let (ty, components) = split_pt(x); - Param { - name: String::new(), - ty, - internal_type: None, - components: components.into_iter().map(pt_to_param).collect(), - } -} - -impl ToAlloy for ethabi::StateMutability { - type To = StateMutability; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - match self { - ethabi::StateMutability::Pure => StateMutability::Pure, - ethabi::StateMutability::View => StateMutability::View, - ethabi::StateMutability::NonPayable => StateMutability::NonPayable, - ethabi::StateMutability::Payable => StateMutability::Payable, - } - } -} - -/// Conversion trait to easily convert from Alloy types to Ethers types. -pub trait ToEthers { - /// The corresponding Ethers type. - type To; - - /// Converts the Alloy type to the corresponding Ethers type. - fn to_ethers(self) -> Self::To; -} - -impl ToEthers for Address { - type To = H160; - - #[inline(always)] - fn to_ethers(self) -> Self::To { - H160(self.0 .0) - } -} - -impl ToEthers for B256 { - type To = H256; - - #[inline(always)] - fn to_ethers(self) -> Self::To { - H256(self.0) - } -} - -impl ToEthers for U256 { - type To = EthersU256; - - #[inline(always)] - fn to_ethers(self) -> Self::To { - EthersU256(self.into_limbs()) - } -} - -impl ToEthers for U64 { - type To = EthersU64; - - #[inline(always)] - fn to_ethers(self) -> Self::To { - EthersU64(self.into_limbs()) - } -} - -impl ToEthers for Bytes { - type To = EthersBytes; - - #[inline(always)] - fn to_ethers(self) -> Self::To { - EthersBytes(self.0) - } -} diff --git a/crates/common/src/units.rs b/crates/common/src/units.rs deleted file mode 100644 index 3a85ba3e04b19..0000000000000 --- a/crates/common/src/units.rs +++ /dev/null @@ -1,329 +0,0 @@ -//! Unit conversion utilities. - -use alloy_primitives::{Address, ParseSignedError, I256, U256}; -use std::{fmt, str::FromStr}; -use thiserror::Error; - -/// I256 overflows for numbers wider than 77 units. -const OVERFLOW_I256_UNITS: usize = 77; -/// U256 overflows for numbers wider than 78 units. -const OVERFLOW_U256_UNITS: usize = 78; - -/// Common Ethereum unit types. -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum Units { - /// Wei is equivalent to 1 wei. - Wei, - /// Kwei is equivalent to 1e3 wei. - Kwei, - /// Mwei is equivalent to 1e6 wei. - Mwei, - /// Gwei is equivalent to 1e9 wei. - Gwei, - /// Twei is equivalent to 1e12 wei. - Twei, - /// Pwei is equivalent to 1e15 wei. - Pwei, - /// Ether is equivalent to 1e18 wei. - Ether, - /// Other less frequent unit sizes, equivalent to 1e{0} wei. - Other(u32), -} - -impl fmt::Display for Units { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.pad(self.as_num().to_string().as_str()) - } -} - -impl TryFrom for Units { - type Error = ConversionError; - - fn try_from(value: u32) -> Result { - Ok(Units::Other(value)) - } -} - -impl TryFrom for Units { - type Error = ConversionError; - - fn try_from(value: i32) -> Result { - Ok(Units::Other(value as u32)) - } -} - -impl TryFrom for Units { - type Error = ConversionError; - - fn try_from(value: usize) -> Result { - Ok(Units::Other(value as u32)) - } -} - -impl TryFrom for Units { - type Error = ConversionError; - - fn try_from(value: String) -> Result { - Self::from_str(&value) - } -} - -impl<'a> TryFrom<&'a String> for Units { - type Error = ConversionError; - - fn try_from(value: &'a String) -> Result { - Self::from_str(value) - } -} - -impl TryFrom<&str> for Units { - type Error = ConversionError; - - fn try_from(value: &str) -> Result { - Self::from_str(value) - } -} - -impl FromStr for Units { - type Err = ConversionError; - - fn from_str(s: &str) -> Result { - Ok(match s.to_lowercase().as_str() { - "eth" | "ether" => Units::Ether, - "pwei" | "milli" | "milliether" | "finney" => Units::Pwei, - "twei" | "micro" | "microether" | "szabo" => Units::Twei, - "gwei" | "nano" | "nanoether" | "shannon" => Units::Gwei, - "mwei" | "pico" | "picoether" | "lovelace" => Units::Mwei, - "kwei" | "femto" | "femtoether" | "babbage" => Units::Kwei, - "wei" => Units::Wei, - _ => return Err(ConversionError::UnrecognizedUnits(s.to_string())), - }) - } -} - -impl From for u32 { - fn from(units: Units) -> Self { - units.as_num() - } -} - -impl From for i32 { - fn from(units: Units) -> Self { - units.as_num() as i32 - } -} - -impl From for usize { - fn from(units: Units) -> Self { - units.as_num() as usize - } -} - -impl Units { - /// Converts the ethereum unit to its numeric representation. - pub fn as_num(&self) -> u32 { - match self { - Units::Wei => 0, - Units::Kwei => 3, - Units::Mwei => 6, - Units::Gwei => 9, - Units::Twei => 12, - Units::Pwei => 15, - Units::Ether => 18, - Units::Other(inner) => *inner, - } - } -} - -/// This enum holds the numeric types that a possible to be returned by `parse_units` and -/// that are taken by `format_units`. -#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] -pub enum ParseUnits { - /// Unsigned 256-bit integer. - U256(U256), - /// Signed 256-bit integer. - I256(I256), -} - -impl From for U256 { - fn from(n: ParseUnits) -> Self { - match n { - ParseUnits::U256(n) => n, - ParseUnits::I256(n) => n.into_raw(), - } - } -} - -impl From for I256 { - fn from(n: ParseUnits) -> Self { - match n { - ParseUnits::I256(n) => n, - ParseUnits::U256(n) => I256::from_raw(n), - } - } -} - -impl From> for ParseUnits { - fn from(n: alloy_primitives::Signed<256, 4>) -> Self { - Self::I256(n) - } -} - -impl fmt::Display for ParseUnits { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ParseUnits::U256(val) => val.fmt(f), - ParseUnits::I256(val) => val.fmt(f), - } - } -} - -macro_rules! construct_format_units_from { - ($( $t:ty[$convert:ident] ),*) => { - $( - impl From<$t> for ParseUnits { - fn from(num: $t) -> Self { - Self::$convert(U256::from(num)) - } - } - )* - } -} - -macro_rules! construct_signed_format_units_from { - ($( $t:ty[$convert:ident] ),*) => { - $( - impl From<$t> for ParseUnits { - fn from(num: $t) -> Self { - Self::$convert(I256::from_raw(U256::from(num))) - } - } - )* - } -} - -// Generate the From code for the given numeric types below. -construct_format_units_from! { - u8[U256], u16[U256], u32[U256], u64[U256], u128[U256], U256[U256], usize[U256] -} - -construct_signed_format_units_from! { - i8[I256], i16[I256], i32[I256], i64[I256], i128[I256], isize[I256] -} - -/// Handles all possible conversion errors. -#[derive(Error, Debug)] -pub enum ConversionError { - /// The unit is unrecognized. - #[error("Unknown units: {0}")] - UnrecognizedUnits(String), - /// The provided hex string is invalid (too long). - #[error("bytes32 strings must not exceed 32 bytes in length")] - TextTooLong, - /// The provided string cannot be converted from Utf8. - #[error(transparent)] - Utf8Error(#[from] std::str::Utf8Error), - /// Invalid float. - #[error(transparent)] - InvalidFloat(#[from] std::num::ParseFloatError), - /// Could not convert from decimal string. - #[error("Invalid decimal string: {0}")] - FromDecStrError(String), - /// Overflowed while parsing. - #[error("Overflow parsing string")] - ParseOverflow, - /// Could not convert from signed decimal string. - #[error("Parse Signed Error")] - ParseI256Error(#[from] ParseSignedError), - /// Invalid checksum. - #[error("Invalid address checksum")] - InvalidAddressChecksum, - /// Invalid hex. - #[error(transparent)] - FromHexError(
::Err), -} - -/// Divides the provided amount with 10^{units} provided. -pub fn format_units(amount: T, units: K) -> Result -where - T: Into, - K: TryInto, -{ - let units: usize = units.try_into()?.into(); - let amount = amount.into(); - - match amount { - // 2**256 ~= 1.16e77 - ParseUnits::U256(_) if units >= OVERFLOW_U256_UNITS => { - return Err(ConversionError::ParseOverflow) - } - // 2**255 ~= 5.79e76 - ParseUnits::I256(_) if units >= OVERFLOW_I256_UNITS => { - return Err(ConversionError::ParseOverflow) - } - _ => {} - }; - let exp10 = U256::pow(U256::from(10), U256::from(units)); - - // `decimals` are formatted twice because U256 does not support alignment (`:0>width`). - match amount { - ParseUnits::U256(amount) => { - let integer = amount / exp10; - let decimals = (amount % exp10).to_string(); - Ok(format!("{integer}.{decimals:0>units$}")) - } - ParseUnits::I256(amount) => { - let exp10 = I256::from_raw(exp10); - let sign = if amount.is_negative() { "-" } else { "" }; - let integer = (amount / exp10).twos_complement(); - let decimals = ((amount % exp10).twos_complement()).to_string(); - Ok(format!("{sign}{integer}.{decimals:0>units$}")) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use Units::*; - - #[test] - fn test_units() { - assert_eq!(Wei.as_num(), 0); - assert_eq!(Kwei.as_num(), 3); - assert_eq!(Mwei.as_num(), 6); - assert_eq!(Gwei.as_num(), 9); - assert_eq!(Twei.as_num(), 12); - assert_eq!(Pwei.as_num(), 15); - assert_eq!(Ether.as_num(), 18); - assert_eq!(Other(10).as_num(), 10); - assert_eq!(Other(20).as_num(), 20); - } - - #[test] - fn test_into() { - assert_eq!(Units::try_from("wei").unwrap(), Wei); - assert_eq!(Units::try_from("kwei").unwrap(), Kwei); - assert_eq!(Units::try_from("mwei").unwrap(), Mwei); - assert_eq!(Units::try_from("gwei").unwrap(), Gwei); - assert_eq!(Units::try_from("twei").unwrap(), Twei); - assert_eq!(Units::try_from("pwei").unwrap(), Pwei); - assert_eq!(Units::try_from("ether").unwrap(), Ether); - - assert_eq!(Units::try_from("wei".to_string()).unwrap(), Wei); - assert_eq!(Units::try_from("kwei".to_string()).unwrap(), Kwei); - assert_eq!(Units::try_from("mwei".to_string()).unwrap(), Mwei); - assert_eq!(Units::try_from("gwei".to_string()).unwrap(), Gwei); - assert_eq!(Units::try_from("twei".to_string()).unwrap(), Twei); - assert_eq!(Units::try_from("pwei".to_string()).unwrap(), Pwei); - assert_eq!(Units::try_from("ether".to_string()).unwrap(), Ether); - - assert_eq!(Units::try_from(&"wei".to_string()).unwrap(), Wei); - assert_eq!(Units::try_from(&"kwei".to_string()).unwrap(), Kwei); - assert_eq!(Units::try_from(&"mwei".to_string()).unwrap(), Mwei); - assert_eq!(Units::try_from(&"gwei".to_string()).unwrap(), Gwei); - assert_eq!(Units::try_from(&"twei".to_string()).unwrap(), Twei); - assert_eq!(Units::try_from(&"pwei".to_string()).unwrap(), Pwei); - assert_eq!(Units::try_from(&"ether".to_string()).unwrap(), Ether); - } -} diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 9afbc7dfb8c9f..7a65678d4f8a0 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -16,9 +16,12 @@ foundry-compilers = { workspace = true, features = ["svm-solc"] } alloy-chains = { workspace = true, features = ["serde"] } alloy-primitives = { workspace = true, features = ["serde"] } -revm-primitives = { workspace = true, default-features = false, features = ["std"] } +revm-primitives.workspace = true + +solang-parser.workspace = true dirs-next = "2" +dunce = "1" eyre.workspace = true figment = { version = "0.10", features = ["toml", "env"] } globset = "0.4" @@ -26,14 +29,14 @@ Inflector = "0.11" number_prefix = "0.4" once_cell = "1" regex = "1" -reqwest = { version = "0.11", default-features = false } +reqwest.workspace = true semver = { version = "1", features = ["serde"] } serde_json.workspace = true serde_regex = "1" serde.workspace = true thiserror = "1" toml = { version = "0.8", features = ["preserve_order"] } -toml_edit = "0.21" +toml_edit = "0.22.4" tracing.workspace = true walkdir = "2" @@ -43,4 +46,8 @@ path-slash = "0.2.1" [dev-dependencies] pretty_assertions.workspace = true figment = { version = "0.10", features = ["test"] } -tempfile = "3" +tempfile.workspace = true + +[features] +default = ["rustls"] +rustls = ["reqwest/rustls-tls-native-roots"] diff --git a/crates/config/README.md b/crates/config/README.md index 6e4667293cf5b..ffa4cdc750257 100644 --- a/crates/config/README.md +++ b/crates/config/README.md @@ -56,7 +56,7 @@ The selected profile is the value of the `FOUNDRY_PROFILE` environment variable, ### All Options -The following is a foundry.toml file with all configuration options set. See also [/config/src/lib.rs](/config/src/lib.rs) and [/cli/tests/it/config.rs](/cli/tests/it/config.rs). +The following is a foundry.toml file with all configuration options set. See also [/config/src/lib.rs](./src/lib.rs) and [/cli/tests/it/config.rs](../forge/tests/it/config.rs). ```toml ## defaults for _all_ profiles @@ -106,6 +106,7 @@ etherscan_api_key = "YOURETHERSCANAPIKEY" # known error codes are: ["unreachable", "unused-return", "unused-param", "unused-var", "code-size", "shadowing", "func-mutability", "license", "pragma-solidity", "virtual-interfaces", "same-varname"] # additional warnings can be added using their numeric error code: ["license", 1337] ignored_error_codes = ["license", "code-size"] +ignored_warnings_from = ["path_to_ignore"] deny_warnings = false match_test = "Foo" no_match_test = "Bar" @@ -114,6 +115,8 @@ no_match_contract = "Bar" match_path = "*/Foo*" no_match_path = "*/Bar*" ffi = false +always_use_create_2_factory = false +prompt_timeout = 120 # These are the default callers, generated using `address(uint160(uint256(keccak256("foundry default caller"))))` sender = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' tx_origin = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' @@ -137,6 +140,7 @@ extra_output_files = [] names = false sizes = false via_ir = false +ast = false # caches storage retrieved locally for certain chains and endpoints # can also be restricted to `chains = ["optimism", "mainnet"]` # by default all endpoints will be cached, alternative options are "remote" for only caching non localhost endpoints and "" @@ -192,6 +196,7 @@ dictionary_weight = 80 include_storage = true include_push_bytes = true shrink_sequence = true +preserve_state = false [fmt] line_length = 100 @@ -249,7 +254,7 @@ The optional `url` attribute can be used to explicitly set the Etherscan API url [etherscan] mainnet = { key = "${ETHERSCAN_MAINNET_KEY}" } mainnet2 = { key = "ABCDEFG", chain = "mainnet" } -optimism = { key = "1234576" } +optimism = { key = "1234576", chain = 42 } unknownchain = { key = "ABCDEFG", url = "https://" } ``` diff --git a/crates/config/src/cache.rs b/crates/config/src/cache.rs index bebb3e5284cc3..ef0ca40a468ea 100644 --- a/crates/config/src/cache.rs +++ b/crates/config/src/cache.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{fmt, fmt::Formatter, str::FromStr}; /// Settings to configure caching of remote -#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct StorageCachingConfig { /// chains to cache pub chains: CachedChains, @@ -31,7 +31,7 @@ impl StorageCachingConfig { } /// What chains to cache -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub enum CachedChains { /// Cache all chains #[default] @@ -89,7 +89,7 @@ impl<'de> Deserialize<'de> for CachedChains { } /// What endpoints to enable caching for -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub enum CachedEndpoints { /// Cache all endpoints #[default] diff --git a/crates/config/src/doc.rs b/crates/config/src/doc.rs index 2dfac01b4df44..36e8a12b08df2 100644 --- a/crates/config/src/doc.rs +++ b/crates/config/src/doc.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; /// Contains the config for parsing and rendering docs -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct DocConfig { /// Doc output path. pub out: PathBuf, diff --git a/crates/config/src/endpoints.rs b/crates/config/src/endpoints.rs index 1f0708c109694..36cc8c7b6a83f 100644 --- a/crates/config/src/endpoints.rs +++ b/crates/config/src/endpoints.rs @@ -1,7 +1,7 @@ //! Support for multiple RPC-endpoints use crate::resolve::{interpolate, UnresolvedEnvVarError, RE_PLACEHOLDER}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; use std::{ collections::BTreeMap, fmt, @@ -9,18 +9,30 @@ use std::{ }; /// Container type for API endpoints, like various RPC endpoints -#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(transparent)] pub struct RpcEndpoints { - endpoints: BTreeMap, + endpoints: BTreeMap, } // === impl RpcEndpoints === impl RpcEndpoints { /// Creates a new list of endpoints - pub fn new(endpoints: impl IntoIterator, RpcEndpoint)>) -> Self { - Self { endpoints: endpoints.into_iter().map(|(name, url)| (name.into(), url)).collect() } + pub fn new( + endpoints: impl IntoIterator, impl Into)>, + ) -> Self { + Self { + endpoints: endpoints + .into_iter() + .map(|(name, e)| match e.into() { + RpcEndpointType::String(url) => { + (name.into(), RpcEndpointConfig { endpoint: url, ..Default::default() }) + } + RpcEndpointType::Config(config) => (name.into(), config), + }) + .collect(), + } } /// Returns `true` if this type doesn't contain any endpoints @@ -37,20 +49,80 @@ impl RpcEndpoints { } impl Deref for RpcEndpoints { - type Target = BTreeMap; + type Target = BTreeMap; fn deref(&self) -> &Self::Target { &self.endpoints } } +/// RPC endpoint wrapper type +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[serde(untagged)] +pub enum RpcEndpointType { + /// Raw Endpoint url string + String(RpcEndpoint), + /// Config object + Config(RpcEndpointConfig), +} + +impl RpcEndpointType { + /// Returns the string variant + pub fn as_endpoint_string(&self) -> Option<&RpcEndpoint> { + match self { + RpcEndpointType::String(url) => Some(url), + RpcEndpointType::Config(_) => None, + } + } + + /// Returns the config variant + pub fn as_endpoint_config(&self) -> Option<&RpcEndpointConfig> { + match self { + RpcEndpointType::Config(config) => Some(config), + RpcEndpointType::String(_) => None, + } + } + + /// Returns the url or config this type holds + /// + /// # Error + /// + /// Returns an error if the type holds a reference to an env var and the env var is not set + pub fn resolve(self) -> Result { + match self { + RpcEndpointType::String(url) => url.resolve(), + RpcEndpointType::Config(config) => config.endpoint.resolve(), + } + } +} + +impl fmt::Display for RpcEndpointType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RpcEndpointType::String(url) => url.fmt(f), + RpcEndpointType::Config(config) => config.fmt(f), + } + } +} + +impl TryFrom for String { + type Error = UnresolvedEnvVarError; + + fn try_from(value: RpcEndpointType) -> Result { + match value { + RpcEndpointType::String(url) => url.resolve(), + RpcEndpointType::Config(config) => config.endpoint.resolve(), + } + } +} + /// Represents a single endpoint /// /// This type preserves the value as it's stored in the config. If the value is a reference to an /// env var, then the `Endpoint::Env` var will hold the reference (`${MAIN_NET}`) and _not_ the /// value of the env var itself. /// In other words, this type does not resolve env vars when it's being deserialized -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum RpcEndpoint { /// A raw Url (ws, http) Url(String), @@ -134,8 +206,135 @@ impl<'de> Deserialize<'de> for RpcEndpoint { } } +impl From for RpcEndpointType { + fn from(endpoint: RpcEndpoint) -> Self { + RpcEndpointType::String(endpoint) + } +} + +impl From for RpcEndpointConfig { + fn from(endpoint: RpcEndpoint) -> Self { + RpcEndpointConfig { endpoint, ..Default::default() } + } +} + +/// Rpc endpoint configuration variant +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RpcEndpointConfig { + /// endpoint url or env + pub endpoint: RpcEndpoint, + + /// The number of retries. + pub retries: Option, + + /// Initial retry backoff. + pub retry_backoff: Option, + + /// The available compute units per second. + /// + /// See also + pub compute_units_per_second: Option, +} + +impl RpcEndpointConfig { + /// Returns the url this type holds, see [RpcEndpoints::resolve()] + pub fn resolve(self) -> Result { + self.endpoint.resolve() + } +} + +impl fmt::Display for RpcEndpointConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let RpcEndpointConfig { endpoint, retries, retry_backoff, compute_units_per_second } = self; + + write!(f, "{}", endpoint)?; + + if let Some(retries) = retries { + write!(f, ", retries={}", retries)?; + } + + if let Some(retry_backoff) = retry_backoff { + write!(f, ", retry_backoff={}", retry_backoff)?; + } + + if let Some(compute_units_per_second) = compute_units_per_second { + write!(f, ", compute_units_per_second={}", compute_units_per_second)?; + } + + Ok(()) + } +} + +impl Serialize for RpcEndpointConfig { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if self.retries.is_none() && + self.retry_backoff.is_none() && + self.compute_units_per_second.is_none() + { + // serialize as endpoint if there's no additional config + self.endpoint.serialize(serializer) + } else { + let mut map = serializer.serialize_map(Some(4))?; + map.serialize_entry("endpoint", &self.endpoint)?; + map.serialize_entry("retries", &self.retries)?; + map.serialize_entry("retry_backoff", &self.retry_backoff)?; + map.serialize_entry("compute_units_per_second", &self.compute_units_per_second)?; + map.end() + } + } +} + +impl<'de> Deserialize<'de> for RpcEndpointConfig { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = serde_json::Value::deserialize(deserializer)?; + if value.is_string() { + return Ok(Self { + endpoint: serde_json::from_value(value).map_err(serde::de::Error::custom)?, + ..Default::default() + }); + } + + #[derive(Deserialize)] + struct RpcEndpointConfigInner { + #[serde(alias = "url")] + endpoint: RpcEndpoint, + retries: Option, + retry_backoff: Option, + compute_units_per_second: Option, + } + + let RpcEndpointConfigInner { endpoint, retries, retry_backoff, compute_units_per_second } = + serde_json::from_value(value).map_err(serde::de::Error::custom)?; + + Ok(RpcEndpointConfig { endpoint, retries, retry_backoff, compute_units_per_second }) + } +} + +impl From for RpcEndpointType { + fn from(config: RpcEndpointConfig) -> Self { + RpcEndpointType::Config(config) + } +} + +impl Default for RpcEndpointConfig { + fn default() -> Self { + Self { + endpoint: RpcEndpoint::Url("http://localhost:8545".to_string()), + retries: None, + retry_backoff: None, + compute_units_per_second: None, + } + } +} + /// Container type for _resolved_ endpoints, see [RpcEndpoints::resolve_all()] -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct ResolvedRpcEndpoints { /// contains all named endpoints and their URL or an error if we failed to resolve the env var /// alias @@ -164,3 +363,40 @@ impl DerefMut for ResolvedRpcEndpoints { &mut self.endpoints } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde_rpc_config() { + let s = r#"{ + "endpoint": "http://localhost:8545", + "retries": 5, + "retry_backoff": 250, + "compute_units_per_second": 100 + }"#; + let config: RpcEndpointConfig = serde_json::from_str(s).unwrap(); + assert_eq!( + config, + RpcEndpointConfig { + endpoint: RpcEndpoint::Url("http://localhost:8545".to_string()), + retries: Some(5), + retry_backoff: Some(250), + compute_units_per_second: Some(100), + } + ); + + let s = "\"http://localhost:8545\""; + let config: RpcEndpointConfig = serde_json::from_str(s).unwrap(); + assert_eq!( + config, + RpcEndpointConfig { + endpoint: RpcEndpoint::Url("http://localhost:8545".to_string()), + retries: None, + retry_backoff: None, + compute_units_per_second: None, + } + ); + } +} diff --git a/crates/config/src/error.rs b/crates/config/src/error.rs index 2073151533809..00692d67dbea8 100644 --- a/crates/config/src/error.rs +++ b/crates/config/src/error.rs @@ -55,7 +55,7 @@ impl Error for ExtractConfigError { } /// Represents an error that can occur when constructing the `Config` -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum FoundryConfigError { /// An error thrown during toml parsing Toml(figment::Error), @@ -98,10 +98,13 @@ impl Error for FoundryConfigError { } /// A non-exhaustive list of solidity error codes -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum SolidityErrorCode { /// Warning that SPDX license identifier not provided in source file SpdxLicenseNotProvided, + /// Warning: Visibility for constructor is ignored. If you want the contract to be + /// non-deployable, making it "abstract" is sufficient + VisibilityForConstructorIsIgnored, /// Warning that contract code size exceeds 24576 bytes (a limit introduced in Spurious /// Dragon). ContractExceeds24576Bytes, @@ -131,6 +134,8 @@ pub enum SolidityErrorCode { Unreachable, /// Missing pragma solidity PragmaSolidity, + /// Uses transient opcodes + TransientStorageUsed, /// All other error codes Other(u64), } @@ -158,6 +163,8 @@ impl SolidityErrorCode { SolidityErrorCode::Unreachable => "unreachable", SolidityErrorCode::PragmaSolidity => "pragma-solidity", SolidityErrorCode::Other(code) => return Err(*code), + SolidityErrorCode::VisibilityForConstructorIsIgnored => "constructor-visibility", + SolidityErrorCode::TransientStorageUsed => "transient-storage", }; Ok(s) } @@ -180,6 +187,8 @@ impl From for u64 { SolidityErrorCode::Unreachable => 5740, SolidityErrorCode::PragmaSolidity => 3420, SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes => 3860, + SolidityErrorCode::VisibilityForConstructorIsIgnored => 2462, + SolidityErrorCode::TransientStorageUsed => 2394, SolidityErrorCode::Other(code) => code, } } @@ -212,6 +221,8 @@ impl FromStr for SolidityErrorCode { "virtual-interfaces" => SolidityErrorCode::InterfacesExplicitlyVirtual, "missing-receive-ether" => SolidityErrorCode::PayableNoReceiveEther, "same-varname" => SolidityErrorCode::DeclarationSameNameAsAnother, + "constructor-visibility" => SolidityErrorCode::VisibilityForConstructorIsIgnored, + "transient-storage" => SolidityErrorCode::TransientStorageUsed, _ => return Err(format!("Unknown variant {s}")), }; @@ -236,6 +247,8 @@ impl From for SolidityErrorCode { 6321 => SolidityErrorCode::UnnamedReturnVariable, 3420 => SolidityErrorCode::PragmaSolidity, 5740 => SolidityErrorCode::Unreachable, + 2462 => SolidityErrorCode::VisibilityForConstructorIsIgnored, + 2394 => SolidityErrorCode::TransientStorageUsed, other => SolidityErrorCode::Other(other), } } diff --git a/crates/config/src/etherscan.rs b/crates/config/src/etherscan.rs index 01e0f3b924501..fb254a6357d8e 100644 --- a/crates/config/src/etherscan.rs +++ b/crates/config/src/etherscan.rs @@ -4,6 +4,12 @@ use crate::{ resolve::{interpolate, UnresolvedEnvVarError, RE_PLACEHOLDER}, Chain, Config, NamedChain, }; +use figment::{ + providers::Env, + value::{Dict, Map}, + Error, Metadata, Profile, Provider, +}; +use inflector::Inflector; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{ collections::BTreeMap, @@ -15,8 +21,33 @@ use std::{ /// The user agent to use when querying the etherscan API. pub const ETHERSCAN_USER_AGENT: &str = concat!("foundry/", env!("CARGO_PKG_VERSION")); +/// A [Provider] that provides Etherscan API key from the environment if it's not empty. +/// +/// This prevents `ETHERSCAN_API_KEY=""` if it's set but empty +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[non_exhaustive] +pub(crate) struct EtherscanEnvProvider; + +impl Provider for EtherscanEnvProvider { + fn metadata(&self) -> Metadata { + Env::raw().metadata() + } + + fn data(&self) -> Result, Error> { + let mut dict = Dict::default(); + let env_provider = Env::raw().only(&["ETHERSCAN_API_KEY"]); + if let Some((key, value)) = env_provider.iter().next() { + if !value.trim().is_empty() { + dict.insert(key.as_str().to_string(), value.into()); + } + } + + Ok(Map::from([(Config::selected_profile(), dict)])) + } +} + /// Errors that can occur when creating an `EtherscanConfig` -#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] pub enum EtherscanConfigError { #[error(transparent)] Unresolved(#[from] UnresolvedEnvVarError), @@ -29,7 +60,7 @@ pub enum EtherscanConfigError { } /// Container type for Etherscan API keys and URLs. -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(transparent)] pub struct EtherscanConfigs { configs: BTreeMap, @@ -83,7 +114,7 @@ impl DerefMut for EtherscanConfigs { } /// Container type for _resolved_ etherscan keys, see [EtherscanConfigs::resolve_all()] -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct ResolvedEtherscanConfigs { /// contains all named `ResolvedEtherscanConfig` or an error if we failed to resolve the env /// var alias @@ -138,7 +169,7 @@ impl DerefMut for ResolvedEtherscanConfigs { } /// Represents all info required to create an etherscan client -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct EtherscanConfig { /// The chain name or EIP-155 chain ID used to derive the API URL. #[serde(default, skip_serializing_if = "Option::is_none")] @@ -172,7 +203,19 @@ impl EtherscanConfig { let (chain, alias) = match (chain, alias) { // fill one with the other (Some(chain), None) => (Some(chain), Some(chain.to_string())), - (None, Some(alias)) => (alias.parse().ok(), Some(alias.into())), + (None, Some(alias)) => { + // alloy chain is parsed as kebab case + ( + alias.to_kebab_case().parse().ok().or_else(|| { + // if this didn't work try to parse as json because the deserialize impl + // supports more aliases + serde_json::from_str::(&format!("\"{alias}\"")) + .map(Into::into) + .ok() + }), + Some(alias.into()), + ) + } // leave as is (Some(chain), Some(alias)) => (Some(chain), Some(alias.into())), (None, None) => (None, None), @@ -194,7 +237,9 @@ impl EtherscanConfig { Ok(ResolvedEtherscanConfig { api_url, browser_url: None, key, chain: None }) } (None, None) => { - let msg = alias.map(|a| format!(" for Etherscan config `{a}`")).unwrap_or_default(); + let msg = alias + .map(|a| format!(" for Etherscan config with unknown alias `{a}`")) + .unwrap_or_default(); Err(EtherscanConfigError::MissingUrlOrChain(msg)) } } @@ -202,7 +247,7 @@ impl EtherscanConfig { } /// Contains required url + api key to set up an etherscan client -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct ResolvedEtherscanConfig { /// Etherscan API URL. #[serde(rename = "url")] @@ -261,32 +306,29 @@ impl ResolvedEtherscanConfig { let (mainnet_api, mainnet_url) = NamedChain::Mainnet.etherscan_urls().expect("exist; qed"); let cache = chain - .or_else(|| { - if api_url == mainnet_api { - // try to match against mainnet, which is usually the most common target - Some(NamedChain::Mainnet.into()) - } else { - None - } - }) + // try to match against mainnet, which is usually the most common target + .or_else(|| (api_url == mainnet_api).then(Chain::mainnet)) .and_then(Config::foundry_etherscan_chain_cache_dir); - if let Some(ref cache_path) = cache { + if let Some(cache_path) = &cache { // we also create the `sources` sub dir here if let Err(err) = std::fs::create_dir_all(cache_path.join("sources")) { warn!("could not create etherscan cache dir: {:?}", err); } } + let api_url = into_url(&api_url)?; + let client = reqwest::Client::builder() + .user_agent(ETHERSCAN_USER_AGENT) + .tls_built_in_root_certs(api_url.scheme() == "https") + .build()?; foundry_block_explorers::Client::builder() - .with_client(reqwest::Client::builder().user_agent(ETHERSCAN_USER_AGENT).build()?) + .with_client(client) .with_api_key(api_key) - .with_api_url(api_url.as_str())? - .with_url( - // the browser url is not used/required by the client so we can simply set the - // mainnet browser url here - browser_url.as_deref().unwrap_or(mainnet_url), - )? + .with_api_url(api_url)? + // the browser url is not used/required by the client so we can simply set the + // mainnet browser url here + .with_url(browser_url.as_deref().unwrap_or(mainnet_url))? .with_cache(cache, Duration::from_secs(24 * 60 * 60)) .build() } @@ -298,7 +340,7 @@ impl ResolvedEtherscanConfig { /// env var, then the `EtherscanKey::Key` var will hold the reference (`${MAIN_NET}`) and _not_ the /// value of the env var itself. /// In other words, this type does not resolve env vars when it's being deserialized -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum EtherscanApiKey { /// A raw key Key(String), @@ -374,6 +416,13 @@ impl fmt::Display for EtherscanApiKey { } } +/// This is a hack to work around `IntoUrl`'s sealed private functions, which can't be called +/// normally. +#[inline] +fn into_url(url: impl reqwest::IntoUrl) -> std::result::Result { + url.into_url() +} + #[cfg(test)] mod tests { use super::*; @@ -439,4 +488,35 @@ mod tests { std::env::remove_var(env); } + + #[test] + fn resolve_etherscan_alias_config() { + let mut configs = EtherscanConfigs::default(); + configs.insert( + "blast_sepolia".to_string(), + EtherscanConfig { + chain: None, + url: Some("https://api.etherscan.io/api".to_string()), + key: EtherscanApiKey::Key("ABCDEFG".to_string()), + }, + ); + + let mut resolved = configs.clone().resolved(); + let config = resolved.remove("blast_sepolia").unwrap().unwrap(); + assert_eq!(config.chain, Some(Chain::blast_sepolia())); + } + + #[test] + fn resolve_etherscan_alias() { + let config = EtherscanConfig { + chain: None, + url: Some("https://api.etherscan.io/api".to_string()), + key: EtherscanApiKey::Key("ABCDEFG".to_string()), + }; + let resolved = config.clone().resolve(Some("base_sepolia")).unwrap(); + assert_eq!(resolved.chain, Some(Chain::base_sepolia())); + + let resolved = config.resolve(Some("base-sepolia")).unwrap(); + assert_eq!(resolved.chain, Some(Chain::base_sepolia())); + } } diff --git a/crates/config/src/fix.rs b/crates/config/src/fix.rs index 086fbb7db3055..dc43fd25500b5 100644 --- a/crates/config/src/fix.rs +++ b/crates/config/src/fix.rs @@ -10,32 +10,36 @@ use std::{ /// A convenience wrapper around a TOML document and the path it was read from struct TomlFile { - doc: toml_edit::Document, + doc: toml_edit::DocumentMut, path: PathBuf, } impl TomlFile { - fn open(path: impl AsRef) -> Result> { + fn open(path: impl AsRef) -> eyre::Result { let path = path.as_ref().to_owned(); let doc = fs::read_to_string(&path)?.parse()?; Ok(Self { doc, path }) } - fn doc(&self) -> &toml_edit::Document { + + fn doc(&self) -> &toml_edit::DocumentMut { &self.doc } - fn doc_mut(&mut self) -> &mut toml_edit::Document { + + fn doc_mut(&mut self) -> &mut toml_edit::DocumentMut { &mut self.doc } + fn path(&self) -> &Path { self.path.as_ref() } + fn save(&self) -> io::Result<()> { fs::write(self.path(), self.doc().to_string()) } } impl Deref for TomlFile { - type Target = toml_edit::Document; + type Target = toml_edit::DocumentMut; fn deref(&self) -> &Self::Target { self.doc() } diff --git a/crates/config/src/fmt.rs b/crates/config/src/fmt.rs index 09e8098f0f954..a1cc66c080648 100644 --- a/crates/config/src/fmt.rs +++ b/crates/config/src/fmt.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; /// Contains the config and rule set -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct FormatterConfig { /// Maximum line length where formatter will try to wrap the line pub line_length: usize, @@ -31,10 +31,12 @@ pub struct FormatterConfig { pub ignore: Vec, /// Add new line at start and end of contract declarations pub contract_new_lines: bool, + /// Sort import statements alphabetically in groups (a group is separated by a newline). + pub sort_imports: bool, } /// Style of uint/int256 types -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum IntTypes { /// Print the explicit uint256 or int256 @@ -46,7 +48,7 @@ pub enum IntTypes { } /// Style of underscores in number literals -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum NumberUnderscore { /// Use the underscores defined in the source code @@ -80,7 +82,7 @@ impl NumberUnderscore { } /// Style of underscores in hex literals -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum HexUnderscore { /// Use the underscores defined in the source code @@ -113,7 +115,7 @@ impl HexUnderscore { } /// Style of string quotes -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum QuoteStyle { /// Use double quotes where possible @@ -136,7 +138,7 @@ impl QuoteStyle { } /// Style of single line blocks in statements -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum SingleLineBlockStyle { /// Prefer single line block when possible @@ -148,7 +150,7 @@ pub enum SingleLineBlockStyle { } /// Style of function header in case it doesn't fit -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum MultilineFuncHeaderStyle { /// Write function parameters multiline first @@ -176,6 +178,7 @@ impl Default for FormatterConfig { wrap_comments: false, ignore: vec![], contract_new_lines: false, + sort_imports: false, } } } diff --git a/crates/config/src/fs_permissions.rs b/crates/config/src/fs_permissions.rs index 3128cd6014c22..fbbe67af9fdb8 100644 --- a/crates/config/src/fs_permissions.rs +++ b/crates/config/src/fs_permissions.rs @@ -10,7 +10,7 @@ use std::{ /// Configures file system access /// /// E.g. for cheat codes (`vm.writeFile`) -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(transparent)] pub struct FsPermissions { /// what kind of access is allowed @@ -25,6 +25,11 @@ impl FsPermissions { Self { permissions: permissions.into_iter().collect() } } + /// Adds a new permission + pub fn add(&mut self, permission: PathPermission) { + self.permissions.push(permission) + } + /// Returns true if access to the specified path is allowed with the specified. /// /// This first checks permission, and only if it is granted, whether the path is allowed. @@ -37,9 +42,30 @@ impl FsPermissions { self.find_permission(path).map(|perm| perm.is_granted(kind)).unwrap_or_default() } - /// Returns the permission for the matching path + /// Returns the permission for the matching path. + /// + /// This finds the longest matching path with resolved sym links, e.g. if we have the following + /// permissions: + /// + /// `./out` = `read` + /// `./out/contracts` = `read-write` + /// + /// And we check for `./out/contracts/MyContract.sol` we will get `read-write` as permission. pub fn find_permission(&self, path: &Path) -> Option { - self.permissions.iter().find(|perm| path.starts_with(&perm.path)).map(|perm| perm.access) + let mut permission: Option<&PathPermission> = None; + for perm in &self.permissions { + let permission_path = dunce::canonicalize(&perm.path).unwrap_or(perm.path.clone()); + if path.starts_with(permission_path) { + if let Some(active_perm) = permission.as_ref() { + // the longest path takes precedence + if perm.path < active_perm.path { + continue; + } + } + permission = Some(perm); + } + } + permission.map(|perm| perm.access) } /// Updates all `allowed_paths` and joins ([`Path::join`]) the `root` with all entries @@ -74,7 +100,7 @@ impl FsPermissions { } /// Represents an access permission to a single path -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct PathPermission { /// Permission level to access the `path` pub access: FsAccessPermission, @@ -117,7 +143,7 @@ impl PathPermission { } /// Represents the operation on the fs -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum FsAccessKind { /// read from fs (`vm.readFile`) Read, @@ -135,7 +161,7 @@ impl fmt::Display for FsAccessKind { } /// Determines the status of file system access -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum FsAccessPermission { /// FS access is _not_ allowed #[default] @@ -238,4 +264,19 @@ mod tests { assert_eq!(FsAccessPermission::Read, "read".parse().unwrap()); assert_eq!(FsAccessPermission::Write, "write".parse().unwrap()); } + + #[test] + fn nested_permissions() { + let permissions = FsPermissions::new(vec![ + PathPermission::read("./"), + PathPermission::write("./out"), + PathPermission::read_write("./out/contracts"), + ]); + + let permission = + permissions.find_permission(Path::new("./out/contracts/MyContract.sol")).unwrap(); + assert_eq!(FsAccessPermission::ReadWrite, permission); + let permission = permissions.find_permission(Path::new("./out/MyContract.sol")).unwrap(); + assert_eq!(FsAccessPermission::Write, permission); + } } diff --git a/crates/config/src/fuzz.rs b/crates/config/src/fuzz.rs index ff59f10fc32e7..7049a401a5f3e 100644 --- a/crates/config/src/fuzz.rs +++ b/crates/config/src/fuzz.rs @@ -5,9 +5,10 @@ use crate::inline::{ }; use alloy_primitives::U256; use serde::{Deserialize, Serialize}; +use std::path::PathBuf; /// Contains for fuzz testing -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct FuzzConfig { /// The number of test cases that must execute for each property test pub runs: u32, @@ -22,6 +23,12 @@ pub struct FuzzConfig { /// The fuzz dictionary configuration #[serde(flatten)] pub dictionary: FuzzDictionaryConfig, + /// Number of runs to execute and include in the gas report. + pub gas_report_samples: u32, + /// Path where fuzz failures are recorded and replayed. + pub failure_persist_dir: Option, + /// Name of the file to record fuzz failures, defaults to `failures`. + pub failure_persist_file: Option, } impl Default for FuzzConfig { @@ -31,6 +38,24 @@ impl Default for FuzzConfig { max_test_rejects: 65536, seed: None, dictionary: FuzzDictionaryConfig::default(), + gas_report_samples: 256, + failure_persist_dir: None, + failure_persist_file: None, + } + } +} + +impl FuzzConfig { + /// Creates fuzz configuration to write failures in `{PROJECT_ROOT}/cache/fuzz` dir. + pub fn new(cache_dir: PathBuf) -> Self { + FuzzConfig { + runs: 256, + max_test_rejects: 65536, + seed: None, + dictionary: FuzzDictionaryConfig::default(), + gas_report_samples: 256, + failure_persist_dir: Some(cache_dir), + failure_persist_file: Some("failures".to_string()), } } } @@ -47,8 +72,7 @@ impl InlineConfigParser for FuzzConfig { return Ok(None) } - // self is Copy. We clone it with dereference. - let mut conf_clone = *self; + let mut conf_clone = self.clone(); for pair in overrides { let key = pair.0; @@ -59,6 +83,7 @@ impl InlineConfigParser for FuzzConfig { "dictionary-weight" => { conf_clone.dictionary.dictionary_weight = parse_config_u32(key, value)? } + "failure-persist-file" => conf_clone.failure_persist_file = Some(value), _ => Err(InlineConfigParserError::InvalidConfigProperty(key))?, } } @@ -67,7 +92,7 @@ impl InlineConfigParser for FuzzConfig { } /// Contains for fuzz testing -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct FuzzDictionaryConfig { /// The weight of the dictionary #[serde(deserialize_with = "crate::deserialize_stringified_percent")] @@ -122,11 +147,13 @@ mod tests { let configs = &[ "forge-config: default.fuzz.runs = 42424242".to_string(), "forge-config: default.fuzz.dictionary-weight = 42".to_string(), + "forge-config: default.fuzz.failure-persist-file = fuzz-failure".to_string(), ]; let base_config = FuzzConfig::default(); let merged: FuzzConfig = base_config.try_merge(configs).expect("No errors").unwrap(); assert_eq!(merged.runs, 42424242); assert_eq!(merged.dictionary.dictionary_weight, 42); + assert_eq!(merged.failure_persist_file, Some("fuzz-failure".to_string())); } #[test] diff --git a/crates/config/src/inline/conf_parser.rs b/crates/config/src/inline/conf_parser.rs index acad057ae0d73..1f6fca6c7ac55 100644 --- a/crates/config/src/inline/conf_parser.rs +++ b/crates/config/src/inline/conf_parser.rs @@ -149,7 +149,7 @@ mod tests { function: Default::default(), line: Default::default(), docs: r" - forge-config: ciii.invariant.depth = 1 + forge-config: ciii.invariant.depth = 1 forge-config: default.invariant.depth = 1 " .into(), @@ -167,7 +167,7 @@ mod tests { function: Default::default(), line: Default::default(), docs: r" - forge-config: ci.invariant.depth = 1 + forge-config: ci.invariant.depth = 1 forge-config: default.invariant.depth = 1 " .into(), diff --git a/crates/config/src/inline/error.rs b/crates/config/src/inline/error.rs index 4c4f7725b0847..e998a2156d8cd 100644 --- a/crates/config/src/inline/error.rs +++ b/crates/config/src/inline/error.rs @@ -1,5 +1,5 @@ /// Errors returned by the [`InlineConfigParser`] trait. -#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] pub enum InlineConfigParserError { /// An invalid configuration property has been provided. /// The property cannot be mapped to the configuration object @@ -19,7 +19,7 @@ pub enum InlineConfigParserError { /// Wrapper error struct that catches config parsing /// errors [`InlineConfigParserError`], enriching them with context information /// reporting the misconfigured line. -#[derive(thiserror::Error, Debug)] +#[derive(Debug, thiserror::Error)] #[error("Inline config error detected at {line}")] pub struct InlineConfigError { /// Specifies the misconfigured line. This is something of the form diff --git a/crates/config/src/inline/mod.rs b/crates/config/src/inline/mod.rs index 4ce3c62f04c02..f2222901f9065 100644 --- a/crates/config/src/inline/mod.rs +++ b/crates/config/src/inline/mod.rs @@ -3,7 +3,7 @@ pub use conf_parser::{parse_config_bool, parse_config_u32, validate_profiles, In pub use error::{InlineConfigError, InlineConfigParserError}; pub use natspec::NatSpec; use once_cell::sync::Lazy; -use std::{borrow::Cow, collections::HashMap}; +use std::collections::HashMap; mod conf_parser; mod error; @@ -21,51 +21,33 @@ static INLINE_CONFIG_PREFIX_SELECTED_PROFILE: Lazy = Lazy::new(|| { /// Represents per-test configurations, declared inline /// as structured comments in Solidity test files. This allows /// to create configs directly bound to a solidity test. -#[derive(Default, Debug, Clone)] +#[derive(Clone, Debug, Default)] pub struct InlineConfig { /// Maps a (test-contract, test-function) pair /// to a specific configuration provided by the user. - configs: HashMap, T>, + configs: HashMap<(String, String), T>, } impl InlineConfig { /// Returns an inline configuration, if any, for a test function. /// Configuration is identified by the pair "contract", "function". - pub fn get(&self, contract_id: C, fn_name: F) -> Option<&T> - where - C: Into, - F: Into, - { - // TODO use borrow - let key = InlineConfigKey { - contract: Cow::Owned(contract_id.into()), - function: Cow::Owned(fn_name.into()), - }; + pub fn get(&self, contract_id: &str, fn_name: &str) -> Option<&T> { + let key = (contract_id.to_string(), fn_name.to_string()); self.configs.get(&key) } /// Inserts an inline configuration, for a test function. - /// Configuration is identified by the pair "contract", "function". + /// Configuration is identified by the pair "contract", "function". pub fn insert(&mut self, contract_id: C, fn_name: F, config: T) where C: Into, F: Into, { - let key = InlineConfigKey { - contract: Cow::Owned(contract_id.into()), - function: Cow::Owned(fn_name.into()), - }; + let key = (contract_id.into(), fn_name.into()); self.configs.insert(key, config); } } -/// Represents a (test-contract, test-function) pair -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct InlineConfigKey<'a> { - contract: Cow<'a, str>, - function: Cow<'a, str>, -} - pub(crate) fn remove_whitespaces(s: &str) -> String { s.chars().filter(|c| !c.is_whitespace()).collect() } diff --git a/crates/config/src/inline/natspec.rs b/crates/config/src/inline/natspec.rs index c73cc4492dc75..27742eb56e4b9 100644 --- a/crates/config/src/inline/natspec.rs +++ b/crates/config/src/inline/natspec.rs @@ -4,9 +4,11 @@ use foundry_compilers::{ ProjectCompileOutput, }; use serde_json::Value; +use solang_parser::pt; use std::{collections::BTreeMap, path::Path}; /// Convenient struct to hold in-line per-test configurations +#[derive(Clone, Debug, PartialEq, Eq)] pub struct NatSpec { /// The parent contract of the natspec pub contract: String, @@ -27,14 +29,28 @@ impl NatSpec { pub fn parse(output: &ProjectCompileOutput, root: &Path) -> Vec { let mut natspecs: Vec = vec![]; + let solc = SolcParser::new(); + let solang = SolangParser::new(); for (id, artifact) in output.artifact_ids() { - let Some(ast) = &artifact.ast else { continue }; - let path = id.source.as_path(); - let path = path.strip_prefix(root).unwrap_or(path); - // id.identifier + let abs_path = id.source.as_path(); + let path = abs_path.strip_prefix(root).unwrap_or(abs_path); + let contract_name = id.name.split('.').next().unwrap(); + // `id.identifier` but with the stripped path. let contract = format!("{}:{}", path.display(), id.name); - let Some(node) = contract_root_node(&ast.nodes, &contract) else { continue }; - apply(&mut natspecs, &contract, node) + + let mut used_solc_ast = false; + if let Some(ast) = &artifact.ast { + if let Some(node) = solc.contract_root_node(&ast.nodes, &contract) { + solc.parse(&mut natspecs, &contract, node); + used_solc_ast = true; + } + } + + if !used_solc_ast { + if let Ok(src) = std::fs::read_to_string(abs_path) { + solang.parse(&mut natspecs, &src, &contract, contract_name); + } + } } natspecs @@ -62,89 +78,245 @@ impl NatSpec { /// Returns a list of all the configuration lines available in the natspec pub fn config_lines(&self) -> impl Iterator + '_ { - self.docs.lines().map(remove_whitespaces).filter(|line| line.contains(INLINE_CONFIG_PREFIX)) + self.docs.lines().filter(|line| line.contains(INLINE_CONFIG_PREFIX)).map(remove_whitespaces) } } -/// Given a list of nodes, find a "ContractDefinition" node that matches -/// the provided contract_id. -fn contract_root_node<'a>(nodes: &'a [Node], contract_id: &'a str) -> Option<&'a Node> { - for n in nodes.iter() { - if let NodeType::ContractDefinition = n.node_type { - let contract_data = &n.other; - if let Value::String(contract_name) = contract_data.get("name")? { - if contract_id.ends_with(contract_name) { - return Some(n) +struct SolcParser { + _private: (), +} + +impl SolcParser { + fn new() -> Self { + Self { _private: () } + } + + /// Given a list of nodes, find a "ContractDefinition" node that matches + /// the provided contract_id. + fn contract_root_node<'a>(&self, nodes: &'a [Node], contract_id: &str) -> Option<&'a Node> { + for n in nodes.iter() { + if let NodeType::ContractDefinition = n.node_type { + let contract_data = &n.other; + if let Value::String(contract_name) = contract_data.get("name")? { + if contract_id.ends_with(contract_name) { + return Some(n) + } } } } + None + } + + /// Implements a DFS over a compiler output node and its children. + /// If a natspec is found it is added to `natspecs` + fn parse(&self, natspecs: &mut Vec, contract: &str, node: &Node) { + for n in node.nodes.iter() { + if let Some((function, docs, line)) = self.get_fn_data(n) { + natspecs.push(NatSpec { contract: contract.into(), function, line, docs }) + } + self.parse(natspecs, contract, n); + } } - None -} -/// Implements a DFS over a compiler output node and its children. -/// If a natspec is found it is added to `natspecs` -fn apply(natspecs: &mut Vec, contract: &str, node: &Node) { - for n in node.nodes.iter() { - if let Some((function, docs, line)) = get_fn_data(n) { - natspecs.push(NatSpec { contract: contract.into(), function, line, docs }) + /// Given a compilation output node, if it is a function definition + /// that also contains a natspec then return a tuple of: + /// - Function name + /// - Natspec text + /// - Natspec position with format "row:col:length" + /// + /// Return None otherwise. + fn get_fn_data(&self, node: &Node) -> Option<(String, String, String)> { + if let NodeType::FunctionDefinition = node.node_type { + let fn_data = &node.other; + let fn_name: String = self.get_fn_name(fn_data)?; + let (fn_docs, docs_src_line): (String, String) = self.get_fn_docs(fn_data)?; + return Some((fn_name, fn_docs, docs_src_line)) } - apply(natspecs, contract, n); + + None + } + + /// Given a dictionary of function data returns the name of the function. + fn get_fn_name(&self, fn_data: &BTreeMap) -> Option { + match fn_data.get("name")? { + Value::String(fn_name) => Some(fn_name.into()), + _ => None, + } + } + + /// Inspects Solc compiler output for documentation comments. Returns: + /// - `Some((String, String))` in case the function has natspec comments. First item is a + /// textual natspec representation, the second item is the natspec src line, in the form + /// "raw:col:length". + /// - `None` in case the function has not natspec comments. + fn get_fn_docs(&self, fn_data: &BTreeMap) -> Option<(String, String)> { + if let Value::Object(fn_docs) = fn_data.get("documentation")? { + if let Value::String(comment) = fn_docs.get("text")? { + if comment.contains(INLINE_CONFIG_PREFIX) { + let mut src_line = fn_docs + .get("src") + .map(|src| src.to_string()) + .unwrap_or_else(|| String::from("")); + + src_line.retain(|c| c != '"'); + return Some((comment.into(), src_line)) + } + } + } + None } } -/// Given a compilation output node, if it is a function definition -/// that also contains a natspec then return a tuple of: -/// - Function name -/// - Natspec text -/// - Natspec position with format "row:col:length" -/// -/// Return None otherwise. -fn get_fn_data(node: &Node) -> Option<(String, String, String)> { - if let NodeType::FunctionDefinition = node.node_type { - let fn_data = &node.other; - let fn_name: String = get_fn_name(fn_data)?; - let (fn_docs, docs_src_line): (String, String) = get_fn_docs(fn_data)?; - return Some((fn_name, fn_docs, docs_src_line)) - } - - None +struct SolangParser { + _private: (), } -/// Given a dictionary of function data returns the name of the function. -fn get_fn_name(fn_data: &BTreeMap) -> Option { - match fn_data.get("name")? { - Value::String(fn_name) => Some(fn_name.into()), - _ => None, +impl SolangParser { + fn new() -> Self { + Self { _private: () } } -} -/// Inspects Solc compiler output for documentation comments. Returns: -/// - `Some((String, String))` in case the function has natspec comments. First item is a textual -/// natspec representation, the second item is the natspec src line, in the form "raw:col:length". -/// - `None` in case the function has not natspec comments. -fn get_fn_docs(fn_data: &BTreeMap) -> Option<(String, String)> { - if let Value::Object(fn_docs) = fn_data.get("documentation")? { - if let Value::String(comment) = fn_docs.get("text")? { - if comment.contains(INLINE_CONFIG_PREFIX) { - let mut src_line = fn_docs - .get("src") - .map(|src| src.to_string()) - .unwrap_or_else(|| String::from("")); - - src_line.retain(|c| c != '"'); - return Some((comment.into(), src_line)) + fn parse( + &self, + natspecs: &mut Vec, + src: &str, + contract_id: &str, + contract_name: &str, + ) { + // Fast path to avoid parsing the file. + if !src.contains(INLINE_CONFIG_PREFIX) { + return; + } + + let Ok((pt, comments)) = solang_parser::parse(src, 0) else { return }; + for item in &pt.0 { + let pt::SourceUnitPart::ContractDefinition(c) = item else { continue }; + let Some(id) = c.name.as_ref() else { continue }; + if id.name != contract_name { + continue + }; + let mut prev_end = c.loc.start(); + for part in &c.parts { + let pt::ContractPart::FunctionDefinition(f) = part else { continue }; + let start = f.loc.start(); + // Parse doc comments in between the previous function and the current one. + let docs = solang_parser::doccomment::parse_doccomments(&comments, prev_end, start); + let docs = docs + .into_iter() + .flat_map(|doc| doc.into_comments()) + .filter(|doc| doc.value.contains(INLINE_CONFIG_PREFIX)); + for doc in docs { + natspecs.push(NatSpec { + contract: contract_id.to_string(), + function: f.name.as_ref().map(|id| id.to_string()).unwrap_or_default(), + line: "0:0:0".to_string(), + docs: doc.value, + }); + } + prev_end = f.loc.end(); } } } - None } #[cfg(test)] mod tests { - use crate::{inline::natspec::get_fn_docs, NatSpec}; - use serde_json::{json, Value}; - use std::collections::BTreeMap; + use super::*; + use serde_json::json; + + #[test] + fn parse_solang() { + let src = " +contract C { /// forge-config: default.fuzz.runs = 600 + +\t\t\t\t /// forge-config: default.fuzz.runs = 601 + + function f1() {} + /** forge-config: default.fuzz.runs = 700 */ +function f2() {} /** forge-config: default.fuzz.runs = 800 */ function f3() {} + +/** + * forge-config: default.fuzz.runs = 1024 + * forge-config: default.fuzz.max-test-rejects = 500 + */ + function f4() {} +} +"; + let mut natspecs = vec![]; + let solang = SolangParser::new(); + let id = || "path.sol:C".to_string(); + let default_line = || "0:0:0".to_string(); + solang.parse(&mut natspecs, src, &id(), "C"); + assert_eq!( + natspecs, + [ + // f1 + NatSpec { + contract: id(), + function: "f1".to_string(), + line: default_line(), + docs: "forge-config: default.fuzz.runs = 600\nforge-config: default.fuzz.runs = 601".to_string(), + }, + // f2 + NatSpec { + contract: id(), + function: "f2".to_string(), + line: default_line(), + docs: "forge-config: default.fuzz.runs = 700".to_string(), + }, + // f3 + NatSpec { + contract: id(), + function: "f3".to_string(), + line: default_line(), + docs: "forge-config: default.fuzz.runs = 800".to_string(), + }, + // f4 + NatSpec { + contract: id(), + function: "f4".to_string(), + line: default_line(), + docs: "forge-config: default.fuzz.runs = 1024\nforge-config: default.fuzz.max-test-rejects = 500".to_string(), + }, + ] + ); + } + + #[test] + fn parse_solang_2() { + let src = r#" +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import "ds-test/test.sol"; + +contract FuzzInlineConf is DSTest { + /** + * forge-config: default.fuzz.runs = 1024 + * forge-config: default.fuzz.max-test-rejects = 500 + */ + function testInlineConfFuzz(uint8 x) public { + require(true, "this is not going to revert"); + } +} + "#; + let mut natspecs = vec![]; + let solang = SolangParser::new(); + let id = || "inline/FuzzInlineConf.t.sol:FuzzInlineConf".to_string(); + let default_line = || "0:0:0".to_string(); + solang.parse(&mut natspecs, src, &id(), "FuzzInlineConf"); + assert_eq!( + natspecs, + [ + NatSpec { + contract: id(), + function: "testInlineConfFuzz".to_string(), + line: default_line(), + docs: "forge-config: default.fuzz.runs = 1024\nforge-config: default.fuzz.max-test-rejects = 500".to_string(), + }, + ] + ); + } #[test] fn config_lines() { @@ -194,7 +366,7 @@ mod tests { let mut fn_data: BTreeMap = BTreeMap::new(); let doc_without_src_field = json!({ "text": "forge-config:default.fuzz.runs=600" }); fn_data.insert("documentation".into(), doc_without_src_field); - let (_, src_line) = get_fn_docs(&fn_data).expect("Some docs"); + let (_, src_line) = SolcParser::new().get_fn_docs(&fn_data).expect("Some docs"); assert_eq!(src_line, "".to_string()); } @@ -204,7 +376,7 @@ mod tests { let doc_without_src_field = json!({ "text": "forge-config:default.fuzz.runs=600", "src": "73:21:12" }); fn_data.insert("documentation".into(), doc_without_src_field); - let (_, src_line) = get_fn_docs(&fn_data).expect("Some docs"); + let (_, src_line) = SolcParser::new().get_fn_docs(&fn_data).expect("Some docs"); assert_eq!(src_line, "73:21:12".to_string()); } @@ -228,4 +400,52 @@ mod tests { docs: conf.to_string(), } } + + #[test] + fn parse_solang_multiple_contracts_from_same_file() { + let src = r#" +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import "ds-test/test.sol"; + +contract FuzzInlineConf is DSTest { + /// forge-config: default.fuzz.runs = 1 + function testInlineConfFuzz1() {} +} + +contract FuzzInlineConf2 is DSTest { + /// forge-config: default.fuzz.runs = 2 + function testInlineConfFuzz2() {} +} + "#; + let mut natspecs = vec![]; + let solang = SolangParser::new(); + let id = || "inline/FuzzInlineConf.t.sol:FuzzInlineConf".to_string(); + let default_line = || "0:0:0".to_string(); + solang.parse(&mut natspecs, src, &id(), "FuzzInlineConf"); + assert_eq!( + natspecs, + [NatSpec { + contract: id(), + function: "testInlineConfFuzz1".to_string(), + line: default_line(), + docs: "forge-config: default.fuzz.runs = 1".to_string(), + },] + ); + + let mut natspecs = vec![]; + let id = || "inline/FuzzInlineConf2.t.sol:FuzzInlineConf2".to_string(); + solang.parse(&mut natspecs, src, &id(), "FuzzInlineConf2"); + assert_eq!( + natspecs, + [NatSpec { + contract: id(), + function: "testInlineConfFuzz2".to_string(), + line: default_line(), + // should not get config from previous contract + docs: "forge-config: default.fuzz.runs = 2".to_string(), + },] + ); + } } diff --git a/crates/config/src/invariant.rs b/crates/config/src/invariant.rs index 1b3cd6218a627..c7462d539461b 100644 --- a/crates/config/src/invariant.rs +++ b/crates/config/src/invariant.rs @@ -10,7 +10,7 @@ use crate::{ use serde::{Deserialize, Serialize}; /// Contains for invariant testing -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct InvariantConfig { /// The number of runs that must execute for each invariant test group. pub runs: u32, @@ -25,7 +25,19 @@ pub struct InvariantConfig { #[serde(flatten)] pub dictionary: FuzzDictionaryConfig, /// Attempt to shrink the failure case to its smallest sequence of calls + /// TODO: remove this setting as it is now redundant with shrink_run_limit = 0 pub shrink_sequence: bool, + /// The maximum number of attempts to shrink the sequence + pub shrink_run_limit: usize, + /// If set to true then VM state is committed and available for next call + /// Useful for handlers that use cheatcodes as roll or warp + /// Use it with caution, introduces performance penalty. + pub preserve_state: bool, + /// The maximum number of rejects via `vm.assume` which can be encountered during a single + /// invariant run. + pub max_assume_rejects: u32, + /// Number of runs to execute and include in the gas report. + pub gas_report_samples: u32, } impl Default for InvariantConfig { @@ -37,6 +49,10 @@ impl Default for InvariantConfig { call_override: false, dictionary: FuzzDictionaryConfig { dictionary_weight: 80, ..Default::default() }, shrink_sequence: true, + shrink_run_limit: 2usize.pow(18_u32), + preserve_state: false, + max_assume_rejects: 65536, + gas_report_samples: 256, } } } @@ -65,6 +81,7 @@ impl InlineConfigParser for InvariantConfig { "fail-on-revert" => conf_clone.fail_on_revert = parse_config_bool(key, value)?, "call-override" => conf_clone.call_override = parse_config_bool(key, value)?, "shrink-sequence" => conf_clone.shrink_sequence = parse_config_bool(key, value)?, + "preserve-state" => conf_clone.preserve_state = parse_config_bool(key, value)?, _ => Err(InlineConfigParserError::InvalidConfigProperty(key.to_string()))?, } } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 31b1bd423cf65..c59b478b143d5 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -20,12 +20,11 @@ use foundry_compilers::{ RevertStrings, Settings, SettingsMetadata, Severity, }, cache::SOLIDITY_FILES_CACHE_FILENAME, - error::SolcError, + error::{SolcError, SolcIoError}, remappings::{RelativeRemapping, Remapping}, ConfigurableArtifacts, EvmVersion, Project, ProjectPathsConfig, Solc, SolcConfig, }; use inflector::Inflector; -use once_cell::sync::Lazy; use regex::Regex; use revm_primitives::SpecId; use semver::Version; @@ -95,6 +94,7 @@ pub use invariant::InvariantConfig; use providers::remappings::RemappingsProvider; mod inline; +use crate::etherscan::EtherscanEnvProvider; pub use inline::{validate_profiles, InlineConfig, InlineConfigError, InlineConfigParser, NatSpec}; /// Foundry configuration @@ -128,7 +128,7 @@ pub use inline::{validate_profiles, InlineConfig, InlineConfigError, InlineConfi /// the "default" meta-profile. /// /// Note that these behaviors differ from those of [`Config::figment()`]. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Config { /// The selected profile. **(default: _default_ `default`)** /// @@ -213,6 +213,9 @@ pub struct Config { pub etherscan: EtherscanConfigs, /// list of solidity error codes to always silence in the compiler output pub ignored_error_codes: Vec, + /// list of file paths to ignore + #[serde(rename = "ignored_warnings_from")] + pub ignored_file_paths: Vec, /// When true, compiler warnings are treated as errors pub deny_warnings: bool, /// Only run test functions matching the specified regex pattern. @@ -239,6 +242,10 @@ pub struct Config { pub invariant: InvariantConfig, /// Whether to allow ffi cheatcodes in test pub ffi: bool, + /// Use the create 2 factory in all cases including tests and non-broadcasting scripts. + pub always_use_create_2_factory: bool, + /// Sets a timeout in seconds for vm.prompt cheatcodes + pub prompt_timeout: u64, /// The address which will be executing all tests pub sender: Address, /// The tx.origin value during EVM execution @@ -315,6 +322,8 @@ pub struct Config { /// If set to true, changes compilation pipeline to go through the Yul intermediate /// representation. pub via_ir: bool, + /// Whether to include the AST as JSON in the compiler output. + pub ast: bool, /// RPC storage caching settings determines what chains and endpoints to cache pub rpc_storage_caching: StorageCachingConfig, /// Disables storage caching entirely. This overrides any settings made in @@ -347,10 +356,8 @@ pub struct Config { /// included in solc's output selection, see also /// [OutputSelection](foundry_compilers::artifacts::output_selection::OutputSelection) pub sparse_mode: bool, - /// Whether to emit additional build info files - /// - /// If set to `true`, `ethers-solc` will generate additional build info json files for every - /// new build, containing the `CompilerInput` and `CompilerOutput` + /// Generates additional build info json files for every new build, containing the + /// `CompilerInput` and `CompilerOutput`. pub build_info: bool, /// The path to the `build-info` directory that contains the build info json files. pub build_info_path: Option, @@ -372,6 +379,21 @@ pub struct Config { /// Temporary config to enable [SpecId::PRAGUE] pub prague: bool, + /// Whether to enable call isolation. + /// + /// Useful for more correct gas accounting and EVM behavior in general. + pub isolate: bool, + + /// Whether to disable the block gas limit. + pub disable_block_gas_limit: bool, + + /// Address labels + pub labels: HashMap, + + /// Whether to enable safety checks for `vm.getCode` and `vm.getDeployedCode` invocations. + /// If disabled, it is possible to access artifacts which were not recompiled or cached. + pub unchecked_cheatcode_artifacts: bool, + /// The root path where the config detection started from, `Config::with_root` #[doc(hidden)] // We're skipping serialization here, so it won't be included in the [`Config::to_string()`] @@ -396,11 +418,12 @@ pub struct Config { } /// Mapping of fallback standalone sections. See [`FallbackProfileProvider`] -pub static STANDALONE_FALLBACK_SECTIONS: Lazy> = - Lazy::new(|| HashMap::from([("invariant", "fuzz")])); +pub const STANDALONE_FALLBACK_SECTIONS: &[(&str, &str)] = &[("invariant", "fuzz")]; -/// Deprecated keys. -pub static DEPRECATIONS: Lazy> = Lazy::new(|| HashMap::from([])); +/// Deprecated keys and their replacements. +/// +/// See [Warning::DeprecatedKey] +pub const DEPRECATIONS: &[(&str, &str)] = &[("cancun", "evm_version = Cancun")]; impl Config { /// The default profile: "default" @@ -414,7 +437,7 @@ impl Config { /// Standalone sections in the config which get integrated into the selected profile pub const STANDALONE_SECTIONS: &'static [&'static str] = - &["rpc_endpoints", "etherscan", "fmt", "doc", "fuzz", "invariant"]; + &["rpc_endpoints", "etherscan", "fmt", "doc", "fuzz", "invariant", "labels"]; /// File name of config toml file pub const FILE_NAME: &'static str = "foundry.toml"; @@ -435,6 +458,14 @@ impl Config { Config::from_provider(Config::figment()) } + /// Returns the current `Config` with the given `providers` preset + /// + /// See `Config::to_figment` + #[track_caller] + pub fn load_with_providers(providers: FigmentProviders) -> Self { + Config::default().to_figment(providers).extract().unwrap() + } + /// Returns the current `Config` /// /// See `Config::figment_with_root` @@ -489,6 +520,86 @@ impl Config { Ok(config) } + /// Returns the populated [Figment] using the requested [FigmentProviders] preset. + /// + /// This will merge various providers, such as env,toml,remappings into the figment. + pub fn to_figment(self, providers: FigmentProviders) -> Figment { + let mut c = self; + let profile = Config::selected_profile(); + let mut figment = Figment::default().merge(DappHardhatDirProvider(&c.__root.0)); + + // merge global foundry.toml file + if let Some(global_toml) = Config::foundry_dir_toml().filter(|p| p.exists()) { + figment = Config::merge_toml_provider( + figment, + TomlFileProvider::new(None, global_toml).cached(), + profile.clone(), + ); + } + // merge local foundry.toml file + figment = Config::merge_toml_provider( + figment, + TomlFileProvider::new(Some("FOUNDRY_CONFIG"), c.__root.0.join(Config::FILE_NAME)) + .cached(), + profile.clone(), + ); + + // merge environment variables + figment = figment + .merge( + Env::prefixed("DAPP_") + .ignore(&["REMAPPINGS", "LIBRARIES", "FFI", "FS_PERMISSIONS"]) + .global(), + ) + .merge( + Env::prefixed("DAPP_TEST_") + .ignore(&["CACHE", "FUZZ_RUNS", "DEPTH", "FFI", "FS_PERMISSIONS"]) + .global(), + ) + .merge(DappEnvCompatProvider) + .merge(EtherscanEnvProvider::default()) + .merge( + Env::prefixed("FOUNDRY_") + .ignore(&["PROFILE", "REMAPPINGS", "LIBRARIES", "FFI", "FS_PERMISSIONS"]) + .map(|key| { + let key = key.as_str(); + if Config::STANDALONE_SECTIONS.iter().any(|section| { + key.starts_with(&format!("{}_", section.to_ascii_uppercase())) + }) { + key.replacen('_', ".", 1).into() + } else { + key.into() + } + }) + .global(), + ) + .select(profile.clone()); + + // only resolve remappings if all providers are requested + if providers.is_all() { + // we try to merge remappings after we've merged all other providers, this prevents + // redundant fs lookups to determine the default remappings that are eventually updated + // by other providers, like the toml file + let remappings = RemappingsProvider { + auto_detect_remappings: figment + .extract_inner::("auto_detect_remappings") + .unwrap_or(true), + lib_paths: figment + .extract_inner::>("libs") + .map(Cow::Owned) + .unwrap_or_else(|_| Cow::Borrowed(&c.libs)), + root: &c.__root.0, + remappings: figment.extract_inner::>("remappings"), + }; + figment = figment.merge(remappings); + } + + // normalize defaults + figment = c.normalize_defaults(figment); + + Figment::from(c).merge(figment).select(profile) + } + /// The config supports relative paths and tracks the root path separately see /// `Config::with_root` /// @@ -558,6 +669,30 @@ impl Config { self } + /// Normalizes the evm version if a [SolcReq] is set + pub fn normalized_evm_version(mut self) -> Self { + self.normalize_evm_version(); + self + } + + /// Normalizes the evm version if a [SolcReq] is set to a valid version. + pub fn normalize_evm_version(&mut self) { + self.evm_version = self.get_normalized_evm_version(); + } + + /// Returns the normalized [EvmVersion] if a [SolcReq] is set to a valid version or if the solc + /// path is a valid solc binary. + /// + /// Otherwise it returns the configured [EvmVersion]. + pub fn get_normalized_evm_version(&self) -> EvmVersion { + if let Some(version) = self.solc.as_ref().and_then(|solc| solc.try_version().ok()) { + if let Some(evm_version) = self.evm_version.normalize_version(&version) { + return evm_version; + } + } + self.evm_version + } + /// Returns a sanitized version of the Config where are paths are set correctly and potential /// duplicates are resolved /// @@ -622,7 +757,8 @@ impl Config { self.create_project(false, true) } - fn create_project(&self, cached: bool, no_artifacts: bool) -> Result { + /// Creates a [Project] with the given `cached` and `no_artifacts` flags + pub fn create_project(&self, cached: bool, no_artifacts: bool) -> Result { let mut project = Project::builder() .artifacts(self.configured_artifacts_handler()) .paths(self.project_paths()) @@ -632,6 +768,7 @@ impl Config { .include_paths(&self.include_paths) .solc_config(SolcConfig::builder().settings(self.solc_settings()?).build()) .ignore_error_codes(self.ignored_error_codes.iter().copied().map(Into::into)) + .ignore_paths(self.ignored_file_paths.clone()) .set_compiler_severity_filter(if self.deny_warnings { Severity::Warning } else { @@ -639,13 +776,13 @@ impl Config { }) .set_auto_detect(self.is_auto_detect()) .set_offline(self.offline) - .set_cached(cached) - .set_build_info(cached & self.build_info) + .set_cached(cached && !self.build_info) + .set_build_info(!no_artifacts && self.build_info) .set_no_artifacts(no_artifacts) .build()?; if self.force { - project.cleanup()?; + self.cleanup(&project)?; } if let Some(solc) = self.ensure_solc()? { @@ -655,6 +792,21 @@ impl Config { Ok(project) } + /// Cleans the project. + pub fn cleanup(&self, project: &Project) -> Result<(), SolcError> { + project.cleanup()?; + + // Remove fuzz cache directory. + if let Some(fuzz_cache) = &self.fuzz.failure_persist_dir { + let path = project.root().join(fuzz_cache); + if path.exists() { + std::fs::remove_dir_all(&path).map_err(|e| SolcIoError::new(e, path))?; + } + } + + Ok(()) + } + /// Ensures that the configured version is installed if explicitly set /// /// If `solc` is [`SolcReq::Version`] then this will download and install the solc version if @@ -723,7 +875,7 @@ impl Config { self.rpc_storage_caching.enable_for_endpoint(endpoint) } - /// Returns the `ProjectPathsConfig` sub set of the config. + /// Returns the `ProjectPathsConfig` sub set of the config. /// /// **NOTE**: this uses the paths as they are and does __not__ modify them, see /// `[Self::sanitized]` @@ -742,7 +894,7 @@ impl Config { .tests(&self.test) .scripts(&self.script) .artifacts(&self.out) - .libs(self.libs.clone()) + .libs(self.libs.iter()) .remappings(self.get_all_remappings()); if let Some(build_info_path) = &self.build_info_path { @@ -770,8 +922,8 @@ impl Config { /// contracts/tokens/token.sol /// contracts/math/math.sol /// ``` - pub fn get_all_remappings(&self) -> Vec { - self.remappings.iter().map(|m| m.clone().into()).collect() + pub fn get_all_remappings(&self) -> impl Iterator + '_ { + self.remappings.iter().map(|m| m.clone().into()) } /// Returns the configured rpc jwt secret @@ -881,6 +1033,8 @@ impl Config { /// Returns /// - the matching `ResolvedEtherscanConfig` of the `etherscan` table if `etherscan_api_key` is /// an alias + /// - the matching `ResolvedEtherscanConfig` of the `etherscan` table if a `chain` is + /// configured. an alias /// - the Mainnet `ResolvedEtherscanConfig` if `etherscan_api_key` is set, `None` otherwise /// /// # Example @@ -896,18 +1050,7 @@ impl Config { pub fn get_etherscan_config( &self, ) -> Option> { - let maybe_alias = self.etherscan_api_key.as_ref().or(self.eth_rpc_url.as_ref())?; - if self.etherscan.contains_key(maybe_alias) { - // etherscan points to an alias in the `etherscan` table, so we try to resolve that - let mut resolved = self.etherscan.clone().resolved(); - return resolved.remove(maybe_alias) - } - - // we treat the `etherscan_api_key` as actual API key - // if no chain provided, we assume mainnet - let chain = self.chain.unwrap_or(Chain::mainnet()); - let api_key = self.etherscan_api_key.as_ref()?; - ResolvedEtherscanConfig::create(api_key, chain).map(Ok) + self.get_etherscan_config_with_chain(None).transpose() } /// Same as [`Self::get_etherscan_config()`] but optionally updates the config with the given @@ -927,14 +1070,15 @@ impl Config { } // try to find by comparing chain IDs after resolving - if let Some(res) = - chain.and_then(|chain| self.etherscan.clone().resolved().find_chain(chain)) + if let Some(res) = chain + .or(self.chain) + .and_then(|chain| self.etherscan.clone().resolved().find_chain(chain)) { match (res, self.etherscan_api_key.as_ref()) { (Ok(mut config), Some(key)) => { // we update the key, because if an etherscan_api_key is set, it should take // precedence over the entry, since this is usually set via env var or CLI args. - config.key = key.clone(); + config.key.clone_from(key); return Ok(Some(config)) } (Ok(config), None) => return Ok(Some(config)), @@ -955,6 +1099,10 @@ impl Config { } /// Helper function to just get the API key + /// + /// Optionally updates the config with the given `chain`. + /// + /// See also [Self::get_etherscan_config_with_chain] pub fn get_etherscan_api_key(&self, chain: Option) -> Option { self.get_etherscan_config_with_chain(chain).ok().flatten().map(|c| c.key) } @@ -988,17 +1136,25 @@ impl Config { } /// Returns the `Optimizer` based on the configured settings + /// + /// Note: optimizer details can be set independently of `enabled` + /// See also: + /// and pub fn optimizer(&self) -> Optimizer { - // only configure optimizer settings if optimizer is enabled - let details = if self.optimizer { self.optimizer_details.clone() } else { None }; - - Optimizer { enabled: Some(self.optimizer), runs: Some(self.optimizer_runs), details } + Optimizer { + enabled: Some(self.optimizer), + runs: Some(self.optimizer_runs), + // we always set the details because `enabled` is effectively a specific details profile + // that can still be modified + details: self.optimizer_details.clone(), + } } /// returns the [`foundry_compilers::ConfigurableArtifacts`] for this config, that includes the /// `extra_output` fields pub fn configured_artifacts_handler(&self) -> ConfigurableArtifacts { let mut extra_output = self.extra_output.clone(); + // Sourcify verification requires solc metadata output. Since, it doesn't // affect the UX & performance of the compiler, output the metadata files // by default. @@ -1008,7 +1164,7 @@ impl Config { extra_output.push(ContractOutputSelection::Metadata); } - ConfigurableArtifacts::new(extra_output, self.extra_output_files.clone()) + ConfigurableArtifacts::new(extra_output, self.extra_output_files.iter().cloned()) } /// Parses all libraries in the form of @@ -1017,28 +1173,30 @@ impl Config { Libraries::parse(&self.libraries) } + /// Returns all libraries with applied remappings. Same as `self.solc_settings()?.libraries`. + pub fn libraries_with_remappings(&self) -> Result { + Ok(self.parsed_libraries()?.with_applied_remappings(&self.project_paths())) + } + /// Returns the configured `solc` `Settings` that includes: - /// - all libraries - /// - the optimizer (including details, if configured) - /// - evm version + /// - all libraries + /// - the optimizer (including details, if configured) + /// - evm version pub fn solc_settings(&self) -> Result { - let libraries = self.parsed_libraries()?.with_applied_remappings(&self.project_paths()); - let optimizer = self.optimizer(); - // By default if no targets are specifically selected the model checker uses all targets. // This might be too much here, so only enable assertion checks. // If users wish to enable all options they need to do so explicitly. let mut model_checker = self.model_checker.clone(); - if let Some(ref mut model_checker_settings) = model_checker { + if let Some(model_checker_settings) = &mut model_checker { if model_checker_settings.targets.is_none() { model_checker_settings.targets = Some(vec![ModelCheckerTarget::Assert]); } } let mut settings = Settings { - optimizer, + libraries: self.libraries_with_remappings()?, + optimizer: self.optimizer(), evm_version: Some(self.evm_version), - libraries, metadata: Some(SettingsMetadata { use_literal_content: Some(self.use_literal_content), bytecode_hash: Some(self.bytecode_hash), @@ -1046,16 +1204,23 @@ impl Config { }), debug: self.revert_strings.map(|revert_strings| DebuggingSettings { revert_strings: Some(revert_strings), + // Not used. debug_info: Vec::new(), }), model_checker, - ..Default::default() + via_ir: Some(self.via_ir), + // Not used. + stop_after: None, + // Set in project paths. + remappings: Vec::new(), + // Set with `with_extra_output` below. + output_selection: Default::default(), } - .with_extra_output(self.configured_artifacts_handler().output_selection()) - .with_ast(); + .with_extra_output(self.configured_artifacts_handler().output_selection()); - if self.via_ir { - settings = settings.with_via_ir(); + // We're keeping AST in `--build-info` for backwards compatibility with HardHat. + if self.ast || self.build_info { + settings = settings.with_ast(); } Ok(settings) @@ -1172,7 +1337,7 @@ impl Config { /// [Self::get_config_path()] and if the closure returns `true`. pub fn update_at(root: impl Into, f: F) -> eyre::Result<()> where - F: FnOnce(&Config, &mut toml_edit::Document) -> bool, + F: FnOnce(&Config, &mut toml_edit::DocumentMut) -> bool, { let config = Self::load_with_root(root).sanitized(); config.update(|doc| f(&config, doc)) @@ -1184,14 +1349,14 @@ impl Config { /// [Self::get_config_path()] and if the closure returns `true` pub fn update(&self, f: F) -> eyre::Result<()> where - F: FnOnce(&mut toml_edit::Document) -> bool, + F: FnOnce(&mut toml_edit::DocumentMut) -> bool, { let file_path = self.get_config_path(); if !file_path.exists() { return Ok(()) } let contents = fs::read_to_string(&file_path)?; - let mut doc = contents.parse::()?; + let mut doc = contents.parse::()?; if f(&mut doc) { fs::write(file_path, doc.to_string())?; } @@ -1477,12 +1642,19 @@ impl Config { if !chain_path.exists() { return Ok(blocks) } - for block in chain_path.read_dir()?.flatten().filter(|x| x.file_type().unwrap().is_dir()) { - let filepath = block.path().join("storage.json"); - blocks.push(( - block.file_name().to_string_lossy().into_owned(), - fs::metadata(filepath)?.len(), - )); + for block in chain_path.read_dir()?.flatten() { + let file_type = block.file_type()?; + let file_name = block.file_name(); + let filepath = if file_type.is_dir() { + block.path().join("storage.json") + } else if file_type.is_file() && + file_name.to_string_lossy().chars().all(char::is_numeric) + { + block.path() + } else { + continue + }; + blocks.push((file_name.to_string_lossy().into_owned(), fs::metadata(filepath)?.len())); } Ok(blocks) } @@ -1536,7 +1708,9 @@ impl Config { } // merge special keys into config for standalone_key in Config::STANDALONE_SECTIONS { - if let Some(fallback) = STANDALONE_FALLBACK_SECTIONS.get(standalone_key) { + if let Some((_, fallback)) = + STANDALONE_FALLBACK_SECTIONS.iter().find(|(key, _)| standalone_key == key) + { figment = figment.merge( provider .fallback(standalone_key, fallback) @@ -1550,82 +1724,68 @@ impl Config { figment = figment.merge(provider); figment } + + /// Check if any defaults need to be normalized. + /// + /// This normalizes the default `evm_version` if a `solc` was provided in the config. + /// + /// See also + fn normalize_defaults(&mut self, figment: Figment) -> Figment { + if let Ok(solc) = figment.extract_inner::("solc") { + // check if evm_version is set + // TODO: add a warning if evm_version is provided but incompatible + if figment.find_value("evm_version").is_err() { + if let Some(version) = solc + .try_version() + .ok() + .and_then(|version| self.evm_version.normalize_version(&version)) + { + // normalize evm_version based on the provided solc version + self.evm_version = version; + } + } + } + + figment + } } impl From for Figment { fn from(c: Config) -> Figment { - let profile = Config::selected_profile(); - let mut figment = Figment::default().merge(DappHardhatDirProvider(&c.__root.0)); - - // merge global foundry.toml file - if let Some(global_toml) = Config::foundry_dir_toml().filter(|p| p.exists()) { - figment = Config::merge_toml_provider( - figment, - TomlFileProvider::new(None, global_toml).cached(), - profile.clone(), - ); - } - // merge local foundry.toml file - figment = Config::merge_toml_provider( - figment, - TomlFileProvider::new(Some("FOUNDRY_CONFIG"), c.__root.0.join(Config::FILE_NAME)) - .cached(), - profile.clone(), - ); + c.to_figment(FigmentProviders::All) + } +} - // merge environment variables - figment = figment - .merge( - Env::prefixed("DAPP_") - .ignore(&["REMAPPINGS", "LIBRARIES", "FFI", "FS_PERMISSIONS"]) - .global(), - ) - .merge( - Env::prefixed("DAPP_TEST_") - .ignore(&["CACHE", "FUZZ_RUNS", "DEPTH", "FFI", "FS_PERMISSIONS"]) - .global(), - ) - .merge(DappEnvCompatProvider) - .merge(Env::raw().only(&["ETHERSCAN_API_KEY"])) - .merge( - Env::prefixed("FOUNDRY_") - .ignore(&["PROFILE", "REMAPPINGS", "LIBRARIES", "FFI", "FS_PERMISSIONS"]) - .map(|key| { - let key = key.as_str(); - if Config::STANDALONE_SECTIONS.iter().any(|section| { - key.starts_with(&format!("{}_", section.to_ascii_uppercase())) - }) { - key.replacen('_', ".", 1).into() - } else { - key.into() - } - }) - .global(), - ) - .select(profile.clone()); +/// Determines what providers should be used when loading the [Figment] for a [Config] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum FigmentProviders { + /// Include all providers + #[default] + All, + /// Only include necessary providers that are useful for cast commands + /// + /// This will exclude more expensive providers such as remappings + Cast, + /// Only include necessary providers that are useful for anvil + /// + /// This will exclude more expensive providers such as remappings + Anvil, +} - // we try to merge remappings after we've merged all other providers, this prevents - // redundant fs lookups to determine the default remappings that are eventually updated by - // other providers, like the toml file - let remappings = RemappingsProvider { - auto_detect_remappings: figment - .extract_inner::("auto_detect_remappings") - .unwrap_or(true), - lib_paths: figment - .extract_inner::>("libs") - .map(Cow::Owned) - .unwrap_or_else(|_| Cow::Borrowed(&c.libs)), - root: &c.__root.0, - remappings: figment.extract_inner::>("remappings"), - }; - let merge = figment.merge(remappings); +impl FigmentProviders { + /// Returns true if all providers should be included + pub const fn is_all(&self) -> bool { + matches!(self, Self::All) + } - Figment::from(c).merge(merge).select(profile) + /// Returns true if this is the cast preset + pub const fn is_cast(&self) -> bool { + matches!(self, Self::Cast) } } /// Wrapper type for `regex::Regex` that implements `PartialEq` -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(transparent)] pub struct RegexWrapper { #[serde(with = "serde_regex")] @@ -1685,7 +1845,7 @@ pub(crate) mod from_opt_glob { } /// A helper wrapper around the root path used during Config detection -#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(transparent)] pub struct RootPath(pub PathBuf); @@ -1758,6 +1918,7 @@ impl Default for Config { fs_permissions: FsPermissions::new([PathPermission::read("out")]), cancun: false, prague: false, + isolate: false, __root: Default::default(), src: "src".into(), test: "test".into(), @@ -1790,9 +1951,11 @@ impl Default for Config { contract_pattern_inverse: None, path_pattern: None, path_pattern_inverse: None, - fuzz: Default::default(), + fuzz: FuzzConfig::new("cache/fuzz".into()), invariant: Default::default(), + always_use_create_2_factory: false, ffi: false, + prompt_timeout: 120, sender: Config::DEFAULT_SENDER, tx_origin: Config::DEFAULT_SENDER, initial_balance: U256::from(0xffffffffffffffffffffffffu128), @@ -1808,6 +1971,7 @@ impl Default for Config { block_difficulty: 0, block_prevrandao: Default::default(), block_gas_limit: None, + disable_block_gas_limit: false, memory_limit: 1 << 27, // 2**27 = 128MiB = 134_217_728 bytes eth_rpc_url: None, eth_rpc_jwt: None, @@ -1820,9 +1984,12 @@ impl Default for Config { SolidityErrorCode::SpdxLicenseNotProvided, SolidityErrorCode::ContractExceeds24576Bytes, SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes, + SolidityErrorCode::TransientStorageUsed, ], + ignored_file_paths: vec![], deny_warnings: false, via_ir: false, + ast: false, rpc_storage_caching: Default::default(), rpc_endpoints: Default::default(), etherscan: Default::default(), @@ -1837,6 +2004,8 @@ impl Default for Config { build_info_path: None, fmt: Default::default(), doc: Default::default(), + labels: Default::default(), + unchecked_cheatcode_artifacts: false, __non_exhaustive: (), __warnings: vec![], } @@ -1847,7 +2016,7 @@ impl Default for Config { /// /// Due to this limitation this type will be serialized/deserialized as String if it's larger than /// `i64` -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct GasLimit(pub u64); impl From for GasLimit { @@ -1917,7 +2086,7 @@ impl<'de> Deserialize<'de> for GasLimit { } /// Variants for selecting the [`Solc`] instance -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum SolcReq { /// Requires a specific solc version, that's either already installed (via `svm`) or will be @@ -1927,6 +2096,22 @@ pub enum SolcReq { Local(PathBuf), } +impl SolcReq { + /// Tries to get the solc version from the `SolcReq` + /// + /// If the `SolcReq` is a `Version` it will return the version, if it's a path to a binary it + /// will try to get the version from the binary. + fn try_version(&self) -> Result { + match self { + SolcReq::Version(version) => Ok(version.clone()), + SolcReq::Local(path) => Solc::new(path).version().map_err(|err| { + warn!("failed to get solc version from {}: {}", path.display(), err); + err + }), + } + } +} + impl> From for SolcReq { fn from(s: T) -> Self { let s = s.as_ref(); @@ -2457,7 +2642,7 @@ impl ProviderExt for P {} /// /// let my_config = Config::figment().extract::(); /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct BasicConfig { /// the profile tag: `[profile.default]` #[serde(skip)] @@ -2521,15 +2706,13 @@ mod tests { use super::*; use crate::{ cache::{CachedChains, CachedEndpoints}, - endpoints::RpcEndpoint, + endpoints::{RpcEndpointConfig, RpcEndpointType}, etherscan::ResolvedEtherscanConfigs, - fs_permissions::PathPermission, }; - use alloy_primitives::Address; - use figment::{error::Kind::InvalidType, value::Value, Figment}; + use figment::error::Kind::InvalidType; use foundry_compilers::artifacts::{ModelCheckerEngine, YulDetails}; use pretty_assertions::assert_eq; - use std::{collections::BTreeMap, fs::File, io::Write, str::FromStr}; + use std::{collections::BTreeMap, fs::File, io::Write}; use tempfile::tempdir; use NamedChain::Moonbeam; @@ -2832,7 +3015,7 @@ mod tests { // contains additional remapping to the source dir assert_eq!( - config.get_all_remappings(), + config.get_all_remappings().collect::>(), vec![ Remapping::from_str("ds-test/=lib/ds-test/src/").unwrap(), Remapping::from_str("env-lib/=lib/env-lib/").unwrap(), @@ -3017,6 +3200,29 @@ mod tests { }); } + #[test] + fn test_resolve_etherscan_chain_id() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + chain_id = "sepolia" + + [etherscan] + sepolia = { key = "FX42Z3BBJJEWXWGYV2X1CIPRSCN" } + "#, + )?; + + let config = Config::load(); + let etherscan = config.get_etherscan_config().unwrap().unwrap(); + assert_eq!(etherscan.chain, Some(NamedChain::Sepolia.into())); + assert_eq!(etherscan.key, "FX42Z3BBJJEWXWGYV2X1CIPRSCN"); + + Ok(()) + }); + } + #[test] fn test_resolve_rpc_url() { figment::Jail::expect_with(|jail| { @@ -3095,6 +3301,84 @@ mod tests { }) } + #[test] + fn test_resolve_rpc_aliases() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + [etherscan] + arbitrum_alias = { key = "${TEST_RESOLVE_RPC_ALIAS_ARBISCAN}" } + [rpc_endpoints] + arbitrum_alias = "https://arb-mainnet.g.alchemy.com/v2/${TEST_RESOLVE_RPC_ALIAS_ARB_ONE}" + "#, + )?; + + jail.set_env("TEST_RESOLVE_RPC_ALIAS_ARB_ONE", "123455"); + jail.set_env("TEST_RESOLVE_RPC_ALIAS_ARBISCAN", "123455"); + + let config = Config::load(); + + let config = config.get_etherscan_config_with_chain(Some(NamedChain::Arbitrum.into())); + assert!(config.is_err()); + assert_eq!(config.unwrap_err().to_string(), "At least one of `url` or `chain` must be present for Etherscan config with unknown alias `arbitrum_alias`"); + + Ok(()) + }); + } + + #[test] + fn test_resolve_rpc_config() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [rpc_endpoints] + optimism = "https://example.com/" + mainnet = { endpoint = "${_CONFIG_MAINNET}", retries = 3, retry_backoff = 1000, compute_units_per_second = 1000 } + "#, + )?; + jail.set_env("_CONFIG_MAINNET", "https://eth-mainnet.alchemyapi.io/v2/123455"); + + let config = Config::load(); + assert_eq!( + RpcEndpoints::new([ + ( + "optimism", + RpcEndpointType::String(RpcEndpoint::Url( + "https://example.com/".to_string() + )) + ), + ( + "mainnet", + RpcEndpointType::Config(RpcEndpointConfig { + endpoint: RpcEndpoint::Env("${_CONFIG_MAINNET}".to_string()), + retries: Some(3), + retry_backoff: Some(1000), + compute_units_per_second: Some(1000), + }) + ), + ]), + config.rpc_endpoints + ); + + let resolved = config.rpc_endpoints.resolved(); + assert_eq!( + RpcEndpoints::new([ + ("optimism", RpcEndpoint::Url("https://example.com/".to_string())), + ( + "mainnet", + RpcEndpoint::Url("https://eth-mainnet.alchemyapi.io/v2/123455".to_string()) + ), + ]) + .resolved(), + resolved + ); + Ok(()) + }) + } + #[test] fn test_resolve_endpoints() { figment::Jail::expect_with(|jail| { @@ -3280,6 +3564,7 @@ mod tests { revert_strings = "strip" allow_paths = ["allow", "paths"] build_info_path = "build-info" + always_use_create_2_factory = true [rpc_endpoints] optimism = "https://example.com/" @@ -3331,6 +3616,7 @@ mod tests { ), ]), build_info_path: Some("build-info".into()), + always_use_create_2_factory: true, ..Config::default() } ); @@ -3382,12 +3668,14 @@ mod tests { evm_version = 'london' extra_output = [] extra_output_files = [] + always_use_create_2_factory = false ffi = false force = false gas_limit = 9223372036854775807 gas_price = 0 gas_reports = ['*'] ignored_error_codes = [1878] + ignored_warnings_from = ["something"] deny_warnings = false initial_balance = '0xffffffffffffffffffffffff' libraries = [] @@ -3435,6 +3723,7 @@ mod tests { let config = Config::load_with_root(jail.directory()); + assert_eq!(config.ignored_file_paths, vec![PathBuf::from("something")]); assert_eq!(config.fuzz.seed, Some(U256::from(1000))); assert_eq!( config.remappings, @@ -3797,7 +4086,7 @@ mod tests { #[test] fn can_handle_deviating_dapp_aliases() { figment::Jail::expect_with(|jail| { - let addr = Address::random(); + let addr = Address::ZERO; jail.set_env("DAPP_TEST_NUMBER", 1337); jail.set_env("DAPP_TEST_ADDRESS", format!("{addr:?}")); jail.set_env("DAPP_TEST_FUZZ_RUNS", 420); @@ -4254,6 +4543,66 @@ mod tests { }); } + #[test] + fn test_etherscan_api_key() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r" + [default] + ", + )?; + jail.set_env("ETHERSCAN_API_KEY", ""); + let loaded = Config::load().sanitized(); + assert!(loaded.etherscan_api_key.is_none()); + + jail.set_env("ETHERSCAN_API_KEY", "DUMMY"); + let loaded = Config::load().sanitized(); + assert_eq!(loaded.etherscan_api_key, Some("DUMMY".into())); + + Ok(()) + }); + } + + #[test] + fn test_etherscan_api_key_figment() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r" + [default] + etherscan_api_key = 'DUMMY' + ", + )?; + jail.set_env("ETHERSCAN_API_KEY", "ETHER"); + + let figment = Config::figment_with_root(jail.directory()) + .merge(("etherscan_api_key", "USER_KEY")); + + let loaded = Config::from_provider(figment); + assert_eq!(loaded.etherscan_api_key, Some("USER_KEY".into())); + + Ok(()) + }); + } + + #[test] + fn test_normalize_defaults() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r" + [default] + solc = '0.8.13' + ", + )?; + + let loaded = Config::load().sanitized(); + assert_eq!(loaded.evm_version, EvmVersion::London); + Ok(()) + }); + } + // a test to print the config, mainly used to update the example config in the README #[test] #[ignore] @@ -4272,6 +4621,7 @@ mod tests { stack_allocation: None, optimizer_steps: Some("dhfoDgvulfnTUtnIf".to_string()), }), + simple_counter_for_loop_unchecked_increment: None, }), ..Default::default() }; @@ -4279,6 +4629,7 @@ mod tests { } #[test] + #[allow(unknown_lints, non_local_definitions)] fn can_use_impl_figment_macro() { #[derive(Default, Serialize)] struct MyArgs { @@ -4325,23 +4676,38 @@ mod tests { writeln!(file, "{}", vec![' '; size_bytes - 1].iter().collect::()).unwrap(); } + fn fake_block_cache_block_path_as_file( + chain_path: &Path, + block_number: &str, + size_bytes: usize, + ) { + let block_path = chain_path.join(block_number); + let mut file = File::create(block_path).unwrap(); + writeln!(file, "{}", vec![' '; size_bytes - 1].iter().collect::()).unwrap(); + } + let chain_dir = tempdir()?; fake_block_cache(chain_dir.path(), "1", 100); fake_block_cache(chain_dir.path(), "2", 500); + fake_block_cache_block_path_as_file(chain_dir.path(), "3", 900); // Pollution file that should not show up in the cached block let mut pol_file = File::create(chain_dir.path().join("pol.txt")).unwrap(); writeln!(pol_file, "{}", [' '; 10].iter().collect::()).unwrap(); let result = Config::get_cached_blocks(chain_dir.path())?; - assert_eq!(result.len(), 2); + assert_eq!(result.len(), 3); let block1 = &result.iter().find(|x| x.0 == "1").unwrap(); let block2 = &result.iter().find(|x| x.0 == "2").unwrap(); + let block3 = &result.iter().find(|x| x.0 == "3").unwrap(); + assert_eq!(block1.0, "1"); assert_eq!(block1.1, 100); assert_eq!(block2.0, "2"); assert_eq!(block2.1, 500); + assert_eq!(block3.0, "3"); + assert_eq!(block3.1, 900); chain_dir.close()?; Ok(()) @@ -4404,6 +4770,24 @@ mod tests { }); } + #[test] + fn test_parse_file_paths() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [default] + ignored_warnings_from = ["something"] + "#, + )?; + + let config = Config::load(); + assert_eq!(config.ignored_file_paths, vec![Path::new("something").to_path_buf()]); + + Ok(()) + }); + } + #[test] fn test_parse_optimizer_settings() { figment::Jail::expect_with(|jail| { @@ -4411,7 +4795,7 @@ mod tests { "foundry.toml", r" [default] - [profile.default.optimizer_details] + [profile.default.optimizer_details] ", )?; @@ -4421,4 +4805,35 @@ mod tests { Ok(()) }); } + + #[test] + fn test_parse_labels() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [labels] + 0x1F98431c8aD98523631AE4a59f267346ea31F984 = "Uniswap V3: Factory" + 0xC36442b4a4522E871399CD717aBDD847Ab11FE88 = "Uniswap V3: Positions NFT" + "#, + )?; + + let config = Config::load(); + assert_eq!( + config.labels, + HashMap::from_iter(vec![ + ( + Address::from_str("0x1F98431c8aD98523631AE4a59f267346ea31F984").unwrap(), + "Uniswap V3: Factory".to_string() + ), + ( + Address::from_str("0xC36442b4a4522E871399CD717aBDD847Ab11FE88").unwrap(), + "Uniswap V3: Positions NFT".to_string() + ), + ]) + ); + + Ok(()) + }); + } } diff --git a/crates/config/src/macros.rs b/crates/config/src/macros.rs index 4bd501354f113..d7e206a978250 100644 --- a/crates/config/src/macros.rs +++ b/crates/config/src/macros.rs @@ -30,7 +30,7 @@ /// Metadata::default() /// } /// -/// fn data(&self) -> Result, Error> { +/// fn data(&self) -> std::result::Result, Error> { /// let value = Value::serialize(self)?; /// let error = InvalidType(value.to_actual(), "map".into()); /// let mut dict = value.into_dict().ok_or(error)?; @@ -59,12 +59,10 @@ macro_rules! impl_figment_convert { ($name:ty) => { impl<'a> From<&'a $name> for $crate::figment::Figment { fn from(args: &'a $name) -> Self { - if let Some(root) = args.root.clone() { - $crate::Config::figment_with_root(root) - } else { - $crate::Config::figment_with_root($crate::find_project_root_path(None).unwrap()) - } - .merge(args) + let root = args.root.clone() + .unwrap_or_else(|| $crate::find_project_root_path(None) + .unwrap_or_else(|e| panic!("could not find project root: {e}"))); + $crate::Config::figment_with_root(root).merge(args) } } @@ -79,8 +77,8 @@ macro_rules! impl_figment_convert { impl<'a> From<&'a $name> for $crate::figment::Figment { fn from(args: &'a $name) -> Self { let mut figment: $crate::figment::Figment = From::from(&args.$start); - $ ( - figment = figment.merge(&args.$more); + $( + figment = figment.merge(&args.$more); )* figment } @@ -97,8 +95,8 @@ macro_rules! impl_figment_convert { impl<'a> From<&'a $name> for $crate::figment::Figment { fn from(args: &'a $name) -> Self { let mut figment: $crate::figment::Figment = From::from(&args.$start); - $ ( - figment = figment.merge(&args.$more); + $( + figment = figment.merge(&args.$more); )* figment = figment.merge(args); figment @@ -137,7 +135,7 @@ macro_rules! impl_figment_convert { /// Metadata::default() /// } /// -/// fn data(&self) -> Result, Error> { +/// fn data(&self) -> std::result::Result, Error> { /// todo!() /// } /// } @@ -155,7 +153,7 @@ macro_rules! impl_figment_convert { /// Metadata::default() /// } /// -/// fn data(&self) -> Result, Error> { +/// fn data(&self) -> std::result::Result, Error> { /// todo!() /// } /// } @@ -186,12 +184,16 @@ macro_rules! merge_impl_figment_convert { } /// A macro to implement converters from a type to [`Config`] and [`figment::Figment`] +/// +/// Via [Config::to_figment](crate::Config::to_figment) and the +/// [Cast](crate::FigmentProviders::Cast) profile. #[macro_export] macro_rules! impl_figment_convert_cast { ($name:ty) => { impl<'a> From<&'a $name> for $crate::figment::Figment { fn from(args: &'a $name) -> Self { - $crate::Config::figment_with_root($crate::find_project_root_path(None).unwrap()) + $crate::Config::with_root($crate::find_project_root_path(None).unwrap()) + .to_figment($crate::FigmentProviders::Cast) .merge(args) } } diff --git a/crates/config/src/providers/mod.rs b/crates/config/src/providers/mod.rs index a7e567788ba2c..239ee0c74a36b 100644 --- a/crates/config/src/providers/mod.rs +++ b/crates/config/src/providers/mod.rs @@ -66,10 +66,17 @@ impl WarningsProvider

{ .unwrap_or_default() .iter() .flat_map(|(profile, dict)| dict.keys().map(move |key| format!("{profile}.{key}"))) - .filter(|k| DEPRECATIONS.contains_key(k)) - .map(|deprecated_key| Warning::DeprecatedKey { - old: deprecated_key.clone(), - new: DEPRECATIONS.get(&deprecated_key).unwrap().to_string(), + .filter_map(|key| { + DEPRECATIONS.iter().find_map(|(deprecated_key, new_value)| { + if key == *deprecated_key { + Some(Warning::DeprecatedKey { + old: deprecated_key.to_string(), + new: new_value.to_string(), + }) + } else { + None + } + }) }), ); Ok(out) diff --git a/crates/config/src/providers/remappings.rs b/crates/config/src/providers/remappings.rs index 7d81b568c8902..67ae449fb8141 100644 --- a/crates/config/src/providers/remappings.rs +++ b/crates/config/src/providers/remappings.rs @@ -12,7 +12,7 @@ use std::{ }; /// Wrapper types over a `Vec` that only appends unique remappings. -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct Remappings { /// Remappings. remappings: Vec, diff --git a/crates/config/src/resolve.rs b/crates/config/src/resolve.rs index b062558b353d3..b817f43677c62 100644 --- a/crates/config/src/resolve.rs +++ b/crates/config/src/resolve.rs @@ -9,7 +9,7 @@ pub static RE_PLACEHOLDER: Lazy = Lazy::new(|| Regex::new(r"(?m)(?P\$\{\s*(?P.*?)\s*})").unwrap()); /// Error when we failed to resolve an env var -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct UnresolvedEnvVarError { /// The unresolved input string pub unresolved: String, diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index 8fb3289227c6d..cb23500088b4e 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -14,7 +14,7 @@ use std::{ path::{Path, PathBuf}, str::FromStr, }; -use toml_edit::{Document, Item}; +use toml_edit::{DocumentMut, Item}; /// Loads the config for the current project workspace pub fn load_config() -> Config { @@ -216,9 +216,9 @@ pub fn get_available_profiles(toml_path: impl AsRef) -> eyre::Result) -> eyre::Result { +fn read_toml(path: impl AsRef) -> eyre::Result { let path = path.as_ref().to_owned(); - let doc: Document = std::fs::read_to_string(path)?.parse()?; + let doc: DocumentMut = std::fs::read_to_string(path)?.parse()?; Ok(doc) } @@ -264,7 +264,7 @@ where } /// Helper type to parse both `u64` and `U256` -#[derive(Copy, Clone, Deserialize)] +#[derive(Clone, Copy, Deserialize)] #[serde(untagged)] pub enum Numeric { /// A [U256] value. @@ -309,6 +309,7 @@ pub fn evm_spec_id(evm_version: &EvmVersion) -> SpecId { EvmVersion::London => SpecId::LONDON, EvmVersion::Paris => SpecId::MERGE, EvmVersion::Shanghai => SpecId::SHANGHAI, + EvmVersion::Cancun => SpecId::CANCUN, } } diff --git a/crates/config/src/warning.rs b/crates/config/src/warning.rs index fc98be3d4fa25..a19104eaf7b4d 100644 --- a/crates/config/src/warning.rs +++ b/crates/config/src/warning.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use std::{fmt, path::PathBuf}; /// Warnings emitted during loading or managing Configuration -#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "type")] pub enum Warning { /// An unknown section was encountered in a TOML file @@ -53,30 +53,37 @@ impl fmt::Display for Warning { match self { Self::UnknownSection { unknown_section, source } => { let source = source.as_ref().map(|src| format!(" in {src}")).unwrap_or_default(); - f.write_fmt(format_args!("Unknown section [{unknown_section}] found{source}. This notation for profiles has been deprecated and may result in the profile not being registered in future versions. Please use [profile.{unknown_section}] instead or run `forge config --fix`.")) - } - Self::NoLocalToml(tried) => { - let path = tried.display(); - f.write_fmt(format_args!("No local TOML found to fix at {path}. Change the current directory to a project path or set the foundry.toml path with the FOUNDRY_CONFIG environment variable")) + write!( + f, + "Found unknown config section{source}: [{unknown_section}]\n\ + This notation for profiles has been deprecated and may result in the profile \ + not being registered in future versions.\n\ + Please use [profile.{unknown_section}] instead or run `forge config --fix`." + ) } + Self::NoLocalToml(path) => write!( + f, + "No local TOML found to fix at {}.\n\ + Change the current directory to a project path or set the foundry.toml path with \ + the `FOUNDRY_CONFIG` environment variable", + path.display() + ), + Self::CouldNotReadToml { path, err } => { - f.write_fmt(format_args!("Could not read TOML at {}: {err}", path.display())) + write!(f, "Could not read TOML at {}: {err}", path.display()) } Self::CouldNotWriteToml { path, err } => { - f.write_fmt(format_args!("Could not write TOML to {}: {err}", path.display())) + write!(f, "Could not write TOML to {}: {err}", path.display()) + } + Self::CouldNotFixProfile { path, profile, err } => { + write!(f, "Could not fix [{profile}] in TOML at {}: {err}", path.display()) + } + Self::DeprecatedKey { old, new } if new.is_empty() => { + write!(f, "Key `{old}` is being deprecated and will be removed in future versions.") + } + Self::DeprecatedKey { old, new } => { + write!(f, "Key `{old}` is being deprecated in favor of `{new}`. It will be removed in future versions.") } - Self::CouldNotFixProfile { path, profile, err } => f.write_fmt(format_args!( - "Could not fix [{}] in TOML at {}: {}", - profile, - path.display(), - err - )), - Self::DeprecatedKey { old, new } if new.is_empty() => f.write_fmt(format_args!( - "Key `{old}` is being deprecated and will be removed in future versions.", - )), - Self::DeprecatedKey { old, new } => f.write_fmt(format_args!( - "Key `{old}` is being deprecated in favor of `{new}`. It will be removed in future versions.", - )), } } } diff --git a/crates/debugger/Cargo.toml b/crates/debugger/Cargo.toml index fa67b59294c80..bc3c746e3d859 100644 --- a/crates/debugger/Cargo.toml +++ b/crates/debugger/Cargo.toml @@ -10,14 +10,16 @@ homepage.workspace = true repository.workspace = true [dependencies] +foundry-common.workspace = true +foundry-compilers.workspace = true foundry-evm-core.workspace = true foundry-evm-traces.workspace = true -foundry-common.workspace = true +revm-inspectors.workspace = true alloy-primitives.workspace = true crossterm = "0.27" eyre.workspace = true -ratatui = { version = "0.24.0", default-features = false, features = ["crossterm"] } +ratatui = { version = "0.26", default-features = false, features = ["crossterm"] } revm.workspace = true tracing.workspace = true diff --git a/crates/debugger/src/lib.rs b/crates/debugger/src/lib.rs index db5a012eb5990..5683cb8a5b7c4 100644 --- a/crates/debugger/src/lib.rs +++ b/crates/debugger/src/lib.rs @@ -2,15 +2,12 @@ //! //! Interactive Solidity TUI debugger. -#![warn(unused_crate_dependencies)] +#![warn(unused_crate_dependencies, unreachable_pub)] #[macro_use] extern crate tracing; -mod builder; -pub use builder::DebuggerBuilder; - mod op; mod tui; -pub use tui::{Debugger, ExitReason}; +pub use tui::{Debugger, DebuggerBuilder, ExitReason}; diff --git a/crates/debugger/src/op.rs b/crates/debugger/src/op.rs index a6755483678aa..486fbe09fbeb0 100644 --- a/crates/debugger/src/op.rs +++ b/crates/debugger/src/op.rs @@ -1,16 +1,16 @@ /// Named parameter of an EVM opcode. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct OpcodeParam { +pub(crate) struct OpcodeParam { /// The name of the parameter. - pub name: &'static str, + pub(crate) name: &'static str, /// The index of the parameter on the stack. This is relative to the top of the stack. - pub index: usize, + pub(crate) index: usize, } impl OpcodeParam { /// Returns the list of named parameters for the given opcode. #[inline] - pub fn of(op: u8) -> &'static [Self] { + pub(crate) fn of(op: u8) -> &'static [Self] { MAP[op as usize] } } diff --git a/crates/debugger/src/tui.rs b/crates/debugger/src/tui.rs deleted file mode 100644 index 9d9fbfcab8297..0000000000000 --- a/crates/debugger/src/tui.rs +++ /dev/null @@ -1,1402 +0,0 @@ -//! The TUI implementation. - -use crate::{op::OpcodeParam, DebuggerBuilder}; -use alloy_primitives::{Address, U256}; -use crossterm::{ - event::{ - self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, - MouseEvent, MouseEventKind, - }, - execute, - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, -}; -use eyre::Result; -use foundry_common::{compile::ContractSources, evm::Breakpoints}; -use foundry_evm_core::{ - debug::{DebugStep, Instruction}, - utils::{build_pc_ic_map, CallKind, PCICMap}, -}; -use ratatui::{ - backend::CrosstermBackend, - layout::{Alignment, Constraint, Direction, Layout, Rect}, - style::{Color, Modifier, Style}, - terminal::Frame, - text::{Line, Span, Text}, - widgets::{Block, Borders, Paragraph, Wrap}, - Terminal, -}; -use revm::{interpreter::opcode, primitives::SpecId}; -use std::{ - cmp::{max, min}, - collections::{BTreeMap, HashMap, VecDeque}, - io, - sync::mpsc, - thread, - time::{Duration, Instant}, -}; - -/// Used to indicate why the debugger quit. -#[derive(Debug)] -pub enum ExitReason { - /// Exit using 'q'. - CharExit, -} - -/// The TUI debugger. -pub struct Debugger { - debug_arena: Vec<(Address, Vec, CallKind)>, - terminal: Terminal>, - /// Buffer for keys prior to execution, i.e. '10' + 'k' => move up 10 operations - key_buffer: String, - /// Current step in the debug steps - current_step: usize, - identified_contracts: HashMap, - /// Source map of contract sources - contracts_sources: ContractSources, - /// A mapping of source -> (PC -> IC map for deploy code, PC -> IC map for runtime code) - pc_ic_maps: BTreeMap, - breakpoints: Breakpoints, -} - -impl Debugger { - /// Creates a new debugger builder. - #[inline] - pub fn builder() -> DebuggerBuilder { - DebuggerBuilder::new() - } - - /// Creates a new debugger. - pub fn new( - debug_arena: Vec<(Address, Vec, CallKind)>, - current_step: usize, - identified_contracts: HashMap, - contracts_sources: ContractSources, - breakpoints: Breakpoints, - ) -> Result { - let backend = CrosstermBackend::new(io::stdout()); - let terminal = Terminal::new(backend)?; - let pc_ic_maps = contracts_sources - .0 - .iter() - .flat_map(|(contract_name, files_sources)| { - files_sources.iter().filter_map(|(_, (_, contract))| { - Some(( - contract_name.clone(), - ( - build_pc_ic_map( - SpecId::LATEST, - contract.bytecode.object.as_bytes()?.as_ref(), - ), - build_pc_ic_map( - SpecId::LATEST, - contract - .deployed_bytecode - .bytecode - .as_ref()? - .object - .as_bytes()? - .as_ref(), - ), - ), - )) - }) - }) - .collect(); - Ok(Debugger { - debug_arena, - terminal, - key_buffer: String::new(), - current_step, - identified_contracts, - contracts_sources, - pc_ic_maps, - breakpoints, - }) - } - - /// Starts the debugger TUI. Terminates the current process on failure or user exit. - pub fn run_exit(mut self) -> ! { - let code = match self.try_run() { - Ok(ExitReason::CharExit) => 0, - Err(e) => { - println!("{e}"); - 1 - } - }; - std::process::exit(code) - } - - /// Starts the debugger TUI. - pub fn try_run(&mut self) -> Result { - let mut guard = DebuggerGuard::setup(self)?; - let r = guard.0.try_run_real(); - // Cleanup only once. - guard.restore()?; - std::mem::forget(guard); - r - } - - #[instrument(target = "debugger", name = "run", skip_all, ret)] - fn try_run_real(&mut self) -> Result { - // Setup a channel to send interrupts - let (tx, rx) = mpsc::channel(); - thread::Builder::new() - .name("event-listener".into()) - .spawn(move || event_listener(tx)) - .expect("failed to spawn thread"); - - self.terminal.clear()?; - let mut draw_memory = DrawMemory::default(); - - let debug_call = self.debug_arena.clone(); - let mut opcode_list: Vec = - debug_call[0].1.iter().map(|step| step.pretty_opcode()).collect(); - let mut last_index = 0; - - let mut stack_labels = false; - let mut mem_utf = false; - let mut show_shortcuts = true; - - // UI thread that manages drawing - loop { - if last_index != draw_memory.inner_call_index { - opcode_list = debug_call[draw_memory.inner_call_index] - .1 - .iter() - .map(|step| step.pretty_opcode()) - .collect(); - last_index = draw_memory.inner_call_index; - } - - // Grab interrupt - let receiver = rx.recv()?; - - if let Some(c) = receiver.char_press() { - if self.key_buffer.ends_with('\'') { - // Find the location of the called breakpoint in the whole debug arena (at this - // address with this pc) - if let Some((caller, pc)) = self.breakpoints.get(&c) { - for (i, (_caller, debug_steps, _)) in debug_call.iter().enumerate() { - if _caller == caller { - if let Some(step) = - debug_steps.iter().position(|step| step.pc == *pc) - { - draw_memory.inner_call_index = i; - self.current_step = step; - break - } - } - } - } - self.key_buffer.clear(); - } else if let Interrupt::KeyEvent(event) = receiver { - match event.code { - // Exit - KeyCode::Char('q') => return Ok(ExitReason::CharExit), - // Move down - KeyCode::Char('j') | KeyCode::Down => { - // Grab number of times to do it - for _ in 0..Debugger::buffer_as_number(&self.key_buffer, 1) { - if event.modifiers.contains(KeyModifiers::CONTROL) { - let max_mem = (debug_call[draw_memory.inner_call_index].1 - [self.current_step] - .memory - .len() / - 32) - .saturating_sub(1); - if draw_memory.current_mem_startline < max_mem { - draw_memory.current_mem_startline += 1; - } - } else if self.current_step < opcode_list.len() - 1 { - self.current_step += 1; - } else if draw_memory.inner_call_index < debug_call.len() - 1 { - draw_memory.inner_call_index += 1; - self.current_step = 0; - } - } - self.key_buffer.clear(); - } - KeyCode::Char('J') => { - for _ in 0..Debugger::buffer_as_number(&self.key_buffer, 1) { - let max_stack = debug_call[draw_memory.inner_call_index].1 - [self.current_step] - .stack - .len() - .saturating_sub(1); - if draw_memory.current_stack_startline < max_stack { - draw_memory.current_stack_startline += 1; - } - } - self.key_buffer.clear(); - } - // Move up - KeyCode::Char('k') | KeyCode::Up => { - for _ in 0..Debugger::buffer_as_number(&self.key_buffer, 1) { - if event.modifiers.contains(KeyModifiers::CONTROL) { - draw_memory.current_mem_startline = - draw_memory.current_mem_startline.saturating_sub(1); - } else if self.current_step > 0 { - self.current_step -= 1; - } else if draw_memory.inner_call_index > 0 { - draw_memory.inner_call_index -= 1; - self.current_step = - debug_call[draw_memory.inner_call_index].1.len() - 1; - } - } - self.key_buffer.clear(); - } - KeyCode::Char('K') => { - for _ in 0..Debugger::buffer_as_number(&self.key_buffer, 1) { - draw_memory.current_stack_startline = - draw_memory.current_stack_startline.saturating_sub(1); - } - self.key_buffer.clear(); - } - // Go to top of file - KeyCode::Char('g') => { - draw_memory.inner_call_index = 0; - self.current_step = 0; - self.key_buffer.clear(); - } - // Go to bottom of file - KeyCode::Char('G') => { - draw_memory.inner_call_index = debug_call.len() - 1; - self.current_step = - debug_call[draw_memory.inner_call_index].1.len() - 1; - self.key_buffer.clear(); - } - // Go to previous call - KeyCode::Char('c') => { - draw_memory.inner_call_index = - draw_memory.inner_call_index.saturating_sub(1); - self.current_step = - debug_call[draw_memory.inner_call_index].1.len() - 1; - self.key_buffer.clear(); - } - // Go to next call - KeyCode::Char('C') => { - if debug_call.len() > draw_memory.inner_call_index + 1 { - draw_memory.inner_call_index += 1; - self.current_step = 0; - } - self.key_buffer.clear(); - } - // Step forward - KeyCode::Char('s') => { - for _ in 0..Debugger::buffer_as_number(&self.key_buffer, 1) { - let remaining_ops = - opcode_list[self.current_step..].to_vec().clone(); - self.current_step += remaining_ops - .iter() - .enumerate() - .find_map(|(i, op)| { - if i < remaining_ops.len() - 1 { - match ( - op.contains("JUMP") && op != "JUMPDEST", - &*remaining_ops[i + 1], - ) { - (true, "JUMPDEST") => Some(i + 1), - _ => None, - } - } else { - None - } - }) - .unwrap_or(opcode_list.len() - 1); - if self.current_step > opcode_list.len() { - self.current_step = opcode_list.len() - 1 - }; - } - self.key_buffer.clear(); - } - // Step backwards - KeyCode::Char('a') => { - for _ in 0..Debugger::buffer_as_number(&self.key_buffer, 1) { - let prev_ops = opcode_list[..self.current_step].to_vec().clone(); - self.current_step = prev_ops - .iter() - .enumerate() - .rev() - .find_map(|(i, op)| { - if i > 0 { - match ( - prev_ops[i - 1].contains("JUMP") && - prev_ops[i - 1] != "JUMPDEST", - &**op, - ) { - (true, "JUMPDEST") => Some(i - 1), - _ => None, - } - } else { - None - } - }) - .unwrap_or_default(); - } - self.key_buffer.clear(); - } - // toggle stack labels - KeyCode::Char('t') => stack_labels = !stack_labels, - // toggle memory utf8 decoding - KeyCode::Char('m') => mem_utf = !mem_utf, - // toggle help notice - KeyCode::Char('h') => show_shortcuts = !show_shortcuts, - KeyCode::Char(other) => match other { - '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '\'' => { - self.key_buffer.push(other); - } - // Invalid key, clear buffer - _ => self.key_buffer.clear(), - }, - _ => self.key_buffer.clear(), - } - } - } else { - match receiver { - Interrupt::MouseEvent(event) => match event.kind { - MouseEventKind::ScrollUp => { - if self.current_step > 0 { - self.current_step -= 1; - } else if draw_memory.inner_call_index > 0 { - draw_memory.inner_call_index -= 1; - draw_memory.current_mem_startline = 0; - draw_memory.current_stack_startline = 0; - self.current_step = - debug_call[draw_memory.inner_call_index].1.len() - 1; - } - } - MouseEventKind::ScrollDown => { - if self.current_step < opcode_list.len() - 1 { - self.current_step += 1; - } else if draw_memory.inner_call_index < debug_call.len() - 1 { - draw_memory.inner_call_index += 1; - draw_memory.current_mem_startline = 0; - draw_memory.current_stack_startline = 0; - self.current_step = 0; - } - } - _ => {} - }, - Interrupt::IntervalElapsed => {} - _ => (), - } - } - - // Draw - let current_step = self.current_step; - self.terminal.draw(|f| { - Debugger::draw_layout( - f, - debug_call[draw_memory.inner_call_index].0, - &self.identified_contracts, - &self.pc_ic_maps, - &self.contracts_sources, - &debug_call[draw_memory.inner_call_index].1[..], - &opcode_list, - current_step, - debug_call[draw_memory.inner_call_index].2, - &mut draw_memory, - stack_labels, - mem_utf, - show_shortcuts, - ) - })?; - } - } - - /// Grab number from buffer. Used for something like '10k' to move up 10 operations - fn buffer_as_number(s: &str, default_value: usize) -> usize { - match s.parse() { - Ok(num) if num >= 1 => num, - _ => default_value, - } - } - - /// Create layout and subcomponents - #[allow(clippy::too_many_arguments)] - fn draw_layout( - f: &mut Frame<'_>, - address: Address, - identified_contracts: &HashMap, - pc_ic_maps: &BTreeMap, - contracts_sources: &ContractSources, - debug_steps: &[DebugStep], - opcode_list: &[String], - current_step: usize, - call_kind: CallKind, - draw_memory: &mut DrawMemory, - stack_labels: bool, - mem_utf: bool, - show_shortcuts: bool, - ) { - let total_size = f.size(); - if total_size.width < 225 { - Debugger::vertical_layout( - f, - address, - identified_contracts, - pc_ic_maps, - contracts_sources, - debug_steps, - opcode_list, - current_step, - call_kind, - draw_memory, - stack_labels, - mem_utf, - show_shortcuts, - ); - } else { - Debugger::square_layout( - f, - address, - identified_contracts, - pc_ic_maps, - contracts_sources, - debug_steps, - opcode_list, - current_step, - call_kind, - draw_memory, - stack_labels, - mem_utf, - show_shortcuts, - ); - } - } - - #[allow(clippy::too_many_arguments)] - fn vertical_layout( - f: &mut Frame<'_>, - address: Address, - identified_contracts: &HashMap, - pc_ic_maps: &BTreeMap, - contracts_sources: &ContractSources, - debug_steps: &[DebugStep], - opcode_list: &[String], - current_step: usize, - call_kind: CallKind, - draw_memory: &mut DrawMemory, - stack_labels: bool, - mem_utf: bool, - show_shortcuts: bool, - ) { - let total_size = f.size(); - let h_height = if show_shortcuts { 4 } else { 0 }; - - if let [app, footer] = Layout::default() - .constraints( - [Constraint::Ratio(100 - h_height, 100), Constraint::Ratio(h_height, 100)].as_ref(), - ) - .direction(Direction::Vertical) - .split(total_size)[..] - { - if let [op_pane, stack_pane, memory_pane, src_pane] = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Ratio(1, 6), - Constraint::Ratio(1, 6), - Constraint::Ratio(1, 6), - Constraint::Ratio(3, 6), - ] - .as_ref(), - ) - .split(app)[..] - { - if show_shortcuts { - Debugger::draw_footer(f, footer); - } - Debugger::draw_src( - f, - address, - identified_contracts, - pc_ic_maps, - contracts_sources, - debug_steps[current_step].pc, - call_kind, - src_pane, - ); - Debugger::draw_op_list( - f, - address, - debug_steps, - opcode_list, - current_step, - draw_memory, - op_pane, - ); - Debugger::draw_stack( - f, - debug_steps, - current_step, - stack_pane, - stack_labels, - draw_memory, - ); - Debugger::draw_memory( - f, - debug_steps, - current_step, - memory_pane, - mem_utf, - draw_memory, - ); - } else { - panic!("unable to create vertical panes") - } - } else { - panic!("unable to create footer / app") - }; - } - - #[allow(clippy::too_many_arguments)] - fn square_layout( - f: &mut Frame<'_>, - address: Address, - identified_contracts: &HashMap, - pc_ic_maps: &BTreeMap, - contracts_sources: &ContractSources, - debug_steps: &[DebugStep], - opcode_list: &[String], - current_step: usize, - call_kind: CallKind, - draw_memory: &mut DrawMemory, - stack_labels: bool, - mem_utf: bool, - show_shortcuts: bool, - ) { - let total_size = f.size(); - let h_height = if show_shortcuts { 4 } else { 0 }; - - // split in 2 vertically - - if let [app, footer] = Layout::default() - .direction(Direction::Vertical) - .constraints( - [Constraint::Ratio(100 - h_height, 100), Constraint::Ratio(h_height, 100)].as_ref(), - ) - .split(total_size)[..] - { - if let [left_pane, right_pane] = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)].as_ref()) - .split(app)[..] - { - // split right pane horizontally to construct stack and memory - if let [op_pane, src_pane] = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Ratio(1, 4), Constraint::Ratio(3, 4)].as_ref()) - .split(left_pane)[..] - { - if let [stack_pane, memory_pane] = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Ratio(1, 4), Constraint::Ratio(3, 4)].as_ref()) - .split(right_pane)[..] - { - if show_shortcuts { - Debugger::draw_footer(f, footer) - }; - Debugger::draw_src( - f, - address, - identified_contracts, - pc_ic_maps, - contracts_sources, - debug_steps[current_step].pc, - call_kind, - src_pane, - ); - Debugger::draw_op_list( - f, - address, - debug_steps, - opcode_list, - current_step, - draw_memory, - op_pane, - ); - Debugger::draw_stack( - f, - debug_steps, - current_step, - stack_pane, - stack_labels, - draw_memory, - ); - Debugger::draw_memory( - f, - debug_steps, - current_step, - memory_pane, - mem_utf, - draw_memory, - ); - } - } else { - panic!("Couldn't generate horizontal split layout 1:2."); - } - } else { - panic!("Couldn't generate vertical split layout 1:2."); - } - } else { - panic!("Couldn't generate application & footer") - } - } - - fn draw_footer(f: &mut Frame<'_>, area: Rect) { - let block_controls = Block::default(); - - let text_output = vec![Line::from(Span::styled( - "[q]: quit | [k/j]: prev/next op | [a/s]: prev/next jump | [c/C]: prev/next call | [g/G]: start/end", Style::default().add_modifier(Modifier::DIM))), -Line::from(Span::styled("[t]: stack labels | [m]: memory decoding | [shift + j/k]: scroll stack | [ctrl + j/k]: scroll memory | [']: goto breakpoint | [h] toggle help", Style::default().add_modifier(Modifier::DIM)))]; - - let paragraph = Paragraph::new(text_output) - .block(block_controls) - .alignment(Alignment::Center) - .wrap(Wrap { trim: false }); - - f.render_widget(paragraph, area); - } - - #[allow(clippy::too_many_arguments)] - fn draw_src( - f: &mut Frame<'_>, - address: Address, - identified_contracts: &HashMap, - pc_ic_maps: &BTreeMap, - contracts_sources: &ContractSources, - pc: usize, - call_kind: CallKind, - area: Rect, - ) { - let block_source_code = Block::default() - .title(match call_kind { - CallKind::Create | CallKind::Create2 => "Contract creation", - CallKind::Call => "Contract call", - CallKind::StaticCall => "Contract staticcall", - CallKind::CallCode => "Contract callcode", - CallKind::DelegateCall => "Contract delegatecall", - CallKind::AuthCall => "Contract authcall", - }) - .borders(Borders::ALL); - - let mut text_output: Text = Text::from(""); - - if let Some(contract_name) = identified_contracts.get(&address) { - if let Some(files_source_code) = contracts_sources.0.get(contract_name) { - let pc_ic_map = pc_ic_maps.get(contract_name); - // find the contract source with the correct source_element's file_id - if let Some((source_element, source_code)) = files_source_code.iter().find_map( - |(file_id, (source_code, contract_source))| { - // grab either the creation source map or runtime sourcemap - if let Some((Ok(source_map), ic)) = - if matches!(call_kind, CallKind::Create | CallKind::Create2) { - contract_source - .bytecode - .source_map() - .zip(pc_ic_map.and_then(|(c, _)| c.get(&pc))) - } else { - contract_source - .deployed_bytecode - .bytecode - .as_ref() - .expect("no bytecode") - .source_map() - .zip(pc_ic_map.and_then(|(_, r)| r.get(&pc))) - } - { - let source_element = source_map[*ic].clone(); - if let Some(index) = source_element.index { - if *file_id == index { - Some((source_element, source_code)) - } else { - None - } - } else { - None - } - } else { - None - } - }, - ) { - // we are handed a vector of SourceElements that give - // us a span of sourcecode that is currently being executed - // This includes an offset and length. This vector is in - // instruction pointer order, meaning the location of - // the instruction - sum(push_bytes[..pc]) - let offset = source_element.offset; - let len = source_element.length; - let max = source_code.len(); - - // split source into before, relevant, and after chunks - // split by line as well to do some formatting stuff - let mut before = source_code[..std::cmp::min(offset, max)] - .split_inclusive('\n') - .collect::>(); - let actual = source_code - [std::cmp::min(offset, max)..std::cmp::min(offset + len, max)] - .split_inclusive('\n') - .map(|s| s.to_string()) - .collect::>(); - let mut after = source_code[std::cmp::min(offset + len, max)..] - .split_inclusive('\n') - .collect::>(); - - let mut line_number = 0; - - let num_lines = before.len() + actual.len() + after.len(); - let height = area.height as usize; - let needed_highlight = actual.len(); - let mid_len = before.len() + actual.len(); - - // adjust what text we show of the source code - let (start_line, end_line) = if needed_highlight > height { - // highlighted section is more lines than we have avail - (before.len(), before.len() + needed_highlight) - } else if height > num_lines { - // we can fit entire source - (0, num_lines) - } else { - let remaining = height - needed_highlight; - let mut above = remaining / 2; - let mut below = remaining / 2; - if below > after.len() { - // unused space below the highlight - above += below - after.len(); - } else if above > before.len() { - // we have unused space above the highlight - below += above - before.len(); - } else { - // no unused space - } - - (before.len().saturating_sub(above), mid_len + below) - }; - - let max_line_num = num_lines.to_string().len(); - // We check if there is other text on the same line before the - // highlight starts - if let Some(last) = before.pop() { - if !last.ends_with('\n') { - before.iter().skip(start_line).for_each(|line| { - text_output.lines.push(Line::from(vec![ - Span::styled( - format!( - "{: >max_line_num$}", - line_number.to_string(), - max_line_num = max_line_num - ), - Style::default().fg(Color::Gray).bg(Color::DarkGray), - ), - Span::styled( - "\u{2800} ".to_string() + line, - Style::default().add_modifier(Modifier::DIM), - ), - ])); - line_number += 1; - }); - - text_output.lines.push(Line::from(vec![ - Span::styled( - format!( - "{: >max_line_num$}", - line_number.to_string(), - max_line_num = max_line_num - ), - Style::default() - .fg(Color::Cyan) - .bg(Color::DarkGray) - .add_modifier(Modifier::BOLD), - ), - Span::raw("\u{2800} "), - Span::raw(last), - Span::styled( - actual[0].to_string(), - Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD), - ), - ])); - line_number += 1; - - actual.iter().skip(1).for_each(|s| { - text_output.lines.push(Line::from(vec![ - Span::styled( - format!( - "{: >max_line_num$}", - line_number.to_string(), - max_line_num = max_line_num - ), - Style::default() - .fg(Color::Cyan) - .bg(Color::DarkGray) - .add_modifier(Modifier::BOLD), - ), - Span::raw("\u{2800} "), - Span::styled( - // this is a hack to add coloring - // because tui does weird trimming - if s.is_empty() || s == "\n" { - "\u{2800} \n".to_string() - } else { - s.to_string() - }, - Style::default() - .fg(Color::Cyan) - .add_modifier(Modifier::BOLD), - ), - ])); - line_number += 1; - }); - } else { - before.push(last); - before.iter().skip(start_line).for_each(|line| { - text_output.lines.push(Line::from(vec![ - Span::styled( - format!( - "{: >max_line_num$}", - line_number.to_string(), - max_line_num = max_line_num - ), - Style::default().fg(Color::Gray).bg(Color::DarkGray), - ), - Span::styled( - "\u{2800} ".to_string() + line, - Style::default().add_modifier(Modifier::DIM), - ), - ])); - - line_number += 1; - }); - actual.iter().for_each(|s| { - text_output.lines.push(Line::from(vec![ - Span::styled( - format!( - "{: >max_line_num$}", - line_number.to_string(), - max_line_num = max_line_num - ), - Style::default() - .fg(Color::Cyan) - .bg(Color::DarkGray) - .add_modifier(Modifier::BOLD), - ), - Span::raw("\u{2800} "), - Span::styled( - if s.is_empty() || s == "\n" { - "\u{2800} \n".to_string() - } else { - s.to_string() - }, - Style::default() - .fg(Color::Cyan) - .add_modifier(Modifier::BOLD), - ), - ])); - line_number += 1; - }); - } - } else { - actual.iter().for_each(|s| { - text_output.lines.push(Line::from(vec![ - Span::styled( - format!( - "{: >max_line_num$}", - line_number.to_string(), - max_line_num = max_line_num - ), - Style::default() - .fg(Color::Cyan) - .bg(Color::DarkGray) - .add_modifier(Modifier::BOLD), - ), - Span::raw("\u{2800} "), - Span::styled( - if s.is_empty() || s == "\n" { - "\u{2800} \n".to_string() - } else { - s.to_string() - }, - Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD), - ), - ])); - line_number += 1; - }); - } - - // fill in the rest of the line as unhighlighted - if let Some(last) = actual.last() { - if !last.ends_with('\n') { - if let Some(post) = after.pop_front() { - if let Some(last) = text_output.lines.last_mut() { - last.spans.push(Span::raw(post)); - } - } - } - } - - // add after highlighted text - while mid_len + after.len() > end_line { - after.pop_back(); - } - after.iter().for_each(|line| { - text_output.lines.push(Line::from(vec![ - Span::styled( - format!( - "{: >max_line_num$}", - line_number.to_string(), - max_line_num = max_line_num - ), - Style::default().fg(Color::Gray).bg(Color::DarkGray), - ), - Span::styled( - "\u{2800} ".to_string() + line, - Style::default().add_modifier(Modifier::DIM), - ), - ])); - line_number += 1; - }); - } else { - text_output.extend(Text::from("No sourcemap for contract")); - } - } else { - text_output - .extend(Text::from(format!("No srcmap index for contract {contract_name}"))); - } - } else { - text_output.extend(Text::from(format!("Unknown contract at address {address}"))); - } - - let paragraph = - Paragraph::new(text_output).block(block_source_code).wrap(Wrap { trim: false }); - f.render_widget(paragraph, area); - } - - /// Draw opcode list into main component - fn draw_op_list( - f: &mut Frame<'_>, - address: Address, - debug_steps: &[DebugStep], - opcode_list: &[String], - current_step: usize, - draw_memory: &mut DrawMemory, - area: Rect, - ) { - let block_source_code = Block::default() - .title(format!( - "Address: {} | PC: {} | Gas used in call: {}", - address, - if let Some(step) = debug_steps.get(current_step) { - step.pc.to_string() - } else { - "END".to_string() - }, - debug_steps[current_step].total_gas_used, - )) - .borders(Borders::ALL); - let mut text_output: Vec = Vec::new(); - - // Scroll: - // Focused line is line that should always be at the center of the screen. - let display_start; - - let height = area.height as i32; - let extra_top_lines = height / 2; - let prev_start = draw_memory.current_startline; - // Absolute minimum start line - let abs_min_start = 0; - // Adjust for weird scrolling for max top line - let abs_max_start = (opcode_list.len() as i32 - 1) - (height / 2); - // actual minimum start line - let mut min_start = - max(current_step as i32 - height + extra_top_lines, abs_min_start) as usize; - - // actual max start line - let mut max_start = - max(min(current_step as i32 - extra_top_lines, abs_max_start), abs_min_start) as usize; - - // Sometimes, towards end of file, maximum and minim lines have swapped values. Swap if the - // case - if min_start > max_start { - std::mem::swap(&mut min_start, &mut max_start); - } - - if prev_start < min_start { - display_start = min_start; - } else if prev_start > max_start { - display_start = max_start; - } else { - display_start = prev_start; - } - draw_memory.current_startline = display_start; - - let max_pc_len = - debug_steps.iter().fold(0, |max_val, val| val.pc.max(max_val)).to_string().len(); - - // Define closure that prints one more line of source code - let mut add_new_line = |line_number| { - let bg_color = if line_number == current_step { Color::DarkGray } else { Color::Reset }; - - // Format line number - let line_number_format = if line_number == current_step { - let step: &DebugStep = &debug_steps[line_number]; - format!("{:0>max_pc_len$x}|▶", step.pc) - } else if line_number < debug_steps.len() { - let step: &DebugStep = &debug_steps[line_number]; - format!("{:0>max_pc_len$x}| ", step.pc) - } else { - "END CALL".to_string() - }; - - if let Some(op) = opcode_list.get(line_number) { - text_output.push(Line::from(Span::styled( - format!("{line_number_format}{op}"), - Style::default().fg(Color::White).bg(bg_color), - ))); - } else { - text_output.push(Line::from(Span::styled( - line_number_format, - Style::default().fg(Color::White).bg(bg_color), - ))); - } - }; - for number in display_start..opcode_list.len() { - add_new_line(number); - } - // Add one more "phantom" line so we see line where current segment execution ends - add_new_line(opcode_list.len()); - let paragraph = - Paragraph::new(text_output).block(block_source_code).wrap(Wrap { trim: true }); - f.render_widget(paragraph, area); - } - - /// Draw the stack into the stack pane - fn draw_stack( - f: &mut Frame<'_>, - debug_steps: &[DebugStep], - current_step: usize, - area: Rect, - stack_labels: bool, - draw_memory: &DrawMemory, - ) { - let stack = &debug_steps[current_step].stack; - let stack_space = - Block::default().title(format!("Stack: {}", stack.len())).borders(Borders::ALL); - let min_len = usize::max(format!("{}", stack.len()).len(), 2); - - let params = if let Instruction::OpCode(op) = debug_steps[current_step].instruction { - OpcodeParam::of(op) - } else { - &[] - }; - - let text: Vec = stack - .iter() - .rev() - .enumerate() - .skip(draw_memory.current_stack_startline) - .map(|(i, stack_item)| { - let param = params.iter().find(|param| param.index == i); - let mut words: Vec = (0..32) - .rev() - .map(|i| stack_item.byte(i)) - .map(|byte| { - Span::styled( - format!("{byte:02x} "), - if param.is_some() { - Style::default().fg(Color::Cyan) - } else if byte == 0 { - // this improves compatibility across terminals by not combining - // color with DIM modifier - Style::default().add_modifier(Modifier::DIM) - } else { - Style::default().fg(Color::White) - }, - ) - }) - .collect(); - - if stack_labels { - if let Some(param) = param { - words.push(Span::raw(format!("| {}", param.name))); - } else { - words.push(Span::raw("| ".to_string())); - } - } - - let mut spans = vec![Span::styled( - format!("{i:0min_len$}| "), - Style::default().fg(Color::White), - )]; - spans.extend(words); - spans.push(Span::raw("\n")); - - Line::from(spans) - }) - .collect(); - - let paragraph = Paragraph::new(text).block(stack_space).wrap(Wrap { trim: true }); - f.render_widget(paragraph, area); - } - - /// The memory_access variable stores the index on the stack that indicates the memory - /// offset/size accessed by the given opcode: - /// (read memory offset, read memory size, write memory offset, write memory size) - /// >= 1: the stack index - /// 0: no memory access - /// -1: a fixed size of 32 bytes - /// -2: a fixed size of 1 byte - /// The return value is a tuple about accessed memory region by the given opcode: - /// (read memory offset, read memory size, write memory offset, write memory size) - fn get_memory_access( - op: u8, - stack: &Vec, - ) -> (Option, Option, Option, Option) { - let memory_access = match op { - opcode::KECCAK256 | opcode::RETURN | opcode::REVERT => (1, 2, 0, 0), - opcode::CALLDATACOPY | opcode::CODECOPY | opcode::RETURNDATACOPY => (0, 0, 1, 3), - opcode::EXTCODECOPY => (0, 0, 2, 4), - opcode::MLOAD => (1, -1, 0, 0), - opcode::MSTORE => (0, 0, 1, -1), - opcode::MSTORE8 => (0, 0, 1, -2), - opcode::LOG0 | opcode::LOG1 | opcode::LOG2 | opcode::LOG3 | opcode::LOG4 => { - (1, 2, 0, 0) - } - opcode::CREATE | opcode::CREATE2 => (2, 3, 0, 0), - opcode::CALL | opcode::CALLCODE => (4, 5, 0, 0), - opcode::DELEGATECALL | opcode::STATICCALL => (3, 4, 0, 0), - _ => Default::default(), - }; - - let stack_len = stack.len(); - let get_size = |stack_index| match stack_index { - -2 => Some(1), - -1 => Some(32), - 0 => None, - 1.. => Some(stack[stack_len - stack_index as usize].saturating_to()), - _ => panic!("invalid stack index"), - }; - - let (read_offset, read_size, write_offset, write_size) = ( - get_size(memory_access.0), - get_size(memory_access.1), - get_size(memory_access.2), - get_size(memory_access.3), - ); - (read_offset, read_size, write_offset, write_size) - } - - /// Draw memory in memory pane - fn draw_memory( - f: &mut Frame<'_>, - debug_steps: &[DebugStep], - current_step: usize, - area: Rect, - mem_utf8: bool, - draw_mem: &DrawMemory, - ) { - let memory = &debug_steps[current_step].memory; - let memory_space = Block::default() - .title(format!("Memory (max expansion: {} bytes)", memory.len())) - .borders(Borders::ALL); - let max_i = memory.len() / 32; - let min_len = format!("{:x}", max_i * 32).len(); - - // color memory region based on write/read - let mut offset: Option = None; - let mut size: Option = None; - let mut color = None; - if let Instruction::OpCode(op) = debug_steps[current_step].instruction { - let stack_len = debug_steps[current_step].stack.len(); - if stack_len > 0 { - let (read_offset, read_size, write_offset, write_size) = - Debugger::get_memory_access(op, &debug_steps[current_step].stack); - if read_offset.is_some() { - offset = read_offset; - size = read_size; - color = Some(Color::Cyan); - } else if write_offset.is_some() { - offset = write_offset; - size = write_size; - color = Some(Color::Red); - } - } - } - - // color word on previous write op - if current_step > 0 { - let prev_step = current_step - 1; - if let Instruction::OpCode(op) = debug_steps[prev_step].instruction { - let (_, _, write_offset, write_size) = - Debugger::get_memory_access(op, &debug_steps[prev_step].stack); - if write_offset.is_some() { - offset = write_offset; - size = write_size; - color = Some(Color::Green); - } - } - } - - let height = area.height as usize; - let end_line = draw_mem.current_mem_startline + height; - - let text: Vec = memory - .chunks(32) - .enumerate() - .skip(draw_mem.current_mem_startline) - .take_while(|(i, _)| i < &end_line) - .map(|(i, mem_word)| { - let words: Vec = mem_word - .iter() - .enumerate() - .map(|(j, byte)| { - Span::styled( - format!("{byte:02x} "), - if let (Some(offset), Some(size), Some(color)) = (offset, size, color) { - if (i == offset / 32 && j >= offset % 32) || - (i > offset / 32 && i < (offset + size - 1) / 32) || - (i == (offset + size - 1) / 32 && - j <= (offset + size - 1) % 32) - { - // [offset, offset + size] is the memory region to be - // colored. - // If a byte at row i and column j in the memory panel - // falls in this region, set the color. - Style::default().fg(color) - } else if *byte == 0 { - Style::default().add_modifier(Modifier::DIM) - } else { - Style::default().fg(Color::White) - } - } else if *byte == 0 { - Style::default().add_modifier(Modifier::DIM) - } else { - Style::default().fg(Color::White) - }, - ) - }) - .collect(); - - let mut spans = vec![Span::styled( - format!("{:0min_len$x}| ", i * 32), - Style::default().fg(Color::White), - )]; - spans.extend(words); - - if mem_utf8 { - let chars: Vec = mem_word - .chunks(4) - .map(|utf| { - if let Ok(utf_str) = std::str::from_utf8(utf) { - Span::raw(utf_str.replace(char::from(0), ".")) - } else { - Span::raw(".") - } - }) - .collect(); - spans.push(Span::raw("|")); - spans.extend(chars); - } - - spans.push(Span::raw("\n")); - - Line::from(spans) - }) - .collect(); - let paragraph = Paragraph::new(text).block(memory_space).wrap(Wrap { trim: true }); - f.render_widget(paragraph, area); - } -} - -/// Message sent from the event listener to the main thread. -enum Interrupt { - KeyEvent(KeyEvent), - MouseEvent(MouseEvent), - IntervalElapsed, -} - -impl Interrupt { - fn char_press(&self) -> Option { - match *self { - Self::KeyEvent(KeyEvent { code: KeyCode::Char(code), .. }) - if code.is_alphanumeric() || code == '\'' => - { - Some(code) - } - _ => None, - } - } -} - -/// This is currently used to remember last scroll position so screen doesn't wiggle as much. -#[derive(Default)] -struct DrawMemory { - current_startline: usize, - inner_call_index: usize, - current_mem_startline: usize, - current_stack_startline: usize, -} - -/// Handles terminal state. `restore` should be called before drop to handle errors. -#[must_use] -struct DebuggerGuard<'a>(&'a mut Debugger); - -impl<'a> DebuggerGuard<'a> { - fn setup(dbg: &'a mut Debugger) -> Result { - let this = Self(dbg); - enable_raw_mode()?; - execute!(*this.0.terminal.backend_mut(), EnterAlternateScreen, EnableMouseCapture)?; - this.0.terminal.hide_cursor()?; - Ok(this) - } - - fn restore(&mut self) -> Result<()> { - disable_raw_mode()?; - execute!(*self.0.terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture)?; - self.0.terminal.show_cursor()?; - Ok(()) - } -} - -impl Drop for DebuggerGuard<'_> { - #[inline] - fn drop(&mut self) { - let _ = self.restore(); - } -} - -fn event_listener(tx: mpsc::Sender) { - // This is the recommend tick rate from `ratatui`, based on their examples - let tick_rate = Duration::from_millis(200); - - let mut last_tick = Instant::now(); - loop { - // Poll events since last tick - if last tick is greater than tick_rate, we - // demand immediate availability of the event. This may affect interactivity, - // but I'm not sure as it is hard to test. - if event::poll(tick_rate.saturating_sub(last_tick.elapsed())).unwrap() { - let event = event::read().unwrap(); - match event { - Event::Key(key) => { - if tx.send(Interrupt::KeyEvent(key)).is_err() { - return - } - } - Event::Mouse(mouse) => { - if tx.send(Interrupt::MouseEvent(mouse)).is_err() { - return - } - } - Event::FocusGained | Event::FocusLost | Event::Paste(_) | Event::Resize(..) => {} - } - } - - // Force update if time has passed - if last_tick.elapsed() > tick_rate { - if tx.send(Interrupt::IntervalElapsed).is_err() { - return - } - last_tick = Instant::now(); - } - } -} diff --git a/crates/debugger/src/builder.rs b/crates/debugger/src/tui/builder.rs similarity index 89% rename from crates/debugger/src/builder.rs rename to crates/debugger/src/tui/builder.rs index ff2721ec7ce7b..6289b0b8814fa 100644 --- a/crates/debugger/src/builder.rs +++ b/crates/debugger/src/tui/builder.rs @@ -1,11 +1,9 @@ +//! TUI debugger builder. + use crate::Debugger; use alloy_primitives::Address; -use eyre::Result; use foundry_common::{compile::ContractSources, evm::Breakpoints, get_contract_name}; -use foundry_evm_core::{ - debug::{DebugArena, DebugStep}, - utils::CallKind, -}; +use foundry_evm_core::debug::{DebugArena, DebugNodeFlat}; use foundry_evm_traces::CallTraceDecoder; use std::collections::HashMap; @@ -14,7 +12,7 @@ use std::collections::HashMap; #[must_use = "builders do nothing unless you call `build` on them"] pub struct DebuggerBuilder { /// Debug traces returned from the EVM execution. - debug_arena: Vec<(Address, Vec, CallKind)>, + debug_arena: Vec, /// Identified contracts. identified_contracts: HashMap, /// Map of source files. @@ -88,8 +86,8 @@ impl DebuggerBuilder { /// Builds the debugger. #[inline] - pub fn build(self) -> Result { + pub fn build(self) -> Debugger { let Self { debug_arena, identified_contracts, sources, breakpoints } = self; - Debugger::new(debug_arena, 0, identified_contracts, sources, breakpoints) + Debugger::new(debug_arena, identified_contracts, sources, breakpoints) } } diff --git a/crates/debugger/src/tui/context.rs b/crates/debugger/src/tui/context.rs new file mode 100644 index 0000000000000..58a8e57f42751 --- /dev/null +++ b/crates/debugger/src/tui/context.rs @@ -0,0 +1,347 @@ +//! Debugger context and event handler implementation. + +use crate::{Debugger, ExitReason}; +use alloy_primitives::Address; +use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind}; +use foundry_evm_core::debug::{DebugNodeFlat, DebugStep}; +use revm_inspectors::tracing::types::CallKind; +use std::ops::ControlFlow; + +/// This is currently used to remember last scroll position so screen doesn't wiggle as much. +#[derive(Default)] +pub(crate) struct DrawMemory { + pub(crate) inner_call_index: usize, + pub(crate) current_buf_startline: usize, + pub(crate) current_stack_startline: usize, +} + +/// Used to keep track of which buffer is currently active to be drawn by the debugger. +#[derive(Debug, PartialEq)] +pub(crate) enum BufferKind { + Memory, + Calldata, + Returndata, +} + +impl BufferKind { + /// Helper to cycle through the active buffers. + pub(crate) fn next(&self) -> Self { + match self { + BufferKind::Memory => BufferKind::Calldata, + BufferKind::Calldata => BufferKind::Returndata, + BufferKind::Returndata => BufferKind::Memory, + } + } + + /// Helper to format the title of the active buffer pane + pub(crate) fn title(&self, size: usize) -> String { + match self { + BufferKind::Memory => format!("Memory (max expansion: {} bytes)", size), + BufferKind::Calldata => format!("Calldata (size: {} bytes)", size), + BufferKind::Returndata => format!("Returndata (size: {} bytes)", size), + } + } +} + +pub(crate) struct DebuggerContext<'a> { + pub(crate) debugger: &'a mut Debugger, + + /// Buffer for keys prior to execution, i.e. '10' + 'k' => move up 10 operations. + pub(crate) key_buffer: String, + /// Current step in the debug steps. + pub(crate) current_step: usize, + pub(crate) draw_memory: DrawMemory, + pub(crate) opcode_list: Vec, + pub(crate) last_index: usize, + + pub(crate) stack_labels: bool, + /// Whether to decode active buffer as utf8 or not. + pub(crate) buf_utf: bool, + pub(crate) show_shortcuts: bool, + /// The currently active buffer (memory, calldata, returndata) to be drawn. + pub(crate) active_buffer: BufferKind, +} + +impl<'a> DebuggerContext<'a> { + pub(crate) fn new(debugger: &'a mut Debugger) -> Self { + DebuggerContext { + debugger, + + key_buffer: String::with_capacity(64), + current_step: 0, + draw_memory: DrawMemory::default(), + opcode_list: Vec::new(), + last_index: 0, + + stack_labels: false, + buf_utf: false, + show_shortcuts: true, + active_buffer: BufferKind::Memory, + } + } + + pub(crate) fn init(&mut self) { + self.gen_opcode_list(); + } + + pub(crate) fn debug_arena(&self) -> &[DebugNodeFlat] { + &self.debugger.debug_arena + } + + pub(crate) fn debug_call(&self) -> &DebugNodeFlat { + &self.debug_arena()[self.draw_memory.inner_call_index] + } + + /// Returns the current call address. + pub(crate) fn address(&self) -> &Address { + &self.debug_call().address + } + + /// Returns the current call kind. + pub(crate) fn call_kind(&self) -> CallKind { + self.debug_call().kind + } + + /// Returns the current debug steps. + pub(crate) fn debug_steps(&self) -> &[DebugStep] { + &self.debug_call().steps + } + + /// Returns the current debug step. + pub(crate) fn current_step(&self) -> &DebugStep { + &self.debug_steps()[self.current_step] + } + + fn gen_opcode_list(&mut self) { + self.opcode_list.clear(); + let debug_steps = &self.debugger.debug_arena[self.draw_memory.inner_call_index].steps; + self.opcode_list.extend(debug_steps.iter().map(DebugStep::pretty_opcode)); + } + + fn gen_opcode_list_if_necessary(&mut self) { + if self.last_index != self.draw_memory.inner_call_index { + self.gen_opcode_list(); + self.last_index = self.draw_memory.inner_call_index; + } + } + + fn active_buffer(&self) -> &[u8] { + match self.active_buffer { + BufferKind::Memory => &self.current_step().memory, + BufferKind::Calldata => &self.current_step().calldata, + BufferKind::Returndata => &self.current_step().returndata, + } + } +} + +impl DebuggerContext<'_> { + pub(crate) fn handle_event(&mut self, event: Event) -> ControlFlow { + let ret = match event { + Event::Key(event) => self.handle_key_event(event), + Event::Mouse(event) => self.handle_mouse_event(event), + _ => ControlFlow::Continue(()), + }; + // Generate the list after the event has been handled. + self.gen_opcode_list_if_necessary(); + ret + } + + fn handle_key_event(&mut self, event: KeyEvent) -> ControlFlow { + // Breakpoints + if let KeyCode::Char(c) = event.code { + if c.is_alphabetic() && self.key_buffer.starts_with('\'') { + self.handle_breakpoint(c); + return ControlFlow::Continue(()); + } + } + + let control = event.modifiers.contains(KeyModifiers::CONTROL); + + match event.code { + // Exit + KeyCode::Char('q') => return ControlFlow::Break(ExitReason::CharExit), + + // Scroll up the memory buffer + KeyCode::Char('k') | KeyCode::Up if control => self.repeat(|this| { + this.draw_memory.current_buf_startline = + this.draw_memory.current_buf_startline.saturating_sub(1); + }), + // Scroll down the memory buffer + KeyCode::Char('j') | KeyCode::Down if control => self.repeat(|this| { + let max_buf = (this.active_buffer().len() / 32).saturating_sub(1); + if this.draw_memory.current_buf_startline < max_buf { + this.draw_memory.current_buf_startline += 1; + } + }), + + // Move up + KeyCode::Char('k') | KeyCode::Up => self.repeat(Self::step_back), + // Move down + KeyCode::Char('j') | KeyCode::Down => self.repeat(Self::step), + + // Scroll up the stack + KeyCode::Char('K') => self.repeat(|this| { + this.draw_memory.current_stack_startline = + this.draw_memory.current_stack_startline.saturating_sub(1); + }), + // Scroll down the stack + KeyCode::Char('J') => self.repeat(|this| { + let max_stack = this.current_step().stack.len().saturating_sub(1); + if this.draw_memory.current_stack_startline < max_stack { + this.draw_memory.current_stack_startline += 1; + } + }), + + // Cycle buffers + KeyCode::Char('b') => { + self.active_buffer = self.active_buffer.next(); + self.draw_memory.current_buf_startline = 0; + } + + // Go to top of file + KeyCode::Char('g') => { + self.draw_memory.inner_call_index = 0; + self.current_step = 0; + } + + // Go to bottom of file + KeyCode::Char('G') => { + self.draw_memory.inner_call_index = self.debug_arena().len() - 1; + self.current_step = self.n_steps() - 1; + } + + // Go to previous call + KeyCode::Char('c') => { + self.draw_memory.inner_call_index = + self.draw_memory.inner_call_index.saturating_sub(1); + self.current_step = self.n_steps() - 1; + } + + // Go to next call + KeyCode::Char('C') => { + if self.debug_arena().len() > self.draw_memory.inner_call_index + 1 { + self.draw_memory.inner_call_index += 1; + self.current_step = 0; + } + } + + // Step forward + KeyCode::Char('s') => self.repeat(|this| { + let remaining_ops = &this.opcode_list[this.current_step..]; + if let Some((i, _)) = remaining_ops.iter().enumerate().skip(1).find(|&(i, op)| { + let prev = &remaining_ops[i - 1]; + let prev_is_jump = prev.contains("JUMP") && prev != "JUMPDEST"; + let is_jumpdest = op == "JUMPDEST"; + prev_is_jump && is_jumpdest + }) { + this.current_step += i; + } + }), + + // Step backwards + KeyCode::Char('a') => self.repeat(|this| { + let ops = &this.opcode_list[..this.current_step]; + this.current_step = ops + .iter() + .enumerate() + .skip(1) + .rev() + .find(|&(i, op)| { + let prev = &ops[i - 1]; + let prev_is_jump = prev.contains("JUMP") && prev != "JUMPDEST"; + let is_jumpdest = op == "JUMPDEST"; + prev_is_jump && is_jumpdest + }) + .map(|(i, _)| i) + .unwrap_or_default(); + }), + + // Toggle stack labels + KeyCode::Char('t') => self.stack_labels = !self.stack_labels, + + // Toggle memory UTF-8 decoding + KeyCode::Char('m') => self.buf_utf = !self.buf_utf, + + // Toggle help notice + KeyCode::Char('h') => self.show_shortcuts = !self.show_shortcuts, + + // Numbers for repeating commands or breakpoints + KeyCode::Char( + other @ ('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '\''), + ) => { + // Early return to not clear the buffer. + self.key_buffer.push(other); + return ControlFlow::Continue(()); + } + + // Unknown/unhandled key code + _ => {} + }; + + self.key_buffer.clear(); + ControlFlow::Continue(()) + } + + fn handle_breakpoint(&mut self, c: char) { + // Find the location of the called breakpoint in the whole debug arena (at this address with + // this pc) + if let Some((caller, pc)) = self.debugger.breakpoints.get(&c) { + for (i, node) in self.debug_arena().iter().enumerate() { + if node.address == *caller { + if let Some(step) = node.steps.iter().position(|step| step.pc == *pc) { + self.draw_memory.inner_call_index = i; + self.current_step = step; + break; + } + } + } + } + self.key_buffer.clear(); + } + + fn handle_mouse_event(&mut self, event: MouseEvent) -> ControlFlow { + match event.kind { + MouseEventKind::ScrollUp => self.step_back(), + MouseEventKind::ScrollDown => self.step(), + _ => {} + } + + ControlFlow::Continue(()) + } + + fn step_back(&mut self) { + if self.current_step > 0 { + self.current_step -= 1; + } else if self.draw_memory.inner_call_index > 0 { + self.draw_memory.inner_call_index -= 1; + self.current_step = self.n_steps() - 1; + } + } + + fn step(&mut self) { + if self.current_step < self.n_steps() - 1 { + self.current_step += 1; + } else if self.draw_memory.inner_call_index < self.debug_arena().len() - 1 { + self.draw_memory.inner_call_index += 1; + self.current_step = 0; + } + } + + /// Calls a closure `f` the number of times specified in the key buffer, and at least once. + fn repeat(&mut self, mut f: impl FnMut(&mut Self)) { + for _ in 0..buffer_as_number(&self.key_buffer) { + f(self); + } + } + + fn n_steps(&self) -> usize { + self.debug_steps().len() + } +} + +/// Grab number from buffer. Used for something like '10k' to move up 10 operations +fn buffer_as_number(s: &str) -> usize { + const MIN: usize = 1; + const MAX: usize = 100_000; + s.parse().unwrap_or(MIN).clamp(MIN, MAX) +} diff --git a/crates/debugger/src/tui/draw.rs b/crates/debugger/src/tui/draw.rs new file mode 100644 index 0000000000000..a486ec220c19d --- /dev/null +++ b/crates/debugger/src/tui/draw.rs @@ -0,0 +1,761 @@ +//! TUI draw implementation. + +use super::context::{BufferKind, DebuggerContext}; +use crate::op::OpcodeParam; +use alloy_primitives::U256; +use foundry_compilers::sourcemap::SourceElement; +use ratatui::{ + layout::{Alignment, Constraint, Direction, Layout, Rect}, + style::{Color, Modifier, Style}, + terminal::Frame, + text::{Line, Span, Text}, + widgets::{Block, Borders, List, ListItem, ListState, Paragraph, Wrap}, +}; +use revm::interpreter::opcode; +use revm_inspectors::tracing::types::CallKind; +use std::{collections::VecDeque, fmt::Write, io}; + +impl DebuggerContext<'_> { + /// Draws the TUI layout and subcomponents to the given terminal. + pub(crate) fn draw(&self, terminal: &mut super::DebuggerTerminal) -> io::Result<()> { + terminal.draw(|f| self.draw_layout(f)).map(drop) + } + + #[inline] + fn draw_layout(&self, f: &mut Frame<'_>) { + // We need 100 columns to display a 32 byte word in the memory and stack panes. + let size = f.size(); + let min_width = 100; + let min_height = 16; + if size.width < min_width || size.height < min_height { + self.size_too_small(f, min_width, min_height); + return; + } + + // The horizontal layout draws these panes at 50% width. + let min_column_width_for_horizontal = 200; + if size.width >= min_column_width_for_horizontal { + self.horizontal_layout(f); + } else { + self.vertical_layout(f); + } + } + + fn size_too_small(&self, f: &mut Frame<'_>, min_width: u16, min_height: u16) { + let mut lines = Vec::with_capacity(4); + + let l1 = "Terminal size too small:"; + lines.push(Line::from(l1)); + + let size = f.size(); + let width_color = if size.width >= min_width { Color::Green } else { Color::Red }; + let height_color = if size.height >= min_height { Color::Green } else { Color::Red }; + let l2 = vec![ + Span::raw("Width = "), + Span::styled(size.width.to_string(), Style::new().fg(width_color)), + Span::raw(" Height = "), + Span::styled(size.height.to_string(), Style::new().fg(height_color)), + ]; + lines.push(Line::from(l2)); + + let l3 = "Needed for current config:"; + lines.push(Line::from(l3)); + let l4 = format!("Width = {min_width} Height = {min_height}"); + lines.push(Line::from(l4)); + + let paragraph = + Paragraph::new(lines).alignment(Alignment::Center).wrap(Wrap { trim: true }); + f.render_widget(paragraph, size) + } + + /// Draws the layout in vertical mode. + /// + /// ```text + /// |-----------------------------| + /// | op | + /// |-----------------------------| + /// | stack | + /// |-----------------------------| + /// | buf | + /// |-----------------------------| + /// | | + /// | src | + /// | | + /// |-----------------------------| + /// ``` + fn vertical_layout(&self, f: &mut Frame<'_>) { + let area = f.size(); + let h_height = if self.show_shortcuts { 4 } else { 0 }; + + // NOTE: `Layout::split` always returns a slice of the same length as the number of + // constraints, so the `else` branch is unreachable. + + // Split off footer. + let [app, footer] = Layout::new( + Direction::Vertical, + [Constraint::Ratio(100 - h_height, 100), Constraint::Ratio(h_height, 100)], + ) + .split(area)[..] else { + unreachable!() + }; + + // Split the app in 4 vertically to construct all the panes. + let [op_pane, stack_pane, memory_pane, src_pane] = Layout::new( + Direction::Vertical, + [ + Constraint::Ratio(1, 6), + Constraint::Ratio(1, 6), + Constraint::Ratio(1, 6), + Constraint::Ratio(3, 6), + ], + ) + .split(app)[..] else { + unreachable!() + }; + + if self.show_shortcuts { + self.draw_footer(f, footer); + } + self.draw_src(f, src_pane); + self.draw_op_list(f, op_pane); + self.draw_stack(f, stack_pane); + self.draw_buffer(f, memory_pane); + } + + /// Draws the layout in horizontal mode. + /// + /// ```text + /// |-----------------|-----------| + /// | op | stack | + /// |-----------------|-----------| + /// | | | + /// | src | buf | + /// | | | + /// |-----------------|-----------| + /// ``` + fn horizontal_layout(&self, f: &mut Frame<'_>) { + let area = f.size(); + let h_height = if self.show_shortcuts { 4 } else { 0 }; + + // Split off footer. + let [app, footer] = Layout::new( + Direction::Vertical, + [Constraint::Ratio(100 - h_height, 100), Constraint::Ratio(h_height, 100)], + ) + .split(area)[..] else { + unreachable!() + }; + + // Split app in 2 horizontally. + let [app_left, app_right] = + Layout::new(Direction::Horizontal, [Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]) + .split(app)[..] + else { + unreachable!() + }; + + // Split left pane in 2 vertically to opcode list and source. + let [op_pane, src_pane] = + Layout::new(Direction::Vertical, [Constraint::Ratio(1, 4), Constraint::Ratio(3, 4)]) + .split(app_left)[..] + else { + unreachable!() + }; + + // Split right pane horizontally to construct stack and memory. + let [stack_pane, memory_pane] = + Layout::new(Direction::Vertical, [Constraint::Ratio(1, 4), Constraint::Ratio(3, 4)]) + .split(app_right)[..] + else { + unreachable!() + }; + + if self.show_shortcuts { + self.draw_footer(f, footer); + } + self.draw_src(f, src_pane); + self.draw_op_list(f, op_pane); + self.draw_stack(f, stack_pane); + self.draw_buffer(f, memory_pane); + } + + fn draw_footer(&self, f: &mut Frame<'_>, area: Rect) { + let l1 = "[q]: quit | [k/j]: prev/next op | [a/s]: prev/next jump | [c/C]: prev/next call | [g/G]: start/end | [b]: cycle memory/calldata/returndata buffers"; + let l2 = "[t]: stack labels | [m]: buffer decoding | [shift + j/k]: scroll stack | [ctrl + j/k]: scroll buffer | [']: goto breakpoint | [h] toggle help"; + let dimmed = Style::new().add_modifier(Modifier::DIM); + let lines = + vec![Line::from(Span::styled(l1, dimmed)), Line::from(Span::styled(l2, dimmed))]; + let paragraph = + Paragraph::new(lines).alignment(Alignment::Center).wrap(Wrap { trim: false }); + f.render_widget(paragraph, area); + } + + fn draw_src(&self, f: &mut Frame<'_>, area: Rect) { + let text_output = self.src_text(area); + let title = match self.call_kind() { + CallKind::AuthCall => "Auth call", + CallKind::Create | CallKind::Create2 => "Contract creation", + CallKind::Call => "Contract call", + CallKind::StaticCall => "Contract staticcall", + CallKind::CallCode => "Contract callcode", + CallKind::DelegateCall => "Contract delegatecall", + }; + let block = Block::default().title(title).borders(Borders::ALL); + let paragraph = Paragraph::new(text_output).block(block).wrap(Wrap { trim: false }); + f.render_widget(paragraph, area); + } + + fn src_text(&self, area: Rect) -> Text<'_> { + let (source_element, source_code) = match self.src_map() { + Ok(r) => r, + Err(e) => return Text::from(e), + }; + + // We are handed a vector of SourceElements that give us a span of sourcecode that is + // currently being executed. This includes an offset and length. + // This vector is in instruction pointer order, meaning the location of the instruction + // minus `sum(push_bytes[..pc])`. + let offset = source_element.offset; + let len = source_element.length; + let max = source_code.len(); + + // Split source into before, relevant, and after chunks, split by line, for formatting. + let actual_start = offset.min(max); + let actual_end = (offset + len).min(max); + + let mut before: Vec<_> = source_code[..actual_start].split_inclusive('\n').collect(); + let actual: Vec<_> = source_code[actual_start..actual_end].split_inclusive('\n').collect(); + let mut after: VecDeque<_> = source_code[actual_end..].split_inclusive('\n').collect(); + + let num_lines = before.len() + actual.len() + after.len(); + let height = area.height as usize; + let needed_highlight = actual.len(); + let mid_len = before.len() + actual.len(); + + // adjust what text we show of the source code + let (start_line, end_line) = if needed_highlight > height { + // highlighted section is more lines than we have available + let start_line = before.len().saturating_sub(1); + (start_line, before.len() + needed_highlight) + } else if height > num_lines { + // we can fit entire source + (0, num_lines) + } else { + let remaining = height - needed_highlight; + let mut above = remaining / 2; + let mut below = remaining / 2; + if below > after.len() { + // unused space below the highlight + above += below - after.len(); + } else if above > before.len() { + // we have unused space above the highlight + below += above - before.len(); + } else { + // no unused space + } + + // since above is subtracted from before.len(), and the resulting + // start_line is used to index into before, above must be at least + // 1 to avoid out-of-range accesses. + if above == 0 { + above = 1; + } + (before.len().saturating_sub(above), mid_len + below) + }; + + // Unhighlighted line number: gray. + let u_num = Style::new().fg(Color::Gray); + // Unhighlighted text: default, dimmed. + let u_text = Style::new().add_modifier(Modifier::DIM); + // Highlighted line number: cyan. + let h_num = Style::new().fg(Color::Cyan); + // Highlighted text: cyan, bold. + let h_text = Style::new().fg(Color::Cyan).add_modifier(Modifier::BOLD); + + let mut lines = SourceLines::new(decimal_digits(num_lines)); + + // We check if there is other text on the same line before the highlight starts. + if let Some(last) = before.pop() { + let last_has_nl = last.ends_with('\n'); + + if last_has_nl { + before.push(last); + } + for line in &before[start_line..] { + lines.push(u_num, line, u_text); + } + + let first = if !last_has_nl { + lines.push_raw(h_num, &[Span::raw(last), Span::styled(actual[0], h_text)]); + 1 + } else { + 0 + }; + + // Skip the first line if it has already been handled above. + for line in &actual[first..] { + lines.push(h_num, line, h_text); + } + } else { + // No text before the current line. + for line in &actual { + lines.push(h_num, line, h_text); + } + } + + // Fill in the rest of the line as unhighlighted. + if let Some(last) = actual.last() { + if !last.ends_with('\n') { + if let Some(post) = after.pop_front() { + if let Some(last) = lines.lines.last_mut() { + last.spans.push(Span::raw(post)); + } + } + } + } + + // Add after highlighted text. + while mid_len + after.len() > end_line { + after.pop_back(); + } + for line in after { + lines.push(u_num, line, u_text); + } + + Text::from(lines.lines) + } + + fn src_map(&self) -> Result<(SourceElement, &str), String> { + let address = self.address(); + let Some(contract_name) = self.debugger.identified_contracts.get(address) else { + return Err(format!("Unknown contract at address {address}")); + }; + + let Some(mut files_source_code) = + self.debugger.contracts_sources.get_sources(contract_name) + else { + return Err(format!("No source map index for contract {contract_name}")); + }; + + let Some((create_map, rt_map)) = self.debugger.pc_ic_maps.get(contract_name) else { + return Err(format!("No PC-IC maps for contract {contract_name}")); + }; + + let is_create = matches!(self.call_kind(), CallKind::Create | CallKind::Create2); + let pc = self.current_step().pc; + let Some((source_element, source_code)) = + files_source_code.find_map(|(file_id, source_code, contract_source)| { + let bytecode = if is_create { + &contract_source.bytecode + } else { + contract_source.deployed_bytecode.bytecode.as_ref()? + }; + let mut source_map = bytecode.source_map()?.ok()?; + + let pc_ic_map = if is_create { create_map } else { rt_map }; + let ic = pc_ic_map.get(pc)?; + let source_element = source_map.swap_remove(ic); + // if the source element has an index, find the sourcemap for that index + source_element + .index + .and_then(|index| + // if index matches current file_id, return current source code + (index == file_id).then(|| (source_element.clone(), source_code))) + .or_else(|| { + // otherwise find the source code for the element's index + self.debugger + .contracts_sources + .sources_by_id + .get(&(source_element.index?)) + .map(|source_code| (source_element.clone(), source_code.as_ref())) + }) + }) + else { + return Err(format!("No source map for contract {contract_name}")); + }; + + Ok((source_element, source_code)) + } + + fn draw_op_list(&self, f: &mut Frame<'_>, area: Rect) { + let debug_steps = self.debug_steps(); + let max_pc = debug_steps.iter().map(|step| step.pc).max().unwrap_or(0); + let max_pc_len = hex_digits(max_pc); + + let items = debug_steps + .iter() + .enumerate() + .map(|(i, step)| { + let mut content = String::with_capacity(64); + write!(content, "{:0>max_pc_len$x}|", step.pc).unwrap(); + if let Some(op) = self.opcode_list.get(i) { + content.push_str(op); + } + ListItem::new(Span::styled(content, Style::new().fg(Color::White))) + }) + .collect::>(); + + let title = format!( + "Address: {} | PC: {} | Gas used in call: {}", + self.address(), + self.current_step().pc, + self.current_step().total_gas_used, + ); + let block = Block::default().title(title).borders(Borders::ALL); + let list = List::new(items) + .block(block) + .highlight_symbol("▶") + .highlight_style(Style::new().fg(Color::White).bg(Color::DarkGray)) + .scroll_padding(1); + let mut state = ListState::default().with_selected(Some(self.current_step)); + f.render_stateful_widget(list, area, &mut state); + } + + fn draw_stack(&self, f: &mut Frame<'_>, area: Rect) { + let step = self.current_step(); + let stack = &step.stack; + + let min_len = decimal_digits(stack.len()).max(2); + + let params = OpcodeParam::of(step.instruction); + + let text: Vec = stack + .iter() + .rev() + .enumerate() + .skip(self.draw_memory.current_stack_startline) + .map(|(i, stack_item)| { + let param = params.iter().find(|param| param.index == i); + + let mut spans = Vec::with_capacity(1 + 32 * 2 + 3); + + // Stack index. + spans.push(Span::styled(format!("{i:0min_len$}| "), Style::new().fg(Color::White))); + + // Item hex bytes. + hex_bytes_spans(&stack_item.to_be_bytes::<32>(), &mut spans, |_, _| { + if param.is_some() { + Style::new().fg(Color::Cyan) + } else { + Style::new().fg(Color::White) + } + }); + + if self.stack_labels { + if let Some(param) = param { + spans.push(Span::raw("| ")); + spans.push(Span::raw(param.name)); + } + } + + spans.push(Span::raw("\n")); + + Line::from(spans) + }) + .collect(); + + let title = format!("Stack: {}", stack.len()); + let block = Block::default().title(title).borders(Borders::ALL); + let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true }); + f.render_widget(paragraph, area); + } + + fn draw_buffer(&self, f: &mut Frame<'_>, area: Rect) { + let step = self.current_step(); + let buf = match self.active_buffer { + BufferKind::Memory => step.memory.as_ref(), + BufferKind::Calldata => step.calldata.as_ref(), + BufferKind::Returndata => step.returndata.as_ref(), + }; + + let min_len = hex_digits(buf.len()); + + // Color memory region based on read/write. + let mut offset = None; + let mut size = None; + let mut write_offset = None; + let mut write_size = None; + let mut color = None; + let stack_len = step.stack.len(); + if stack_len > 0 { + if let Some(accesses) = get_buffer_accesses(step.instruction, &step.stack) { + if let Some(read_access) = accesses.read { + offset = Some(read_access.1.offset); + size = Some(read_access.1.size); + color = Some(Color::Cyan); + } + if let Some(write_access) = accesses.write { + if self.active_buffer == BufferKind::Memory { + write_offset = Some(write_access.offset); + write_size = Some(write_access.size); + } + } + } + } + + // color word on previous write op + // TODO: technically it's possible for this to conflict with the current op, ie, with + // subsequent MCOPYs, but solc can't seem to generate that code even with high optimizer + // settings + if self.current_step > 0 { + let prev_step = self.current_step - 1; + let prev_step = &self.debug_steps()[prev_step]; + if let Some(write_access) = + get_buffer_accesses(prev_step.instruction, &prev_step.stack).and_then(|a| a.write) + { + if self.active_buffer == BufferKind::Memory { + offset = Some(write_access.offset); + size = Some(write_access.size); + color = Some(Color::Green); + } + } + } + + let height = area.height as usize; + let end_line = self.draw_memory.current_buf_startline + height; + + let text: Vec = buf + .chunks(32) + .enumerate() + .skip(self.draw_memory.current_buf_startline) + .take_while(|(i, _)| *i < end_line) + .map(|(i, buf_word)| { + let mut spans = Vec::with_capacity(1 + 32 * 2 + 1 + 32 / 4 + 1); + + // Buffer index. + spans.push(Span::styled( + format!("{:0min_len$x}| ", i * 32), + Style::new().fg(Color::White), + )); + + // Word hex bytes. + hex_bytes_spans(buf_word, &mut spans, |j, _| { + let mut byte_color = Color::White; + let mut end = None; + let idx = i * 32 + j; + if let (Some(offset), Some(size), Some(color)) = (offset, size, color) { + end = Some(offset + size); + if (offset..offset + size).contains(&idx) { + // [offset, offset + size] is the memory region to be colored. + // If a byte at row i and column j in the memory panel + // falls in this region, set the color. + byte_color = color; + } + } + if let (Some(write_offset), Some(write_size)) = (write_offset, write_size) { + // check for overlap with read region + let write_end = write_offset + write_size; + if let Some(read_end) = end { + let read_start = offset.unwrap(); + if (write_offset..write_end).contains(&read_end) { + // if it contains end, start from write_start up to read_end + if (write_offset..read_end).contains(&idx) { + return Style::new().fg(Color::Yellow); + } + } else if (write_offset..write_end).contains(&read_start) { + // otherwise if it contains read start, start from read_start up to + // write_end + if (read_start..write_end).contains(&idx) { + return Style::new().fg(Color::Yellow); + } + } + } + if (write_offset..write_end).contains(&idx) { + byte_color = Color::Red; + } + } + + Style::new().fg(byte_color) + }); + + if self.buf_utf { + spans.push(Span::raw("|")); + for utf in buf_word.chunks(4) { + if let Ok(utf_str) = std::str::from_utf8(utf) { + spans.push(Span::raw(utf_str.replace('\0', "."))); + } else { + spans.push(Span::raw(".")); + } + } + } + + spans.push(Span::raw("\n")); + + Line::from(spans) + }) + .collect(); + + let title = self.active_buffer.title(buf.len()); + let block = Block::default().title(title).borders(Borders::ALL); + let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true }); + f.render_widget(paragraph, area); + } +} + +/// Wrapper around a list of [`Line`]s that prepends the line number on each new line. +struct SourceLines<'a> { + lines: Vec>, + max_line_num: usize, +} + +impl<'a> SourceLines<'a> { + fn new(max_line_num: usize) -> Self { + Self { lines: Vec::new(), max_line_num } + } + + fn push(&mut self, line_number_style: Style, line: &'a str, line_style: Style) { + self.push_raw(line_number_style, &[Span::styled(line, line_style)]); + } + + fn push_raw(&mut self, line_number_style: Style, spans: &[Span<'a>]) { + let mut line_spans = Vec::with_capacity(4); + + let line_number = + format!("{number: >width$} ", number = self.lines.len() + 1, width = self.max_line_num); + line_spans.push(Span::styled(line_number, line_number_style)); + + // Space between line number and line text. + line_spans.push(Span::raw(" ")); + + line_spans.extend_from_slice(spans); + + self.lines.push(Line::from(line_spans)); + } +} + +/// Container for buffer access information. +struct BufferAccess { + offset: usize, + size: usize, +} + +/// Container for read and write buffer access information. +struct BufferAccesses { + /// The read buffer kind and access information. + read: Option<(BufferKind, BufferAccess)>, + /// The only mutable buffer is the memory buffer, so don't store the buffer kind. + write: Option, +} + +/// The memory_access variable stores the index on the stack that indicates the buffer +/// offset/size accessed by the given opcode: +/// (read buffer, buffer read offset, buffer read size, write memory offset, write memory size) +/// >= 1: the stack index +/// 0: no memory access +/// -1: a fixed size of 32 bytes +/// -2: a fixed size of 1 byte +/// The return value is a tuple about accessed buffer region by the given opcode: +/// (read buffer, buffer read offset, buffer read size, write memory offset, write memory size) +fn get_buffer_accesses(op: u8, stack: &[U256]) -> Option { + let buffer_access = match op { + opcode::KECCAK256 | opcode::RETURN | opcode::REVERT => { + (Some((BufferKind::Memory, 1, 2)), None) + } + opcode::CALLDATACOPY => (Some((BufferKind::Calldata, 2, 3)), Some((1, 3))), + opcode::RETURNDATACOPY => (Some((BufferKind::Returndata, 2, 3)), Some((1, 3))), + opcode::CALLDATALOAD => (Some((BufferKind::Calldata, 1, -1)), None), + opcode::CODECOPY => (None, Some((1, 3))), + opcode::EXTCODECOPY => (None, Some((2, 4))), + opcode::MLOAD => (Some((BufferKind::Memory, 1, -1)), None), + opcode::MSTORE => (None, Some((1, -1))), + opcode::MSTORE8 => (None, Some((1, -2))), + opcode::LOG0 | opcode::LOG1 | opcode::LOG2 | opcode::LOG3 | opcode::LOG4 => { + (Some((BufferKind::Memory, 1, 2)), None) + } + opcode::CREATE | opcode::CREATE2 => (Some((BufferKind::Memory, 2, 3)), None), + opcode::CALL | opcode::CALLCODE => (Some((BufferKind::Memory, 4, 5)), None), + opcode::DELEGATECALL | opcode::STATICCALL => (Some((BufferKind::Memory, 3, 4)), None), + opcode::MCOPY => (Some((BufferKind::Memory, 2, 3)), Some((1, 3))), + _ => Default::default(), + }; + + let stack_len = stack.len(); + let get_size = |stack_index| match stack_index { + -2 => Some(1), + -1 => Some(32), + 0 => None, + 1.. => { + if (stack_index as usize) <= stack_len { + Some(stack[stack_len - stack_index as usize].saturating_to()) + } else { + None + } + } + _ => panic!("invalid stack index"), + }; + + if buffer_access.0.is_some() || buffer_access.1.is_some() { + let (read, write) = buffer_access; + let read_access = read.and_then(|b| { + let (buffer, offset, size) = b; + Some((buffer, BufferAccess { offset: get_size(offset)?, size: get_size(size)? })) + }); + let write_access = write.and_then(|b| { + let (offset, size) = b; + Some(BufferAccess { offset: get_size(offset)?, size: get_size(size)? }) + }); + Some(BufferAccesses { read: read_access, write: write_access }) + } else { + None + } +} + +fn hex_bytes_spans(bytes: &[u8], spans: &mut Vec>, f: impl Fn(usize, u8) -> Style) { + for (i, &byte) in bytes.iter().enumerate() { + if i > 0 { + spans.push(Span::raw(" ")); + } + spans.push(Span::styled(alloy_primitives::hex::encode([byte]), f(i, byte))); + } +} + +/// Returns the number of decimal digits in the given number. +/// +/// This is the same as `n.to_string().len()`. +fn decimal_digits(n: usize) -> usize { + n.checked_ilog10().unwrap_or(0) as usize + 1 +} + +/// Returns the number of hexadecimal digits in the given number. +/// +/// This is the same as `format!("{n:x}").len()`. +fn hex_digits(n: usize) -> usize { + n.checked_ilog(16).unwrap_or(0) as usize + 1 +} + +#[cfg(test)] +mod tests { + #[test] + fn decimal_digits() { + assert_eq!(super::decimal_digits(0), 1); + assert_eq!(super::decimal_digits(1), 1); + assert_eq!(super::decimal_digits(2), 1); + assert_eq!(super::decimal_digits(9), 1); + assert_eq!(super::decimal_digits(10), 2); + assert_eq!(super::decimal_digits(11), 2); + assert_eq!(super::decimal_digits(50), 2); + assert_eq!(super::decimal_digits(99), 2); + assert_eq!(super::decimal_digits(100), 3); + assert_eq!(super::decimal_digits(101), 3); + assert_eq!(super::decimal_digits(201), 3); + assert_eq!(super::decimal_digits(999), 3); + assert_eq!(super::decimal_digits(1000), 4); + assert_eq!(super::decimal_digits(1001), 4); + } + + #[test] + fn hex_digits() { + assert_eq!(super::hex_digits(0), 1); + assert_eq!(super::hex_digits(1), 1); + assert_eq!(super::hex_digits(2), 1); + assert_eq!(super::hex_digits(9), 1); + assert_eq!(super::hex_digits(10), 1); + assert_eq!(super::hex_digits(11), 1); + assert_eq!(super::hex_digits(15), 1); + assert_eq!(super::hex_digits(16), 2); + assert_eq!(super::hex_digits(17), 2); + assert_eq!(super::hex_digits(0xff), 2); + assert_eq!(super::hex_digits(0x100), 3); + assert_eq!(super::hex_digits(0x101), 3); + } +} diff --git a/crates/debugger/src/tui/mod.rs b/crates/debugger/src/tui/mod.rs new file mode 100644 index 0000000000000..1fac3d051de2e --- /dev/null +++ b/crates/debugger/src/tui/mod.rs @@ -0,0 +1,215 @@ +//! The TUI implementation. + +use alloy_primitives::Address; +use crossterm::{ + event::{self, DisableMouseCapture, EnableMouseCapture, Event}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use eyre::Result; +use foundry_common::{compile::ContractSources, evm::Breakpoints}; +use foundry_evm_core::{debug::DebugNodeFlat, utils::PcIcMap}; +use ratatui::{ + backend::{Backend, CrosstermBackend}, + Terminal, +}; +use revm::primitives::SpecId; +use std::{ + collections::{BTreeMap, HashMap}, + io, + ops::ControlFlow, + sync::{mpsc, Arc}, + thread, + time::{Duration, Instant}, +}; + +mod builder; +pub use builder::DebuggerBuilder; + +mod context; +use context::DebuggerContext; + +mod draw; + +type DebuggerTerminal = Terminal>; + +/// Debugger exit reason. +#[derive(Debug)] +pub enum ExitReason { + /// Exit using 'q'. + CharExit, +} + +/// The TUI debugger. +pub struct Debugger { + debug_arena: Vec, + identified_contracts: HashMap, + /// Source map of contract sources + contracts_sources: ContractSources, + /// A mapping of source -> (PC -> IC map for deploy code, PC -> IC map for runtime code) + pc_ic_maps: BTreeMap, + breakpoints: Breakpoints, +} + +impl Debugger { + /// Creates a new debugger builder. + #[inline] + pub fn builder() -> DebuggerBuilder { + DebuggerBuilder::new() + } + + /// Creates a new debugger. + pub fn new( + debug_arena: Vec, + identified_contracts: HashMap, + contracts_sources: ContractSources, + breakpoints: Breakpoints, + ) -> Self { + let pc_ic_maps = contracts_sources + .entries() + .filter_map(|(contract_name, _, contract)| { + Some(( + contract_name.to_owned(), + ( + PcIcMap::new(SpecId::LATEST, contract.bytecode.bytes()?), + PcIcMap::new(SpecId::LATEST, contract.deployed_bytecode.bytes()?), + ), + )) + }) + .collect(); + Self { debug_arena, identified_contracts, contracts_sources, pc_ic_maps, breakpoints } + } + + /// Starts the debugger TUI. Terminates the current process on failure or user exit. + pub fn run_exit(mut self) -> ! { + let code = match self.try_run() { + Ok(ExitReason::CharExit) => 0, + Err(e) => { + println!("{e}"); + 1 + } + }; + std::process::exit(code) + } + + /// Starts the debugger TUI. + pub fn try_run(&mut self) -> Result { + eyre::ensure!(!self.debug_arena.is_empty(), "debug arena is empty"); + + let backend = CrosstermBackend::new(io::stdout()); + let terminal = Terminal::new(backend)?; + TerminalGuard::with(terminal, |terminal| self.try_run_real(terminal)) + } + + #[instrument(target = "debugger", name = "run", skip_all, ret)] + fn try_run_real(&mut self, terminal: &mut DebuggerTerminal) -> Result { + // Create the context. + let mut cx = DebuggerContext::new(self); + + cx.init(); + + // Create an event listener in a different thread. + let (tx, rx) = mpsc::channel(); + thread::Builder::new() + .name("event-listener".into()) + .spawn(move || Self::event_listener(tx)) + .expect("failed to spawn thread"); + + // Start the event loop. + loop { + cx.draw(terminal)?; + match cx.handle_event(rx.recv()?) { + ControlFlow::Continue(()) => {} + ControlFlow::Break(reason) => return Ok(reason), + } + } + } + + fn event_listener(tx: mpsc::Sender) { + // This is the recommend tick rate from `ratatui`, based on their examples + let tick_rate = Duration::from_millis(200); + + let mut last_tick = Instant::now(); + loop { + // Poll events since last tick - if last tick is greater than tick_rate, we + // demand immediate availability of the event. This may affect interactivity, + // but I'm not sure as it is hard to test. + if event::poll(tick_rate.saturating_sub(last_tick.elapsed())).unwrap() { + let event = event::read().unwrap(); + if tx.send(event).is_err() { + return; + } + } + + // Force update if time has passed + if last_tick.elapsed() > tick_rate { + last_tick = Instant::now(); + } + } + } +} + +type PanicHandler = Box) + 'static + Sync + Send>; + +/// Handles terminal state. +#[must_use] +struct TerminalGuard { + terminal: Terminal, + hook: Option>, +} + +impl TerminalGuard { + fn with(terminal: Terminal, mut f: impl FnMut(&mut Terminal) -> T) -> T { + let mut guard = Self { terminal, hook: None }; + guard.setup(); + f(&mut guard.terminal) + } + + fn setup(&mut self) { + let previous = Arc::new(std::panic::take_hook()); + self.hook = Some(previous.clone()); + // We need to restore the terminal state before displaying the panic message. + // TODO: Use `std::panic::update_hook` when it's stable + std::panic::set_hook(Box::new(move |info| { + Self::half_restore(&mut std::io::stdout()); + (previous)(info) + })); + + let _ = enable_raw_mode(); + let _ = execute!(*self.terminal.backend_mut(), EnterAlternateScreen, EnableMouseCapture); + let _ = self.terminal.hide_cursor(); + let _ = self.terminal.clear(); + } + + fn restore(&mut self) { + if !std::thread::panicking() { + // Drop the current hook to guarantee that `self.hook` is the only reference to it. + let _ = std::panic::take_hook(); + // Restore the previous panic hook. + let prev = self.hook.take().unwrap(); + let prev = match Arc::try_unwrap(prev) { + Ok(prev) => prev, + Err(_) => unreachable!("`self.hook` is not the only reference to the panic hook"), + }; + std::panic::set_hook(prev); + + // NOTE: Our panic handler calls this function, so we only have to call it here if we're + // not panicking. + Self::half_restore(self.terminal.backend_mut()); + } + + let _ = self.terminal.show_cursor(); + } + + fn half_restore(w: &mut impl io::Write) { + let _ = disable_raw_mode(); + let _ = execute!(*w, LeaveAlternateScreen, DisableMouseCapture); + } +} + +impl Drop for TerminalGuard { + #[inline] + fn drop(&mut self) { + self.restore(); + } +} diff --git a/crates/doc/Cargo.toml b/crates/doc/Cargo.toml index 523c0e3a4be77..73368bd9f1f26 100644 --- a/crates/doc/Cargo.toml +++ b/crates/doc/Cargo.toml @@ -31,3 +31,4 @@ solang-parser.workspace = true thiserror = "1" toml.workspace = true tracing.workspace = true +regex = "1.10.2" diff --git a/crates/doc/src/builder.rs b/crates/doc/src/builder.rs index 1826f3518f642..e80393f4dff77 100644 --- a/crates/doc/src/builder.rs +++ b/crates/doc/src/builder.rs @@ -42,7 +42,7 @@ pub struct DocBuilder { // TODO: consider using `tfio` impl DocBuilder { - const SRC: &'static str = "src"; + pub(crate) const SRC: &'static str = "src"; const SOL_EXT: &'static str = "sol"; const README: &'static str = "README.md"; const SUMMARY: &'static str = "SUMMARY.md"; diff --git a/crates/doc/src/document.rs b/crates/doc/src/document.rs index dcd54247e6ab3..3f1f2935c0bd8 100644 --- a/crates/doc/src/document.rs +++ b/crates/doc/src/document.rs @@ -1,5 +1,10 @@ -use crate::{ParseItem, PreprocessorId, PreprocessorOutput}; -use std::{collections::HashMap, path::PathBuf, sync::Mutex}; +use crate::{DocBuilder, ParseItem, PreprocessorId, PreprocessorOutput}; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + slice::IterMut, + sync::Mutex, +}; /// The wrapper around the [ParseItem] containing additional /// information the original item and extra context for outputting it. @@ -62,6 +67,15 @@ impl Document { let context = self.context.lock().expect("failed to lock context"); context.get(&id).cloned() } + + fn try_relative_output_path(&self) -> Option<&Path> { + self.target_path.strip_prefix(&self.out_target_dir).ok()?.strip_prefix(DocBuilder::SRC).ok() + } + + /// Returns the relative path of the document output. + pub fn relative_output_path(&self) -> &Path { + self.try_relative_output_path().unwrap_or(self.target_path.as_path()) + } } /// The content of the document. @@ -73,6 +87,100 @@ pub enum DocumentContent { OverloadedFunctions(Vec), } +impl DocumentContent { + pub(crate) fn len(&self) -> usize { + match self { + DocumentContent::Empty => 0, + DocumentContent::Single(_) => 1, + DocumentContent::Constants(items) => items.len(), + DocumentContent::OverloadedFunctions(items) => items.len(), + } + } + + pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut ParseItem> { + match self { + DocumentContent::Empty => None, + DocumentContent::Single(item) => { + if index == 0 { + Some(item) + } else { + None + } + } + DocumentContent::Constants(items) => items.get_mut(index), + DocumentContent::OverloadedFunctions(items) => items.get_mut(index), + } + } + + pub fn iter_items(&self) -> ParseItemIter<'_> { + match self { + DocumentContent::Empty => ParseItemIter { next: None, other: None }, + DocumentContent::Single(item) => ParseItemIter { next: Some(item), other: None }, + DocumentContent::Constants(items) => { + ParseItemIter { next: None, other: Some(items.iter()) } + } + DocumentContent::OverloadedFunctions(items) => { + ParseItemIter { next: None, other: Some(items.iter()) } + } + } + } + + pub fn iter_items_mut(&mut self) -> ParseItemIterMut<'_> { + match self { + DocumentContent::Empty => ParseItemIterMut { next: None, other: None }, + DocumentContent::Single(item) => ParseItemIterMut { next: Some(item), other: None }, + DocumentContent::Constants(items) => { + ParseItemIterMut { next: None, other: Some(items.iter_mut()) } + } + DocumentContent::OverloadedFunctions(items) => { + ParseItemIterMut { next: None, other: Some(items.iter_mut()) } + } + } + } +} + +#[derive(Debug)] +pub struct ParseItemIter<'a> { + next: Option<&'a ParseItem>, + other: Option>, +} + +impl<'a> Iterator for ParseItemIter<'a> { + type Item = &'a ParseItem; + + fn next(&mut self) -> Option { + if let Some(next) = self.next.take() { + return Some(next) + } + if let Some(other) = self.other.as_mut() { + return other.next() + } + + None + } +} + +#[derive(Debug)] +pub struct ParseItemIterMut<'a> { + next: Option<&'a mut ParseItem>, + other: Option>, +} + +impl<'a> Iterator for ParseItemIterMut<'a> { + type Item = &'a mut ParseItem; + + fn next(&mut self) -> Option { + if let Some(next) = self.next.take() { + return Some(next) + } + if let Some(other) = self.other.as_mut() { + return other.next() + } + + None + } +} + /// Read the preprocessor output variant from document context. /// Returns [None] if there is no output. macro_rules! read_context { diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index ca06b468ce6bb..b6e6d08af276a 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; /// The natspec comment tag explaining the purpose of the comment. /// See: https://docs.soliditylang.org/en/v0.8.17/natspec-format.html#tags. -#[derive(PartialEq, Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum CommentTag { /// A title that should describe the contract/interface Title, @@ -55,7 +55,7 @@ impl CommentTag { /// The natspec documentation comment. /// https://docs.soliditylang.org/en/v0.8.17/natspec-format.html -#[derive(PartialEq, Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Comment { /// The doc comment tag. pub tag: CommentTag, @@ -98,7 +98,7 @@ impl Comment { } /// The collection of natspec [Comment] items. -#[derive(Deref, DerefMut, PartialEq, Default, Clone, Debug)] +#[derive(Clone, Debug, Default, PartialEq, Deref, DerefMut)] pub struct Comments(Vec); /// Forward the [Comments] function implementation to the [CommentsRef] @@ -152,7 +152,7 @@ impl From> for Comments { } /// The collection of references to natspec [Comment] items. -#[derive(Deref, PartialEq, Default, Debug)] +#[derive(Debug, Default, PartialEq, Deref)] pub struct CommentsRef<'a>(Vec<&'a Comment>); impl<'a> CommentsRef<'a> { diff --git a/crates/doc/src/parser/error.rs b/crates/doc/src/parser/error.rs index 26ffb6256fc60..770137f99d173 100644 --- a/crates/doc/src/parser/error.rs +++ b/crates/doc/src/parser/error.rs @@ -2,7 +2,7 @@ use forge_fmt::FormatterError; use thiserror::Error; /// The parser error. -#[derive(Error, Debug)] +#[derive(Debug, Error)] #[error(transparent)] pub enum ParserError { /// Formatter error. diff --git a/crates/doc/src/parser/item.rs b/crates/doc/src/parser/item.rs index 0321fc411a975..60faf78b411b1 100644 --- a/crates/doc/src/parser/item.rs +++ b/crates/doc/src/parser/item.rs @@ -9,7 +9,7 @@ use solang_parser::pt::{ }; /// The parsed item. -#[derive(PartialEq, Debug)] +#[derive(Debug, PartialEq)] pub struct ParseItem { /// The parse tree source. pub source: ParseSource, @@ -147,7 +147,7 @@ impl ParseItem { } /// A wrapper type around pt token. -#[derive(PartialEq, Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum ParseSource { /// Source contract definition. Contract(Box), diff --git a/crates/doc/src/parser/mod.rs b/crates/doc/src/parser/mod.rs index 4b3e99ce13b03..6f03a5fb2b1dd 100644 --- a/crates/doc/src/parser/mod.rs +++ b/crates/doc/src/parser/mod.rs @@ -171,6 +171,14 @@ impl Visitor for Parser { Ok(()) } + fn visit_enum(&mut self, enumerable: &mut EnumDefinition) -> ParserResult<()> { + self.add_element_to_parent(ParseSource::Enum(enumerable.clone()), enumerable.loc) + } + + fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> ParserResult<()> { + self.add_element_to_parent(ParseSource::Variable(var.clone()), var.loc) + } + fn visit_function(&mut self, func: &mut FunctionDefinition) -> ParserResult<()> { // If the function parameter doesn't have a name, try to set it with // `@custom:name` tag if any was provided @@ -195,8 +203,8 @@ impl Visitor for Parser { self.add_element_to_parent(ParseSource::Function(func.clone()), func.loc) } - fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Variable(var.clone()), var.loc) + fn visit_struct(&mut self, structure: &mut StructDefinition) -> ParserResult<()> { + self.add_element_to_parent(ParseSource::Struct(structure.clone()), structure.loc) } fn visit_event(&mut self, event: &mut EventDefinition) -> ParserResult<()> { @@ -207,14 +215,6 @@ impl Visitor for Parser { self.add_element_to_parent(ParseSource::Error(error.clone()), error.loc) } - fn visit_struct(&mut self, structure: &mut StructDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Struct(structure.clone()), structure.loc) - } - - fn visit_enum(&mut self, enumerable: &mut EnumDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Enum(enumerable.clone()), enumerable.loc) - } - fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> ParserResult<()> { self.add_element_to_parent(ParseSource::Type(def.clone()), def.loc) } diff --git a/crates/doc/src/preprocessor/contract_inheritance.rs b/crates/doc/src/preprocessor/contract_inheritance.rs index b4972ac901777..d0b5cd00bd010 100644 --- a/crates/doc/src/preprocessor/contract_inheritance.rs +++ b/crates/doc/src/preprocessor/contract_inheritance.rs @@ -12,7 +12,7 @@ pub const CONTRACT_INHERITANCE_ID: PreprocessorId = PreprocessorId("contract_inh /// to link them with the paths of the other contract documents. /// /// This preprocessor writes to [Document]'s context. -#[derive(Default, Debug)] +#[derive(Debug, Default)] pub struct ContractInheritance { /// Whether to capture inherited contracts from libraries. pub include_libraries: bool, diff --git a/crates/doc/src/preprocessor/deployments.rs b/crates/doc/src/preprocessor/deployments.rs index b081ed59a0d71..9ad360c57487f 100644 --- a/crates/doc/src/preprocessor/deployments.rs +++ b/crates/doc/src/preprocessor/deployments.rs @@ -1,7 +1,10 @@ use super::{Preprocessor, PreprocessorId}; use crate::{Document, PreprocessorOutput}; use alloy_primitives::Address; -use std::{fs, path::PathBuf}; +use std::{ + fs, + path::{Path, PathBuf}, +}; /// [Deployments] preprocessor id. pub const DEPLOYMENTS_ID: PreprocessorId = PreprocessorId("deployments"); @@ -18,7 +21,7 @@ pub struct Deployments { } /// A contract deployment. -#[derive(serde::Deserialize, Debug, Clone)] +#[derive(Clone, Debug, serde::Deserialize)] pub struct Deployment { /// The contract address pub address: Address, @@ -33,20 +36,21 @@ impl Preprocessor for Deployments { fn preprocess(&self, documents: Vec) -> Result, eyre::Error> { let deployments_dir = - self.root.join(self.deployments.as_ref().unwrap_or(&PathBuf::from("deployments"))); + self.root.join(self.deployments.as_deref().unwrap_or(Path::new("deployments"))); // Gather all networks from the deployments directory. let networks = fs::read_dir(&deployments_dir)? - .map(|x| { - x.map(|y| { - if y.file_type()?.is_dir() { - Ok(y.file_name().into_string().map_err(|e| { - eyre::eyre!("Failed to extract directory name: {:?}", e) - })?) - } else { - eyre::bail!("Not a directory.") - } - })? + .map(|entry| { + let entry = entry?; + let path = entry.path(); + if entry.file_type()?.is_dir() { + entry + .file_name() + .into_string() + .map_err(|e| eyre::eyre!("failed to extract directory name: {e:?}")) + } else { + eyre::bail!("not a directory: {}", path.display()) + } }) .collect::, _>>()?; diff --git a/crates/doc/src/preprocessor/infer_hyperlinks.rs b/crates/doc/src/preprocessor/infer_hyperlinks.rs new file mode 100644 index 0000000000000..865c4302fdf46 --- /dev/null +++ b/crates/doc/src/preprocessor/infer_hyperlinks.rs @@ -0,0 +1,324 @@ +use super::{Preprocessor, PreprocessorId}; +use crate::{Comments, Document, ParseItem, ParseSource}; +use forge_fmt::solang_ext::SafeUnwrap; +use once_cell::sync::Lazy; +use regex::{Captures, Match, Regex}; +use std::{ + borrow::Cow, + path::{Path, PathBuf}, +}; + +/// A regex that matches `{identifier-part}` placeholders +/// +/// Overloaded functions are referenced by including the exact function arguments in the `part` +/// section of the placeholder. +static RE_INLINE_LINK: Lazy = Lazy::new(|| { + Regex::new(r"(?m)(\{(?Pxref-)?(?P[a-zA-Z_][0-9a-zA-Z_]*)(-(?P[a-zA-Z_][0-9a-zA-Z_-]*))?}(\[(?P(.*?))\])?)").unwrap() +}); + +/// [InferInlineHyperlinks] preprocessor id. +pub const INFER_INLINE_HYPERLINKS_ID: PreprocessorId = PreprocessorId("infer inline hyperlinks"); + +/// The infer hyperlinks preprocessor tries to map @dev tags to referenced items +/// Traverses the documents and attempts to find referenced items +/// comments for dev comment tags. +/// +/// This preprocessor replaces inline links in comments with the links to the referenced items. +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct InferInlineHyperlinks; + +impl Preprocessor for InferInlineHyperlinks { + fn id(&self) -> PreprocessorId { + INFER_INLINE_HYPERLINKS_ID + } + + fn preprocess(&self, mut documents: Vec) -> Result, eyre::Error> { + // traverse all comments and try to match inline links and replace with inline links for + // markdown + let mut docs = Vec::with_capacity(documents.len()); + while !documents.is_empty() { + let mut document = documents.remove(0); + let target_path = document.relative_output_path().to_path_buf(); + for idx in 0..document.content.len() { + let (mut comments, item_children_len) = { + let item = document.content.get_mut(idx).unwrap(); + let comments = std::mem::take(&mut item.comments); + let children = item.children.len(); + (comments, children) + }; + Self::inline_doc_links(&documents, &target_path, &mut comments, &document); + document.content.get_mut(idx).unwrap().comments = comments; + + // we also need to iterate over all child items + // This is a bit horrible but we need to traverse all items in all documents + for child_idx in 0..item_children_len { + let mut comments = { + let item = document.content.get_mut(idx).unwrap(); + + std::mem::take(&mut item.children[child_idx].comments) + }; + Self::inline_doc_links(&documents, &target_path, &mut comments, &document); + document.content.get_mut(idx).unwrap().children[child_idx].comments = comments; + } + } + + docs.push(document); + } + + Ok(docs) + } +} + +impl InferInlineHyperlinks { + /// Finds the first match for the given link. + /// + /// All items get their own section in the markdown file. + /// This section uses the identifier of the item: `#functionname` + /// + /// Note: the target path is the relative path to the markdown file. + fn find_match<'a>( + link: &InlineLink<'a>, + target_path: &Path, + items: impl Iterator, + ) -> Option> { + for item in items { + match &item.source { + ParseSource::Contract(contract) => { + let name = &contract.name.safe_unwrap().name; + if name == link.identifier { + if link.part.is_none() { + return Some(InlineLinkTarget::borrowed(name, target_path.to_path_buf())) + } + // try to find the referenced item in the contract's children + return Self::find_match(link, target_path, item.children.iter()) + } + } + ParseSource::Function(fun) => { + // TODO: handle overloaded functions + // functions can be overloaded so we need to keep track of how many matches we + // have so we can match the correct one + if let Some(id) = &fun.name { + // Note: constructors don't have a name + if id.name == link.ref_name() { + return Some(InlineLinkTarget::borrowed( + &id.name, + target_path.to_path_buf(), + )) + } + } else if link.ref_name() == "constructor" { + return Some(InlineLinkTarget::borrowed( + "constructor", + target_path.to_path_buf(), + )) + } + } + ParseSource::Variable(_) => {} + ParseSource::Event(ev) => { + let ev_name = &ev.name.safe_unwrap().name; + if ev_name == link.ref_name() { + return Some(InlineLinkTarget::borrowed(ev_name, target_path.to_path_buf())) + } + } + ParseSource::Error(err) => { + let err_name = &err.name.safe_unwrap().name; + if err_name == link.ref_name() { + return Some(InlineLinkTarget::borrowed(err_name, target_path.to_path_buf())) + } + } + ParseSource::Struct(structdef) => { + let struct_name = &structdef.name.safe_unwrap().name; + if struct_name == link.ref_name() { + return Some(InlineLinkTarget::borrowed( + struct_name, + target_path.to_path_buf(), + )) + } + } + ParseSource::Enum(_) => {} + ParseSource::Type(_) => {} + } + } + + None + } + + /// Attempts to convert inline links to markdown links. + fn inline_doc_links( + documents: &[Document], + target_path: &Path, + comments: &mut Comments, + parent: &Document, + ) { + // loop over all comments in the item + for comment in comments.iter_mut() { + let val = comment.value.clone(); + // replace all links with inline markdown links + for link in InlineLink::captures(val.as_str()) { + let target = if link.is_external() { + // find in all documents + documents.iter().find_map(|doc| { + Self::find_match( + &link, + doc.relative_output_path(), + doc.content.iter_items().flat_map(|item| { + Some(item).into_iter().chain(item.children.iter()) + }), + ) + }) + } else { + // find matches in the document + Self::find_match( + &link, + target_path, + parent + .content + .iter_items() + .flat_map(|item| Some(item).into_iter().chain(item.children.iter())), + ) + }; + if let Some(target) = target { + let display_value = link.markdown_link_display_value(); + let markdown_link = format!("[{}]({})", display_value, target); + // replace the link with the markdown link + comment.value = + comment.value.as_str().replacen(link.as_str(), markdown_link.as_str(), 1); + } + } + } + } +} + +struct InlineLinkTarget<'a> { + section: Cow<'a, str>, + target_path: PathBuf, +} + +impl<'a> InlineLinkTarget<'a> { + fn borrowed(section: &'a str, target_path: PathBuf) -> Self { + Self { section: Cow::Borrowed(section), target_path } + } +} + +impl<'a> std::fmt::Display for InlineLinkTarget<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // NOTE: the url should be absolute for markdown and section names are lowercase + write!(f, "/{}#{}", self.target_path.display(), self.section.to_lowercase()) + } +} + +/// A parsed link to an item. +#[derive(Debug)] +struct InlineLink<'a> { + outer: Match<'a>, + identifier: &'a str, + part: Option<&'a str>, + link: Option<&'a str>, +} + +impl<'a> InlineLink<'a> { + fn from_capture(cap: Captures<'a>) -> Option { + Some(Self { + outer: cap.get(1)?, + identifier: cap.name("identifier")?.as_str(), + part: cap.name("part").map(|m| m.as_str()), + link: cap.name("link").map(|m| m.as_str()), + }) + } + + fn captures(s: &'a str) -> impl Iterator + '_ { + RE_INLINE_LINK.captures(s).map(Self::from_capture).into_iter().flatten() + } + + /// Parses the first inline link. + #[allow(unused)] + fn capture(s: &'a str) -> Option { + let cap = RE_INLINE_LINK.captures(s)?; + Self::from_capture(cap) + } + + /// Returns the name of the link + fn markdown_link_display_value(&self) -> Cow<'_, str> { + if let Some(link) = self.link { + Cow::Borrowed(link) + } else if let Some(part) = self.part { + Cow::Owned(format!("{}-{}", self.identifier, part)) + } else { + Cow::Borrowed(self.identifier) + } + } + + /// Returns the name of the referenced item. + fn ref_name(&self) -> &str { + self.exact_identifier().split('-').next().unwrap() + } + + fn exact_identifier(&self) -> &str { + let mut name = self.identifier; + if let Some(part) = self.part { + name = part; + } + name + } + + /// Returns the name of the referenced item and its arguments, if any. + /// + /// Eg: `safeMint-address-uint256-` returns `("safeMint", ["address", "uint256"])` + #[allow(unused)] + fn ref_name_exact(&self) -> (&str, impl Iterator + '_) { + let identifier = self.exact_identifier(); + let mut iter = identifier.split('-'); + (iter.next().unwrap(), iter.filter(|s| !s.is_empty())) + } + + /// Returns the content of the matched link. + fn as_str(&self) -> &str { + self.outer.as_str() + } + + /// Returns true if the link is external. + fn is_external(&self) -> bool { + self.part.is_some() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_inline_links() { + let s = " {IERC165-supportsInterface} "; + let cap = RE_INLINE_LINK.captures(s).unwrap(); + + let identifier = cap.name("identifier").unwrap().as_str(); + assert_eq!(identifier, "IERC165"); + let part = cap.name("part").unwrap().as_str(); + assert_eq!(part, "supportsInterface"); + + let s = " {supportsInterface} "; + let cap = RE_INLINE_LINK.captures(s).unwrap(); + + let identifier = cap.name("identifier").unwrap().as_str(); + assert_eq!(identifier, "supportsInterface"); + + let s = "{xref-ERC721-_safeMint-address-uint256-}"; + let cap = RE_INLINE_LINK.captures(s).unwrap(); + + let identifier = cap.name("identifier").unwrap().as_str(); + assert_eq!(identifier, "ERC721"); + let identifier = cap.name("xref").unwrap().as_str(); + assert_eq!(identifier, "xref-"); + let identifier = cap.name("part").unwrap().as_str(); + assert_eq!(identifier, "_safeMint-address-uint256-"); + + let link = InlineLink::capture(s).unwrap(); + assert_eq!(link.ref_name(), "_safeMint"); + assert_eq!(link.as_str(), "{xref-ERC721-_safeMint-address-uint256-}"); + + let s = "{xref-ERC721-_safeMint-address-uint256-}[`Named link`]"; + let link = InlineLink::capture(s).unwrap(); + assert_eq!(link.link, Some("`Named link`")); + assert_eq!(link.markdown_link_display_value(), "`Named link`"); + } +} diff --git a/crates/doc/src/preprocessor/inheritdoc.rs b/crates/doc/src/preprocessor/inheritdoc.rs index 3989991865574..583df72ba863d 100644 --- a/crates/doc/src/preprocessor/inheritdoc.rs +++ b/crates/doc/src/preprocessor/inheritdoc.rs @@ -13,7 +13,7 @@ pub const INHERITDOC_ID: PreprocessorId = PreprocessorId("inheritdoc"); /// comments for inheritdoc comment tags. /// /// This preprocessor writes to [Document]'s context. -#[derive(Default, Debug)] +#[derive(Debug, Default)] #[non_exhaustive] pub struct Inheritdoc; diff --git a/crates/doc/src/preprocessor/mod.rs b/crates/doc/src/preprocessor/mod.rs index 40cb7843cb669..25ed4db231ae7 100644 --- a/crates/doc/src/preprocessor/mod.rs +++ b/crates/doc/src/preprocessor/mod.rs @@ -9,6 +9,9 @@ pub use contract_inheritance::{ContractInheritance, CONTRACT_INHERITANCE_ID}; mod inheritdoc; pub use inheritdoc::{Inheritdoc, INHERITDOC_ID}; +mod infer_hyperlinks; +pub use infer_hyperlinks::{InferInlineHyperlinks, INFER_INLINE_HYPERLINKS_ID}; + mod git_source; pub use git_source::{GitSource, GIT_SOURCE_ID}; @@ -16,13 +19,13 @@ mod deployments; pub use deployments::{Deployment, Deployments, DEPLOYMENTS_ID}; /// The preprocessor id. -#[derive(Debug, Eq, Hash, PartialEq)] +#[derive(Debug, PartialEq, Eq, Hash)] pub struct PreprocessorId(&'static str); /// Preprocessor output. /// Wraps all existing preprocessor outputs /// in a single abstraction. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub enum PreprocessorOutput { /// The contract inheritance output. /// The map of contract base idents to the path of the base contract. diff --git a/crates/doc/src/writer/as_doc.rs b/crates/doc/src/writer/as_doc.rs index c62e2a106948b..68e4451fae890 100644 --- a/crates/doc/src/writer/as_doc.rs +++ b/crates/doc/src/writer/as_doc.rs @@ -7,8 +7,8 @@ use crate::{ }; use forge_fmt::solang_ext::SafeUnwrap; use itertools::Itertools; -use solang_parser::pt::Base; -use std::path::Path; +use solang_parser::pt::{Base, FunctionDefinition}; +use std::path::{Path, PathBuf}; /// The result of [Asdoc::as_doc] method. pub type AsDocResult = Result; @@ -127,9 +127,8 @@ impl AsDoc for Document { if !contract.base.is_empty() { writer.write_bold("Inherits:")?; - // Where all the source files are written to // we need this to find the _relative_ paths - let src_target_dir = self.out_target_dir.join("src"); + let src_target_dir = self.target_src_dir(); let mut bases = vec![]; let linked = @@ -179,56 +178,10 @@ impl AsDoc for Document { if let Some(funcs) = item.functions() { writer.write_subtitle("Functions")?; - funcs.into_iter().try_for_each(|(func, comments, code)| { - let func_name = func - .name - .as_ref() - .map_or(func.ty.to_string(), |n| n.name.to_owned()); - let comments = comments.merge_inheritdoc( - &func_name, - read_context!(self, INHERITDOC_ID, Inheritdoc), - ); - // Write function name - writer.write_heading(&func_name)?; - writer.writeln()?; - - // Write function docs - writer.writeln_doc( - comments.exclude_tags(&[CommentTag::Param, CommentTag::Return]), - )?; - - // Write function header - writer.write_code(code)?; - - // Write function parameter comments in a table - let params = func - .params - .iter() - .filter_map(|p| p.1.as_ref()) - .collect::>(); - writer.try_write_param_table( - CommentTag::Param, - ¶ms, - &comments, - )?; - - // Write function parameter comments in a table - let returns = func - .returns - .iter() - .filter_map(|p| p.1.as_ref()) - .collect::>(); - writer.try_write_param_table( - CommentTag::Return, - &returns, - &comments, - )?; - - writer.writeln()?; - - Ok::<(), std::fmt::Error>(()) - })?; + for (func, comments, code) in funcs.iter() { + self.write_function(&mut writer, func, comments, code)?; + } } if let Some(events) = item.events() { @@ -317,3 +270,45 @@ impl AsDoc for Document { Ok(writer.finish()) } } + +impl Document { + /// Where all the source files are written to + fn target_src_dir(&self) -> PathBuf { + self.out_target_dir.join("src") + } + + /// Writes a function to the buffer. + fn write_function( + &self, + writer: &mut BufWriter, + func: &FunctionDefinition, + comments: &Comments, + code: &str, + ) -> Result<(), std::fmt::Error> { + let func_name = func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.to_owned()); + let comments = + comments.merge_inheritdoc(&func_name, read_context!(self, INHERITDOC_ID, Inheritdoc)); + + // Write function name + writer.write_heading(&func_name)?; + + writer.writeln()?; + + // Write function docs + writer.writeln_doc(comments.exclude_tags(&[CommentTag::Param, CommentTag::Return]))?; + + // Write function header + writer.write_code(code)?; + + // Write function parameter comments in a table + let params = func.params.iter().filter_map(|p| p.1.as_ref()).collect::>(); + writer.try_write_param_table(CommentTag::Param, ¶ms, &comments)?; + + // Write function parameter comments in a table + let returns = func.returns.iter().filter_map(|p| p.1.as_ref()).collect::>(); + writer.try_write_param_table(CommentTag::Return, &returns, &comments)?; + + writer.writeln()?; + Ok(()) + } +} diff --git a/crates/doc/src/writer/buf_writer.rs b/crates/doc/src/writer/buf_writer.rs index 3c1728b840ba4..7ab47c953c5fe 100644 --- a/crates/doc/src/writer/buf_writer.rs +++ b/crates/doc/src/writer/buf_writer.rs @@ -19,7 +19,7 @@ static DEPLOYMENTS_TABLE_SEPARATOR: Lazy = /// The buffered writer. /// Writes various display items into the internal buffer. -#[derive(Default, Debug)] +#[derive(Debug, Default)] pub struct BufWriter { buf: String, } diff --git a/crates/evm/core/Cargo.toml b/crates/evm/core/Cargo.toml index 51d9ad6df13ca..9b558e350d530 100644 --- a/crates/evm/core/Cargo.toml +++ b/crates/evm/core/Cargo.toml @@ -20,8 +20,13 @@ foundry-macros.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-json-abi.workspace = true alloy-primitives = { workspace = true, features = ["serde", "getrandom", "arbitrary", "rlp"] } +alloy-genesis.workspace = true +alloy-provider.workspace = true +alloy-transport.workspace = true +alloy-rpc-types.workspace = true alloy-sol-types.workspace = true -revm = { workspace = true, default-features = false, features = [ + +revm = { workspace = true, features = [ "std", "serde", "memory_limit", @@ -30,11 +35,12 @@ revm = { workspace = true, default-features = false, features = [ "optional_no_base_fee", "arbitrary", "optimism", + "c-kzg" ] } +revm-inspectors.workspace = true -ethers-core.workspace = true -ethers-providers.workspace = true - +arrayvec.workspace = true +auto_impl = "1" derive_more.workspace = true eyre = "0.6" futures = "0.3" @@ -42,9 +48,13 @@ hex.workspace = true itertools.workspace = true once_cell = "1" parking_lot = "0.12" +rustc-hash.workspace = true serde = "1" serde_json = "1" thiserror = "1" tokio = { version = "1", features = ["time", "macros"] } tracing = "0.1" url = "2" + +[dev-dependencies] +foundry-test-utils.workspace = true diff --git a/crates/evm/core/src/abi/HardhatConsole.json b/crates/evm/core/src/abi/HardhatConsole.json index c1b1b46cf4fb4..4013d875357ea 100644 --- a/crates/evm/core/src/abi/HardhatConsole.json +++ b/crates/evm/core/src/abi/HardhatConsole.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"}],"name":"logAddress","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"}],"name":"logBool","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"p0","type":"bytes"}],"name":"logBytes","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes1","name":"p0","type":"bytes1"}],"name":"logBytes1","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes10","name":"p0","type":"bytes10"}],"name":"logBytes10","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes11","name":"p0","type":"bytes11"}],"name":"logBytes11","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes12","name":"p0","type":"bytes12"}],"name":"logBytes12","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes13","name":"p0","type":"bytes13"}],"name":"logBytes13","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes14","name":"p0","type":"bytes14"}],"name":"logBytes14","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes15","name":"p0","type":"bytes15"}],"name":"logBytes15","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes16","name":"p0","type":"bytes16"}],"name":"logBytes16","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes17","name":"p0","type":"bytes17"}],"name":"logBytes17","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes18","name":"p0","type":"bytes18"}],"name":"logBytes18","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes19","name":"p0","type":"bytes19"}],"name":"logBytes19","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes2","name":"p0","type":"bytes2"}],"name":"logBytes2","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes20","name":"p0","type":"bytes20"}],"name":"logBytes20","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes21","name":"p0","type":"bytes21"}],"name":"logBytes21","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes22","name":"p0","type":"bytes22"}],"name":"logBytes22","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes23","name":"p0","type":"bytes23"}],"name":"logBytes23","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes24","name":"p0","type":"bytes24"}],"name":"logBytes24","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes25","name":"p0","type":"bytes25"}],"name":"logBytes25","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes26","name":"p0","type":"bytes26"}],"name":"logBytes26","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes27","name":"p0","type":"bytes27"}],"name":"logBytes27","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes28","name":"p0","type":"bytes28"}],"name":"logBytes28","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes29","name":"p0","type":"bytes29"}],"name":"logBytes29","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes3","name":"p0","type":"bytes3"}],"name":"logBytes3","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes30","name":"p0","type":"bytes30"}],"name":"logBytes30","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes31","name":"p0","type":"bytes31"}],"name":"logBytes31","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"p0","type":"bytes32"}],"name":"logBytes32","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"p0","type":"bytes4"}],"name":"logBytes4","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes5","name":"p0","type":"bytes5"}],"name":"logBytes5","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes6","name":"p0","type":"bytes6"}],"name":"logBytes6","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes7","name":"p0","type":"bytes7"}],"name":"logBytes7","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes8","name":"p0","type":"bytes8"}],"name":"logBytes8","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes9","name":"p0","type":"bytes9"}],"name":"logBytes9","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"p0","type":"int256"}],"name":"logInt","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"}],"name":"logString","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"}],"name":"logUint","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"p0","type":"int256"}],"outputs":[],"stateMutability":"view","type":"function","name":"log"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"int256","name":"p1","type":"int256"}],"outputs":[],"stateMutability":"view","type":"function","name":"log"}] +[{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"}],"name":"logAddress","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"}],"name":"logBool","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"p0","type":"bytes"}],"name":"logBytes","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes1","name":"p0","type":"bytes1"}],"name":"logBytes1","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes10","name":"p0","type":"bytes10"}],"name":"logBytes10","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes11","name":"p0","type":"bytes11"}],"name":"logBytes11","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes12","name":"p0","type":"bytes12"}],"name":"logBytes12","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes13","name":"p0","type":"bytes13"}],"name":"logBytes13","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes14","name":"p0","type":"bytes14"}],"name":"logBytes14","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes15","name":"p0","type":"bytes15"}],"name":"logBytes15","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes16","name":"p0","type":"bytes16"}],"name":"logBytes16","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes17","name":"p0","type":"bytes17"}],"name":"logBytes17","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes18","name":"p0","type":"bytes18"}],"name":"logBytes18","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes19","name":"p0","type":"bytes19"}],"name":"logBytes19","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes2","name":"p0","type":"bytes2"}],"name":"logBytes2","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes20","name":"p0","type":"bytes20"}],"name":"logBytes20","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes21","name":"p0","type":"bytes21"}],"name":"logBytes21","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes22","name":"p0","type":"bytes22"}],"name":"logBytes22","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes23","name":"p0","type":"bytes23"}],"name":"logBytes23","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes24","name":"p0","type":"bytes24"}],"name":"logBytes24","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes25","name":"p0","type":"bytes25"}],"name":"logBytes25","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes26","name":"p0","type":"bytes26"}],"name":"logBytes26","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes27","name":"p0","type":"bytes27"}],"name":"logBytes27","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes28","name":"p0","type":"bytes28"}],"name":"logBytes28","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes29","name":"p0","type":"bytes29"}],"name":"logBytes29","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes3","name":"p0","type":"bytes3"}],"name":"logBytes3","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes30","name":"p0","type":"bytes30"}],"name":"logBytes30","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes31","name":"p0","type":"bytes31"}],"name":"logBytes31","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"p0","type":"bytes32"}],"name":"logBytes32","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"p0","type":"bytes4"}],"name":"logBytes4","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes5","name":"p0","type":"bytes5"}],"name":"logBytes5","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes6","name":"p0","type":"bytes6"}],"name":"logBytes6","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes7","name":"p0","type":"bytes7"}],"name":"logBytes7","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes8","name":"p0","type":"bytes8"}],"name":"logBytes8","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes9","name":"p0","type":"bytes9"}],"name":"logBytes9","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"p0","type":"int256"}],"name":"logInt","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"}],"name":"logString","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"}],"name":"logUint","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"p0","type":"int256"}],"outputs":[],"stateMutability":"view","type":"function","name":"log"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"int256","name":"p1","type":"int256"}],"outputs":[],"stateMutability":"view","type":"function","name":"log"}] \ No newline at end of file diff --git a/crates/evm/core/src/abi/console.rs b/crates/evm/core/src/abi/console.rs index ce8dc6d5752cc..e9757a4768de8 100644 --- a/crates/evm/core/src/abi/console.rs +++ b/crates/evm/core/src/abi/console.rs @@ -1,7 +1,6 @@ use alloy_primitives::{hex, I256, U256}; use alloy_sol_types::sol; use derive_more::Display; -use foundry_common::types::ToEthers; use itertools::Itertools; // TODO: Use `UiFmt` @@ -78,15 +77,14 @@ interface Console { } } -fn format_units_int(x: &I256, decimals: &U256) -> String { +pub fn format_units_int(x: &I256, decimals: &U256) -> String { let (sign, x) = x.into_sign_and_abs(); format!("{sign}{}", format_units_uint(&x, decimals)) } -fn format_units_uint(x: &U256, decimals: &U256) -> String { - // TODO: rm ethers_core - match ethers_core::utils::format_units(x.to_ethers(), decimals.saturating_to::()) { - Ok(s) => s, - Err(_) => x.to_string(), +pub fn format_units_uint(x: &U256, decimals: &U256) -> String { + match alloy_primitives::utils::Unit::new(decimals.saturating_to::()) { + Some(units) => alloy_primitives::utils::ParseUnits::U256(*x).format_units(units), + None => x.to_string(), } } diff --git a/crates/evm/core/src/abi/hardhat_console.rs b/crates/evm/core/src/abi/hardhat_console.rs index 955ebc087f9dd..4b9aa3ba2a898 100644 --- a/crates/evm/core/src/abi/hardhat_console.rs +++ b/crates/evm/core/src/abi/hardhat_console.rs @@ -1,3 +1,4 @@ +use alloy_primitives::Selector; use alloy_sol_types::sol; use foundry_macros::ConsoleFmt; use once_cell::sync::Lazy; @@ -10,25 +11,34 @@ sol!( "src/abi/HardhatConsole.json" ); -/// If the input starts with a known `hardhat/console.log` `uint` selector, then this will replace -/// it with the selector `abigen!` bindings expect. -pub fn patch_hardhat_console_selector(input: &mut [u8]) { - if let Some(selector) = input.get_mut(..4) { - let selector: &mut [u8; 4] = selector.try_into().unwrap(); - if let Some(generated_selector) = HARDHAT_CONSOLE_SELECTOR_PATCHES.get(selector) { - *selector = *generated_selector; - } +/// Patches the given Hardhat `console` function selector to its ABI-normalized form. +/// +/// See [`HARDHAT_CONSOLE_SELECTOR_PATCHES`] for more details. +pub fn patch_hh_console_selector(input: &mut [u8]) { + if let Some(selector) = hh_console_selector(input) { + input[..4].copy_from_slice(selector.as_slice()); + } +} + +/// Returns the ABI-normalized selector for the given Hardhat `console` function selector. +/// +/// See [`HARDHAT_CONSOLE_SELECTOR_PATCHES`] for more details. +pub fn hh_console_selector(input: &[u8]) -> Option<&'static Selector> { + if let Some(selector) = input.get(..4) { + let selector: &[u8; 4] = selector.try_into().unwrap(); + HARDHAT_CONSOLE_SELECTOR_PATCHES.get(selector).map(Into::into) + } else { + None } } -/// This contains a map with all the `hardhat/console.log` log selectors that use `uint` or `int` -/// as key and the selector of the call with `uint256`, +/// Maps all the `hardhat/console.log` log selectors that use the legacy ABI (`int`, `uint`) to +/// their normalized counterparts (`int256`, `uint256`). /// -/// This is a bit terrible but a workaround for the differing selectors used by hardhat and the call -/// bindings which `abigen!` creates. `hardhat/console.log` logs its events in functions that accept -/// `uint` manually as `abi.encodeWithSignature("log(int)", p0)`, but `abigen!` uses `uint256` for -/// its call bindings (`HardhatConsoleCalls`) as generated by solc. -static HARDHAT_CONSOLE_SELECTOR_PATCHES: Lazy> = Lazy::new(|| { +/// `hardhat/console.log` logs its events manually, and in functions that accept integers they're +/// encoded as `abi.encodeWithSignature("log(int)", p0)`, which is not the canonical ABI encoding +/// for `int` that Solc (and [`sol!`]) uses. +pub static HARDHAT_CONSOLE_SELECTOR_PATCHES: Lazy> = Lazy::new(|| { HashMap::from([ // log(bool,uint256,uint256,address) ([241, 97, 178, 33], [0, 221, 135, 185]), @@ -549,7 +559,7 @@ mod tests { fn hardhat_console_patch() { for (hh, generated) in HARDHAT_CONSOLE_SELECTOR_PATCHES.iter() { let mut hh = *hh; - patch_hardhat_console_selector(&mut hh); + patch_hh_console_selector(&mut hh); assert_eq!(hh, *generated); } } diff --git a/crates/evm/core/src/abi/mod.rs b/crates/evm/core/src/abi/mod.rs index 6fba308b2d268..54f35c966af29 100644 --- a/crates/evm/core/src/abi/mod.rs +++ b/crates/evm/core/src/abi/mod.rs @@ -3,7 +3,10 @@ pub use foundry_cheatcodes_spec::Vm; mod console; -pub use console::Console; +pub use console::{format_units_int, format_units_uint, Console}; mod hardhat_console; -pub use hardhat_console::{patch_hardhat_console_selector, HardhatConsole}; +pub use hardhat_console::{ + hh_console_selector, patch_hh_console_selector, HardhatConsole, + HARDHAT_CONSOLE_SELECTOR_PATCHES, +}; diff --git a/crates/evm/core/src/backend/fuzz.rs b/crates/evm/core/src/backend/cow.rs similarity index 70% rename from crates/evm/core/src/backend/fuzz.rs rename to crates/evm/core/src/backend/cow.rs index bf32c6f8800f0..9b53fb4ee29aa 100644 --- a/crates/evm/core/src/backend/fuzz.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -3,17 +3,23 @@ use crate::{ backend::{ diagnostic::RevertDiagnostic, error::DatabaseError, Backend, DatabaseExt, LocalForkId, + RevertSnapshotAction, }, fork::{CreateFork, ForkId}, + InspectorExt, }; +use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, B256, U256}; -use ethers_core::utils::GenesisAccount; +use eyre::WrapErr; use revm::{ db::DatabaseRef, - primitives::{AccountInfo, Bytecode, Env, ResultAndState}, - Database, Inspector, JournaledState, + primitives::{ + Account, AccountInfo, Bytecode, Env, EnvWithHandlerCfg, HashMap as Map, ResultAndState, + SpecId, + }, + Database, DatabaseCommit, JournaledState, }; -use std::{borrow::Cow, collections::HashMap}; +use std::{borrow::Cow, collections::BTreeMap}; /// A wrapper around `Backend` that ensures only `revm::DatabaseRef` functions are called. /// @@ -26,45 +32,52 @@ use std::{borrow::Cow, collections::HashMap}; /// function via immutable raw (no state changes) calls. /// /// **N.B.**: we're assuming cheatcodes that alter the state (like multi fork swapping) are niche. -/// If they executed during fuzzing, it will require a clone of the initial input database. This way -/// we can support these cheatcodes in fuzzing cheaply without adding overhead for fuzz tests that +/// If they executed, it will require a clone of the initial input database. +/// This way we can support these cheatcodes cheaply without adding overhead for tests that /// don't make use of them. Alternatively each test case would require its own `Backend` clone, /// which would add significant overhead for large fuzz sets even if the Database is not big after /// setup. -#[derive(Debug, Clone)] -pub struct FuzzBackendWrapper<'a> { - /// The underlying immutable `Backend` +#[derive(Clone, Debug)] +pub struct CowBackend<'a> { + /// The underlying `Backend`. /// - /// No calls on the `FuzzBackendWrapper` will ever persistently modify the `backend`'s state. + /// No calls on the `CowBackend` will ever persistently modify the `backend`'s state. pub backend: Cow<'a, Backend>, /// Keeps track of whether the backed is already initialized is_initialized: bool, + /// The [SpecId] of the current backend. + spec_id: SpecId, } -impl<'a> FuzzBackendWrapper<'a> { +impl<'a> CowBackend<'a> { + /// Creates a new `CowBackend` with the given `Backend`. pub fn new(backend: &'a Backend) -> Self { - Self { backend: Cow::Borrowed(backend), is_initialized: false } + Self { backend: Cow::Borrowed(backend), is_initialized: false, spec_id: SpecId::LATEST } } /// Executes the configured transaction of the `env` without committing state changes - pub fn inspect_ref( - &mut self, - env: &mut Env, - mut inspector: INSP, - ) -> eyre::Result - where - INSP: Inspector, - { + /// + /// Note: in case there are any cheatcodes executed that modify the environment, this will + /// update the given `env` with the new values. + pub fn inspect<'b, I: InspectorExt<&'b mut Self>>( + &'b mut self, + env: &mut EnvWithHandlerCfg, + inspector: I, + ) -> eyre::Result { // this is a new call to inspect with a new env, so even if we've cloned the backend // already, we reset the initialized state self.is_initialized = false; - match revm::evm_inner::(env, self, Some(&mut inspector)).transact() { - Ok(result) => Ok(result), - Err(e) => eyre::bail!("fuzz: failed to inspect: {e}"), - } + self.spec_id = env.handler_cfg.spec_id; + let mut evm = crate::utils::new_evm_with_inspector(self, env.clone(), inspector); + + let res = evm.transact().wrap_err("backend: failed while inspecting")?; + + env.env = evm.context.evm.inner.env; + + Ok(res) } - /// Returns whether there was a snapshot failure in the fuzz backend. + /// Returns whether there was a snapshot failure in the backend. /// /// This is bubbled up from the underlying Copy-On-Write backend when a revert occurs. pub fn has_snapshot_failure(&self) -> bool { @@ -77,17 +90,25 @@ impl<'a> FuzzBackendWrapper<'a> { fn backend_mut(&mut self, env: &Env) -> &mut Backend { if !self.is_initialized { let backend = self.backend.to_mut(); - backend.initialize(env); + let env = EnvWithHandlerCfg::new_with_spec_id(Box::new(env.clone()), self.spec_id); + backend.initialize(&env); self.is_initialized = true; return backend } self.backend.to_mut() } + + /// Returns a mutable instance of the Backend if it is initialized. + fn initialized_backend_mut(&mut self) -> Option<&mut Backend> { + if self.is_initialized { + return Some(self.backend.to_mut()) + } + None + } } -impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { +impl<'a> DatabaseExt for CowBackend<'a> { fn snapshot(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { - trace!("fuzz: create snapshot"); self.backend_mut(env).snapshot(journaled_state, env) } @@ -96,13 +117,26 @@ impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { id: U256, journaled_state: &JournaledState, current: &mut Env, + action: RevertSnapshotAction, ) -> Option { - trace!(?id, "fuzz: revert snapshot"); - self.backend_mut(current).revert(id, journaled_state, current) + self.backend_mut(current).revert(id, journaled_state, current, action) + } + + fn delete_snapshot(&mut self, id: U256) -> bool { + // delete snapshot requires a previous snapshot to be initialized + if let Some(backend) = self.initialized_backend_mut() { + return backend.delete_snapshot(id) + } + false + } + + fn delete_snapshots(&mut self) { + if let Some(backend) = self.initialized_backend_mut() { + backend.delete_snapshots() + } } fn create_fork(&mut self, fork: CreateFork) -> eyre::Result { - trace!("fuzz: create fork"); self.backend.to_mut().create_fork(fork) } @@ -111,7 +145,6 @@ impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { fork: CreateFork, transaction: B256, ) -> eyre::Result { - trace!(?transaction, "fuzz: create fork at"); self.backend.to_mut().create_fork_at_transaction(fork, transaction) } @@ -121,18 +154,16 @@ impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { env: &mut Env, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { - trace!(?id, "fuzz: select fork"); self.backend_mut(env).select_fork(id, env, journaled_state) } fn roll_fork( &mut self, id: Option, - block_number: U256, + block_number: u64, env: &mut Env, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { - trace!(?id, ?block_number, "fuzz: roll fork"); self.backend_mut(env).roll_fork(id, block_number, env, journaled_state) } @@ -143,11 +174,10 @@ impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { env: &mut Env, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { - trace!(?id, ?transaction, "fuzz: roll fork to transaction"); self.backend_mut(env).roll_fork_to_transaction(id, transaction, env, journaled_state) } - fn transact>( + fn transact>( &mut self, id: Option, transaction: B256, @@ -155,7 +185,6 @@ impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { journaled_state: &mut JournaledState, inspector: &mut I, ) -> eyre::Result<()> { - trace!(?id, ?transaction, "fuzz: execute transaction"); self.backend_mut(env).transact(id, transaction, env, journaled_state, inspector) } @@ -185,7 +214,7 @@ impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { fn load_allocs( &mut self, - allocs: &HashMap, + allocs: &BTreeMap, journaled_state: &mut JournaledState, ) -> Result<(), DatabaseError> { self.backend_mut(&Env::default()).load_allocs(allocs, journaled_state) @@ -207,16 +236,16 @@ impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { self.backend.to_mut().allow_cheatcode_access(account) } - fn revoke_cheatcode_access(&mut self, account: Address) -> bool { + fn revoke_cheatcode_access(&mut self, account: &Address) -> bool { self.backend.to_mut().revoke_cheatcode_access(account) } - fn has_cheatcode_access(&self, account: Address) -> bool { + fn has_cheatcode_access(&self, account: &Address) -> bool { self.backend.has_cheatcode_access(account) } } -impl<'a> DatabaseRef for FuzzBackendWrapper<'a> { +impl<'a> DatabaseRef for CowBackend<'a> { type Error = DatabaseError; fn basic_ref(&self, address: Address) -> Result, Self::Error> { @@ -236,7 +265,7 @@ impl<'a> DatabaseRef for FuzzBackendWrapper<'a> { } } -impl<'a> Database for FuzzBackendWrapper<'a> { +impl<'a> Database for CowBackend<'a> { type Error = DatabaseError; fn basic(&mut self, address: Address) -> Result, Self::Error> { @@ -255,3 +284,9 @@ impl<'a> Database for FuzzBackendWrapper<'a> { DatabaseRef::block_hash_ref(self, number) } } + +impl<'a> DatabaseCommit for CowBackend<'a> { + fn commit(&mut self, changes: Map) { + self.backend.to_mut().commit(changes) + } +} diff --git a/crates/evm/core/src/backend/diagnostic.rs b/crates/evm/core/src/backend/diagnostic.rs index 0dc788e830583..f4de9260ab565 100644 --- a/crates/evm/core/src/backend/diagnostic.rs +++ b/crates/evm/core/src/backend/diagnostic.rs @@ -4,7 +4,7 @@ use itertools::Itertools; use std::collections::HashMap; /// Represents possible diagnostic cases on revert -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub enum RevertDiagnostic { /// The `contract` does not exist on the `active` fork but exist on other fork(s) ContractExistsOnOtherForks { diff --git a/crates/evm/core/src/backend/error.rs b/crates/evm/core/src/backend/error.rs index b22c768d404da..f1f9733d494ee 100644 --- a/crates/evm/core/src/backend/error.rs +++ b/crates/evm/core/src/backend/error.rs @@ -1,6 +1,7 @@ use alloy_primitives::{Address, B256, U256}; -use ethers_core::types::BlockId; +use alloy_rpc_types::BlockId; use futures::channel::mpsc::{SendError, TrySendError}; +use revm::primitives::EVMError; use std::{ convert::Infallible, sync::{mpsc::RecvError, Arc}, @@ -15,11 +16,11 @@ pub type DatabaseResult = Result; pub enum DatabaseError { #[error("{0}")] Message(String), - #[error("no cheats available for {0}")] + #[error("cheatcodes are not enabled for {0}; see `vm.allowCheatcodes(address)`")] NoCheats(Address), - #[error("failed to fetch AccountInfo {0}")] + #[error("failed to fetch account info for {0}")] MissingAccount(Address), - #[error("code should already be loaded: {0}")] + #[error("missing bytecode for code hash {0}")] MissingCode(B256), #[error(transparent)] Recv(#[from] RecvError), @@ -46,6 +47,8 @@ pub enum DatabaseError { For a test environment, you can use `etch` to place the required bytecode at that address." )] MissingCreate2Deployer, + #[error("{0}")] + Other(String), } impl DatabaseError { @@ -76,6 +79,7 @@ impl DatabaseError { Self::BlockNotFound(_) | Self::TransactionNotFound(_) | Self::MissingCreate2Deployer => None, + DatabaseError::Other(_) => None, } } @@ -107,3 +111,13 @@ impl From for DatabaseError { match value {} } } + +// Note: this is mostly necessary to use some revm internals that return an [EVMError] +impl From> for DatabaseError { + fn from(err: EVMError) -> Self { + match err { + EVMError::Database(err) => err, + err => DatabaseError::Other(err.to_string()), + } + } +} diff --git a/crates/evm/core/src/backend/in_memory_db.rs b/crates/evm/core/src/backend/in_memory_db.rs index 6cb2c73eebdee..3b19d0fce00f1 100644 --- a/crates/evm/core/src/backend/in_memory_db.rs +++ b/crates/evm/core/src/backend/in_memory_db.rs @@ -84,10 +84,13 @@ impl DatabaseCommit for MemDb { /// `DbAccount`, this will be set to `AccountState::NotExisting` if the account does not exist yet. /// This is because there's a distinction between "non-existing" and "empty", /// see . -/// If an account is `NotExisting`, `Database(Ref)::basic` will always return `None` for the -/// requested `AccountInfo`. To prevent this, we ensure that a missing account is never marked as -/// `NotExisting` by always returning `Some` with this type. -#[derive(Debug, Default, Clone)] +/// If an account is `NotExisting`, `Database::basic_ref` will always return `None` for the +/// requested `AccountInfo`. +/// +/// To prevent this, we ensure that a missing account is never marked as `NotExisting` by always +/// returning `Some` with this type, which will then insert a default [`AccountInfo`] instead +/// of one marked as `AccountState::NotExisting`. +#[derive(Clone, Debug, Default)] pub struct EmptyDBWrapper(EmptyDB); impl DatabaseRef for EmptyDBWrapper { @@ -143,6 +146,7 @@ mod tests { let mut db = CacheDB::new(EmptyDB::default()); let address = Address::random(); + // We use `basic_ref` here to ensure that the account is not marked as `NotExisting`. let info = DatabaseRef::basic_ref(&db, address).unwrap(); assert!(info.is_none()); let mut info = info.unwrap_or_default(); @@ -165,6 +169,8 @@ mod tests { )); let info = Database::basic(&mut db, address).unwrap(); + // We know info exists, as MemDb always returns `Some(AccountInfo)` due to the + // `EmptyDbWrapper`. assert!(info.is_some()); let mut info = info.unwrap(); info.balance = U256::from(500u64); diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 8cc10795f3a0f..90e27444b2227 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -5,28 +5,27 @@ use crate::{ fork::{CreateFork, ForkId, MultiFork, SharedBackend}, snapshot::Snapshots, utils::configure_tx_env, + InspectorExt, }; -use alloy_primitives::{b256, keccak256, Address, B256, U256, U64}; -use ethers_core::{ - types::{Block, BlockNumber, Transaction}, - utils::GenesisAccount, -}; -use foundry_common::{ - is_known_system_sender, - types::{ToAlloy, ToEthers}, - SYSTEM_TRANSACTION_TYPE, -}; +use alloy_genesis::GenesisAccount; +use alloy_primitives::{b256, keccak256, Address, B256, U256}; +use alloy_rpc_types::{Block, BlockNumberOrTag, BlockTransactions, Transaction, WithOtherFields}; +use eyre::Context; +use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE}; use revm::{ db::{CacheDB, DatabaseRef}, inspectors::NoOpInspector, - precompile::{Precompiles, SpecId}, + precompile::{PrecompileSpecId, Precompiles}, primitives::{ - Account, AccountInfo, Bytecode, CreateScheme, Env, HashMap as Map, Log, ResultAndState, - StorageSlot, TransactTo, KECCAK_EMPTY, + Account, AccountInfo, Bytecode, CreateScheme, Env, EnvWithHandlerCfg, HashMap as Map, Log, + ResultAndState, SpecId, State, StorageSlot, TransactTo, KECCAK_EMPTY, }, - Database, DatabaseCommit, Inspector, JournaledState, EVM, + Database, DatabaseCommit, JournaledState, +}; +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + time::Instant, }; -use std::collections::{HashMap, HashSet}; mod diagnostic; pub use diagnostic::RevertDiagnostic; @@ -34,8 +33,8 @@ pub use diagnostic::RevertDiagnostic; mod error; pub use error::{DatabaseError, DatabaseResult}; -mod fuzz; -pub use fuzz::FuzzBackendWrapper; +mod cow; +pub use cow::CowBackend; mod in_memory_db; pub use in_memory_db::{EmptyDBWrapper, FoundryEvmInMemoryDB, MemDb}; @@ -65,12 +64,13 @@ const GLOBAL_FAILURE_SLOT: B256 = b256!("6661696c65640000000000000000000000000000000000000000000000000000"); /// An extension trait that allows us to easily extend the `revm::Inspector` capabilities +#[auto_impl::auto_impl(&mut)] pub trait DatabaseExt: Database { /// Creates a new snapshot at the current point of execution. /// /// A snapshot is associated with a new unique id that's created for the snapshot. - /// Snapshots can be reverted: [DatabaseExt::revert], however a snapshot can only be reverted - /// once. After a successful revert, the same snapshot id cannot be used again. + /// Snapshots can be reverted: [DatabaseExt::revert], however, depending on the + /// [RevertSnapshotAction], it will keep the snapshot alive or delete it. fn snapshot(&mut self, journaled_state: &JournaledState, env: &Env) -> U256; /// Reverts the snapshot if it exists @@ -82,14 +82,26 @@ pub trait DatabaseExt: Database { /// since the snapshots was created. This way we can show logs that were emitted between /// snapshot and its revert. /// This will also revert any changes in the `Env` and replace it with the captured `Env` of - /// `Self::snapshot` + /// `Self::snapshot`. + /// + /// Depending on [RevertSnapshotAction] it will keep the snapshot alive or delete it. fn revert( &mut self, id: U256, journaled_state: &JournaledState, env: &mut Env, + action: RevertSnapshotAction, ) -> Option; + /// Deletes the snapshot with the given `id` + /// + /// Returns `true` if the snapshot was successfully deleted, `false` if no snapshot for that id + /// exists. + fn delete_snapshot(&mut self, id: U256) -> bool; + + /// Deletes all snapshots. + fn delete_snapshots(&mut self); + /// Creates and also selects a new fork /// /// This is basically `create_fork` + `select_fork` @@ -155,7 +167,7 @@ pub trait DatabaseExt: Database { fn roll_fork( &mut self, id: Option, - block_number: U256, + block_number: u64, env: &mut Env, journaled_state: &mut JournaledState, ) -> eyre::Result<()>; @@ -177,14 +189,16 @@ pub trait DatabaseExt: Database { ) -> eyre::Result<()>; /// Fetches the given transaction for the fork and executes it, committing the state in the DB - fn transact>( + fn transact>( &mut self, id: Option, transaction: B256, env: &mut Env, journaled_state: &mut JournaledState, inspector: &mut I, - ) -> eyre::Result<()>; + ) -> eyre::Result<()> + where + Self: Sized; /// Returns the `ForkId` that's currently used in the database, if fork mode is on fn active_fork_id(&self) -> Option; @@ -192,21 +206,21 @@ pub trait DatabaseExt: Database { /// Returns the Fork url that's currently used in the database, if fork mode is on fn active_fork_url(&self) -> Option; - /// Whether the database is currently in forked + /// Whether the database is currently in forked mode. fn is_forked_mode(&self) -> bool { self.active_fork_id().is_some() } - /// Ensures that an appropriate fork exits + /// Ensures that an appropriate fork exists /// - /// If `id` contains a requested `Fork` this will ensure it exits. + /// If `id` contains a requested `Fork` this will ensure it exists. /// Otherwise, this returns the currently active fork. /// /// # Errors /// /// Returns an error if the given `id` does not match any forks /// - /// Returns an error if no fork exits + /// Returns an error if no fork exists fn ensure_fork(&self, id: Option) -> eyre::Result; /// Ensures that a corresponding `ForkId` exists for the given local `id` @@ -249,7 +263,7 @@ pub trait DatabaseExt: Database { /// Returns [Ok] if all accounts were successfully inserted into the journal, [Err] otherwise. fn load_allocs( &mut self, - allocs: &HashMap, + allocs: &BTreeMap, journaled_state: &mut JournaledState, ) -> Result<(), DatabaseError>; @@ -263,14 +277,20 @@ pub trait DatabaseExt: Database { fn add_persistent_account(&mut self, account: Address) -> bool; /// Removes persistent status from all given accounts - fn remove_persistent_accounts(&mut self, accounts: impl IntoIterator) { + fn remove_persistent_accounts(&mut self, accounts: impl IntoIterator) + where + Self: Sized, + { for acc in accounts { self.remove_persistent_account(&acc); } } /// Extends the persistent accounts with the accounts the iterator yields. - fn extend_persistent_accounts(&mut self, accounts: impl IntoIterator) { + fn extend_persistent_accounts(&mut self, accounts: impl IntoIterator) + where + Self: Sized, + { for acc in accounts { self.add_persistent_account(acc); } @@ -284,31 +304,33 @@ pub trait DatabaseExt: Database { /// Revokes cheatcode access for the given account /// /// Returns true if the `account` was previously allowed cheatcode access - fn revoke_cheatcode_access(&mut self, account: Address) -> bool; + fn revoke_cheatcode_access(&mut self, account: &Address) -> bool; /// Returns `true` if the given account is allowed to execute cheatcodes - fn has_cheatcode_access(&self, account: Address) -> bool; + fn has_cheatcode_access(&self, account: &Address) -> bool; /// Ensures that `account` is allowed to execute cheatcodes /// /// Returns an error if [`Self::has_cheatcode_access`] returns `false` - fn ensure_cheatcode_access(&self, account: Address) -> Result<(), DatabaseError> { + fn ensure_cheatcode_access(&self, account: &Address) -> Result<(), DatabaseError> { if !self.has_cheatcode_access(account) { - return Err(DatabaseError::NoCheats(account)) + return Err(DatabaseError::NoCheats(*account)); } Ok(()) } /// Same as [`Self::ensure_cheatcode_access()`] but only enforces it if the backend is currently /// in forking mode - fn ensure_cheatcode_access_forking_mode(&self, account: Address) -> Result<(), DatabaseError> { + fn ensure_cheatcode_access_forking_mode(&self, account: &Address) -> Result<(), DatabaseError> { if self.is_forked_mode() { - return self.ensure_cheatcode_access(account) + return self.ensure_cheatcode_access(account); } Ok(()) } } +struct _ObjectSafe(dyn DatabaseExt); + /// Provides the underlying `revm::Database` implementation. /// /// A `Backend` can be initialised in two forms: @@ -361,7 +383,7 @@ pub trait DatabaseExt: Database { /// **Note:** Snapshots work across fork-swaps, e.g. if fork `A` is currently active, then a /// snapshot is created before fork `B` is selected, then fork `A` will be the active fork again /// after reverting the snapshot. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct Backend { /// The access point for managing forks forks: MultiFork, @@ -396,8 +418,8 @@ pub struct Backend { impl Backend { /// Creates a new Backend with a spawned multi fork thread. - pub async fn spawn(fork: Option) -> Self { - Self::new(MultiFork::spawn().await, fork) + pub fn spawn(fork: Option) -> Self { + Self::new(MultiFork::spawn(), fork) } /// Creates a new instance of `Backend` @@ -440,16 +462,11 @@ impl Backend { /// Creates a new instance of `Backend` with fork added to the fork database and sets the fork /// as active - pub(crate) async fn new_with_fork( - id: &ForkId, - fork: Fork, - journaled_state: JournaledState, - ) -> Self { - let mut backend = Self::spawn(None).await; + pub(crate) fn new_with_fork(id: &ForkId, fork: Fork, journaled_state: JournaledState) -> Self { + let mut backend = Self::spawn(None); let fork_ids = backend.inner.insert_new_fork(id.clone(), fork.db, journaled_state); backend.inner.launched_with_fork = Some((id.clone(), fork_ids.0, fork_ids.1)); backend.active_fork_ids = Some(fork_ids); - backend } @@ -522,7 +539,7 @@ impl Backend { // toggle the previous sender if let Some(current) = self.inner.test_contract_address.take() { self.remove_persistent_account(¤t); - self.revoke_cheatcode_access(acc); + self.revoke_cheatcode_access(&acc); } self.add_persistent_account(acc); @@ -541,8 +558,8 @@ impl Backend { /// Sets the current spec id pub fn set_spec_id(&mut self, spec_id: SpecId) -> &mut Self { - trace!("setting precompile id"); - self.inner.precompile_id = spec_id; + trace!(?spec_id, "setting spec ID"); + self.inner.spec_id = spec_id; self } @@ -614,7 +631,7 @@ impl Backend { .cloned() .unwrap_or_default() .present_value(); - return value.as_le_bytes()[1] != 0 + return value.as_le_bytes()[1] != 0; } false @@ -627,7 +644,7 @@ impl Backend { if let Some(account) = current_state.state.get(&CHEATCODE_ADDRESS) { let slot: U256 = GLOBAL_FAILURE_SLOT.into(); let value = account.storage.get(&slot).cloned().unwrap_or_default().present_value(); - return value == revm::primitives::U256::from(1) + return value == revm::primitives::U256::from(1); } false @@ -729,7 +746,7 @@ impl Backend { all_logs.extend(f.journaled_state.logs.clone()) } }); - return all_logs + return all_logs; } logs @@ -738,9 +755,9 @@ impl Backend { /// Initializes settings we need to keep track of. /// /// We need to track these mainly to prevent issues when switching between different evms - pub(crate) fn initialize(&mut self, env: &Env) { + pub(crate) fn initialize(&mut self, env: &EnvWithHandlerCfg) { self.set_caller(env.tx.caller); - self.set_spec_id(SpecId::from_spec_id(env.cfg.spec_id)); + self.set_spec_id(env.handler_cfg.spec_id); let test_contract = match env.tx.transact_to { TransactTo::Call(to) => to, @@ -755,21 +772,28 @@ impl Backend { self.set_test_contract(test_contract); } - /// Executes the configured test call of the `env` without committing state changes - pub fn inspect_ref( - &mut self, - env: &mut Env, - mut inspector: INSP, - ) -> eyre::Result - where - INSP: Inspector, - { + /// Returns the `EnvWithHandlerCfg` with the current `spec_id` set. + fn env_with_handler_cfg(&self, env: Env) -> EnvWithHandlerCfg { + EnvWithHandlerCfg::new_with_spec_id(Box::new(env), self.inner.spec_id) + } + + /// Executes the configured test call of the `env` without committing state changes. + /// + /// Note: in case there are any cheatcodes executed that modify the environment, this will + /// update the given `env` with the new values. + pub fn inspect<'a, I: InspectorExt<&'a mut Self>>( + &'a mut self, + env: &mut EnvWithHandlerCfg, + inspector: I, + ) -> eyre::Result { self.initialize(env); + let mut evm = crate::utils::new_evm_with_inspector(self, env.clone(), inspector); - match revm::evm_inner::(env, self, Some(&mut inspector)).transact() { - Ok(res) => Ok(res), - Err(e) => eyre::bail!("backend: failed while inspecting: {e}"), - } + let res = evm.transact().wrap_err("backend: failed while inspecting")?; + + env.env = evm.context.evm.inner.env; + + Ok(res) } /// Returns true if the address is a precompile @@ -777,6 +801,13 @@ impl Backend { self.inner.precompiles().contains(addr) } + /// Sets the initial journaled state to use when initializing forks + #[inline] + fn set_init_journaled_state(&mut self, journaled_state: JournaledState) { + trace!("recording fork init journaled_state"); + self.fork_init_journaled_state = journaled_state; + } + /// Cleans up already loaded accounts that would be initialized without the correct data from /// the fork. /// @@ -800,10 +831,21 @@ impl Backend { let mut journaled_state = self.fork_init_journaled_state.clone(); for loaded_account in loaded_accounts.iter().copied() { trace!(?loaded_account, "replacing account on init"); - let fork_account = Database::basic(&mut fork.db, loaded_account)? - .ok_or(DatabaseError::MissingAccount(loaded_account))?; let init_account = journaled_state.state.get_mut(&loaded_account).expect("exists; qed"); + + // here's an edge case where we need to check if this account has been created, in + // which case we don't need to replace it with the account from the fork because the + // created account takes precedence: for example contract creation in setups + if init_account.is_created() { + trace!(?loaded_account, "skipping created account"); + continue + } + + // otherwise we need to replace the account's info with the one from the fork's + // database + let fork_account = Database::basic(&mut fork.db, loaded_account)? + .ok_or(DatabaseError::MissingAccount(loaded_account))?; init_account.info = fork_account; } fork.journaled_state = journaled_state; @@ -816,26 +858,27 @@ impl Backend { &self, id: LocalForkId, transaction: B256, - ) -> eyre::Result<(U64, Block)> { + ) -> eyre::Result<(u64, Block)> { let fork = self.inner.get_fork_by_id(id)?; let tx = fork.db.db.get_transaction(transaction)?; // get the block number we need to fork if let Some(tx_block) = tx.block_number { - let block = fork.db.db.get_full_block(BlockNumber::Number(tx_block))?; + let block = fork.db.db.get_full_block(tx_block)?; // we need to subtract 1 here because we want the state before the transaction // was mined let fork_block = tx_block - 1; - Ok((U64::from(fork_block.as_u64()), block)) + Ok((fork_block, block)) } else { - let block = fork.db.db.get_full_block(BlockNumber::Latest)?; + let block = fork.db.db.get_full_block(BlockNumberOrTag::Latest)?; let number = block + .header .number - .ok_or_else(|| DatabaseError::BlockNotFound(BlockNumber::Latest.into()))?; + .ok_or_else(|| DatabaseError::BlockNotFound(BlockNumberOrTag::Latest.into()))?; - Ok((U64::from(number.as_u64()), block)) + Ok((number, block)) } } @@ -853,28 +896,36 @@ impl Backend { let fork_id = self.ensure_fork_id(id)?.clone(); + let env = self.env_with_handler_cfg(env); let fork = self.inner.get_fork_by_id_mut(id)?; - let full_block = fork - .db - .db - .get_full_block(BlockNumber::Number(env.block.number.to_ethers().as_u64().into()))?; - - for tx in full_block.transactions.into_iter() { - // System transactions such as on L2s don't contain any pricing info so we skip them - // otherwise this would cause reverts - if is_known_system_sender(tx.from.to_alloy()) || - tx.transaction_type.map(|ty| ty.as_u64()) == Some(SYSTEM_TRANSACTION_TYPE) - { - continue - } + let full_block = fork.db.db.get_full_block(env.block.number.to::())?; + + if let BlockTransactions::Full(txs) = full_block.transactions { + for tx in txs.into_iter() { + // System transactions such as on L2s don't contain any pricing info so we skip them + // otherwise this would cause reverts + if is_known_system_sender(tx.from) || + tx.transaction_type == Some(SYSTEM_TRANSACTION_TYPE) + { + trace!(tx=?tx.hash, "skipping system transaction"); + continue; + } - if tx.hash == tx_hash.to_ethers() { - // found the target transaction - return Ok(Some(tx)) + if tx.hash == tx_hash { + // found the target transaction + return Ok(Some(tx)) + } + trace!(tx=?tx.hash, "committing transaction"); + + commit_transaction( + WithOtherFields::new(tx), + env.clone(), + journaled_state, + fork, + &fork_id, + &mut NoOpInspector, + )?; } - trace!(tx=?tx.hash, "committing transaction"); - - commit_transaction(tx, env.clone(), journaled_state, fork, &fork_id, NoOpInspector)?; } Ok(None) @@ -900,11 +951,14 @@ impl DatabaseExt for Backend { id: U256, current_state: &JournaledState, current: &mut Env, + action: RevertSnapshotAction, ) -> Option { trace!(?id, "revert snapshot"); if let Some(mut snapshot) = self.inner.snapshots.remove_at(id) { // Re-insert snapshot to persist it - self.inner.snapshots.insert_at(snapshot.clone(), id); + if action.is_keep() { + self.inner.snapshots.insert_at(snapshot.clone(), id); + } // need to check whether there's a global failure which means an error occurred either // during the snapshot or even before if self.is_global_failure(current_state) { @@ -923,7 +977,7 @@ impl DatabaseExt for Backend { // another caller, so we need to ensure the caller account is present in the // journaled state and database let caller = current.tx.caller; - if !journaled_state.state.contains_key(&caller) { + journaled_state.state.entry(caller).or_insert_with(|| { let caller_account = current_state .state .get(&caller) @@ -934,8 +988,8 @@ impl DatabaseExt for Backend { // update the caller account which is required by the evm fork.db.insert_account_info(caller, caller_account.clone()); } - journaled_state.state.insert(caller, caller_account.into()); - } + caller_account.into() + }); self.inner.revert_snapshot(id, fork_id, idx, *fork); self.active_fork_ids = Some((id, idx)) } @@ -951,20 +1005,17 @@ impl DatabaseExt for Backend { } } - fn create_fork(&mut self, mut create_fork: CreateFork) -> eyre::Result { + fn delete_snapshot(&mut self, id: U256) -> bool { + self.inner.snapshots.remove_at(id).is_some() + } + + fn delete_snapshots(&mut self) { + self.inner.snapshots.clear() + } + + fn create_fork(&mut self, create_fork: CreateFork) -> eyre::Result { trace!("create fork"); - let (fork_id, fork, _) = self.forks.create_fork(create_fork.clone())?; - - // Check for an edge case where the fork_id already exists, which would mess with the - // internal mappings. This can happen when two forks are created with the same - // endpoint and block number - // This is a hacky solution but a simple fix to ensure URLs are unique - if self.inner.contains_fork(&fork_id) { - // ensure URL is unique - create_fork.url.push('/'); - debug!(?fork_id, "fork id already exists. making unique"); - return self.create_fork(create_fork) - } + let (fork_id, fork, _) = self.forks.create_fork(create_fork)?; let fork_db = ForkDB::new(fork); let (id, _) = @@ -1007,7 +1058,7 @@ impl DatabaseExt for Backend { trace!(?id, "select fork"); if self.is_active_fork(id) { // nothing to do - return Ok(()) + return Ok(()); } let fork_id = self.ensure_fork_id(id).cloned()?; @@ -1043,8 +1094,8 @@ impl DatabaseExt for Backend { // different forks. Since the `JournaledState` is valid for all forks until the // first fork is selected, we need to update it for all forks and use it as init state // for all future forks - trace!("recording fork init journaled_state"); - self.fork_init_journaled_state = active_journaled_state.clone(); + + self.set_init_journaled_state(active_journaled_state.clone()); self.prepare_init_journal_state()?; // Make sure that the next created fork has a depth of 0. @@ -1065,7 +1116,7 @@ impl DatabaseExt for Backend { // necessarily the same caller as for the test, however we must always // ensure that fork's state contains the current sender let caller = env.tx.caller; - if !fork.journaled_state.state.contains_key(&caller) { + fork.journaled_state.state.entry(caller).or_insert_with(|| { let caller_account = active_journaled_state .state .get(&env.tx.caller) @@ -1076,8 +1127,8 @@ impl DatabaseExt for Backend { // update the caller account which is required by the evm fork.db.insert_account_info(caller, caller_account.clone()); } - fork.journaled_state.state.insert(caller, caller_account.into()); - } + caller_account.into() + }); self.update_fork_db(active_journaled_state, &mut fork); @@ -1092,18 +1143,19 @@ impl DatabaseExt for Backend { Ok(()) } - /// This is effectively the same as [`Self::create_select_fork()`] but updating an existing fork + /// This is effectively the same as [`Self::create_select_fork()`] but updating an existing + /// [ForkId] that is mapped to the [LocalForkId] fn roll_fork( &mut self, id: Option, - block_number: U256, + block_number: u64, env: &mut Env, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { trace!(?id, ?block_number, "roll fork"); let id = self.ensure_fork(id)?; let (fork_id, backend, fork_env) = - self.forks.roll_fork(self.inner.ensure_fork_id(id).cloned()?, block_number.to())?; + self.forks.roll_fork(self.inner.ensure_fork_id(id).cloned()?, block_number)?; // this will update the local mapping self.inner.roll_fork(id, fork_id, backend)?; @@ -1165,16 +1217,9 @@ impl DatabaseExt for Backend { self.get_block_number_and_block_for_transaction(id, transaction)?; // roll the fork to the transaction's block or latest if it's pending - self.roll_fork(Some(id), fork_block.to(), env, journaled_state)?; + self.roll_fork(Some(id), fork_block, env, journaled_state)?; - // update the block's env accordingly - env.block.timestamp = block.timestamp.to_alloy(); - env.block.coinbase = block.author.unwrap_or_default().to_alloy(); - env.block.difficulty = block.difficulty.to_alloy(); - env.block.prevrandao = block.mix_hash.map(|h| h.to_alloy()); - env.block.basefee = block.base_fee_per_gas.unwrap_or_default().to_alloy(); - env.block.gas_limit = block.gas_limit.to_alloy(); - env.block.number = block.number.map(|n| n.to_alloy()).unwrap_or(fork_block).to(); + update_env_block(env, fork_block, &block); // replay all transactions that came before let env = env.clone(); @@ -1184,7 +1229,7 @@ impl DatabaseExt for Backend { Ok(()) } - fn transact>( + fn transact>( &mut self, maybe_id: Option, transaction: B256, @@ -1196,17 +1241,20 @@ impl DatabaseExt for Backend { let id = self.ensure_fork(maybe_id)?; let fork_id = self.ensure_fork_id(id).cloned()?; - let env = if maybe_id.is_none() { - self.forks - .get_env(fork_id.clone())? - .ok_or_else(|| eyre::eyre!("Requested fork `{}` does not exit", id))? - } else { - env.clone() + let tx = { + let fork = self.inner.get_fork_by_id_mut(id)?; + fork.db.db.get_transaction(transaction)? }; - let fork = self.inner.get_fork_by_id_mut(id)?; - let tx = fork.db.db.get_transaction(transaction)?; + // This is a bit ambiguous because the user wants to transact an arbitrary transaction in the current context, but we're assuming the user wants to transact the transaction as it was mined. Usually this is used in a combination of a fork at the transaction's parent transaction in the block and then the transaction is transacted: + // So we modify the env to match the transaction's block + let (fork_block, block) = + self.get_block_number_and_block_for_transaction(id, transaction)?; + let mut env = env.clone(); + update_env_block(&mut env, fork_block, &block); + let env = self.env_with_handler_cfg(env); + let fork = self.inner.get_fork_by_id_mut(id)?; commit_transaction(tx, env, journaled_state, fork, &fork_id, inspector) } @@ -1222,7 +1270,7 @@ impl DatabaseExt for Backend { fn ensure_fork(&self, id: Option) -> eyre::Result { if let Some(id) = id { if self.inner.issued_local_fork_ids.contains_key(&id) { - return Ok(id) + return Ok(id); } eyre::bail!("Requested fork `{}` does not exit", id) } @@ -1248,7 +1296,7 @@ impl DatabaseExt for Backend { if self.inner.forks.len() == 1 { // we only want to provide additional diagnostics here when in multifork mode with > 1 // forks - return None + return None; } if !active_fork.is_contract(callee) && !is_contract_in_state(journaled_state, callee) { @@ -1275,7 +1323,7 @@ impl DatabaseExt for Backend { active: active_id, available_on, }) - } + }; } None } @@ -1285,7 +1333,7 @@ impl DatabaseExt for Backend { /// Returns [Ok] if all accounts were successfully inserted into the journal, [Err] otherwise. fn load_allocs( &mut self, - allocs: &HashMap, + allocs: &BTreeMap, journaled_state: &mut JournaledState, ) -> Result<(), DatabaseError> { // Loop through all of the allocs defined in the map and commit them to the journal. @@ -1323,7 +1371,7 @@ impl DatabaseExt for Backend { } // Set the account's nonce and balance. state_acc.info.nonce = acc.nonce.unwrap_or_default(); - state_acc.info.balance = acc.balance.to_alloy(); + state_acc.info.balance = acc.balance; // Touch the account to ensure the loaded information persists if called in `setUp`. journaled_state.touch(addr); @@ -1332,8 +1380,9 @@ impl DatabaseExt for Backend { Ok(()) } - fn is_persistent(&self, acc: &Address) -> bool { - self.inner.persistent_accounts.contains(acc) + fn add_persistent_account(&mut self, account: Address) -> bool { + trace!(?account, "add persistent account"); + self.inner.persistent_accounts.insert(account) } fn remove_persistent_account(&mut self, account: &Address) -> bool { @@ -1341,9 +1390,8 @@ impl DatabaseExt for Backend { self.inner.persistent_accounts.remove(account) } - fn add_persistent_account(&mut self, account: Address) -> bool { - trace!(?account, "add persistent account"); - self.inner.persistent_accounts.insert(account) + fn is_persistent(&self, acc: &Address) -> bool { + self.inner.persistent_accounts.contains(acc) } fn allow_cheatcode_access(&mut self, account: Address) -> bool { @@ -1351,13 +1399,13 @@ impl DatabaseExt for Backend { self.inner.cheatcode_access_accounts.insert(account) } - fn revoke_cheatcode_access(&mut self, account: Address) -> bool { + fn revoke_cheatcode_access(&mut self, account: &Address) -> bool { trace!(?account, "revoke cheatcode access"); - self.inner.cheatcode_access_accounts.remove(&account) + self.inner.cheatcode_access_accounts.remove(account) } - fn has_cheatcode_access(&self, account: Address) -> bool { - self.inner.cheatcode_access_accounts.contains(&account) + fn has_cheatcode_access(&self, account: &Address) -> bool { + self.inner.cheatcode_access_accounts.contains(account) } } @@ -1443,7 +1491,7 @@ impl Database for Backend { } /// Variants of a [revm::Database] -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub enum BackendDatabaseSnapshot { /// Simple in-memory [revm::Database] InMemory(FoundryEvmInMemoryDB), @@ -1452,7 +1500,7 @@ pub enum BackendDatabaseSnapshot { } /// Represents a fork -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct Fork { db: ForkDB, journaled_state: JournaledState, @@ -1465,7 +1513,7 @@ impl Fork { pub fn is_contract(&self, acc: Address) -> bool { if let Ok(Some(acc)) = self.db.basic_ref(acc) { if acc.code_hash != KECCAK_EMPTY { - return true + return true; } } is_contract_in_state(&self.journaled_state, acc) @@ -1473,7 +1521,7 @@ impl Fork { } /// Container type for various Backend related data -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct BackendInner { /// Stores the `ForkId` of the fork the `Backend` launched with from the start. /// @@ -1525,8 +1573,8 @@ pub struct BackendInner { /// /// See also [`clone_data()`] pub persistent_accounts: HashSet

, - /// The configured precompile spec id - pub precompile_id: revm::precompile::SpecId, + /// The configured spec id + pub spec_id: SpecId, /// All accounts that are allowed to execute cheatcodes pub cheatcode_access_accounts: HashSet
, } @@ -1534,11 +1582,6 @@ pub struct BackendInner { // === impl BackendInner === impl BackendInner { - /// Returns `true` if the given [ForkId] already exists. - fn contains_fork(&self, id: &ForkId) -> bool { - self.created_forks.contains_key(id) - } - pub fn ensure_fork_id(&self, id: LocalForkId) -> eyre::Result<&ForkId> { self.issued_local_fork_ids .get(&id) @@ -1694,29 +1737,12 @@ impl BackendInner { } pub fn precompiles(&self) -> &'static Precompiles { - Precompiles::new(self.precompile_id) + Precompiles::new(PrecompileSpecId::from_spec_id(self.spec_id)) } /// Returns a new, empty, `JournaledState` with set precompiles pub fn new_journaled_state(&self) -> JournaledState { - /// Helper function to convert from a `revm::precompile::SpecId` into a - /// `revm::primitives::SpecId` This only matters if the spec is Cancun or later, or - /// pre-Spurious Dragon. - fn precompiles_spec_id_to_primitives_spec_id(spec: SpecId) -> revm::primitives::SpecId { - match spec { - SpecId::HOMESTEAD => revm::primitives::SpecId::HOMESTEAD, - SpecId::BYZANTIUM => revm::primitives::SpecId::BYZANTIUM, - SpecId::ISTANBUL => revm::primitives::ISTANBUL, - SpecId::BERLIN => revm::primitives::BERLIN, - SpecId::CANCUN => revm::primitives::CANCUN, - // Point latest to berlin for now, as we don't wanna accidentally point to Cancun. - SpecId::LATEST => revm::primitives::BERLIN, - } - } - JournaledState::new( - precompiles_spec_id_to_primitives_spec_id(self.precompile_id), - self.precompiles().addresses().into_iter().copied().collect(), - ) + JournaledState::new(self.spec_id, self.precompiles().addresses().copied().collect()) } } @@ -1733,7 +1759,7 @@ impl Default for BackendInner { caller: None, next_fork_id: Default::default(), persistent_accounts: Default::default(), - precompile_id: revm::precompile::SpecId::LATEST, + spec_id: SpecId::LATEST, // grant the cheatcode,default test and caller address access to execute cheatcodes // itself cheatcode_access_accounts: HashSet::from([ @@ -1749,6 +1775,7 @@ impl Default for BackendInner { pub(crate) fn update_current_env_with_fork_env(current: &mut Env, fork: Env) { current.block = fork.block; current.cfg = fork.cfg; + current.tx.chain_id = fork.tx.chain_id; } /// Clones the data of the given `accounts` from the `active` database into the `fork_db` @@ -1803,7 +1830,7 @@ fn merge_db_account_data( acc } else { // Account does not exist - return + return; }; if let Some(code) = active.contracts.get(&acc.info.code_hash).cloned() { @@ -1829,35 +1856,53 @@ fn is_contract_in_state(journaled_state: &JournaledState, acc: Address) -> bool .unwrap_or_default() } +/// Updates the env's block with the block's data +fn update_env_block(env: &mut Env, fork_block: u64, block: &Block) { + env.block.timestamp = U256::from(block.header.timestamp); + env.block.coinbase = block.header.miner; + env.block.difficulty = block.header.difficulty; + env.block.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); + env.block.basefee = U256::from(block.header.base_fee_per_gas.unwrap_or_default()); + env.block.gas_limit = U256::from(block.header.gas_limit); + env.block.number = U256::from(block.header.number.unwrap_or(fork_block)); +} + /// Executes the given transaction and commits state changes to the database _and_ the journaled /// state, with an optional inspector -fn commit_transaction>( - tx: Transaction, - mut env: Env, +fn commit_transaction>( + tx: WithOtherFields, + mut env: EnvWithHandlerCfg, journaled_state: &mut JournaledState, fork: &mut Fork, fork_id: &ForkId, inspector: I, ) -> eyre::Result<()> { - configure_tx_env(&mut env, &tx); - - let state = { - let mut evm = EVM::new(); - evm.env = env; + configure_tx_env(&mut env.env, &tx); + let now = Instant::now(); + let res = { let fork = fork.clone(); let journaled_state = journaled_state.clone(); - let db = crate::utils::RuntimeOrHandle::new() - .block_on(async move { Backend::new_with_fork(fork_id, fork, journaled_state).await }); - evm.database(db); + let db = Backend::new_with_fork(fork_id, fork, journaled_state); + crate::utils::new_evm_with_inspector(db, env, inspector) + .transact() + .wrap_err("backend: failed committing transaction")? + }; + trace!(elapsed = ?now.elapsed(), "transacted transaction"); - match evm.inspect(inspector) { - Ok(res) => res.state, - Err(e) => eyre::bail!("backend: failed committing transaction: {e}"), + apply_state_changeset(res.state, journaled_state, fork)?; + Ok(()) +} + +/// Helper method which updates data in the state with the data from the database. +pub fn update_state(state: &mut State, db: &mut DB) -> Result<(), DB::Error> { + for (addr, acc) in state.iter_mut() { + acc.info = db.basic(*addr)?.unwrap_or_default(); + for (key, val) in acc.storage.iter_mut() { + val.present_value = db.storage(*addr, *key)?; } - }; + } - apply_state_changeset(state, journaled_state, fork); Ok(()) } @@ -1867,19 +1912,12 @@ fn apply_state_changeset( state: Map, journaled_state: &mut JournaledState, fork: &mut Fork, -) { - let changed_accounts = state.keys().copied().collect::>(); +) -> Result<(), DatabaseError> { // commit the state and update the loaded accounts fork.db.commit(state); - for addr in changed_accounts { - // reload all changed accounts by removing them from the journaled state and reloading them - // from the now updated database - if journaled_state.state.remove(&addr).is_some() { - let _ = journaled_state.load_account(addr, &mut fork.db); - } - if fork.journaled_state.state.remove(&addr).is_some() { - let _ = fork.journaled_state.load_account(addr, &mut fork.db); - } - } + update_state(&mut journaled_state.state, &mut fork.db)?; + update_state(&mut fork.journaled_state.state, &mut fork.db)?; + + Ok(()) } diff --git a/crates/evm/core/src/backend/snapshot.rs b/crates/evm/core/src/backend/snapshot.rs index 8ce506d0f0714..4c0c665f299ec 100644 --- a/crates/evm/core/src/backend/snapshot.rs +++ b/crates/evm/core/src/backend/snapshot.rs @@ -1,16 +1,16 @@ use alloy_primitives::{Address, B256, U256}; use revm::{ - primitives::{AccountInfo, Env, HashMap as Map}, + primitives::{AccountInfo, Env, HashMap}, JournaledState, }; use serde::{Deserialize, Serialize}; /// A minimal abstraction of a state at a certain point in time -#[derive(Default, Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct StateSnapshot { - pub accounts: Map, - pub storage: Map>, - pub block_hashes: Map, + pub accounts: HashMap, + pub storage: HashMap>, + pub block_hashes: HashMap, } /// Represents a snapshot taken during evm execution @@ -39,14 +39,14 @@ impl BackendSnapshot { /// journaled_state includes the same logs, we can simply replace use that See also /// `DatabaseExt::revert` pub fn merge(&mut self, current: &JournaledState) { - self.journaled_state.logs = current.logs.clone(); + self.journaled_state.logs.clone_from(¤t.logs); } } /// What to do when reverting a snapshot /// /// Whether to remove the snapshot or keep it -#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum RevertSnapshotAction { /// Remove the snapshot after reverting #[default] diff --git a/crates/evm/core/src/debug.rs b/crates/evm/core/src/debug.rs index ce2f2f03afcbf..056bde54cfc61 100644 --- a/crates/evm/core/src/debug.rs +++ b/crates/evm/core/src/debug.rs @@ -1,11 +1,12 @@ -use crate::utils::CallKind; -use alloy_primitives::{Address, U256}; +use crate::opcodes; +use alloy_primitives::{Address, Bytes, U256}; +use arrayvec::ArrayVec; use revm::interpreter::OpCode; +use revm_inspectors::tracing::types::CallKind; use serde::{Deserialize, Serialize}; -use std::fmt::Display; /// An arena of [DebugNode]s -#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct DebugArena { /// The arena of nodes pub arena: Vec, @@ -64,7 +65,7 @@ impl DebugArena { /// - An enum denoting the type of call this is /// /// This makes it easy to pretty print the execution steps. - pub fn flatten(&self, entry: usize) -> Vec<(Address, Vec, CallKind)> { + pub fn flatten(&self, entry: usize) -> Vec { let mut flattened = Vec::new(); self.flatten_to(entry, &mut flattened); flattened @@ -73,11 +74,11 @@ impl DebugArena { /// Recursively traverses the tree of debug nodes and flattens it into the given list. /// /// See [`flatten`](Self::flatten) for more information. - pub fn flatten_to(&self, entry: usize, out: &mut Vec<(Address, Vec, CallKind)>) { + pub fn flatten_to(&self, entry: usize, out: &mut Vec) { let node = &self.arena[entry]; if !node.steps.is_empty() { - out.push((node.address, node.steps.clone(), node.kind)); + out.push(node.flat()); } for child in &node.children { @@ -86,31 +87,76 @@ impl DebugArena { } } -/// A node in the arena -#[derive(Default, Debug, Clone, Serialize, Deserialize)] +/// A node in the arena. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct DebugNode { - /// Parent node index in the arena + /// Parent node index in the arena. pub parent: Option, - /// Children node indexes in the arena + /// Children node indexes in the arena. pub children: Vec, - /// Location in parent + /// Location in parent. pub location: usize, /// Execution context. /// /// Note that this is the address of the *code*, not necessarily the address of the storage. pub address: Address, - /// The kind of call this is + /// The kind of call this is. pub kind: CallKind, - /// Depth + /// Depth of the call. pub depth: usize, - /// The debug steps + /// The debug steps. pub steps: Vec, } +impl From for DebugNodeFlat { + #[inline] + fn from(node: DebugNode) -> Self { + node.into_flat() + } +} + +impl From<&DebugNode> for DebugNodeFlat { + #[inline] + fn from(node: &DebugNode) -> Self { + node.flat() + } +} + impl DebugNode { + /// Creates a new debug node. pub fn new(address: Address, depth: usize, steps: Vec) -> Self { Self { address, depth, steps, ..Default::default() } } + + /// Flattens this node into a [`DebugNodeFlat`]. + pub fn flat(&self) -> DebugNodeFlat { + DebugNodeFlat { address: self.address, kind: self.kind, steps: self.steps.clone() } + } + + /// Flattens this node into a [`DebugNodeFlat`]. + pub fn into_flat(self) -> DebugNodeFlat { + DebugNodeFlat { address: self.address, kind: self.kind, steps: self.steps } + } +} + +/// Flattened [`DebugNode`] from an arena. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct DebugNodeFlat { + /// Execution context. + /// + /// Note that this is the address of the *code*, not necessarily the address of the storage. + pub address: Address, + /// The kind of call this is. + pub kind: CallKind, + /// The debug steps. + pub steps: Vec, +} + +impl DebugNodeFlat { + /// Creates a new debug node flat. + pub fn new(address: Address, kind: CallKind, steps: Vec) -> Self { + Self { address, kind, steps } + } } /// A `DebugStep` is a snapshot of the EVM's runtime state. @@ -118,16 +164,22 @@ impl DebugNode { /// It holds the current program counter (where in the program you are), /// the stack and memory (prior to the opcodes execution), any bytes to be /// pushed onto the stack, and the instruction counter for use with sourcemap. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct DebugStep { /// Stack *prior* to running the associated opcode pub stack: Vec, /// Memory *prior* to running the associated opcode - pub memory: Vec, + pub memory: Bytes, + /// Calldata *prior* to running the associated opcode + pub calldata: Bytes, + /// Returndata *prior* to running the associated opcode + pub returndata: Bytes, /// Opcode to be executed - pub instruction: Instruction, - /// Optional bytes that are being pushed onto the stack - pub push_bytes: Option>, + pub instruction: u8, + /// Optional bytes that are being pushed onto the stack. + /// Empty if the opcode is not a push or PUSH0. + #[serde(serialize_with = "hex::serialize", deserialize_with = "deserialize_arrayvec_hex")] + pub push_bytes: ArrayVec, /// The program counter at this step. /// /// Note: To map this step onto source code using a source map, you must convert the program @@ -142,8 +194,10 @@ impl Default for DebugStep { Self { stack: vec![], memory: Default::default(), - instruction: Instruction::OpCode(revm::interpreter::opcode::INVALID), - push_bytes: None, + calldata: Default::default(), + returndata: Default::default(), + instruction: revm::interpreter::opcode::INVALID, + push_bytes: Default::default(), pc: 0, total_gas_used: 0, } @@ -153,48 +207,25 @@ impl Default for DebugStep { impl DebugStep { /// Pretty print the step's opcode pub fn pretty_opcode(&self) -> String { - if let Some(push_bytes) = &self.push_bytes { - format!("{}(0x{})", self.instruction, hex::encode(push_bytes)) + let instruction = OpCode::new(self.instruction).map_or("INVALID", |op| op.as_str()); + if !self.push_bytes.is_empty() { + format!("{instruction}(0x{})", hex::encode(&self.push_bytes)) } else { - self.instruction.to_string() + instruction.to_string() } } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub enum Instruction { - OpCode(u8), - Cheatcode([u8; 4]), -} -impl From for Instruction { - fn from(op: u8) -> Instruction { - Instruction::OpCode(op) + /// Returns `true` if the opcode modifies memory. + pub fn opcode_modifies_memory(&self) -> bool { + OpCode::new(self.instruction).map_or(false, opcodes::modifies_memory) } } -impl Display for Instruction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Instruction::OpCode(op) => write!( - f, - "{}", - OpCode::new(*op).map_or_else( - || format!("UNDEFINED(0x{op:02x})"), - |opcode| opcode.as_str().to_string(), - ) - ), - Instruction::Cheatcode(cheat) => write!( - f, - "VM_{}", - crate::abi::Vm::CHEATCODES - .iter() - .map(|c| &c.func) - .find(|c| c.selector_bytes == *cheat) - .expect("unknown cheatcode found in debugger") - .id - .to_uppercase() - ), - } - } +fn deserialize_arrayvec_hex<'de, D: serde::Deserializer<'de>>( + deserializer: D, +) -> Result, D::Error> { + let bytes: Vec = hex::deserialize(deserializer)?; + let mut array = ArrayVec::new(); + array.try_extend_from_slice(&bytes).map_err(serde::de::Error::custom)?; + Ok(array) } diff --git a/crates/evm/core/src/decode.rs b/crates/evm/core/src/decode.rs index 3d09bb51b7d74..bf82c2d349208 100644 --- a/crates/evm/core/src/decode.rs +++ b/crates/evm/core/src/decode.rs @@ -2,12 +2,13 @@ use crate::abi::{Console, Vm}; use alloy_dyn_abi::JsonAbiExt; -use alloy_json_abi::JsonAbi; +use alloy_json_abi::{Error, JsonAbi}; +use alloy_primitives::{Log, Selector}; use alloy_sol_types::{SolCall, SolError, SolEventInterface, SolInterface, SolValue}; -use ethers_core::types::Log; use foundry_common::SELECTOR_LEN; use itertools::Itertools; use revm::interpreter::InstructionResult; +use std::{collections::HashMap, sync::OnceLock}; /// Decode a set of logs, only returning logs from DSTest logging events and Hardhat's `console.log` pub fn decode_console_logs(logs: &[Log]) -> Vec { @@ -18,125 +19,194 @@ pub fn decode_console_logs(logs: &[Log]) -> Vec { /// /// This function returns [None] if it is not a DSTest log or the result of a Hardhat /// `console.log`. -#[instrument(level = "debug", skip_all, fields(topics=?log.topics, data=%log.data), ret)] pub fn decode_console_log(log: &Log) -> Option { - let topics = log.topics.as_slice(); - // SAFETY: Same type - // TODO: Remove when `ethers::Log` has been replaced - let topics = unsafe { - &*(topics as *const [ethers_core::types::H256] as *const [alloy_primitives::B256]) - }; - Console::ConsoleEvents::decode_log(topics, &log.data, false) - .ok() - .map(|decoded| decoded.to_string()) + Console::ConsoleEvents::decode_log(log, false).ok().map(|decoded| decoded.to_string()) } -/// Tries to decode an error message from the given revert bytes. -/// -/// Note that this is just a best-effort guess, and should not be relied upon for anything other -/// than user output. -pub fn decode_revert( - err: &[u8], - maybe_abi: Option<&JsonAbi>, - status: Option, -) -> String { - maybe_decode_revert(err, maybe_abi, status).unwrap_or_else(|| { - if err.is_empty() { - "".to_string() - } else { - trimmed_hex(err) - } - }) +/// Decodes revert data. +#[derive(Clone, Debug, Default)] +pub struct RevertDecoder { + /// The custom errors to use for decoding. + pub errors: HashMap>, } -pub fn maybe_decode_revert( - err: &[u8], - maybe_abi: Option<&JsonAbi>, - status: Option, -) -> Option { - if err.len() < SELECTOR_LEN { - if let Some(status) = status { - if !status.is_ok() { - return Some(format!("EvmError: {status:?}")) - } - } - return if err.is_empty() { - None - } else { - Some(format!("custom error bytes {}", hex::encode_prefixed(err))) - } +impl Default for &RevertDecoder { + fn default() -> Self { + static EMPTY: OnceLock = OnceLock::new(); + EMPTY.get_or_init(RevertDecoder::new) } +} - if err == crate::constants::MAGIC_SKIP { - // Also used in forge fuzz runner - return Some("SKIPPED".to_string()) +impl RevertDecoder { + /// Creates a new, empty revert decoder. + pub fn new() -> Self { + Self::default() } - // Solidity's `Error(string)` or `Panic(uint256)` - if let Ok(e) = alloy_sol_types::GenericContractError::abi_decode(err, false) { - return Some(e.to_string()) + /// Sets the ABIs to use for error decoding. + /// + /// Note that this is decently expensive as it will hash all errors for faster indexing. + pub fn with_abis<'a>(mut self, abi: impl IntoIterator) -> Self { + self.extend_from_abis(abi); + self } - let (selector, data) = err.split_at(SELECTOR_LEN); - let selector: &[u8; 4] = selector.try_into().unwrap(); + /// Sets the ABI to use for error decoding. + /// + /// Note that this is decently expensive as it will hash all errors for faster indexing. + pub fn with_abi(mut self, abi: &JsonAbi) -> Self { + self.extend_from_abi(abi); + self + } - match *selector { - // `CheatcodeError(string)` - Vm::CheatcodeError::SELECTOR => { - let e = Vm::CheatcodeError::abi_decode_raw(data, false).ok()?; - return Some(e.message) + /// Sets the ABI to use for error decoding, if it is present. + /// + /// Note that this is decently expensive as it will hash all errors for faster indexing. + pub fn with_abi_opt(mut self, abi: Option<&JsonAbi>) -> Self { + if let Some(abi) = abi { + self.extend_from_abi(abi); } - // `expectRevert(bytes)` - Vm::expectRevert_2Call::SELECTOR => { - let e = Vm::expectRevert_2Call::abi_decode_raw(data, false).ok()?; - return maybe_decode_revert(&e.revertData[..], maybe_abi, status) - } - // `expectRevert(bytes4)` - Vm::expectRevert_1Call::SELECTOR => { - let e = Vm::expectRevert_1Call::abi_decode_raw(data, false).ok()?; - return maybe_decode_revert(&e.revertData[..], maybe_abi, status) + self + } + + /// Extends the decoder with the given ABI's custom errors. + pub fn extend_from_abis<'a>(&mut self, abi: impl IntoIterator) { + for abi in abi { + self.extend_from_abi(abi); } - _ => {} } - // Custom error from the given ABI - if let Some(abi) = maybe_abi { - if let Some(abi_error) = abi.errors().find(|e| selector == e.selector()) { - // if we don't decode, don't return an error, try to decode as a string later - if let Ok(decoded) = abi_error.abi_decode_input(data, false) { - return Some(format!( - "{}({})", - abi_error.name, - decoded.iter().map(foundry_common::fmt::format_token).format(", ") - )) - } + /// Extends the decoder with the given ABI's custom errors. + pub fn extend_from_abi(&mut self, abi: &JsonAbi) { + for error in abi.errors() { + self.push_error(error.clone()); } } - // ABI-encoded `string` - if let Ok(s) = String::abi_decode(err, false) { - return Some(s) + /// Adds a custom error to use for decoding. + pub fn push_error(&mut self, error: Error) { + self.errors.entry(error.selector()).or_default().push(error); } - // UTF-8-encoded string - if let Ok(s) = std::str::from_utf8(err) { - return Some(s.to_string()) + /// Tries to decode an error message from the given revert bytes. + /// + /// Note that this is just a best-effort guess, and should not be relied upon for anything other + /// than user output. + pub fn decode(&self, err: &[u8], status: Option) -> String { + self.maybe_decode(err, status).unwrap_or_else(|| { + if err.is_empty() { + "".to_string() + } else { + trimmed_hex(err) + } + }) } - // Generic custom error - Some(format!( - "custom error {}:{}", - hex::encode(selector), - std::str::from_utf8(data).map_or_else(|_| trimmed_hex(data), String::from) - )) + /// Tries to decode an error message from the given revert bytes. + /// + /// See [`decode_revert`] for more information. + pub fn maybe_decode(&self, err: &[u8], status: Option) -> Option { + if err.len() < SELECTOR_LEN { + if let Some(status) = status { + if !status.is_ok() { + return Some(format!("EvmError: {status:?}")); + } + } + return if err.is_empty() { + None + } else { + Some(format!("custom error bytes {}", hex::encode_prefixed(err))) + }; + } + + if err == crate::constants::MAGIC_SKIP { + // Also used in forge fuzz runner + return Some("SKIPPED".to_string()); + } + + // Solidity's `Error(string)` or `Panic(uint256)` + if let Ok(e) = alloy_sol_types::GenericContractError::abi_decode(err, false) { + return Some(e.to_string()); + } + + let (selector, data) = err.split_at(SELECTOR_LEN); + let selector: &[u8; 4] = selector.try_into().unwrap(); + + match *selector { + // `CheatcodeError(string)` + Vm::CheatcodeError::SELECTOR => { + let e = Vm::CheatcodeError::abi_decode_raw(data, false).ok()?; + return Some(e.message); + } + // `expectRevert(bytes)` + Vm::expectRevert_2Call::SELECTOR => { + let e = Vm::expectRevert_2Call::abi_decode_raw(data, false).ok()?; + return self.maybe_decode(&e.revertData[..], status); + } + // `expectRevert(bytes4)` + Vm::expectRevert_1Call::SELECTOR => { + let e = Vm::expectRevert_1Call::abi_decode_raw(data, false).ok()?; + return self.maybe_decode(&e.revertData[..], status); + } + _ => {} + } + + // Custom errors. + if let Some(errors) = self.errors.get(selector) { + for error in errors { + // If we don't decode, don't return an error, try to decode as a string later. + if let Ok(decoded) = error.abi_decode_input(data, false) { + return Some(format!( + "{}({})", + error.name, + decoded.iter().map(foundry_common::fmt::format_token).format(", ") + )); + } + } + } + + // ABI-encoded `string`. + if let Ok(s) = String::abi_decode(err, false) { + return Some(s); + } + + // UTF-8-encoded string. + if let Ok(s) = std::str::from_utf8(err) { + return Some(s.to_string()); + } + + // Generic custom error. + Some(format!( + "custom error {}:{}", + hex::encode(selector), + std::str::from_utf8(data).map_or_else(|_| trimmed_hex(data), String::from) + )) + } } fn trimmed_hex(s: &[u8]) -> String { - let s = hex::encode(s); - let n = 32 * 2; + let n = 32; if s.len() <= n { - s + hex::encode(s) } else { - format!("{}…{} ({} bytes)", &s[..n / 2], &s[s.len() - n / 2..], s.len()) + format!( + "{}…{} ({} bytes)", + &hex::encode(&s[..n / 2]), + &hex::encode(&s[s.len() - n / 2..]), + s.len() + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_trimmed_hex() { + assert_eq!(trimmed_hex(&hex::decode("1234567890").unwrap()), "1234567890"); + assert_eq!( + trimmed_hex(&hex::decode("492077697368207275737420737570706F72746564206869676865722D6B696E646564207479706573").unwrap()), + "49207769736820727573742073757070…6865722d6b696e646564207479706573 (41 bytes)" + ); } } diff --git a/crates/evm/core/src/fork/backend.rs b/crates/evm/core/src/fork/backend.rs index ed708bc011736..db59118844e3e 100644 --- a/crates/evm/core/src/fork/backend.rs +++ b/crates/evm/core/src/fork/backend.rs @@ -4,15 +4,11 @@ use crate::{ fork::{cache::FlushJsonBlockCacheDB, BlockchainDb}, }; use alloy_primitives::{keccak256, Address, Bytes, B256, U256}; -use ethers_core::{ - abi::ethereum_types::BigEndianHash, - types::{Block, BlockId, NameOrAddress, Transaction}, -}; -use ethers_providers::Middleware; -use foundry_common::{ - types::{ToAlloy, ToEthers}, - NON_ARCHIVE_NODE_WARNING, -}; +use alloy_provider::{network::AnyNetwork, Provider}; +use alloy_rpc_types::{Block, BlockId, Transaction, WithOtherFields}; +use alloy_transport::Transport; +use eyre::WrapErr; +use foundry_common::NON_ARCHIVE_NODE_WARNING; use futures::{ channel::mpsc::{channel, Receiver, Sender}, stream::Stream, @@ -23,8 +19,10 @@ use revm::{ db::DatabaseRef, primitives::{AccountInfo, Bytecode, KECCAK_EMPTY}, }; +use rustc_hash::FxHashMap; use std::{ collections::{hash_map::Entry, HashMap, VecDeque}, + marker::PhantomData, pin::Pin, sync::{ mpsc::{channel as oneshot_channel, Sender as OneshotSender}, @@ -35,24 +33,23 @@ use std::{ // Various future/request type aliases type AccountFuture = - Pin, Address)> + Send>>; + Pin, Address)> + Send>>; type StorageFuture = Pin, Address, U256)> + Send>>; type BlockHashFuture = Pin, u64)> + Send>>; -type FullBlockFuture = Pin< +type FullBlockFuture = + Pin, Err>, BlockId)> + Send>>; +type TransactionFuture = Pin< Box< - dyn Future>, Err>, BlockId)> + dyn Future, Err>, B256)> + Send, >, >; -type TransactionFuture = Pin< - Box, Err>, B256)> + Send>, ->; type AccountInfoSender = OneshotSender>; type StorageSender = OneshotSender>; type BlockHashSender = OneshotSender>; -type FullBlockSender = OneshotSender>>; -type TransactionSender = OneshotSender>; +type FullBlockSender = OneshotSender>; +type TransactionSender = OneshotSender>>; /// Request variants that are executed by the provider enum ProviderRequest { @@ -85,18 +82,19 @@ enum BackendRequest { /// This handler will remain active as long as it is reachable (request channel still open) and /// requests are in progress. #[must_use = "futures do nothing unless polled"] -pub struct BackendHandler { - provider: M, +pub struct BackendHandler { + provider: P, + transport: PhantomData, /// Stores all the data. db: BlockchainDb, /// Requests currently in progress - pending_requests: Vec>, + pending_requests: Vec>, /// Listeners that wait for a `get_account` related response account_requests: HashMap>, /// Listeners that wait for a `get_storage_at` response storage_requests: HashMap<(Address, U256), Vec>, /// Listeners that wait for a `get_block` response - block_requests: HashMap>, + block_requests: FxHashMap>, /// Incoming commands. incoming: Receiver, /// unprocessed queued requests @@ -106,12 +104,13 @@ pub struct BackendHandler { block_id: Option, } -impl BackendHandler +impl BackendHandler where - M: Middleware + Clone + 'static, + T: Transport + Clone, + P: Provider + Clone + Unpin + 'static, { fn new( - provider: M, + provider: P, db: BlockchainDb, rx: Receiver, block_id: Option, @@ -126,6 +125,7 @@ where queued_requests: Default::default(), incoming: rx, block_id, + transport: PhantomData, } } @@ -184,21 +184,13 @@ where entry.get_mut().push(listener); } Entry::Vacant(entry) => { - trace!(target: "backendhandler", "preparing storage request, address={:?}, idx={}", address, idx); + trace!(target: "backendhandler", %address, %idx, "preparing storage request"); entry.insert(vec![listener]); let provider = self.provider.clone(); - let block_id = self.block_id; + let block_id = self.block_id.unwrap_or(BlockId::latest()); let fut = Box::pin(async move { - // serialize & deserialize back to U256 - let idx_req = B256::from(idx); - let storage = provider - .get_storage_at( - NameOrAddress::Address(address.to_ethers()), - idx_req.to_ethers(), - block_id, - ) - .await; - let storage = storage.map(|storage| storage.into_uint()).map(|s| s.to_alloy()); + let storage = + provider.get_storage_at(address, idx, block_id).await.map_err(Into::into); (storage, address, idx) }); self.pending_requests.push(ProviderRequest::Storage(fut)); @@ -207,19 +199,15 @@ where } /// returns the future that fetches the account data - fn get_account_req(&self, address: Address) -> ProviderRequest { + fn get_account_req(&self, address: Address) -> ProviderRequest { trace!(target: "backendhandler", "preparing account request, address={:?}", address); let provider = self.provider.clone(); - let block_id = self.block_id; + let block_id = self.block_id.unwrap_or(BlockId::latest()); let fut = Box::pin(async move { - let balance = - provider.get_balance(NameOrAddress::Address(address.to_ethers()), block_id); - let nonce = provider - .get_transaction_count(NameOrAddress::Address(address.to_ethers()), block_id); - let code = provider.get_code(NameOrAddress::Address(address.to_ethers()), block_id); - let resp = tokio::try_join!(balance, nonce, code).map(|(balance, nonce, code)| { - (balance.to_alloy(), nonce.to_alloy(), Bytes::from(code.0)) - }); + let balance = provider.get_balance(address, block_id); + let nonce = provider.get_transaction_count(address, block_id); + let code = provider.get_code_at(address, block_id); + let resp = tokio::try_join!(balance, nonce, code).map_err(Into::into); (resp, address) }); ProviderRequest::Account(fut) @@ -242,7 +230,8 @@ where fn request_full_block(&mut self, number: BlockId, sender: FullBlockSender) { let provider = self.provider.clone(); let fut = Box::pin(async move { - let block = provider.get_block_with_txs(number).await; + let block = + provider.get_block(number, true).await.wrap_err("could not fetch block {number:?}"); (sender, block, number) }); @@ -253,7 +242,10 @@ where fn request_transaction(&mut self, tx: B256, sender: TransactionSender) { let provider = self.provider.clone(); let fut = Box::pin(async move { - let block = provider.get_transaction(tx.to_ethers()).await; + let block = provider + .get_transaction_by_hash(tx) + .await + .wrap_err("could not get transaction {tx}"); (sender, block, tx) }); @@ -267,28 +259,32 @@ where entry.get_mut().push(listener); } Entry::Vacant(entry) => { - trace!(target: "backendhandler", "preparing block hash request, number={}", number); + trace!(target: "backendhandler", number, "preparing block hash request"); entry.insert(vec![listener]); let provider = self.provider.clone(); let fut = Box::pin(async move { - let block = provider.get_block(number).await; + let block = provider + .get_block_by_number(number.into(), false) + .await + .wrap_err("failed to get block"); let block_hash = match block { Ok(Some(block)) => Ok(block + .header .hash .expect("empty block hash on mined block, this should never happen")), Ok(None) => { warn!(target: "backendhandler", ?number, "block not found"); // if no block was returned then the block does not exist, in which case // we return empty hash - Ok(KECCAK_EMPTY.to_ethers()) + Ok(KECCAK_EMPTY) } Err(err) => { error!(target: "backendhandler", %err, ?number, "failed to get block"); Err(err) } }; - (block_hash.map(|h| h.to_alloy()), number) + (block_hash, number) }); self.pending_requests.push(ProviderRequest::BlockHash(fut)); } @@ -296,9 +292,10 @@ where } } -impl Future for BackendHandler +impl Future for BackendHandler where - M: Middleware + Clone + Unpin + 'static, + T: Transport + Clone + Unpin, + P: Provider + Clone + Unpin + 'static, { type Output = (); @@ -318,7 +315,7 @@ where } Poll::Ready(None) => { trace!(target: "backendhandler", "last sender dropped, ready to drop (&flush cache)"); - return Poll::Ready(()) + return Poll::Ready(()); } Poll::Pending => break, } @@ -334,7 +331,7 @@ where let (balance, nonce, code) = match resp { Ok(res) => res, Err(err) => { - let err = Arc::new(eyre::Error::new(err)); + let err = Arc::new(err); if let Some(listeners) = pin.account_requests.remove(&addr) { listeners.into_iter().for_each(|l| { let _ = l.send(Err(DatabaseError::GetAccount( @@ -343,7 +340,7 @@ where ))); }) } - continue + continue; } }; @@ -356,7 +353,7 @@ where // update the cache let acc = AccountInfo { - nonce: nonce.to(), + nonce, balance, code: Some(Bytecode::new_raw(code).to_checked()), code_hash, @@ -369,7 +366,7 @@ where let _ = l.send(Ok(acc.clone())); }) } - continue + continue; } } ProviderRequest::Storage(fut) => { @@ -378,7 +375,7 @@ where Ok(value) => value, Err(err) => { // notify all listeners - let err = Arc::new(eyre::Error::new(err)); + let err = Arc::new(err); if let Some(listeners) = pin.storage_requests.remove(&(addr, idx)) { @@ -390,7 +387,7 @@ where ))); }) } - continue + continue; } }; @@ -403,7 +400,7 @@ where let _ = l.send(Ok(value)); }) } - continue + continue; } } ProviderRequest::BlockHash(fut) => { @@ -411,7 +408,7 @@ where let value = match block_hash { Ok(value) => value, Err(err) => { - let err = Arc::new(eyre::Error::new(err)); + let err = Arc::new(err); // notify all listeners if let Some(listeners) = pin.block_requests.remove(&number) { listeners.into_iter().for_each(|l| { @@ -421,7 +418,7 @@ where ))); }) } - continue + continue; } }; @@ -434,7 +431,7 @@ where let _ = l.send(Ok(value)); }) } - continue + continue; } } ProviderRequest::FullBlock(fut) => { @@ -443,26 +440,25 @@ where Ok(Some(block)) => Ok(block), Ok(None) => Err(DatabaseError::BlockNotFound(number)), Err(err) => { - let err = Arc::new(eyre::Error::new(err)); + let err = Arc::new(err); Err(DatabaseError::GetFullBlock(number, err)) } }; let _ = sender.send(msg); - continue + continue; } } ProviderRequest::Transaction(fut) => { if let Poll::Ready((sender, tx, tx_hash)) = fut.poll_unpin(cx) { let msg = match tx { - Ok(Some(tx)) => Ok(tx), - Ok(None) => Err(DatabaseError::TransactionNotFound(tx_hash)), + Ok(tx) => Ok(tx), Err(err) => { - let err = Arc::new(eyre::Error::new(err)); + let err = Arc::new(err); Err(DatabaseError::GetTransaction(tx_hash, err)) } }; let _ = sender.send(msg); - continue + continue; } } } @@ -473,7 +469,7 @@ where // If no new requests have been queued, break to // be polled again later. if pin.queued_requests.is_empty() { - return Poll::Pending + return Poll::Pending; } } } @@ -507,7 +503,7 @@ where // > Runs the provided blocking function on the current thread without blocking the executor. // This prevents issues (hangs) we ran into were the [SharedBackend] itself is called from a spawned // task. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct SharedBackend { /// channel used for sending commands related to database operations backend: Sender, @@ -526,9 +522,14 @@ impl SharedBackend { /// dropped. /// /// NOTE: this should be called with `Arc` - pub async fn spawn_backend(provider: M, db: BlockchainDb, pin_block: Option) -> Self + pub async fn spawn_backend( + provider: P, + db: BlockchainDb, + pin_block: Option, + ) -> Self where - M: Middleware + Unpin + 'static + Clone, + T: Transport + Clone + Unpin, + P: Provider + Unpin + 'static + Clone, { let (shared, handler) = Self::new(provider, db, pin_block); // spawn the provider handler to a task @@ -539,13 +540,14 @@ impl SharedBackend { /// Same as `Self::spawn_backend` but spawns the `BackendHandler` on a separate `std::thread` in /// its own `tokio::Runtime` - pub fn spawn_backend_thread( - provider: M, + pub fn spawn_backend_thread( + provider: P, db: BlockchainDb, pin_block: Option, ) -> Self where - M: Middleware + Unpin + 'static + Clone, + T: Transport + Clone + Unpin, + P: Provider + Unpin + 'static + Clone, { let (shared, handler) = Self::new(provider, db, pin_block); @@ -568,13 +570,14 @@ impl SharedBackend { } /// Returns a new `SharedBackend` and the `BackendHandler` - pub fn new( - provider: M, + pub fn new( + provider: P, db: BlockchainDb, pin_block: Option, - ) -> (Self, BackendHandler) + ) -> (Self, BackendHandler) where - M: Middleware + Unpin + 'static + Clone, + T: Transport + Clone + Unpin, + P: Provider + Unpin + 'static + Clone, { let (backend, backend_rx) = channel(1); let cache = Arc::new(FlushJsonBlockCacheDB(Arc::clone(db.cache()))); @@ -589,7 +592,7 @@ impl SharedBackend { } /// Returns the full block for the given block identifier - pub fn get_full_block(&self, block: impl Into) -> DatabaseResult> { + pub fn get_full_block(&self, block: impl Into) -> DatabaseResult { tokio::task::block_in_place(|| { let (sender, rx) = oneshot_channel(); let req = BackendRequest::FullBlock(block.into(), sender); @@ -599,7 +602,7 @@ impl SharedBackend { } /// Returns the transaction for the hash - pub fn get_transaction(&self, tx: B256) -> DatabaseResult { + pub fn get_transaction(&self, tx: B256) -> DatabaseResult> { tokio::task::block_in_place(|| { let (sender, rx) = oneshot_channel(); let req = BackendRequest::Transaction(tx, sender); @@ -661,35 +664,29 @@ impl DatabaseRef for SharedBackend { fn storage_ref(&self, address: Address, index: U256) -> Result { trace!(target: "sharedbackend", "request storage {:?} at {:?}", address, index); - match self.do_get_storage(address, index).map_err(|err| { + self.do_get_storage(address, index).map_err(|err| { error!(target: "sharedbackend", %err, %address, %index, "Failed to send/recv `storage`"); if err.is_possibly_non_archive_node_error() { error!(target: "sharedbackend", "{NON_ARCHIVE_NODE_WARNING}"); } err - }) { - Ok(val) => Ok(val), - Err(err) => Err(err), - } + }) } fn block_hash_ref(&self, number: U256) -> Result { if number > U256::from(u64::MAX) { - return Ok(KECCAK_EMPTY) + return Ok(KECCAK_EMPTY); } let number: U256 = number; let number = number.to(); trace!(target: "sharedbackend", "request block hash for number {:?}", number); - match self.do_get_block_hash(number).map_err(|err| { + self.do_get_block_hash(number).map_err(|err| { error!(target: "sharedbackend", %err, %number, "Failed to send/recv `block_hash`"); if err.is_possibly_non_archive_node_error() { error!(target: "sharedbackend", "{NON_ARCHIVE_NODE_WARNING}"); } err - }) { - Ok(val) => Ok(val), - Err(err) => Err(err), - } + }) } } @@ -701,18 +698,21 @@ mod tests { fork::{BlockchainDbMeta, CreateFork, JsonBlockCacheDB}, opts::EvmOpts, }; - use foundry_common::get_http_provider; + use foundry_common::provider::get_http_provider; use foundry_config::{Config, NamedChain}; - use std::{collections::BTreeSet, path::PathBuf, sync::Arc}; - const ENDPOINT: &str = "https://mainnet.infura.io/v3/40bee2d557ed4b52908c3e62345a3d8b"; + use std::{collections::BTreeSet, path::PathBuf}; + + const ENDPOINT: Option<&str> = option_env!("ETH_RPC_URL"); #[tokio::test(flavor = "multi_thread")] async fn shared_backend() { - let provider = get_http_provider(ENDPOINT); + let Some(endpoint) = ENDPOINT else { return }; + + let provider = get_http_provider(endpoint); let meta = BlockchainDbMeta { cfg_env: Default::default(), block_env: Default::default(), - hosts: BTreeSet::from([ENDPOINT.to_string()]), + hosts: BTreeSet::from([endpoint.to_string()]), }; let db = BlockchainDb::new(meta, None); @@ -758,24 +758,26 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn can_read_write_cache() { - let provider = get_http_provider(ENDPOINT); + let Some(endpoint) = ENDPOINT else { return }; + + let provider = get_http_provider(endpoint); - let block_num = provider.get_block_number().await.unwrap().as_u64(); + let block_num = provider.get_block_number().await.unwrap(); let config = Config::figment(); let mut evm_opts = config.extract::().unwrap(); evm_opts.fork_block_number = Some(block_num); - let (env, _block) = evm_opts.fork_evm_env(ENDPOINT).await.unwrap(); + let (env, _block) = evm_opts.fork_evm_env(endpoint).await.unwrap(); let fork = CreateFork { enable_caching: true, - url: ENDPOINT.to_string(), + url: endpoint.to_string(), env: env.clone(), evm_opts, }; - let backend = Backend::spawn(Some(fork)).await; + let backend = Backend::spawn(Some(fork)); // some rng contract from etherscan let address: Address = "63091244180ae240c87d1f528f5f269134cb07b3".parse().unwrap(); diff --git a/crates/evm/core/src/fork/cache.rs b/crates/evm/core/src/fork/cache.rs index ca467dbdf4b5e..607eced688945 100644 --- a/crates/evm/core/src/fork/cache.rs +++ b/crates/evm/core/src/fork/cache.rs @@ -14,7 +14,6 @@ use std::{ path::PathBuf, sync::Arc, }; - use url::Url; pub type StorageInfo = Map; @@ -117,7 +116,7 @@ impl BlockchainDb { } /// relevant identifying markers in the context of [BlockchainDb] -#[derive(Debug, Clone, Eq, Serialize)] +#[derive(Clone, Debug, Eq, Serialize)] pub struct BlockchainDbMeta { pub cfg_env: revm::primitives::CfgEnv, pub block_env: revm::primitives::BlockEnv, @@ -154,10 +153,10 @@ impl<'de> Deserialize<'de> for BlockchainDbMeta { where D: Deserializer<'de>, { - /// A backwards compatible representation of [revm::CfgEnv] + /// A backwards compatible representation of [revm::primitives::CfgEnv] /// /// This prevents deserialization errors of cache files caused by breaking changes to the - /// default [revm::CfgEnv], for example enabling an optional feature. + /// default [revm::primitives::CfgEnv], for example enabling an optional feature. /// By hand rolling deserialize impl we can prevent cache file issues struct CfgEnvBackwardsCompat { inner: revm::primitives::CfgEnv, @@ -172,27 +171,12 @@ impl<'de> Deserialize<'de> for BlockchainDbMeta { // we check for breaking changes here if let Some(obj) = value.as_object_mut() { - // additional field `disable_eip3607` enabled by the `optional_eip3607` feature - let key = "disable_eip3607"; - if !obj.contains_key(key) { - obj.insert(key.to_string(), true.into()); - } - // additional field `disable_block_gas_limit` enabled by the - // `optional_block_gas_limit` feature - let key = "disable_block_gas_limit"; - if !obj.contains_key(key) { - // keep default value - obj.insert(key.to_string(), false.into()); - } - let key = "disable_base_fee"; - if !obj.contains_key(key) { - // keep default value - obj.insert(key.to_string(), false.into()); - } - let key = "optimism"; - if !obj.contains_key(key) { - // keep default value - obj.insert(key.to_string(), false.into()); + let default_value = + serde_json::to_value(revm::primitives::CfgEnv::default()).unwrap(); + for (key, value) in default_value.as_object().unwrap() { + if !obj.contains_key(key) { + obj.insert(key.to_string(), value.clone()); + } } } @@ -202,11 +186,44 @@ impl<'de> Deserialize<'de> for BlockchainDbMeta { } } + /// A backwards compatible representation of [revm::primitives::BlockEnv] + /// + /// This prevents deserialization errors of cache files caused by breaking changes to the + /// default [revm::primitives::BlockEnv], for example enabling an optional feature. + /// By hand rolling deserialize impl we can prevent cache file issues + struct BlockEnvBackwardsCompat { + inner: revm::primitives::BlockEnv, + } + + impl<'de> Deserialize<'de> for BlockEnvBackwardsCompat { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let mut value = serde_json::Value::deserialize(deserializer)?; + + // we check for any missing fields here + if let Some(obj) = value.as_object_mut() { + let default_value = + serde_json::to_value(revm::primitives::BlockEnv::default()).unwrap(); + for (key, value) in default_value.as_object().unwrap() { + if !obj.contains_key(key) { + obj.insert(key.to_string(), value.clone()); + } + } + } + + let cfg_env: revm::primitives::BlockEnv = + serde_json::from_value(value).map_err(serde::de::Error::custom)?; + Ok(Self { inner: cfg_env }) + } + } + // custom deserialize impl to not break existing cache files #[derive(Deserialize)] struct Meta { cfg_env: CfgEnvBackwardsCompat, - block_env: revm::primitives::BlockEnv, + block_env: BlockEnvBackwardsCompat, /// all the hosts used to connect to #[serde(alias = "host")] hosts: Hosts, @@ -222,7 +239,7 @@ impl<'de> Deserialize<'de> for BlockchainDbMeta { let Meta { cfg_env, block_env, hosts } = Meta::deserialize(deserializer)?; Ok(Self { cfg_env: cfg_env.inner, - block_env, + block_env: block_env.inner, hosts: match hosts { Hosts::Multi(hosts) => hosts, Hosts::Single(host) => BTreeSet::from([host]), @@ -284,7 +301,7 @@ impl MemDb { acc_storage.clear(); } for (index, value) in acc.storage { - if value.present_value() == U256::from(0) { + if value.present_value().is_zero() { acc_storage.remove(&index); } else { acc_storage.insert(index, value.present_value()); @@ -533,4 +550,61 @@ mod tests { let _s = serde_json::to_string(&cache).unwrap(); } + + #[test] + fn can_deserialize_cache_post_4844() { + let s = r#"{ + "meta": { + "cfg_env": { + "chain_id": 1, + "spec_id": "LATEST", + "perf_analyse_created_bytecodes": "Analyse", + "limit_contract_code_size": 18446744073709551615, + "memory_limit": 134217728, + "disable_block_gas_limit": false, + "disable_eip3607": true, + "disable_base_fee": false, + "optimism": false + }, + "block_env": { + "number": "0x11c99bc", + "coinbase": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97", + "timestamp": "0x65627003", + "gas_limit": "0x1c9c380", + "basefee": "0x64288ff1f", + "difficulty": "0xc6b1a299886016dea3865689f8393b9bf4d8f4fe8c0ad25f0058b3569297c057", + "prevrandao": "0xc6b1a299886016dea3865689f8393b9bf4d8f4fe8c0ad25f0058b3569297c057", + "blob_excess_gas_and_price": { + "excess_blob_gas": 0, + "blob_gasprice": 1 + } + }, + "hosts": [ + "eth-mainnet.alchemyapi.io" + ] + }, + "accounts": { + "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97": { + "balance": "0x8e0c373cfcdfd0eb", + "nonce": 128912, + "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "code": { + "bytecode": "0x000000000000000000000000000000000000000000000000000000000000000000", + "state": { + "Checked": { + "len": 0 + } + } + } + } + }, + "storage": {}, + "block_hashes": {} +}"#; + + let cache: JsonBlockCacheData = serde_json::from_str(s).unwrap(); + assert_eq!(cache.data.accounts.read().len(), 1); + + let _s = serde_json::to_string(&cache).unwrap(); + } } diff --git a/crates/evm/core/src/fork/database.rs b/crates/evm/core/src/fork/database.rs index 1c39b99e1f9e5..4b1e3d6b0f9b1 100644 --- a/crates/evm/core/src/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -6,7 +6,7 @@ use crate::{ snapshot::Snapshots, }; use alloy_primitives::{Address, B256, U256}; -use ethers_core::types::BlockId; +use alloy_rpc_types::BlockId; use parking_lot::Mutex; use revm::{ db::{CacheDB, DatabaseRef}, @@ -21,7 +21,7 @@ use std::sync::Arc; /// endpoint. The inner in-memory database holds this storage and will be used for write operations. /// This database uses the `backend` for read and the `db` for write operations. But note the /// `backend` will also write (missing) data to the `db` in the background -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ForkedDatabase { /// responsible for fetching missing data /// @@ -267,14 +267,14 @@ impl DatabaseRef for ForkDbSnapshot { mod tests { use super::*; use crate::fork::BlockchainDbMeta; - use foundry_common::get_http_provider; + use foundry_common::provider::get_http_provider; use std::collections::BTreeSet; /// Demonstrates that `Database::basic` for `ForkedDatabase` will always return the /// `AccountInfo` #[tokio::test(flavor = "multi_thread")] async fn fork_db_insert_basic_default() { - let rpc = foundry_common::rpc::next_http_rpc_endpoint(); + let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint(); let provider = get_http_provider(rpc.clone()); let meta = BlockchainDbMeta { cfg_env: Default::default(), diff --git a/crates/evm/core/src/fork/init.rs b/crates/evm/core/src/fork/init.rs index 35c15f6d96f2d..b69e02aad9db2 100644 --- a/crates/evm/core/src/fork/init.rs +++ b/crates/evm/core/src/fork/init.rs @@ -1,40 +1,34 @@ use crate::utils::apply_chain_and_block_specific_env_changes; use alloy_primitives::{Address, U256}; -use ethers_core::types::{Block, TxHash}; -use ethers_providers::Middleware; +use alloy_provider::{Network, Provider}; +use alloy_rpc_types::{Block, BlockNumberOrTag}; +use alloy_transport::Transport; use eyre::WrapErr; -use foundry_common::{types::ToAlloy, NON_ARCHIVE_NODE_WARNING}; -use futures::TryFutureExt; +use foundry_common::NON_ARCHIVE_NODE_WARNING; + use revm::primitives::{BlockEnv, CfgEnv, Env, TxEnv}; /// Initializes a REVM block environment based on a forked /// ethereum provider. -pub async fn environment( - provider: &M, +// todo(onbjerg): these bounds needed cus of the bounds in `Provider`, can simplify? +pub async fn environment>( + provider: &P, memory_limit: u64, - gas_price: Option, + gas_price: Option, override_chain_id: Option, pin_block: Option, origin: Address, -) -> eyre::Result<(Env, Block)> -where - M::Error: 'static, -{ + disable_block_gas_limit: bool, +) -> eyre::Result<(Env, Block)> { let block_number = if let Some(pin_block) = pin_block { pin_block } else { - provider.get_block_number().await.wrap_err("Failed to get latest block number")?.as_u64() + provider.get_block_number().await.wrap_err("Failed to get latest block number")? }; let (fork_gas_price, rpc_chain_id, block) = tokio::try_join!( - provider - .get_gas_price() - .map_err(|err| { eyre::Error::new(err).wrap_err("Failed to get gas price") }), - provider - .get_chainid() - .map_err(|err| { eyre::Error::new(err).wrap_err("Failed to get chain id") }), - provider.get_block(block_number).map_err(|err| { - eyre::Error::new(err).wrap_err(format!("Failed to get block {block_number}")) - }) + provider.get_gas_price(), + provider.get_chain_id(), + provider.get_block_by_number(BlockNumberOrTag::Number(block_number), false) )?; let block = if let Some(block) = block { block @@ -43,7 +37,7 @@ where // If the `eth_getBlockByNumber` call succeeds, but returns null instead of // the block, and the block number is less than equal the latest block, then // the user is forking from a non-archive node with an older block number. - if block_number <= latest_block.as_u64() { + if block_number <= latest_block { error!("{NON_ARCHIVE_NODE_WARNING}"); } eyre::bail!( @@ -56,31 +50,32 @@ where }; let mut cfg = CfgEnv::default(); - cfg.chain_id = override_chain_id.unwrap_or(rpc_chain_id.as_u64()); + cfg.chain_id = override_chain_id.unwrap_or(rpc_chain_id); cfg.memory_limit = memory_limit; cfg.limit_contract_code_size = Some(usize::MAX); // EIP-3607 rejects transactions from senders with deployed code. // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the caller // is a contract. So we disable the check by default. cfg.disable_eip3607 = true; + cfg.disable_block_gas_limit = disable_block_gas_limit; let mut env = Env { cfg, block: BlockEnv { - number: U256::from(block.number.expect("block number not found").as_u64()), - timestamp: block.timestamp.to_alloy(), - coinbase: block.author.unwrap_or_default().to_alloy(), - difficulty: block.difficulty.to_alloy(), - prevrandao: Some(block.mix_hash.map(|h| h.to_alloy()).unwrap_or_default()), - basefee: block.base_fee_per_gas.unwrap_or_default().to_alloy(), - gas_limit: block.gas_limit.to_alloy(), + number: U256::from(block.header.number.expect("block number not found")), + timestamp: U256::from(block.header.timestamp), + coinbase: block.header.miner, + difficulty: block.header.difficulty, + prevrandao: Some(block.header.mix_hash.unwrap_or_default()), + basefee: U256::from(block.header.base_fee_per_gas.unwrap_or_default()), + gas_limit: U256::from(block.header.gas_limit), ..Default::default() }, tx: TxEnv { caller: origin, - gas_price: gas_price.map(U256::from).unwrap_or(fork_gas_price.to_alloy()), - chain_id: Some(override_chain_id.unwrap_or(rpc_chain_id.as_u64())), - gas_limit: block.gas_limit.as_u64(), + gas_price: U256::from(gas_price.unwrap_or(fork_gas_price)), + chain_id: Some(override_chain_id.unwrap_or(rpc_chain_id)), + gas_limit: block.header.gas_limit as u64, ..Default::default() }, }; diff --git a/crates/evm/core/src/fork/mod.rs b/crates/evm/core/src/fork/mod.rs index f9042ac91d3c5..61dd7bf47bf4b 100644 --- a/crates/evm/core/src/fork/mod.rs +++ b/crates/evm/core/src/fork/mod.rs @@ -16,7 +16,7 @@ mod multi; pub use multi::{ForkId, MultiFork, MultiForkHandler}; /// Represents a _fork_ of a remote chain whose data is available only via the `url` endpoint. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct CreateFork { /// Whether to enable rpc storage caching for this fork pub enable_caching: bool, diff --git a/crates/evm/core/src/fork/multi.rs b/crates/evm/core/src/fork/multi.rs index f00d5ffeda77f..da1f980ffbcb2 100644 --- a/crates/evm/core/src/fork/multi.rs +++ b/crates/evm/core/src/fork/multi.rs @@ -4,9 +4,9 @@ //! concurrently active pairs at once. use crate::fork::{BackendHandler, BlockchainDb, BlockchainDbMeta, CreateFork, SharedBackend}; -use ethers_core::types::{BlockId, BlockNumber}; -use ethers_providers::Provider; -use foundry_common::{runtime_client::RuntimeClient, types::ToEthers, ProviderBuilder}; +use foundry_common::provider::{ + runtime_transport::RuntimeTransport, tower::RetryBackoffService, ProviderBuilder, RetryProvider, +}; use foundry_config::Config; use futures::{ channel::mpsc::{channel, Receiver, Sender}, @@ -17,20 +17,39 @@ use futures::{ use revm::primitives::Env; use std::{ collections::HashMap, - fmt, + fmt::{self, Write}, pin::Pin, sync::{ + atomic::AtomicUsize, mpsc::{channel as oneshot_channel, Sender as OneshotSender}, Arc, }, time::Duration, }; -/// The identifier for a specific fork, this could be the name of the network a custom descriptive -/// name. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] +/// The _unique_ identifier for a specific fork, this could be the name of the network a custom +/// descriptive name. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ForkId(pub String); +impl ForkId { + /// Returns the identifier for a Fork from a URL and block number. + pub fn new(url: &str, num: Option) -> Self { + let mut id = url.to_string(); + id.push('@'); + match num { + Some(n) => write!(id, "{n:#x}").unwrap(), + None => id.push_str("latest"), + } + ForkId(id) + } + + /// Returns the identifier of the fork. + pub fn as_str(&self) -> &str { + &self.0 + } +} + impl fmt::Display for ForkId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) @@ -45,7 +64,7 @@ impl> From for ForkId { /// The Sender half of multi fork pair. /// Can send requests to the `MultiForkHandler` to create forks -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct MultiFork { /// Channel to send `Request`s to the handler handler: Sender, @@ -64,7 +83,7 @@ impl MultiFork { } /// Creates a new pair and spawns the `MultiForkHandler` on a background thread. - pub async fn spawn() -> Self { + pub fn spawn() -> Self { trace!(target: "fork::multi", "spawning multifork"); let (fork, mut handler) = Self::new(); @@ -150,9 +169,10 @@ impl MultiFork { } } -type Handler = BackendHandler>>; +type Handler = BackendHandler, Arc>; -type CreateFuture = Pin> + Send>>; +type CreateFuture = + Pin> + Send>>; type CreateSender = OneshotSender>; type GetEnvSender = OneshotSender>; @@ -163,7 +183,7 @@ enum Request { CreateFork(Box, CreateSender), /// Returns the Fork backend for the `ForkId` if it exists GetFork(ForkId, OneshotSender>), - /// Adjusts the block that's being forked + /// Adjusts the block that's being forked, by creating a new fork at the new block RollFork(ForkId, u64, CreateSender), /// Returns the environment of the fork GetEnv(ForkId, GetEnvSender), @@ -192,7 +212,10 @@ pub struct MultiForkHandler { // tasks currently in progress pending_tasks: Vec, - /// All created Forks in order to reuse them + /// All _unique_ forkids mapped to their corresponding backend. + /// + /// Note: The backend can be shared by multiple ForkIds if the target the same provider and + /// block number. forks: HashMap, /// Optional periodic interval to flush rpc cache @@ -225,7 +248,7 @@ impl MultiForkHandler { #[allow(irrefutable_let_patterns)] if let ForkTask::Create(_, in_progress, _, additional) = task { if in_progress == id { - return Some(additional) + return Some(additional); } } } @@ -233,22 +256,35 @@ impl MultiForkHandler { } fn create_fork(&mut self, fork: CreateFork, sender: CreateSender) { - let fork_id = create_fork_id(&fork.url, fork.evm_opts.fork_block_number); + let fork_id = ForkId::new(&fork.url, fork.evm_opts.fork_block_number); trace!(?fork_id, "created new forkId"); - if let Some(fork) = self.forks.get_mut(&fork_id) { - fork.num_senders += 1; - let _ = sender.send(Ok((fork_id, fork.backend.clone(), fork.opts.env.clone()))); - } else { - // there could already be a task for the requested fork in progress - if let Some(in_progress) = self.find_in_progress_task(&fork_id) { - in_progress.push(sender); - return - } + // there could already be a task for the requested fork in progress + if let Some(in_progress) = self.find_in_progress_task(&fork_id) { + in_progress.push(sender); + return; + } - // need to create a new fork - let task = Box::pin(create_fork(fork)); - self.pending_tasks.push(ForkTask::Create(task, fork_id, sender, Vec::new())); + // need to create a new fork + let task = Box::pin(create_fork(fork)); + self.pending_tasks.push(ForkTask::Create(task, fork_id, sender, Vec::new())); + } + + fn insert_new_fork( + &mut self, + fork_id: ForkId, + fork: CreatedFork, + sender: CreateSender, + additional_senders: Vec, + ) { + self.forks.insert(fork_id.clone(), fork.clone()); + let _ = sender.send(Ok((fork_id.clone(), fork.backend.clone(), fork.opts.env.clone()))); + + // notify all additional senders and track unique forkIds + for sender in additional_senders { + let next_fork_id = fork.inc_senders(fork_id.clone()); + self.forks.insert(next_fork_id.clone(), fork.clone()); + let _ = sender.send(Ok((next_fork_id, fork.backend.clone(), fork.opts.env.clone()))); } } @@ -304,7 +340,7 @@ impl Future for MultiForkHandler { Poll::Ready(None) => { // channel closed, but we still need to drive the fork handlers to completion trace!(target: "fork::multi", "request channel closed"); - break + break; } Poll::Pending => break, } @@ -317,18 +353,17 @@ impl Future for MultiForkHandler { ForkTask::Create(mut fut, id, sender, additional_senders) => { if let Poll::Ready(resp) = fut.poll_unpin(cx) { match resp { - Ok((fork, handler)) => { - pin.handlers.push((id.clone(), handler)); - let backend = fork.backend.clone(); - let env = fork.opts.env.clone(); - pin.forks.insert(id.clone(), fork); - - let _ = sender.send(Ok((id.clone(), backend.clone(), env.clone()))); - - // also notify all additional senders - for sender in additional_senders { - let _ = - sender.send(Ok((id.clone(), backend.clone(), env.clone()))); + Ok((fork_id, fork, handler)) => { + if let Some(fork) = pin.forks.get(&fork_id).cloned() { + pin.insert_new_fork( + fork.inc_senders(fork_id), + fork, + sender, + additional_senders, + ); + } else { + pin.handlers.push((fork_id.clone(), handler)); + pin.insert_new_fork(fork_id, fork, sender, additional_senders); } } Err(err) => { @@ -365,7 +400,7 @@ impl Future for MultiForkHandler { if pin.handlers.is_empty() && pin.incoming.is_done() { trace!(target: "fork::multi", "completed"); - return Poll::Ready(()) + return Poll::Ready(()); } // periodically flush cached RPC state @@ -392,7 +427,7 @@ impl Future for MultiForkHandler { } /// Tracks the created Fork -#[derive(Debug)] +#[derive(Debug, Clone)] struct CreatedFork { /// How the fork was initially created opts: CreateFork, @@ -400,14 +435,24 @@ struct CreatedFork { backend: SharedBackend, /// How many consumers there are, since a `SharedBacked` can be used by multiple /// consumers - num_senders: usize, + num_senders: Arc, } // === impl CreatedFork === impl CreatedFork { pub fn new(opts: CreateFork, backend: SharedBackend) -> Self { - Self { opts, backend, num_senders: 1 } + Self { opts, backend, num_senders: Arc::new(AtomicUsize::new(1)) } + } + + /// Increment senders and return unique identifier of the fork + fn inc_senders(&self, fork_id: ForkId) -> ForkId { + format!( + "{}-{}", + fork_id.as_str(), + self.num_senders.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + ) + .into() } } @@ -436,16 +481,10 @@ impl Drop for ShutDownMultiFork { } } -/// Returns the identifier for a Fork which consists of the url and the block number -fn create_fork_id(url: &str, num: Option) -> ForkId { - let num = num.map(|num| BlockNumber::Number(num.into())).unwrap_or(BlockNumber::Latest); - ForkId(format!("{url}@{num}")) -} - /// Creates a new fork /// /// This will establish a new `Provider` to the endpoint and return the Fork Backend -async fn create_fork(mut fork: CreateFork) -> eyre::Result<(CreatedFork, Handler)> { +async fn create_fork(mut fork: CreateFork) -> eyre::Result<(ForkId, CreatedFork, Handler)> { let provider = Arc::new( ProviderBuilder::new(fork.url.as_str()) .maybe_max_retry(fork.evm_opts.fork_retries) @@ -461,10 +500,7 @@ async fn create_fork(mut fork: CreateFork) -> eyre::Result<(CreatedFork, Handler // we need to use the block number from the block because the env's number can be different on // some L2s (e.g. Arbitrum). - let number = block - .number - .map(|num| num.as_u64()) - .unwrap_or_else(|| meta.block_env.number.to_ethers().as_u64()); + let number = block.header.number.unwrap_or(meta.block_env.number.to()); // determine the cache path if caching is enabled let cache_path = if fork.enable_caching { @@ -474,8 +510,9 @@ async fn create_fork(mut fork: CreateFork) -> eyre::Result<(CreatedFork, Handler }; let db = BlockchainDb::new(meta, cache_path); - let (backend, handler) = - SharedBackend::new(provider, db, Some(BlockId::Number(BlockNumber::Number(number.into())))); + let (backend, handler) = SharedBackend::new(provider, db, Some(number.into())); let fork = CreatedFork::new(fork, backend); - Ok((fork, handler)) + let fork_id = ForkId::new(&fork.opts.url, number.into()); + + Ok((fork_id, fork, handler)) } diff --git a/crates/evm/core/src/ic.rs b/crates/evm/core/src/ic.rs new file mode 100644 index 0000000000000..9f679fc36e50f --- /dev/null +++ b/crates/evm/core/src/ic.rs @@ -0,0 +1,70 @@ +use revm::{ + interpreter::{opcode, opcode::spec_opcode_gas}, + primitives::SpecId, +}; +use rustc_hash::FxHashMap; + +/// Maps from program counter to instruction counter. +/// +/// Inverse of [`IcPcMap`]. +pub struct PcIcMap { + pub inner: FxHashMap, +} + +impl PcIcMap { + /// Creates a new `PcIcMap` for the given code. + pub fn new(spec: SpecId, code: &[u8]) -> Self { + Self { inner: make_map::(spec, code) } + } + + /// Returns the instruction counter for the given program counter. + pub fn get(&self, pc: usize) -> Option { + self.inner.get(&pc).copied() + } +} + +/// Map from instruction counter to program counter. +/// +/// Inverse of [`PcIcMap`]. +pub struct IcPcMap { + pub inner: FxHashMap, +} + +impl IcPcMap { + /// Creates a new `IcPcMap` for the given code. + pub fn new(spec: SpecId, code: &[u8]) -> Self { + Self { inner: make_map::(spec, code) } + } + + /// Returns the program counter for the given instruction counter. + pub fn get(&self, ic: usize) -> Option { + self.inner.get(&ic).copied() + } +} + +fn make_map(spec: SpecId, code: &[u8]) -> FxHashMap { + let opcode_infos = spec_opcode_gas(spec); + let mut map = FxHashMap::default(); + + let mut pc = 0; + let mut cumulative_push_size = 0; + while pc < code.len() { + let ic = pc - cumulative_push_size; + if PC_FIRST { + map.insert(pc, ic); + } else { + map.insert(ic, pc); + } + + let op = code[pc]; + if opcode_infos[op as usize].is_push() { + // Skip the push bytes. + let push_size = (op - opcode::PUSH0) as usize; + pc += push_size; + cumulative_push_size += push_size; + } + + pc += 1; + } + map +} diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs index 9864ded06f0b0..ef28ad9223adb 100644 --- a/crates/evm/core/src/lib.rs +++ b/crates/evm/core/src/lib.rs @@ -4,15 +4,42 @@ #![warn(unused_crate_dependencies)] +use auto_impl::auto_impl; +use revm::{inspectors::NoOpInspector, interpreter::CreateInputs, Database, EvmContext, Inspector}; +use revm_inspectors::access_list::AccessListInspector; + #[macro_use] extern crate tracing; +mod ic; + pub mod abi; pub mod backend; pub mod constants; pub mod debug; pub mod decode; pub mod fork; +pub mod opcodes; pub mod opts; pub mod snapshot; pub mod utils; + +/// An extension trait that allows us to add additional hooks to Inspector for later use in +/// handlers. +#[auto_impl(&mut, Box)] +pub trait InspectorExt: Inspector { + /// Determines whether the `DEFAULT_CREATE2_DEPLOYER` should be used for a CREATE2 frame. + /// + /// If this function returns true, we'll replace CREATE2 frame with a CALL frame to CREATE2 + /// factory. + fn should_use_create2_factory( + &mut self, + _context: &mut EvmContext, + _inputs: &mut CreateInputs, + ) -> bool { + false + } +} + +impl InspectorExt for NoOpInspector {} +impl InspectorExt for AccessListInspector {} diff --git a/crates/evm/core/src/opcodes.rs b/crates/evm/core/src/opcodes.rs new file mode 100644 index 0000000000000..3251036c78f7a --- /dev/null +++ b/crates/evm/core/src/opcodes.rs @@ -0,0 +1,25 @@ +//! Opcode utils + +use revm::interpreter::OpCode; + +/// Returns true if the opcode modifies memory. +/// +/// +#[inline] +pub const fn modifies_memory(opcode: OpCode) -> bool { + matches!( + opcode, + OpCode::EXTCODECOPY | + OpCode::MLOAD | + OpCode::MSTORE | + OpCode::MSTORE8 | + OpCode::MCOPY | + OpCode::CODECOPY | + OpCode::CALLDATACOPY | + OpCode::RETURNDATACOPY | + OpCode::CALL | + OpCode::CALLCODE | + OpCode::DELEGATECALL | + OpCode::STATICCALL + ) +} diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index dd99a391fb7f0..51e62b72c3e11 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -1,16 +1,16 @@ use super::fork::environment; use crate::fork::CreateFork; use alloy_primitives::{Address, B256, U256}; -use ethers_core::types::{Block, TxHash}; -use ethers_providers::{Middleware, Provider}; +use alloy_provider::Provider; +use alloy_rpc_types::Block; use eyre::WrapErr; -use foundry_common::{self, ProviderBuilder, RpcUrl, ALCHEMY_FREE_TIER_CUPS}; +use foundry_common::{provider::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS}; use foundry_compilers::utils::RuntimeOrHandle; use foundry_config::{Chain, Config}; -use revm::primitives::{BlockEnv, CfgEnv, SpecId, TxEnv}; +use revm::primitives::{BlockEnv, CfgEnv, TxEnv}; use serde::{Deserialize, Deserializer, Serialize}; -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct EvmOpts { /// The EVM environment configuration. #[serde(flatten)] @@ -18,7 +18,7 @@ pub struct EvmOpts { /// Fetch state over a remote instead of starting from empty state. #[serde(rename = "eth_rpc_url")] - pub fork_url: Option, + pub fork_url: Option, /// Pins the block number for the state fork. pub fork_block_number: Option, @@ -49,12 +49,21 @@ pub struct EvmOpts { /// Enables the FFI cheatcode. pub ffi: bool, + /// Use the create 2 factory in all cases including tests and non-broadcasting scripts. + pub always_use_create_2_factory: bool, + /// Verbosity mode of EVM output as number of occurrences. pub verbosity: u8, /// The memory limit per EVM execution in bytes. /// If this limit is exceeded, a `MemoryLimitOOG` result is thrown. pub memory_limit: u64, + + /// Whether to enable isolation of calls. + pub isolate: bool, + + /// Whether to disable block gas limit checks. + pub disable_block_gas_limit: bool, } impl EvmOpts { @@ -75,7 +84,7 @@ impl EvmOpts { pub async fn fork_evm_env( &self, fork_url: impl AsRef, - ) -> eyre::Result<(revm::primitives::Env, Block)> { + ) -> eyre::Result<(revm::primitives::Env, Block)> { let fork_url = fork_url.as_ref(); let provider = ProviderBuilder::new(fork_url) .compute_units_per_second(self.get_compute_units_per_second()) @@ -83,10 +92,11 @@ impl EvmOpts { environment( &provider, self.memory_limit, - self.env.gas_price, + self.env.gas_price.map(|v| v as u128), self.env.chain_id, self.fork_block_number, self.sender, + self.disable_block_gas_limit, ) .await .wrap_err_with(|| { @@ -98,13 +108,13 @@ impl EvmOpts { pub fn local_evm_env(&self) -> revm::primitives::Env { let mut cfg = CfgEnv::default(); cfg.chain_id = self.env.chain_id.unwrap_or(foundry_common::DEV_CHAIN_ID); - cfg.spec_id = SpecId::MERGE; cfg.limit_contract_code_size = self.env.code_size_limit.or(Some(usize::MAX)); cfg.memory_limit = self.memory_limit; // EIP-3607 rejects transactions from senders with deployed code. // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the // caller is a contract. So we disable the check by default. cfg.disable_eip3607 = true; + cfg.disable_block_gas_limit = self.disable_block_gas_limit; revm::primitives::Env { block: BlockEnv { @@ -158,7 +168,7 @@ impl EvmOpts { /// - mainnet otherwise pub fn get_chain_id(&self) -> u64 { if let Some(id) = self.env.chain_id { - return id + return id; } self.get_remote_chain_id().unwrap_or(Chain::mainnet()).id() } @@ -171,7 +181,7 @@ impl EvmOpts { if self.no_rpc_rate_limit { u64::MAX } else if let Some(cups) = self.compute_units_per_second { - return cups + return cups; } else { ALCHEMY_FREE_TIER_CUPS } @@ -185,11 +195,14 @@ impl EvmOpts { return Some(Chain::mainnet()) } trace!(?url, "retrieving chain via eth_chainId"); - let provider = Provider::try_from(url.as_str()) - .unwrap_or_else(|_| panic!("Failed to establish provider to {url}")); - - if let Ok(id) = RuntimeOrHandle::new().block_on(provider.get_chainid()) { - return Some(Chain::from(id.as_u64())) + let provider = ProviderBuilder::new(url.as_str()) + .compute_units_per_second(self.get_compute_units_per_second()) + .build() + .ok() + .unwrap_or_else(|| panic!("Failed to establish provider to {url}")); + + if let Ok(id) = RuntimeOrHandle::new().block_on(provider.get_chain_id()) { + return Some(Chain::from(id)); } } @@ -197,7 +210,7 @@ impl EvmOpts { } } -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct Env { /// The block gas limit. #[serde(deserialize_with = "string_or_number")] diff --git a/crates/evm/core/src/snapshot.rs b/crates/evm/core/src/snapshot.rs index bcd1c92913191..423f853eed498 100644 --- a/crates/evm/core/src/snapshot.rs +++ b/crates/evm/core/src/snapshot.rs @@ -4,7 +4,7 @@ use alloy_primitives::U256; use std::{collections::HashMap, ops::Add}; /// Represents all snapshots -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct Snapshots { id: U256, snapshots: HashMap, @@ -39,6 +39,11 @@ impl Snapshots { snapshot } + /// Removes all snapshots + pub fn clear(&mut self) { + self.snapshots.clear(); + } + /// Removes the snapshot with the given `id`. /// /// Does not remove snapshots after it. diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index 879d09774e43e..d1e73a1fa9024 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -1,176 +1,51 @@ -use alloy_json_abi::{Function, JsonAbi as Abi}; -use alloy_primitives::{Address, FixedBytes, B256}; -use ethers_core::types::{ActionType, Block, CallType, Chain, Transaction, H256, U256}; +pub use crate::ic::*; +use crate::{constants::DEFAULT_CREATE2_DEPLOYER, InspectorExt}; +use alloy_json_abi::{Function, JsonAbi}; +use alloy_primitives::{Address, FixedBytes, U256}; +use alloy_rpc_types::{Block, Transaction}; use eyre::ContextCompat; -use foundry_common::types::ToAlloy; -use revm::{ - interpreter::{opcode, opcode::spec_opcode_gas, CallScheme, CreateInputs, InstructionResult}, - primitives::{CreateScheme, Eval, Halt, SpecId, TransactTo}, -}; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; - pub use foundry_compilers::utils::RuntimeOrHandle; +use foundry_config::NamedChain; pub use revm::primitives::State as StateChangeset; - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "UPPERCASE")] -#[derive(Default)] -pub enum CallKind { - #[default] - Call, - StaticCall, - CallCode, - DelegateCall, - AuthCall, - Create, - Create2, -} - -impl From for CallKind { - fn from(scheme: CallScheme) -> Self { - match scheme { - CallScheme::Call => CallKind::Call, - CallScheme::StaticCall => CallKind::StaticCall, - CallScheme::CallCode => CallKind::CallCode, - CallScheme::DelegateCall => CallKind::DelegateCall, - CallScheme::AuthCall => CallKind::AuthCall, - } - } -} - -impl From for CallKind { - fn from(create: CreateScheme) -> Self { - match create { - CreateScheme::Create => CallKind::Create, - CreateScheme::Create2 { .. } => CallKind::Create2, - } - } -} - -impl From for ActionType { - fn from(kind: CallKind) -> Self { - match kind { - CallKind::Call | - CallKind::StaticCall | - CallKind::DelegateCall | - CallKind::CallCode | - CallKind::AuthCall => ActionType::Call, - CallKind::Create => ActionType::Create, - CallKind::Create2 => ActionType::Create, - } - } -} - -impl From for CallType { - fn from(ty: CallKind) -> Self { - match ty { - CallKind::Call => CallType::Call, - CallKind::StaticCall => CallType::StaticCall, - CallKind::CallCode => CallType::CallCode, - CallKind::DelegateCall => CallType::DelegateCall, - CallKind::AuthCall => CallType::AuthCall, - CallKind::Create => CallType::None, - CallKind::Create2 => CallType::None, - } - } -} - -/// Small helper function to convert [U256] into [H256]. -#[inline] -pub fn u256_to_h256_le(u: U256) -> H256 { - let mut h = H256::default(); - u.to_little_endian(h.as_mut()); - h -} - -/// Small helper function to convert [U256] into [H256]. -#[inline] -pub fn u256_to_h256_be(u: U256) -> H256 { - let mut h = H256::default(); - u.to_big_endian(h.as_mut()); - h -} - -/// Small helper function to convert [H256] into [U256]. -#[inline] -pub fn h256_to_u256_be(storage: H256) -> U256 { - U256::from_big_endian(storage.as_bytes()) -} - -/// Small helper function to convert [H256] into [U256]. -#[inline] -pub fn h256_to_u256_le(storage: H256) -> U256 { - U256::from_little_endian(storage.as_bytes()) -} - -/// Small helper function to convert an Eval into an InstructionResult -#[inline] -pub fn eval_to_instruction_result(eval: Eval) -> InstructionResult { - match eval { - Eval::Return => InstructionResult::Return, - Eval::Stop => InstructionResult::Stop, - Eval::SelfDestruct => InstructionResult::SelfDestruct, - } -} - -/// Small helper function to convert a Halt into an InstructionResult -#[inline] -pub fn halt_to_instruction_result(halt: Halt) -> InstructionResult { - match halt { - Halt::OutOfGas(_) => InstructionResult::OutOfGas, - Halt::OpcodeNotFound => InstructionResult::OpcodeNotFound, - Halt::InvalidFEOpcode => InstructionResult::InvalidFEOpcode, - Halt::InvalidJump => InstructionResult::InvalidJump, - Halt::NotActivated => InstructionResult::NotActivated, - Halt::StackOverflow => InstructionResult::StackOverflow, - Halt::StackUnderflow => InstructionResult::StackUnderflow, - Halt::OutOfOffset => InstructionResult::OutOfOffset, - Halt::CreateCollision => InstructionResult::CreateCollision, - Halt::PrecompileError => InstructionResult::PrecompileError, - Halt::NonceOverflow => InstructionResult::NonceOverflow, - Halt::CreateContractSizeLimit => InstructionResult::CreateContractSizeLimit, - Halt::CreateContractStartingWithEF => InstructionResult::CreateContractStartingWithEF, - Halt::CreateInitcodeSizeLimit => InstructionResult::CreateInitcodeSizeLimit, - Halt::OverflowPayment => InstructionResult::OverflowPayment, - Halt::StateChangeDuringStaticCall => InstructionResult::StateChangeDuringStaticCall, - Halt::ActiveAccountUnsetAuthCall => InstructionResult::ActiveAccountUnsetAuthCall, - Halt::CallNotAllowedInsideStatic => InstructionResult::CallNotAllowedInsideStatic, - Halt::OutOfFund => InstructionResult::OutOfFund, - Halt::CallTooDeep => InstructionResult::CallTooDeep, - Halt::FailedDeposit => InstructionResult::Return, - } -} +use revm::{ + db::WrapDatabaseRef, + handler::register::EvmHandler, + interpreter::{ + return_ok, CallContext, CallInputs, CallScheme, CreateInputs, CreateOutcome, Gas, + InstructionResult, InterpreterResult, Transfer, + }, + primitives::{CreateScheme, EVMError, SpecId, TransactTo, KECCAK_EMPTY}, + FrameOrResult, FrameResult, +}; +use std::{cell::RefCell, rc::Rc, sync::Arc}; /// Depending on the configured chain id and block number this should apply any specific changes /// -/// This checks for: -/// - prevrandao mixhash after merge -pub fn apply_chain_and_block_specific_env_changes( - env: &mut revm::primitives::Env, - block: &Block, -) { - if let Ok(chain) = Chain::try_from(env.cfg.chain_id) { - let block_number = block.number.unwrap_or_default(); +/// - checks for prevrandao mixhash after merge +/// - applies chain specifics: on Arbitrum `block.number` is the L1 block +/// Should be called with proper chain id (retrieved from provider if not provided). +pub fn apply_chain_and_block_specific_env_changes(env: &mut revm::primitives::Env, block: &Block) { + if let Ok(chain) = NamedChain::try_from(env.cfg.chain_id) { + let block_number = block.header.number.unwrap_or_default(); match chain { - Chain::Mainnet => { + NamedChain::Mainnet => { // after merge difficulty is supplanted with prevrandao EIP-4399 - if block_number.as_u64() >= 15_537_351u64 { + if block_number >= 15_537_351u64 { env.block.difficulty = env.block.prevrandao.unwrap_or_default().into(); } - return + return; } - Chain::Arbitrum | - Chain::ArbitrumGoerli | - Chain::ArbitrumNova | - Chain::ArbitrumTestnet => { + NamedChain::Arbitrum | + NamedChain::ArbitrumGoerli | + NamedChain::ArbitrumNova | + NamedChain::ArbitrumTestnet => { // on arbitrum `block.number` is the L1 block which is included in the // `l1BlockNumber` field if let Some(l1_block_number) = block.other.get("l1BlockNumber").cloned() { if let Ok(l1_block_number) = serde_json::from_value::(l1_block_number) { - env.block.number = l1_block_number.to_alloy(); + env.block.number = l1_block_number; } } } @@ -179,68 +54,16 @@ pub fn apply_chain_and_block_specific_env_changes( } // if difficulty is `0` we assume it's past merge - if block.difficulty.is_zero() { + if block.header.difficulty.is_zero() { env.block.difficulty = env.block.prevrandao.unwrap_or_default().into(); } } -/// A map of program counters to instruction counters. -pub type PCICMap = BTreeMap; - -/// Builds a mapping from program counters to instruction counters. -pub fn build_pc_ic_map(spec: SpecId, code: &[u8]) -> PCICMap { - let opcode_infos = spec_opcode_gas(spec); - let mut pc_ic_map: PCICMap = BTreeMap::new(); - - let mut i = 0; - let mut cumulative_push_size = 0; - while i < code.len() { - let op = code[i]; - pc_ic_map.insert(i, i - cumulative_push_size); - if opcode_infos[op as usize].is_push() { - // Skip the push bytes. - // - // For more context on the math, see: https://github.com/bluealloy/revm/blob/007b8807b5ad7705d3cacce4d92b89d880a83301/crates/revm/src/interpreter/contract.rs#L114-L115 - i += (op - opcode::PUSH1 + 1) as usize; - cumulative_push_size += (op - opcode::PUSH1 + 1) as usize; - } - i += 1; - } - - pc_ic_map -} - -/// A map of instruction counters to program counters. -pub type ICPCMap = BTreeMap; - -/// Builds a mapping from instruction counters to program counters. -pub fn build_ic_pc_map(spec: SpecId, code: &[u8]) -> ICPCMap { - let opcode_infos = spec_opcode_gas(spec); - let mut ic_pc_map: ICPCMap = ICPCMap::new(); - - let mut i = 0; - let mut cumulative_push_size = 0; - while i < code.len() { - let op = code[i]; - ic_pc_map.insert(i - cumulative_push_size, i); - if opcode_infos[op as usize].is_push() { - // Skip the push bytes. - // - // For more context on the math, see: https://github.com/bluealloy/revm/blob/007b8807b5ad7705d3cacce4d92b89d880a83301/crates/revm/src/interpreter/contract.rs#L114-L115 - i += (op - opcode::PUSH1 + 1) as usize; - cumulative_push_size += (op - opcode::PUSH1 + 1) as usize; - } - i += 1; - } - - ic_pc_map -} - /// Given an ABI and selector, it tries to find the respective function. pub fn get_function( contract_name: &str, selector: &FixedBytes<4>, - abi: &Abi, + abi: &JsonAbi, ) -> eyre::Result { abi.functions() .find(|func| func.selector().as_slice() == selector.as_slice()) @@ -250,11 +73,11 @@ pub fn get_function( /// Configures the env for the transaction pub fn configure_tx_env(env: &mut revm::primitives::Env, tx: &Transaction) { - env.tx.caller = tx.from.to_alloy(); - env.tx.gas_limit = tx.gas.as_u64(); - env.tx.gas_price = tx.gas_price.unwrap_or_default().to_alloy(); - env.tx.gas_priority_fee = tx.max_priority_fee_per_gas.map(|g| g.to_alloy()); - env.tx.nonce = Some(tx.nonce.as_u64()); + env.tx.caller = tx.from; + env.tx.gas_limit = tx.gas as u64; + env.tx.gas_price = U256::from(tx.gas_price.unwrap_or_default()); + env.tx.gas_priority_fee = tx.max_priority_fee_per_gas.map(U256::from); + env.tx.nonce = Some(tx.nonce); env.tx.access_list = tx .access_list .clone() @@ -263,25 +86,17 @@ pub fn configure_tx_env(env: &mut revm::primitives::Env, tx: &Transaction) { .into_iter() .map(|item| { ( - item.address.to_alloy(), - item.storage_keys.into_iter().map(h256_to_u256_be).map(|g| g.to_alloy()).collect(), + item.address, + item.storage_keys + .into_iter() + .map(|key| alloy_primitives::U256::from_be_bytes(key.0)) + .collect(), ) }) .collect(); - env.tx.value = tx.value.to_alloy(); + env.tx.value = tx.value.to(); env.tx.data = alloy_primitives::Bytes(tx.input.0.clone()); - env.tx.transact_to = - tx.to.map(|tx| tx.to_alloy()).map(TransactTo::Call).unwrap_or_else(TransactTo::create) -} - -/// Get the address of a contract creation -pub fn get_create_address(call: &CreateInputs, nonce: u64) -> Address { - match call.scheme { - CreateScheme::Create => call.caller.create(nonce), - CreateScheme::Create2 { salt } => { - call.caller.create2_from_code(B256::from(salt), &call.init_code) - } - } + env.tx.transact_to = tx.to.map(TransactTo::Call).unwrap_or_else(TransactTo::create) } /// Get the gas used, accounting for refunds @@ -289,3 +104,180 @@ pub fn gas_used(spec: SpecId, spent: u64, refunded: u64) -> u64 { let refund_quotient = if SpecId::enabled(spec, SpecId::LONDON) { 5 } else { 2 }; spent - (refunded).min(spent / refund_quotient) } + +fn get_create2_factory_call_inputs(salt: U256, inputs: CreateInputs) -> CallInputs { + let calldata = [&salt.to_be_bytes::<32>()[..], &inputs.init_code[..]].concat(); + CallInputs { + contract: DEFAULT_CREATE2_DEPLOYER, + transfer: Transfer { + source: inputs.caller, + target: DEFAULT_CREATE2_DEPLOYER, + value: inputs.value, + }, + input: calldata.into(), + gas_limit: inputs.gas_limit, + context: CallContext { + caller: inputs.caller, + address: DEFAULT_CREATE2_DEPLOYER, + code_address: DEFAULT_CREATE2_DEPLOYER, + apparent_value: inputs.value, + scheme: CallScheme::Call, + }, + is_static: false, + return_memory_offset: 0..0, + } +} + +/// Used for routing certain CREATE2 invocations through [DEFAULT_CREATE2_DEPLOYER]. +/// +/// Overrides create hook with CALL frame if [InspectorExt::should_use_create2_factory] returns +/// true. Keeps track of overriden frames and handles outcome in the overriden insert_call_outcome +/// hook by inserting decoded address directly into interpreter. +/// +/// Should be installed after [revm::inspector_handle_register] and before any other registers. +pub fn create2_handler_register>( + handler: &mut EvmHandler<'_, I, DB>, +) { + let create2_overrides = Rc::>>::new(RefCell::new(Vec::new())); + + let create2_overrides_inner = create2_overrides.clone(); + let old_handle = handler.execution.create.clone(); + handler.execution.create = + Arc::new(move |ctx, mut inputs| -> Result> { + let CreateScheme::Create2 { salt } = inputs.scheme else { + return old_handle(ctx, inputs); + }; + if !ctx.external.should_use_create2_factory(&mut ctx.evm, &mut inputs) { + return old_handle(ctx, inputs); + } + + // Sanity check that CREATE2 deployer exists. + let code_hash = ctx.evm.load_account(DEFAULT_CREATE2_DEPLOYER)?.0.info.code_hash; + if code_hash == KECCAK_EMPTY { + return Ok(FrameOrResult::Result(FrameResult::Create(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: "missing CREATE2 deployer".into(), + gas: Gas::new(inputs.gas_limit), + }, + address: None, + }))) + } + + // Generate call inputs for CREATE2 factory. + let mut call_inputs = get_create2_factory_call_inputs(salt, *inputs); + + // Call inspector to change input or return outcome. + if let Some(outcome) = ctx.external.call(&mut ctx.evm, &mut call_inputs) { + create2_overrides_inner + .borrow_mut() + .push((ctx.evm.journaled_state.depth(), call_inputs.clone())); + return Ok(FrameOrResult::Result(FrameResult::Call(outcome))); + } + + // Push data about current override to the stack. + create2_overrides_inner + .borrow_mut() + .push((ctx.evm.journaled_state.depth(), call_inputs.clone())); + + let mut frame_or_result = ctx.evm.make_call_frame(&call_inputs); + + if let Ok(FrameOrResult::Frame(frame)) = &mut frame_or_result { + ctx.external + .initialize_interp(&mut frame.frame_data_mut().interpreter, &mut ctx.evm) + } + frame_or_result + }); + + let create2_overrides_inner = create2_overrides.clone(); + let old_handle = handler.execution.insert_call_outcome.clone(); + + handler.execution.insert_call_outcome = + Arc::new(move |ctx, frame, shared_memory, mut outcome| { + // If we are on the depth of the latest override, handle the outcome. + if create2_overrides_inner + .borrow() + .last() + .map_or(false, |(depth, _)| *depth == ctx.evm.journaled_state.depth()) + { + let (_, call_inputs) = create2_overrides_inner.borrow_mut().pop().unwrap(); + outcome = ctx.external.call_end(&mut ctx.evm, &call_inputs, outcome); + + // Decode address from output. + let address = match outcome.instruction_result() { + return_ok!() => Address::try_from(outcome.output().as_ref()) + .map_err(|_| { + outcome.result = InterpreterResult { + result: InstructionResult::Revert, + output: "invalid CREATE2 factory output".into(), + gas: Gas::new(call_inputs.gas_limit), + }; + }) + .ok(), + _ => None, + }; + frame + .frame_data_mut() + .interpreter + .insert_create_outcome(CreateOutcome { address, result: outcome.result }); + + Ok(()) + } else { + old_handle(ctx, frame, shared_memory, outcome) + } + }); +} + +/// Creates a new EVM with the given inspector. +pub fn new_evm_with_inspector<'a, DB, I>( + db: DB, + env: revm::primitives::EnvWithHandlerCfg, + inspector: I, +) -> revm::Evm<'a, I, DB> +where + DB: revm::Database, + I: InspectorExt, +{ + // NOTE: We could use `revm::Evm::builder()` here, but on the current patch it has some + // performance issues. + let revm::primitives::EnvWithHandlerCfg { env, handler_cfg } = env; + let context = revm::Context::new(revm::EvmContext::new_with_env(db, env), inspector); + let mut handler = revm::Handler::new(handler_cfg); + handler.append_handler_register_plain(revm::inspector_handle_register); + handler.append_handler_register_plain(create2_handler_register); + revm::Evm::new(context, handler) +} + +/// Creates a new EVM with the given inspector and wraps the database in a `WrapDatabaseRef`. +pub fn new_evm_with_inspector_ref<'a, DB, I>( + db: DB, + env: revm::primitives::EnvWithHandlerCfg, + inspector: I, +) -> revm::Evm<'a, I, WrapDatabaseRef> +where + DB: revm::DatabaseRef, + I: InspectorExt>, +{ + new_evm_with_inspector(WrapDatabaseRef(db), env, inspector) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn build_evm() { + let mut db = revm::db::EmptyDB::default(); + + let env = Box::::default(); + let spec = SpecId::LATEST; + let handler_cfg = revm::primitives::HandlerCfg::new(spec); + let cfg = revm::primitives::EnvWithHandlerCfg::new(env, handler_cfg); + + let mut inspector = revm::inspectors::NoOpInspector; + + let mut evm = new_evm_with_inspector(&mut db, cfg, &mut inspector); + let result = evm.transact().unwrap(); + assert!(result.result.is_success()); + } +} diff --git a/crates/evm/core/test-data/storage.json b/crates/evm/core/test-data/storage.json index 4f25625919b23..6a602f7a642ab 100644 --- a/crates/evm/core/test-data/storage.json +++ b/crates/evm/core/test-data/storage.json @@ -1 +1 @@ -{"meta":{"cfg_env":{"chain_id":1,"spec_id":"LATEST","perf_all_precompiles_have_balance":false,"memory_limit":4294967295, "perf_analyse_created_bytecodes":"Analyse", "limit_contract_code_size": 24576, "disable_coinbase_tip": false},"block_env":{"number":"0xdc42b8","coinbase":"0x0000000000000000000000000000000000000000","timestamp":"0x1","difficulty":"0x0","basefee":"0x0","gas_limit":"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},"hosts":["mainnet.infura.io"]},"accounts":{"0x63091244180ae240c87d1f528f5f269134cb07b3":{"balance":"0x0","code_hash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","code":null,"nonce":0}},"storage":{"0x63091244180ae240c87d1f528f5f269134cb07b3":{"0x0":"0x0","0x1":"0x0","0x2":"0x0","0x3":"0x0","0x4":"0x0","0x5":"0x0","0x6":"0x0","0x7":"0x0","0x8":"0x0","0x9":"0x0"}},"block_hashes":{}} \ No newline at end of file +{"meta":{"cfg_env":{"chain_id":1,"spec_id":"LATEST","perf_all_precompiles_have_balance":false,"memory_limit":4294967295,"perf_analyse_created_bytecodes":"Analyse","limit_contract_code_size":24576,"disable_coinbase_tip":false},"block_env":{"number":"0xdc42b8","coinbase":"0x0000000000000000000000000000000000000000","timestamp":"0x1","difficulty":"0x0","basefee":"0x0","gas_limit":"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},"hosts":["mainnet.infura.io"]},"accounts":{"0x63091244180ae240c87d1f528f5f269134cb07b3":{"balance":"0x0","code_hash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","code":null,"nonce":0}},"storage":{"0x63091244180ae240c87d1f528f5f269134cb07b3":{"0x0":"0x0","0x1":"0x0","0x2":"0x0","0x3":"0x0","0x4":"0x0","0x5":"0x0","0x6":"0x0","0x7":"0x0","0x8":"0x0","0x9":"0x0"}},"block_hashes":{}} \ No newline at end of file diff --git a/crates/evm/coverage/Cargo.toml b/crates/evm/coverage/Cargo.toml index 4a9a39276abfc..6a3dc92ff3b32 100644 --- a/crates/evm/coverage/Cargo.toml +++ b/crates/evm/coverage/Cargo.toml @@ -20,3 +20,4 @@ eyre = "0.6" revm.workspace = true semver = "1" tracing = "0.1" +rustc-hash.workspace = true diff --git a/crates/evm/coverage/src/analysis.rs b/crates/evm/coverage/src/analysis.rs index 8928f44770c1e..040da5a0f4fa9 100644 --- a/crates/evm/coverage/src/analysis.rs +++ b/crates/evm/coverage/src/analysis.rs @@ -1,11 +1,12 @@ use super::{ContractId, CoverageItem, CoverageItemKind, SourceLocation}; use foundry_common::TestFunctionExt; use foundry_compilers::artifacts::ast::{self, Ast, Node, NodeType}; +use rustc_hash::FxHashMap; use semver::Version; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; /// A visitor that walks the AST of a single contract and finds coverage items. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ContractVisitor<'a> { /// The source ID of the contract. source_id: usize, @@ -22,40 +23,24 @@ pub struct ContractVisitor<'a> { /// Coverage items pub items: Vec, - - /// Node IDs of this contract's base contracts, as well as IDs for referenced contracts such as - /// libraries - pub base_contract_node_ids: HashSet, } impl<'a> ContractVisitor<'a> { pub fn new(source_id: usize, source: &'a str, contract_name: String) -> Self { - Self { - source_id, - source, - contract_name, - branch_id: 0, - last_line: 0, - items: Vec::new(), - base_contract_node_ids: HashSet::new(), - } + Self { source_id, source, contract_name, branch_id: 0, last_line: 0, items: Vec::new() } } pub fn visit(mut self, contract_ast: Node) -> eyre::Result { - let linearized_base_contracts: Vec = - contract_ast.attribute("linearizedBaseContracts").ok_or_else(|| { - eyre::eyre!( - "The contract's AST node is missing a list of linearized base contracts" - ) - })?; - - // We skip the first ID because that's the ID of the contract itself - self.base_contract_node_ids.extend(&linearized_base_contracts[1..]); - // Find all functions and walk their AST for node in contract_ast.nodes { - if node.node_type == NodeType::FunctionDefinition { - self.visit_function_definition(node.clone())?; + match &node.node_type { + NodeType::FunctionDefinition => { + self.visit_function_definition(node)?; + } + NodeType::ModifierDefinition => { + self.visit_modifier_definition(node)?; + } + _ => {} } } @@ -66,13 +51,10 @@ impl<'a> ContractVisitor<'a> { let name: String = node.attribute("name").ok_or_else(|| eyre::eyre!("Function has no name"))?; - // TODO(onbjerg): Re-enable constructor parsing when we walk both the deployment and runtime - // sourcemaps. Currently this fails because we are trying to look for anchors in the runtime - // sourcemap. // TODO(onbjerg): Figure out why we cannot find anchors for the receive function let kind: String = node.attribute("kind").ok_or_else(|| eyre::eyre!("Function has no kind"))?; - if kind == "constructor" || kind == "receive" { + if kind == "receive" { return Ok(()) } @@ -89,6 +71,23 @@ impl<'a> ContractVisitor<'a> { } } + fn visit_modifier_definition(&mut self, mut node: Node) -> eyre::Result<()> { + let name: String = + node.attribute("name").ok_or_else(|| eyre::eyre!("Modifier has no name"))?; + + match node.body.take() { + Some(body) => { + self.push_item(CoverageItem { + kind: CoverageItemKind::Function { name }, + loc: self.source_location_for(&node.src), + hits: 0, + }); + self.visit_block(*body) + } + _ => Ok(()), + } + } + fn visit_block(&mut self, node: Node) -> eyre::Result<()> { let statements: Vec = node.attribute("statements").unwrap_or_default(); @@ -114,7 +113,6 @@ impl<'a> ContractVisitor<'a> { NodeType::Break | NodeType::Continue | NodeType::EmitStatement | - NodeType::PlaceholderStatement | NodeType::RevertStatement | NodeType::YulAssignment | NodeType::YulBreak | @@ -128,6 +126,9 @@ impl<'a> ContractVisitor<'a> { Ok(()) } + // Skip placeholder statements as they are never referenced in source maps. + NodeType::PlaceholderStatement => Ok(()), + // Return with eventual subcall NodeType::Return => { self.push_item(CoverageItem { @@ -317,25 +318,13 @@ impl<'a> ContractVisitor<'a> { }); let expr: Option = node.attribute("expression"); - match expr.as_ref().map(|expr| &expr.node_type) { + if let Some(NodeType::Identifier) = expr.as_ref().map(|expr| &expr.node_type) { // Might be a require/assert call - Some(NodeType::Identifier) => { - let name: Option = expr.and_then(|expr| expr.attribute("name")); - if let Some("assert" | "require") = name.as_deref() { - self.push_branches(&node.src, self.branch_id); - self.branch_id += 1; - } - } - // Might be a call to a library - Some(NodeType::MemberAccess) => { - let referenced_declaration_id = expr - .and_then(|expr| expr.attribute("expression")) - .and_then(|subexpr: Node| subexpr.attribute("referencedDeclaration")); - if let Some(id) = referenced_declaration_id { - self.base_contract_node_ids.insert(id); - } + let name: Option = expr.and_then(|expr| expr.attribute("name")); + if let Some("assert" | "require") = name.as_deref() { + self.push_branches(&node.src, self.branch_id); + self.branch_id += 1; } - _ => (), } Ok(()) @@ -374,12 +363,13 @@ impl<'a> ContractVisitor<'a> { NodeType::ForStatement | NodeType::IfStatement | NodeType::InlineAssembly | - NodeType::PlaceholderStatement | NodeType::Return | NodeType::RevertStatement | NodeType::TryStatement | NodeType::VariableDeclarationStatement | NodeType::WhileStatement => self.visit_statement(node), + // Skip placeholder statements as they are never referenced in source maps. + NodeType::PlaceholderStatement => Ok(()), _ => { warn!("unexpected node type, expected block or statement: {:?}", node.node_type); Ok(()) @@ -434,26 +424,17 @@ impl<'a> ContractVisitor<'a> { pub struct SourceAnalysis { /// A collection of coverage items. pub items: Vec, - /// A mapping of contract IDs to item IDs relevant to the contract (including items in base - /// contracts). - pub contract_items: HashMap>, } /// Analyzes a set of sources to find coverage items. -#[derive(Default, Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct SourceAnalyzer { /// A map of source IDs to their source code - sources: HashMap, - /// A map of AST node IDs of contracts to their contract IDs. - contract_ids: HashMap, + sources: FxHashMap, /// A map of contract IDs to their AST nodes. contracts: HashMap, /// A collection of coverage items. items: Vec, - /// A map of contract IDs to item IDs. - contract_items: HashMap>, - /// A map of contracts to their base contracts - contract_bases: HashMap>, } impl SourceAnalyzer { @@ -463,8 +444,8 @@ impl SourceAnalyzer { /// (defined by `version`). pub fn new( version: Version, - asts: HashMap, - sources: HashMap, + asts: FxHashMap, + sources: FxHashMap, ) -> eyre::Result { let mut analyzer = SourceAnalyzer { sources, ..Default::default() }; @@ -475,8 +456,6 @@ impl SourceAnalyzer { continue } - let node_id = - child.id.ok_or_else(|| eyre::eyre!("The contract's AST node has no ID"))?; let contract_id = ContractId { version: version.clone(), source_id, @@ -484,7 +463,6 @@ impl SourceAnalyzer { .attribute("name") .ok_or_else(|| eyre::eyre!("Contract has no name"))?, }; - analyzer.contract_ids.insert(node_id, contract_id.clone()); analyzer.contracts.insert(contract_id, child); } } @@ -496,11 +474,7 @@ impl SourceAnalyzer { /// /// Coverage items are found by: /// - Walking the AST of each contract (except interfaces) - /// - Recording the items and base contracts of each contract - /// - /// Finally, the item IDs of each contract and its base contracts are flattened, and the return - /// value represents all coverage items in the project, along with a mapping of contract IDs to - /// item IDs. + /// - Recording the items of each contract /// /// Each coverage item contains relevant information to find opcodes corresponding to them: the /// source ID the item is in, the source code range of the item, and the contract name the item @@ -510,127 +484,32 @@ impl SourceAnalyzer { /// two different solc versions will produce overlapping source IDs if the compiler version is /// not taken into account. pub fn analyze(mut self) -> eyre::Result { - // Analyze the contracts - self.analyze_contracts()?; - - // Flatten the data - let mut flattened: HashMap> = HashMap::new(); - for contract_id in self.contract_items.keys() { - let mut item_ids: Vec = Vec::new(); - - // - // for a specific contract (id == contract_id): - // - // self.contract_bases.get(contract_id) includes the following contracts: - // 1. all the ancestors of this contract (including parent, grandparent, ... - // contracts) - // 2. the libraries **directly** used by this contract - // - // The missing contracts are: - // 1. libraries used in ancestors of this contracts - // 2. libraries used in libraries (i.e libs indirectly used by this contract) - // - // We want to find out all the above contracts and libraries related to this contract. - - for contract_or_lib in { - // A set of contracts and libraries related to this contract (will include "this" - // contract itself) - let mut contracts_libraries: HashSet<&ContractId> = HashSet::new(); - - // we use a stack for depth-first search. - let mut stack: Vec<&ContractId> = Vec::new(); - - // push "this" contract onto the stack - stack.push(contract_id); - - while let Some(contract_or_lib) = stack.pop() { - // whenever a contract_or_lib is removed from the stack, it is added to the set - contracts_libraries.insert(contract_or_lib); - - // push all ancestors of contract_or_lib and libraries used by contract_or_lib - // onto the stack - if let Some(bases) = self.contract_bases.get(contract_or_lib) { - stack.extend( - bases.iter().filter(|base| !contracts_libraries.contains(base)), - ); - } - } - - contracts_libraries - } { - // get items of each contract or library - if let Some(items) = self.contract_items.get(contract_or_lib) { - item_ids.extend(items.iter()); - } - } - - // If there are no items for this contract, then it was most likely filtered - if !item_ids.is_empty() { - flattened.insert(contract_id.clone(), item_ids); - } - } - - Ok(SourceAnalysis { items: self.items.clone(), contract_items: flattened }) - } - - fn analyze_contracts(&mut self) -> eyre::Result<()> { - for contract_id in self.contracts.keys() { - // Find this contract's coverage items if we haven't already - if self.contract_items.get(contract_id).is_none() { - let ContractVisitor { items, base_contract_node_ids, .. } = ContractVisitor::new( - contract_id.source_id, - self.sources.get(&contract_id.source_id).unwrap_or_else(|| { - panic!( - "We should have the source code for source ID {}", - contract_id.source_id - ) - }), - contract_id.contract_name.clone(), - ) - .visit( - self.contracts - .get(contract_id) - .unwrap_or_else(|| { - panic!("We should have the AST of contract: {contract_id:?}") - }) - .clone(), - )?; - - let is_test = items.iter().any(|item| { - if let CoverageItemKind::Function { name } = &item.kind { - name.is_test() - } else { - false - } - }); - - // Record this contract's base contracts - // We don't do this for test contracts because we don't care about them - if !is_test { - self.contract_bases.insert( - contract_id.clone(), - base_contract_node_ids - .iter() - .filter_map(|base_contract_node_id| { - self.contract_ids.get(base_contract_node_id).cloned() - }) - .collect(), - ); - } - - // For tests and contracts with no items we still record an empty Vec so we don't - // end up here again - if items.is_empty() || is_test { - self.contract_items.insert(contract_id.clone(), Vec::new()); + for (contract_id, ast) in self.contracts { + let ContractVisitor { items, .. } = ContractVisitor::new( + contract_id.source_id, + self.sources.get(&contract_id.source_id).ok_or_else(|| { + eyre::eyre!( + "We should have the source code for source ID {}", + contract_id.source_id + ) + })?, + contract_id.contract_name.clone(), + ) + .visit(ast)?; + + let is_test = items.iter().any(|item| { + if let CoverageItemKind::Function { name } = &item.kind { + name.is_test() } else { - let item_ids: Vec = - (self.items.len()..self.items.len() + items.len()).collect(); - self.items.extend(items); - self.contract_items.insert(contract_id.clone(), item_ids.clone()); + false } + }); + + if !is_test { + self.items.extend(items); } } - Ok(()) + Ok(SourceAnalysis { items: self.items }) } } diff --git a/crates/evm/coverage/src/anchors.rs b/crates/evm/coverage/src/anchors.rs index 0342b38e9527e..d45fdae57dca3 100644 --- a/crates/evm/coverage/src/anchors.rs +++ b/crates/evm/coverage/src/anchors.rs @@ -1,24 +1,33 @@ +use std::collections::HashMap; + use super::{CoverageItem, CoverageItemKind, ItemAnchor, SourceLocation}; use alloy_primitives::Bytes; use foundry_compilers::sourcemap::{SourceElement, SourceMap}; -use foundry_evm_core::utils::ICPCMap; +use foundry_evm_core::utils::IcPcMap; use revm::{ interpreter::opcode::{self, spec_opcode_gas}, - primitives::SpecId, + primitives::{HashSet, SpecId}, }; /// Attempts to find anchors for the given items using the given source map and bytecode. pub fn find_anchors( bytecode: &Bytes, source_map: &SourceMap, - ic_pc_map: &ICPCMap, - item_ids: &[usize], + ic_pc_map: &IcPcMap, items: &[CoverageItem], + items_by_source_id: &HashMap>, ) -> Vec { - item_ids + // Prepare coverage items from all sources referenced in the source map + let potential_item_ids = source_map .iter() + .filter_map(|element| items_by_source_id.get(&(element.index? as usize))) + .flatten() + .collect::>(); + + potential_item_ids + .into_iter() .filter_map(|item_id| { - let item = items.get(*item_id)?; + let item = &items[*item_id]; match item.kind { CoverageItemKind::Branch { path_id, .. } => { @@ -49,7 +58,7 @@ pub fn find_anchors( /// Find an anchor representing the first opcode within the given source range. pub fn find_anchor_simple( source_map: &SourceMap, - ic_pc_map: &ICPCMap, + ic_pc_map: &IcPcMap, item_id: usize, loc: &SourceLocation, ) -> eyre::Result { @@ -62,7 +71,7 @@ pub fn find_anchor_simple( })?; Ok(ItemAnchor { - instruction: *ic_pc_map.get(&instruction).ok_or_else(|| { + instruction: ic_pc_map.get(instruction).ok_or_else(|| { eyre::eyre!("We found an anchor, but we cant translate it to a program counter") })?, item_id, diff --git a/crates/evm/coverage/src/inspector.rs b/crates/evm/coverage/src/inspector.rs index 8a57a2349d877..3c3af1ba21486 100644 --- a/crates/evm/coverage/src/inspector.rs +++ b/crates/evm/coverage/src/inspector.rs @@ -1,8 +1,7 @@ use crate::{HitMap, HitMaps}; -use alloy_primitives::Bytes; -use revm::{interpreter::Interpreter, Database, EVMData, Inspector}; +use revm::{interpreter::Interpreter, Database, EvmContext, Inspector}; -#[derive(Clone, Default, Debug)] +#[derive(Clone, Debug, Default)] pub struct CoverageCollector { /// Maps that track instruction hit data. pub maps: HitMaps, @@ -10,18 +9,16 @@ pub struct CoverageCollector { impl Inspector for CoverageCollector { #[inline] - fn initialize_interp(&mut self, interpreter: &mut Interpreter<'_>, _: &mut EVMData<'_, DB>) { - let hash = interpreter.contract.hash; - self.maps.entry(hash).or_insert_with(|| { - HitMap::new(Bytes::copy_from_slice( - interpreter.contract.bytecode.original_bytecode_slice(), - )) - }); + fn initialize_interp(&mut self, interp: &mut Interpreter, _context: &mut EvmContext) { + let hash = interp.contract.hash; + self.maps + .entry(hash) + .or_insert_with(|| HitMap::new(interp.contract.bytecode.original_bytecode())); } #[inline] - fn step(&mut self, interpreter: &mut Interpreter<'_>, _: &mut EVMData<'_, DB>) { - let hash = interpreter.contract.hash; - self.maps.entry(hash).and_modify(|map| map.hit(interpreter.program_counter())); + fn step(&mut self, interp: &mut Interpreter, _context: &mut EvmContext) { + let hash = interp.contract.hash; + self.maps.entry(hash).and_modify(|map| map.hit(interp.program_counter())); } } diff --git a/crates/evm/coverage/src/lib.rs b/crates/evm/coverage/src/lib.rs index 0ae3d6ea4ae73..7a8f51c2e969f 100644 --- a/crates/evm/coverage/src/lib.rs +++ b/crates/evm/coverage/src/lib.rs @@ -8,6 +8,7 @@ extern crate tracing; use alloy_primitives::{Bytes, B256}; +use foundry_compilers::sourcemap::SourceElement; use semver::Version; use std::{ collections::{BTreeMap, HashMap}, @@ -15,6 +16,8 @@ use std::{ ops::{AddAssign, Deref, DerefMut}, }; +use eyre::{Context, Result}; + pub mod analysis; pub mod anchors; @@ -25,7 +28,7 @@ pub use inspector::CoverageCollector; /// /// A coverage report contains coverage items and opcodes corresponding to those items (called /// "anchors"). A single coverage item may be referred to by multiple anchors. -#[derive(Default, Debug, Clone)] +#[derive(Clone, Debug, Default)] pub struct CoverageReport { /// A map of source IDs to the source path pub source_paths: HashMap<(Version, usize), String>, @@ -34,7 +37,11 @@ pub struct CoverageReport { /// All coverage items for the codebase, keyed by the compiler version. pub items: HashMap>, /// All item anchors for the codebase, keyed by their contract ID. - pub anchors: HashMap>, + pub anchors: HashMap, Vec)>, + /// All the bytecode hits for the codebase + pub bytecode_hits: HashMap, + /// The bytecode -> source mappings + pub source_maps: HashMap, Vec)>, } impl CoverageReport { @@ -49,13 +56,24 @@ impl CoverageReport { self.source_paths_to_ids.get(&(version, path)) } + /// Add the source maps + pub fn add_source_maps( + &mut self, + source_maps: HashMap, Vec)>, + ) { + self.source_maps.extend(source_maps); + } + /// Add coverage items to this report pub fn add_items(&mut self, version: Version, items: Vec) { self.items.entry(version).or_default().extend(items); } /// Add anchors to this report - pub fn add_anchors(&mut self, anchors: HashMap>) { + pub fn add_anchors( + &mut self, + anchors: HashMap, Vec)>, + ) { self.anchors.extend(anchors); } @@ -109,8 +127,27 @@ impl CoverageReport { /// /// This function should only be called *after* all the relevant sources have been processed and /// added to the map (see [add_source]). - pub fn add_hit_map(&mut self, contract_id: &ContractId, hit_map: &HitMap) { + pub fn add_hit_map( + &mut self, + contract_id: &ContractId, + hit_map: &HitMap, + is_deployed_code: bool, + ) -> Result<()> { + // Add bytecode level hits + let e = self + .bytecode_hits + .entry(contract_id.clone()) + .or_insert_with(|| HitMap::new(hit_map.bytecode.clone())); + e.merge(hit_map).context(format!( + "contract_id {:?}, hash {}, hash {}", + contract_id, + e.bytecode.clone(), + hit_map.bytecode.clone(), + ))?; + + // Add source level hits if let Some(anchors) = self.anchors.get(contract_id) { + let anchors = if is_deployed_code { &anchors.1 } else { &anchors.0 }; for anchor in anchors { if let Some(hits) = hit_map.hits.get(&anchor.instruction) { self.items @@ -121,11 +158,12 @@ impl CoverageReport { } } } + Ok(()) } } /// A collection of [HitMap]s -#[derive(Default, Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct HitMaps(pub HashMap); impl HitMaps { @@ -159,7 +197,7 @@ impl DerefMut for HitMaps { /// Hit data for an address. /// /// Contains low-level data about hit counters for the instructions in the bytecode of a contract. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct HitMap { pub bytecode: Bytes, pub hits: BTreeMap, @@ -174,10 +212,32 @@ impl HitMap { pub fn hit(&mut self, pc: usize) { *self.hits.entry(pc).or_default() += 1; } + + /// Merge another hitmap into this, assuming the bytecode is consistent + pub fn merge(&mut self, other: &HitMap) -> Result<(), eyre::Report> { + for (pc, hits) in &other.hits { + *self.hits.entry(*pc).or_default() += hits; + } + Ok(()) + } + + pub fn consistent_bytecode(&self, hm1: &HitMap, hm2: &HitMap) -> bool { + // Consider the bytecodes consistent if they are the same out as far as the + // recorded hits + let len1 = hm1.hits.last_key_value(); + let len2 = hm2.hits.last_key_value(); + if let (Some(len1), Some(len2)) = (len1, len2) { + let len = std::cmp::max(len1.0, len2.0); + let ok = hm1.bytecode.0[..*len] == hm2.bytecode.0[..*len]; + println!("consistent_bytecode: {}, {}, {}, {}", ok, len1.0, len2.0, len); + return ok; + } + true + } } /// A unique identifier for a contract -#[derive(Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ContractId { pub version: Version, pub source_id: usize, @@ -264,7 +324,7 @@ impl Display for CoverageItem { } } -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct SourceLocation { /// The source ID. pub source_id: usize, @@ -292,7 +352,7 @@ impl Display for SourceLocation { } /// Coverage summary for a source file. -#[derive(Default, Debug, Clone)] +#[derive(Clone, Debug, Default)] pub struct CoverageSummary { /// The number of executable lines in the source file. pub line_count: usize, diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index 8594576874f33..72f4a6d171567 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -22,9 +22,13 @@ foundry-evm-traces.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-json-abi.workspace = true -alloy-primitives = { workspace = true, features = ["serde", "getrandom", "arbitrary", "rlp"] } +alloy-primitives = { workspace = true, features = [ + "serde", + "getrandom", + "arbitrary", + "rlp", +] } alloy-sol-types.workspace = true -hashbrown = { version = "0.14", features = ["serde"] } revm = { workspace = true, default-features = false, features = [ "std", "serde", @@ -33,11 +37,11 @@ revm = { workspace = true, default-features = false, features = [ "optional_block_gas_limit", "optional_no_base_fee", "arbitrary", + "c-kzg", ] } +revm-inspectors.workspace = true -ethers-core.workspace = true -ethers-signers.workspace = true - +arrayvec.workspace = true eyre = "0.6" hex.workspace = true parking_lot = "0.12" diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs index ab9bd7629b8e7..370c3bcf3e1c2 100644 --- a/crates/evm/evm/src/executors/builder.rs +++ b/crates/evm/evm/src/executors/builder.rs @@ -1,7 +1,7 @@ use crate::{executors::Executor, inspectors::InspectorStackBuilder}; use alloy_primitives::U256; use foundry_evm_core::backend::Backend; -use revm::primitives::{Env, SpecId}; +use revm::primitives::{Env, EnvWithHandlerCfg, SpecId}; /// The builder that allows to configure an evm [`Executor`] which a stack of optional /// [`revm::Inspector`]s, such as [`Cheatcodes`]. @@ -10,7 +10,7 @@ use revm::primitives::{Env, SpecId}; /// /// [`Cheatcodes`]: super::inspector::Cheatcodes /// [`InspectorStack`]: super::inspector::InspectorStack -#[derive(Debug)] +#[derive(Clone, Debug)] #[must_use = "builders do nothing unless you call `build` on them"] pub struct ExecutorBuilder { /// The configuration used to build an [InspectorStack]. @@ -63,12 +63,16 @@ impl ExecutorBuilder { /// Builds the executor as configured. #[inline] - pub fn build(self, mut env: Env, db: Backend) -> Executor { + pub fn build(self, env: Env, db: Backend) -> Executor { let Self { mut stack, gas_limit, spec_id } = self; - env.cfg.spec_id = spec_id; stack.block = Some(env.block.clone()); stack.gas_price = Some(env.tx.gas_price); let gas_limit = gas_limit.unwrap_or(env.block.gas_limit); - Executor::new(db, env, stack.build(), gas_limit) + Executor::new( + db, + EnvWithHandlerCfg::new_with_spec_id(Box::new(env), spec_id), + stack.build(), + gas_limit, + ) } } diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 321f1ddcd364d..8e3a85bddfaa8 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -1,24 +1,21 @@ use crate::executors::{Executor, RawCallResult}; use alloy_dyn_abi::JsonAbiExt; -use alloy_json_abi::{Function, JsonAbi as Abi}; +use alloy_json_abi::Function; use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; use foundry_config::FuzzConfig; use foundry_evm_core::{ constants::MAGIC_ASSUME, - decode::{self, decode_console_logs}, + decode::{decode_console_logs, RevertDecoder}, }; use foundry_evm_coverage::HitMaps; use foundry_evm_fuzz::{ - strategies::{ - build_initial_state, collect_state_from_call, fuzz_calldata, fuzz_calldata_from_state, - EvmFuzzState, - }, - BaseCounterExample, CounterExample, FuzzCase, FuzzError, FuzzTestResult, + strategies::{build_initial_state, fuzz_calldata, fuzz_calldata_from_state, EvmFuzzState}, + BaseCounterExample, CounterExample, FuzzCase, FuzzError, FuzzFixtures, FuzzTestResult, }; use foundry_evm_traces::CallTraceArena; use proptest::test_runner::{TestCaseError, TestError, TestRunner}; -use std::cell::RefCell; +use std::{borrow::Cow, cell::RefCell}; mod types; pub use types::{CaseOutcome, CounterExampleOutcome, FuzzOutcome}; @@ -58,9 +55,10 @@ impl FuzzedExecutor { pub fn fuzz( &self, func: &Function, + fuzz_fixtures: &FuzzFixtures, address: Address, should_fail: bool, - errors: Option<&Abi>, + rd: &RevertDecoder, ) -> FuzzTestResult { // Stores the first Fuzzcase let first_case: RefCell> = RefCell::default(); @@ -71,30 +69,27 @@ impl FuzzedExecutor { // Stores the result and calldata of the last failed call, if any. let counterexample: RefCell<(Bytes, RawCallResult)> = RefCell::default(); - // Stores the last successful call trace - let traces: RefCell> = RefCell::default(); + // We want to collect at least one trace which will be displayed to user. + let max_traces_to_collect = std::cmp::max(1, self.config.gas_report_samples) as usize; + + // Stores up to `max_traces_to_collect` traces. + let traces: RefCell> = RefCell::default(); // Stores coverage information for all fuzz cases let coverage: RefCell> = RefCell::default(); let state = self.build_fuzz_state(); - let mut weights = vec![]; let dictionary_weight = self.config.dictionary.dictionary_weight.min(100); - if self.config.dictionary.dictionary_weight < 100 { - weights.push((100 - dictionary_weight, fuzz_calldata(func.clone()))); - } - if dictionary_weight > 0 { - weights.push(( - self.config.dictionary.dictionary_weight, - fuzz_calldata_from_state(func.clone(), state.clone()), - )); - } - let strat = proptest::strategy::Union::new_weighted(weights); + let strat = proptest::prop_oneof![ + 100 - dictionary_weight => fuzz_calldata(func.clone(), fuzz_fixtures), + dictionary_weight => fuzz_calldata_from_state(func.clone(), &state), + ]; + debug!(func=?func.name, should_fail, "fuzzing"); let run_result = self.runner.clone().run(&strat, |calldata| { - let fuzz_res = self.single_fuzz(&state, address, should_fail, calldata)?; + let fuzz_res = self.single_fuzz(address, should_fail, calldata)?; match fuzz_res { FuzzOutcome::Case(case) => { @@ -103,8 +98,12 @@ impl FuzzedExecutor { if first_case.is_none() { first_case.replace(case.case); } - - traces.replace(case.traces); + if let Some(call_traces) = case.traces { + if traces.borrow().len() == max_traces_to_collect { + traces.borrow_mut().pop(); + } + traces.borrow_mut().push(call_traces); + } if let Some(prev) = coverage.take() { // Safety: If `Option::or` evaluates to `Some`, then `call.coverage` must @@ -130,13 +129,17 @@ impl FuzzedExecutor { let call_res = _counterexample.1.result.clone(); *counterexample.borrow_mut() = _counterexample; // HACK: we have to use an empty string here to denote `None` - let reason = decode::maybe_decode_revert(&call_res, errors, Some(status)); + let reason = rd.maybe_decode(&call_res, Some(status)); Err(TestCaseError::fail(reason.unwrap_or_default())) } } }); let (calldata, call) = counterexample.into_inner(); + + let mut traces = traces.into_inner(); + let last_run_traces = if run_result.is_ok() { traces.pop() } else { call.traces.clone() }; + let mut result = FuzzTestResult { first_case: first_case.take().unwrap_or_default(), gas_by_case: gas_by_case.take(), @@ -146,7 +149,8 @@ impl FuzzedExecutor { decoded_logs: decode_console_logs(&call.logs), logs: call.logs, labeled_addresses: call.labels, - traces: if run_result.is_ok() { traces.into_inner() } else { call.traces.clone() }, + traces: last_run_traces, + gas_report_traces: traces, coverage: coverage.into_inner(), }; @@ -191,27 +195,15 @@ impl FuzzedExecutor { /// or a `CounterExampleOutcome` pub fn single_fuzz( &self, - state: &EvmFuzzState, address: Address, should_fail: bool, calldata: alloy_primitives::Bytes, ) -> Result { - let call = self + let mut call = self .executor .call_raw(self.sender, address, calldata.clone(), U256::ZERO) .map_err(|_| TestCaseError::fail(FuzzError::FailedContractCall))?; - let state_changeset = call - .state_changeset - .as_ref() - .ok_or_else(|| TestCaseError::fail(FuzzError::EmptyChangeset))?; - - // Build fuzzer state - collect_state_from_call( - &call.logs, - state_changeset, - state.clone(), - &self.config.dictionary, - ); + let state_changeset = call.state_changeset.take().unwrap(); // When the `assume` cheatcode is called it returns a special string if call.result.as_ref() == MAGIC_ASSUME { @@ -223,8 +215,12 @@ impl FuzzedExecutor { .as_ref() .map_or_else(Default::default, |cheats| cheats.breakpoints.clone()); - let success = - self.executor.is_raw_call_success(address, state_changeset.clone(), &call, should_fail); + let success = self.executor.is_raw_call_success( + address, + Cow::Owned(state_changeset), + &call, + should_fail, + ); if success { Ok(FuzzOutcome::Case(CaseOutcome { @@ -247,9 +243,9 @@ impl FuzzedExecutor { /// Stores fuzz state for use with [fuzz_calldata_from_state] pub fn build_fuzz_state(&self) -> EvmFuzzState { if let Some(fork_db) = self.executor.backend.active_fork_db() { - build_initial_state(fork_db, &self.config.dictionary) + build_initial_state(fork_db, self.config.dictionary) } else { - build_initial_state(self.executor.backend.mem_db(), &self.config.dictionary) + build_initial_state(self.executor.backend.mem_db(), self.config.dictionary) } } } diff --git a/crates/evm/evm/src/executors/invariant/error.rs b/crates/evm/evm/src/executors/invariant/error.rs index 19250984cc61e..e797174a53532 100644 --- a/crates/evm/evm/src/executors/invariant/error.rs +++ b/crates/evm/evm/src/executors/invariant/error.rs @@ -1,20 +1,13 @@ use super::{BasicTxDetails, InvariantContract}; -use crate::executors::{Executor, RawCallResult}; -use alloy_json_abi::Function; +use crate::executors::RawCallResult; use alloy_primitives::{Address, Bytes}; -use ethers_core::types::Log; -use eyre::Result; -use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; -use foundry_evm_core::{constants::CALLER, decode::decode_revert}; -use foundry_evm_fuzz::{BaseCounterExample, CounterExample, FuzzedCases, Reason}; -use foundry_evm_traces::{load_contracts, CallTraceArena, TraceKind, Traces}; -use parking_lot::RwLock; +use foundry_config::InvariantConfig; +use foundry_evm_core::decode::RevertDecoder; +use foundry_evm_fuzz::{invariant::FuzzRunIdentifiedContracts, Reason}; use proptest::test_runner::TestError; -use revm::primitives::U256; -use std::sync::Arc; -#[derive(Clone, Default)] /// Stores information about failures and reverts of the invariant tests. +#[derive(Clone, Default)] pub struct InvariantFailures { /// Total number of reverts. pub reverts: usize, @@ -36,24 +29,28 @@ impl InvariantFailures { } } -/// The outcome of an invariant fuzz test -#[derive(Debug)] -pub struct InvariantFuzzTestResult { - pub error: Option, - /// Every successful fuzz test case - pub cases: Vec, - /// Number of reverted fuzz calls - pub reverts: usize, +#[derive(Clone, Debug)] +pub enum InvariantFuzzError { + Revert(FailedInvariantCaseData), + BrokenInvariant(FailedInvariantCaseData), + MaxAssumeRejects(u32), +} - /// The entire inputs of the last run of the invariant campaign, used for - /// replaying the run for collecting traces. - pub last_run_inputs: Vec, +impl InvariantFuzzError { + pub fn revert_reason(&self) -> Option { + match self { + Self::BrokenInvariant(case_data) | Self::Revert(case_data) => { + (!case_data.revert_reason.is_empty()).then(|| case_data.revert_reason.clone()) + } + Self::MaxAssumeRejects(allowed) => Some(format!( + "The `vm.assume` cheatcode rejected too many inputs ({allowed} allowed)" + )), + } + } } -#[derive(Debug, Clone)] -pub struct InvariantFuzzError { - pub logs: Vec, - pub traces: Option, +#[derive(Clone, Debug)] +pub struct FailedInvariantCaseData { /// The proptest error occurred as a result of a test case. pub test_error: TestError>, /// The return reason of the offending call. @@ -63,36 +60,40 @@ pub struct InvariantFuzzError { /// Address of the invariant asserter. pub addr: Address, /// Function data for invariant check. - pub func: Option, + pub func: Bytes, /// Inner fuzzing Sequence coming from overriding calls. pub inner_sequence: Vec>, /// Shrink the failed test case to the smallest sequence. - pub shrink: bool, + pub shrink_sequence: bool, + /// Shrink run limit + pub shrink_run_limit: usize, + /// Fail on revert, used to check sequence when shrinking. + pub fail_on_revert: bool, } -impl InvariantFuzzError { +impl FailedInvariantCaseData { pub fn new( invariant_contract: &InvariantContract<'_>, - error_func: Option<&Function>, + invariant_config: &InvariantConfig, + targeted_contracts: &FuzzRunIdentifiedContracts, calldata: &[BasicTxDetails], call_result: RawCallResult, inner_sequence: &[Option], - shrink: bool, ) -> Self { - let (func, origin) = if let Some(f) = error_func { - (Some(f.selector().to_vec().into()), f.name.as_str()) - } else { - (None, "Revert") - }; - let revert_reason = decode_revert( - call_result.result.as_ref(), - Some(invariant_contract.abi), - Some(call_result.exit_reason), - ); - - InvariantFuzzError { - logs: call_result.logs, - traces: call_result.traces, + // Collect abis of fuzzed and invariant contracts to decode custom error. + let targets = targeted_contracts.targets.lock(); + let abis = targets + .iter() + .map(|contract| &contract.1 .1) + .chain(std::iter::once(invariant_contract.abi)); + + let revert_reason = RevertDecoder::new() + .with_abis(abis) + .decode(call_result.result.as_ref(), Some(call_result.exit_reason)); + + let func = invariant_contract.invariant_function; + let origin = func.name.as_str(); + Self { test_error: proptest::test_runner::TestError::Fail( format!("{origin}, reason: {revert_reason}").into(), calldata.to_vec(), @@ -100,181 +101,11 @@ impl InvariantFuzzError { return_reason: "".into(), revert_reason, addr: invariant_contract.address, - func, + func: func.selector().to_vec().into(), inner_sequence: inner_sequence.to_vec(), - shrink, - } - } - - /// Replays the error case and collects all necessary traces. - pub fn replay( - &self, - mut executor: Executor, - known_contracts: Option<&ContractsByArtifact>, - mut ided_contracts: ContractsByAddress, - logs: &mut Vec, - traces: &mut Traces, - ) -> Result> { - let mut counterexample_sequence = vec![]; - let calls = match self.test_error { - // Don't use at the moment. - TestError::Abort(_) => return Ok(None), - TestError::Fail(_, ref calls) => calls, - }; - - if self.shrink { - let _ = self.try_shrinking(calls, &executor); - } else { - trace!(target: "forge::test", "Shrinking disabled."); - } - - // We want traces for a failed case. - executor.set_tracing(true); - - set_up_inner_replay(&mut executor, &self.inner_sequence); - - // Replay each call from the sequence until we break the invariant. - for (sender, (addr, bytes)) in calls.iter() { - let call_result = executor - .call_raw_committing(*sender, *addr, bytes.clone(), U256::ZERO) - .expect("bad call to evm"); - - logs.extend(call_result.logs); - traces.push((TraceKind::Execution, call_result.traces.clone().unwrap())); - - // Identify newly generated contracts, if they exist. - ided_contracts.extend(load_contracts( - vec![(TraceKind::Execution, call_result.traces.clone().unwrap())], - known_contracts, - )); - - counterexample_sequence.push(BaseCounterExample::create( - *sender, - *addr, - bytes, - &ided_contracts, - call_result.traces, - )); - - // Checks the invariant. - if let Some(func) = &self.func { - let error_call_result = executor - .call_raw(CALLER, self.addr, func.clone(), U256::ZERO) - .expect("bad call to evm"); - - traces.push((TraceKind::Execution, error_call_result.traces.clone().unwrap())); - - logs.extend(error_call_result.logs); - if error_call_result.reverted { - break - } - } - } - - Ok((!counterexample_sequence.is_empty()) - .then_some(CounterExample::Sequence(counterexample_sequence))) - } - - /// Tests that the modified sequence of calls successfully reverts on the error function. - fn fails_successfully<'a>( - &self, - mut executor: Executor, - calls: &'a [BasicTxDetails], - anchor: usize, - removed_calls: &[usize], - ) -> Result, ()> { - let mut new_sequence = Vec::with_capacity(calls.len()); - for (index, details) in calls.iter().enumerate() { - if anchor > index || removed_calls.contains(&index) { - continue - } - - new_sequence.push(details); - - let (sender, (addr, bytes)) = details; - - executor - .call_raw_committing(*sender, *addr, bytes.clone(), U256::ZERO) - .expect("bad call to evm"); - - // Checks the invariant. If we exit before the last call, all the better. - if let Some(func) = &self.func { - let error_call_result = executor - .call_raw(CALLER, self.addr, func.clone(), U256::ZERO) - .expect("bad call to evm"); - - if error_call_result.reverted { - return Ok(new_sequence) - } - } - } - - Err(()) - } - - /// Tries to shrink the failure case to its smallest sequence of calls. - /// - /// Sets an anchor at the beginning (index=0) and tries to remove all other calls one by one, - /// until it reaches the last one. The elements which were removed and lead to a failure are - /// kept in the removal list. The removed ones that didn't lead to a failure are inserted - /// back into the sequence. - /// - /// Once it reaches the end, it increments the anchor, resets the removal list and starts the - /// same process again. - /// - /// Returns the smallest sequence found. - fn try_shrinking<'a>( - &self, - calls: &'a [BasicTxDetails], - executor: &Executor, - ) -> Vec<&'a BasicTxDetails> { - let mut anchor = 0; - let mut removed_calls = vec![]; - let mut shrunk = calls.iter().collect::>(); - trace!(target: "forge::test", "Shrinking."); - - while anchor != calls.len() { - // Get the latest removed element, so we know which one to remove next. - let removed = - match self.fails_successfully(executor.clone(), calls, anchor, &removed_calls) { - Ok(new_sequence) => { - if shrunk.len() > new_sequence.len() { - shrunk = new_sequence; - } - removed_calls.last().cloned() - } - Err(_) => removed_calls.pop(), - }; - - if let Some(last_removed) = removed { - // If we haven't reached the end of the sequence, then remove the next element. - // Otherwise, restart the process with an incremented anchor. - - let next_removed = last_removed + 1; - - if next_removed > calls.len() - 1 { - anchor += 1; - removed_calls = vec![]; - continue - } - - removed_calls.push(next_removed); - } else { - // When the process is restarted, `removed_calls` will be empty. - removed_calls.push(anchor + 1); - } - } - - shrunk - } -} - -/// Sets up the calls generated by the internal fuzzer, if they exist. -fn set_up_inner_replay(executor: &mut Executor, inner_sequence: &[Option]) { - if let Some(fuzzer) = &mut executor.inspector.fuzzer { - if let Some(call_generator) = &mut fuzzer.call_generator { - call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned())); - call_generator.set_replay(true); + shrink_sequence: invariant_config.shrink_sequence, + shrink_run_limit: invariant_config.shrink_run_limit, + fail_on_revert: invariant_config.fail_on_revert, } } } diff --git a/crates/evm/evm/src/executors/invariant/funcs.rs b/crates/evm/evm/src/executors/invariant/funcs.rs deleted file mode 100644 index 2d7688729fe57..0000000000000 --- a/crates/evm/evm/src/executors/invariant/funcs.rs +++ /dev/null @@ -1,128 +0,0 @@ -use super::{InvariantFailures, InvariantFuzzError}; -use crate::executors::{Executor, RawCallResult}; -use alloy_dyn_abi::JsonAbiExt; -use alloy_json_abi::Function; -use ethers_core::types::Log; -use foundry_common::{ContractsByAddress, ContractsByArtifact}; -use foundry_evm_core::constants::CALLER; -use foundry_evm_coverage::HitMaps; -use foundry_evm_fuzz::invariant::{BasicTxDetails, InvariantContract}; -use foundry_evm_traces::{load_contracts, TraceKind, Traces}; -use revm::primitives::U256; - -/// Given the executor state, asserts that no invariant has been broken. Otherwise, it fills the -/// external `invariant_failures.failed_invariant` map and returns a generic error. -/// Either returns the call result if successful, or nothing if there was an error. -pub fn assert_invariants( - invariant_contract: &InvariantContract<'_>, - executor: &Executor, - calldata: &[BasicTxDetails], - invariant_failures: &mut InvariantFailures, - shrink_sequence: bool, -) -> Option { - let mut inner_sequence = vec![]; - - if let Some(fuzzer) = &executor.inspector.fuzzer { - if let Some(call_generator) = &fuzzer.call_generator { - inner_sequence.extend(call_generator.last_sequence.read().iter().cloned()); - } - } - - let func = invariant_contract.invariant_function; - let mut call_result = executor - .call_raw( - CALLER, - invariant_contract.address, - func.abi_encode_input(&[]).expect("invariant should have no inputs").into(), - U256::ZERO, - ) - .expect("EVM error"); - - // This will panic and get caught by the executor - let is_err = call_result.reverted || - !executor.is_raw_call_success( - invariant_contract.address, - call_result.state_changeset.take().expect("we should have a state changeset"), - &call_result, - false, - ); - if is_err { - // We only care about invariants which we haven't broken yet. - if invariant_failures.error.is_none() { - invariant_failures.error = Some(InvariantFuzzError::new( - invariant_contract, - Some(func), - calldata, - call_result, - &inner_sequence, - shrink_sequence, - )); - return None - } - } - - Some(call_result) -} - -/// Replays the provided invariant run for collecting the logs and traces from all depths. -#[allow(clippy::too_many_arguments)] -pub fn replay_run( - invariant_contract: &InvariantContract<'_>, - mut executor: Executor, - known_contracts: Option<&ContractsByArtifact>, - mut ided_contracts: ContractsByAddress, - logs: &mut Vec, - traces: &mut Traces, - coverage: &mut Option, - func: Function, - inputs: Vec, -) { - // We want traces for a failed case. - executor.set_tracing(true); - - // set_up_inner_replay(&mut executor, &inputs); - - // Replay each call from the sequence until we break the invariant. - for (sender, (addr, bytes)) in inputs.iter() { - let call_result = executor - .call_raw_committing(*sender, *addr, bytes.clone(), U256::ZERO) - .expect("bad call to evm"); - - logs.extend(call_result.logs); - traces.push((TraceKind::Execution, call_result.traces.clone().unwrap())); - - let old_coverage = std::mem::take(coverage); - match (old_coverage, call_result.coverage) { - (Some(old_coverage), Some(call_coverage)) => { - *coverage = Some(old_coverage.merge(call_coverage)); - } - (None, Some(call_coverage)) => { - *coverage = Some(call_coverage); - } - (Some(old_coverage), None) => { - *coverage = Some(old_coverage); - } - (None, None) => {} - } - - // Identify newly generated contracts, if they exist. - ided_contracts.extend(load_contracts( - vec![(TraceKind::Execution, call_result.traces.clone().unwrap())], - known_contracts, - )); - - // Checks the invariant. - let error_call_result = executor - .call_raw( - CALLER, - invariant_contract.address, - func.abi_encode_input(&[]).expect("invariant should have no inputs").into(), - U256::ZERO, - ) - .expect("bad call to evm"); - - traces.push((TraceKind::Execution, error_call_result.traces.clone().unwrap())); - - logs.extend(error_call_result.logs); - } -} diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index fa56c97c4dc9b..37d433a315db9 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -2,15 +2,14 @@ use crate::{ executors::{Executor, RawCallResult}, inspectors::Fuzzer, }; -use alloy_dyn_abi::DynSolValue; -use alloy_json_abi::JsonAbi as Abi; use alloy_primitives::{Address, FixedBytes, U256}; +use alloy_sol_types::{sol, SolCall}; use eyre::{eyre, ContextCompat, Result}; use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; -use foundry_config::{FuzzDictionaryConfig, InvariantConfig}; +use foundry_config::InvariantConfig; use foundry_evm_core::{ - constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS}, - utils::{get_function, StateChangeset}, + constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME}, + utils::get_function, }; use foundry_evm_fuzz::{ invariant::{ @@ -18,43 +17,86 @@ use foundry_evm_fuzz::{ RandomCallGenerator, SenderFilters, TargetedContracts, }, strategies::{ - build_initial_state, collect_created_contracts, collect_state_from_call, invariant_strat, - override_call_strat, EvmFuzzState, + build_initial_state, collect_created_contracts, invariant_strat, override_call_strat, + EvmFuzzState, }, - FuzzCase, FuzzedCases, + FuzzCase, FuzzFixtures, FuzzedCases, }; -use parking_lot::{Mutex, RwLock}; +use foundry_evm_traces::CallTraceArena; +use parking_lot::RwLock; use proptest::{ - strategy::{BoxedStrategy, Strategy, ValueTree}, + strategy::{BoxedStrategy, Strategy}, test_runner::{TestCaseError, TestRunner}, }; +use result::{assert_invariants, can_continue}; use revm::{primitives::HashMap, DatabaseCommit}; +use shrink::shrink_sequence; use std::{cell::RefCell, collections::BTreeMap, sync::Arc}; mod error; -pub use error::{InvariantFailures, InvariantFuzzError, InvariantFuzzTestResult}; +pub use error::{InvariantFailures, InvariantFuzzError}; -mod funcs; -pub use funcs::{assert_invariants, replay_run}; +mod replay; +pub use replay::{replay_error, replay_run}; -/// Alias for (Dictionary for fuzzing, initial contracts to fuzz and an InvariantStrategy). -type InvariantPreparation = - (EvmFuzzState, FuzzRunIdentifiedContracts, BoxedStrategy>); +mod result; +pub use result::InvariantFuzzTestResult; -/// Enriched results of an invariant run check. -/// -/// Contains the success condition and call results of the last run -struct RichInvariantResults { - success: bool, - call_result: Option, -} +mod shrink; + +sol! { + interface IInvariantTest { + #[derive(Default)] + struct FuzzSelector { + address addr; + bytes4[] selectors; + } + + #[derive(Default)] + struct FuzzAbiSelector { + string contract_abi; + bytes4[] selectors; + } + + #[derive(Default)] + struct FuzzInterface { + address addr; + string[] artifacts; + } + + #[derive(Default)] + function excludeArtifacts() public view returns (string[] memory excludedArtifacts); -impl RichInvariantResults { - fn new(success: bool, call_result: Option) -> Self { - Self { success, call_result } + #[derive(Default)] + function excludeContracts() public view returns (address[] memory excludedContracts); + + #[derive(Default)] + function excludeSenders() public view returns (address[] memory excludedSenders); + + #[derive(Default)] + function targetArtifacts() public view returns (string[] memory targetedArtifacts); + + #[derive(Default)] + function targetArtifactSelectors() public view returns (FuzzAbiSelector[] memory targetedArtifactSelectors); + + #[derive(Default)] + function targetContracts() public view returns (address[] memory targetedContracts); + + #[derive(Default)] + function targetSelectors() public view returns (FuzzSelector[] memory targetedSelectors); + + #[derive(Default)] + function targetSenders() public view returns (address[] memory targetedSenders); + + #[derive(Default)] + function targetInterfaces() public view returns (FuzzInterface[] memory targetedInterfaces); } } +/// Alias for (Dictionary for fuzzing, initial contracts to fuzz and an InvariantStrategy). +type InvariantPreparation = + (EvmFuzzState, FuzzRunIdentifiedContracts, BoxedStrategy); + /// Wrapper around any [`Executor`] implementor which provides fuzzing support using [`proptest`]. /// /// After instantiation, calling `fuzz` will proceed to hammer the deployed smart contracts with @@ -98,13 +140,19 @@ impl<'a> InvariantExecutor<'a> { pub fn invariant_fuzz( &mut self, invariant_contract: InvariantContract<'_>, + fuzz_fixtures: &FuzzFixtures, ) -> Result { // Throw an error to abort test run if the invariant function accepts input params if !invariant_contract.invariant_function.inputs.is_empty() { return Err(eyre!("Invariant test function should have no inputs")) } - let (fuzz_state, targeted_contracts, strat) = self.prepare_fuzzing(&invariant_contract)?; + if !self.config.shrink_sequence { + error!(target: "forge::test", "shrink_sequence config is deprecated and will be removed, use shrink_run_limit = 0") + } + + let (fuzz_state, targeted_contracts, strat) = + self.prepare_fuzzing(&invariant_contract, fuzz_fixtures)?; // Stores the consumed gas and calldata of every successful fuzz call. let fuzz_cases: RefCell> = RefCell::new(Default::default()); @@ -115,17 +163,21 @@ impl<'a> InvariantExecutor<'a> { // Stores the calldata in the last run. let last_run_calldata: RefCell> = RefCell::new(vec![]); + // Stores additional traces for gas report. + let gas_report_traces: RefCell>> = RefCell::default(); + // Let's make sure the invariant is sound before actually starting the run: // We'll assert the invariant in its initial state, and if it fails, we'll // already know if we can early exit the invariant run. // This does not count as a fuzz run. It will just register the revert. let last_call_results = RefCell::new(assert_invariants( &invariant_contract, + &self.config, + &targeted_contracts, &self.executor, &[], &mut failures.borrow_mut(), - self.config.shrink_sequence, - )); + )?); if last_call_results.borrow().is_none() { fuzz_cases.borrow_mut().push(FuzzedCases::new(vec![])); @@ -136,7 +188,9 @@ impl<'a> InvariantExecutor<'a> { // during the run. We need another proptest runner to query for random // values. let branch_runner = RefCell::new(self.runner.clone()); - let _ = self.runner.run(&strat, |mut inputs| { + let _ = self.runner.run(&strat, |first_input| { + let mut inputs = vec![first_input]; + // We stop the run immediately if we have reverted, and `fail_on_revert` is set. if self.config.fail_on_revert && failures.borrow().reverts > 0 { return Err(TestCaseError::fail("Revert occurred.")) @@ -151,73 +205,93 @@ impl<'a> InvariantExecutor<'a> { // Created contracts during a run. let mut created_contracts = vec![]; - for current_run in 0..self.config.depth { - let (sender, (address, calldata)) = - inputs.last().expect("to have the next randomly generated input."); + // Traces of each call of the sequence. + let mut run_traces = Vec::new(); - // Executes the call from the randomly generated sequence. - let call_result = executor - .call_raw(*sender, *address, calldata.clone(), U256::ZERO) - .expect("could not make raw evm call"); - - // Collect data for fuzzing from the state changeset. - let mut state_changeset = - call_result.state_changeset.to_owned().expect("no changesets"); - - collect_data( - &mut state_changeset, - sender, - &call_result, - fuzz_state.clone(), - &self.config.dictionary, - ); + let mut current_run = 0; + let mut assume_rejects_counter = 0; - if let Err(error) = collect_created_contracts( - &state_changeset, - self.project_contracts, - self.setup_contracts, - &self.artifact_filters, - targeted_contracts.clone(), - &mut created_contracts, - ) { - warn!(target: "forge::test", "{error}"); - } + while current_run < self.config.depth { + let (sender, (address, calldata)) = inputs.last().expect("no input generated"); - // Commit changes to the database. - executor.backend.commit(state_changeset.clone()); + // Executes the call from the randomly generated sequence. + let call_result = if self.config.preserve_state { + executor + .call_raw_committing(*sender, *address, calldata.clone(), U256::ZERO) + .expect("could not make raw evm call") + } else { + executor + .call_raw(*sender, *address, calldata.clone(), U256::ZERO) + .expect("could not make raw evm call") + }; + + if call_result.result.as_ref() == MAGIC_ASSUME { + inputs.pop(); + assume_rejects_counter += 1; + if assume_rejects_counter > self.config.max_assume_rejects { + failures.borrow_mut().error = Some(InvariantFuzzError::MaxAssumeRejects( + self.config.max_assume_rejects, + )); + return Err(TestCaseError::fail("Max number of vm.assume rejects reached.")) + } + } else { + // Collect data for fuzzing from the state changeset. + let mut state_changeset = + call_result.state_changeset.to_owned().expect("no changesets"); + + collect_data(&mut state_changeset, sender, &call_result, &fuzz_state); + + // Collect created contracts and add to fuzz targets only if targeted contracts + // are updatable. + if targeted_contracts.is_updatable { + if let Err(error) = collect_created_contracts( + &state_changeset, + self.project_contracts, + self.setup_contracts, + &self.artifact_filters, + &targeted_contracts, + &mut created_contracts, + ) { + warn!(target: "forge::test", "{error}"); + } + } + // Commit changes to the database. + executor.backend.commit(state_changeset.clone()); - fuzz_runs.push(FuzzCase { - calldata: calldata.clone(), - gas: call_result.gas_used, - stipend: call_result.stipend, - }); + fuzz_runs.push(FuzzCase { + calldata: calldata.clone(), + gas: call_result.gas_used, + stipend: call_result.stipend, + }); - let RichInvariantResults { success: can_continue, call_result: call_results } = - can_continue( + let result = can_continue( &invariant_contract, + &self.config, call_result, &executor, &inputs, &mut failures.borrow_mut(), &targeted_contracts, - state_changeset, - self.config.fail_on_revert, - self.config.shrink_sequence, - ); + &state_changeset, + &mut run_traces, + ) + .map_err(|e| TestCaseError::fail(e.to_string()))?; - if !can_continue || current_run == self.config.depth - 1 { - *last_run_calldata.borrow_mut() = inputs.clone(); - } + if !result.can_continue || current_run == self.config.depth - 1 { + last_run_calldata.borrow_mut().clone_from(&inputs); + } - if !can_continue { - break - } + if !result.can_continue { + break + } - *last_call_results.borrow_mut() = call_results; + *last_call_results.borrow_mut() = result.call_result; + current_run += 1; + } // Generates the next call from the run using the recently updated // dictionary. - inputs.extend( + inputs.push( strat .new_tree(&mut branch_runner.borrow_mut()) .map_err(|_| TestCaseError::Fail("Could not generate case".into()))? @@ -227,18 +301,25 @@ impl<'a> InvariantExecutor<'a> { // We clear all the targeted contracts created during this run. if !created_contracts.is_empty() { - let mut writable_targeted = targeted_contracts.lock(); + let mut writable_targeted = targeted_contracts.targets.lock(); for addr in created_contracts.iter() { writable_targeted.remove(addr); } } + if gas_report_traces.borrow().len() < self.config.gas_report_samples as usize { + gas_report_traces.borrow_mut().push(run_traces); + } fuzz_cases.borrow_mut().push(FuzzedCases::new(fuzz_runs)); + // Revert state to not persist values between runs. + fuzz_state.revert(); + Ok(()) }); - trace!(target: "forge::test::invariant::dictionary", "{:?}", fuzz_state.read().values().iter().map(hex::encode).collect::>()); + trace!(target: "forge::test::invariant::fuzz_fixtures", "{:?}", fuzz_fixtures); + trace!(target: "forge::test::invariant::dictionary", "{:?}", fuzz_state.dictionary_read().values().iter().map(hex::encode).collect::>()); let (reverts, error) = failures.into_inner().into_inner(); @@ -247,6 +328,7 @@ impl<'a> InvariantExecutor<'a> { cases: fuzz_cases.into_inner(), reverts, last_run_inputs: last_run_calldata.take(), + gas_report_traces: gas_report_traces.into_inner(), }) } @@ -257,24 +339,16 @@ impl<'a> InvariantExecutor<'a> { fn prepare_fuzzing( &mut self, invariant_contract: &InvariantContract<'_>, - ) -> eyre::Result { + fuzz_fixtures: &FuzzFixtures, + ) -> Result { // Finds out the chosen deployed contracts and/or senders. - self.select_contract_artifacts(invariant_contract.address, invariant_contract.abi)?; + self.select_contract_artifacts(invariant_contract.address)?; let (targeted_senders, targeted_contracts) = - self.select_contracts_and_senders(invariant_contract.address, invariant_contract.abi)?; - - if targeted_contracts.is_empty() { - eyre::bail!("No contracts to fuzz."); - } + self.select_contracts_and_senders(invariant_contract.address)?; // Stores fuzz state for use with [fuzz_calldata_from_state]. let fuzz_state: EvmFuzzState = - build_initial_state(self.executor.backend.mem_db(), &self.config.dictionary); - - // During execution, any newly created contract is added here and used through the rest of - // the fuzz run. - let targeted_contracts: FuzzRunIdentifiedContracts = - Arc::new(Mutex::new(targeted_contracts)); + build_initial_state(self.executor.backend.mem_db(), self.config.dictionary); // Creates the invariant strategy. let strat = invariant_strat( @@ -282,6 +356,7 @@ impl<'a> InvariantExecutor<'a> { targeted_senders, targeted_contracts.clone(), self.config.dictionary.dictionary_weight, + fuzz_fixtures.clone(), ) .no_shrink() .boxed(); @@ -299,6 +374,7 @@ impl<'a> InvariantExecutor<'a> { fuzz_state.clone(), targeted_contracts.clone(), target_contract_ref.clone(), + fuzz_fixtures.clone(), ), target_contract_ref, )); @@ -319,68 +395,25 @@ impl<'a> InvariantExecutor<'a> { /// Priority: /// /// targetArtifactSelectors > excludeArtifacts > targetArtifacts - pub fn select_contract_artifacts( - &mut self, - invariant_address: Address, - abi: &Abi, - ) -> eyre::Result<()> { - // targetArtifactSelectors -> (string, bytes4[])[]. - let targeted_abi = self - .get_list::<(String, Vec>)>( - invariant_address, - abi, - "targetArtifactSelectors", - |v| { - if let Some(list) = v.as_array() { - list.iter().map(|val| { - if let Some((_, _str, elements)) = val.as_custom_struct() { - let name = elements[0].as_str().unwrap().to_string(); - let selectors = elements[1] - .as_array() - .unwrap() - .iter() - .map(|selector| { - FixedBytes::<4>::from_slice(&selector.as_fixed_bytes().unwrap().0[0..4]) - }) - .collect::>(); - (name, selectors) - } else { - panic!("Could not decode inner value of targetArtifactSelectors. This is a bug.") - } - }).collect::>() - } else { - panic!("Could not decode targetArtifactSelectors as array. This is a bug.") - } - }, - ) - .into_iter() - .collect::>(); + pub fn select_contract_artifacts(&mut self, invariant_address: Address) -> Result<()> { + let result = self + .call_sol_default(invariant_address, &IInvariantTest::targetArtifactSelectorsCall {}); // Insert them into the executor `targeted_abi`. - for (contract, selectors) in targeted_abi { - let identifier = self.validate_selected_contract(contract, &selectors.to_vec())?; - - self.artifact_filters - .targeted - .entry(identifier) - .or_default() - .extend(selectors.to_vec()); + for IInvariantTest::FuzzAbiSelector { contract_abi, selectors } in + result.targetedArtifactSelectors + { + let identifier = self.validate_selected_contract(contract_abi, &selectors)?; + self.artifact_filters.targeted.entry(identifier).or_default().extend(selectors); } - // targetArtifacts -> string[] - // excludeArtifacts -> string[]. - let [selected_abi, excluded_abi] = ["targetArtifacts", "excludeArtifacts"].map(|method| { - self.get_list::(invariant_address, abi, method, |v| { - if let Some(list) = v.as_array() { - list.iter().map(|v| v.as_str().unwrap().to_string()).collect::>() - } else { - panic!("targetArtifacts should be an array") - } - }) - }); + let selected = + self.call_sol_default(invariant_address, &IInvariantTest::targetArtifactsCall {}); + let excluded = + self.call_sol_default(invariant_address, &IInvariantTest::excludeArtifactsCall {}); // Insert `excludeArtifacts` into the executor `excluded_abi`. - for contract in excluded_abi { + for contract in excluded.excludedArtifacts { let identifier = self.validate_selected_contract(contract, &[])?; if !self.artifact_filters.excluded.contains(&identifier) { @@ -389,8 +422,9 @@ impl<'a> InvariantExecutor<'a> { } // Exclude any artifact without mutable functions. - for (artifact, (abi, _)) in self.project_contracts.iter() { - if abi + for (artifact, contract) in self.project_contracts.iter() { + if contract + .abi .functions() .filter(|func| { !matches!( @@ -409,7 +443,7 @@ impl<'a> InvariantExecutor<'a> { // Insert `targetArtifacts` into the executor `targeted_abi`, if they have not been seen // before. - for contract in selected_abi { + for contract in selected.targetedArtifacts { let identifier = self.validate_selected_contract(contract, &[])?; if !self.artifact_filters.targeted.contains_key(&identifier) && @@ -427,13 +461,15 @@ impl<'a> InvariantExecutor<'a> { &mut self, contract: String, selectors: &[FixedBytes<4>], - ) -> eyre::Result { - if let Some((artifact, (abi, _))) = + ) -> Result { + if let Some((artifact, contract_data)) = self.project_contracts.find_by_name_or_identifier(&contract)? { // Check that the selectors really exist for this contract. for selector in selectors { - abi.functions() + contract_data + .abi + .functions() .find(|func| func.selector().as_slice() == selector.as_slice()) .wrap_err(format!("{contract} does not have the selector {selector:?}"))?; } @@ -447,28 +483,23 @@ impl<'a> InvariantExecutor<'a> { /// `targetContracts() -> address[]` and `excludeContracts() -> address[]`. pub fn select_contracts_and_senders( &self, - invariant_address: Address, - abi: &Abi, - ) -> eyre::Result<(SenderFilters, TargetedContracts)> { - let [targeted_senders, excluded_senders, selected, excluded] = - ["targetSenders", "excludeSenders", "targetContracts", "excludeContracts"].map( - |method| { - self.get_list::
(invariant_address, abi, method, |v| { - if let Some(list) = v.as_array() { - list.iter().map(|v| v.as_address().unwrap()).collect::>() - } else { - panic!("targetSenders should be an array") - } - }) - }, - ); + to: Address, + ) -> Result<(SenderFilters, FuzzRunIdentifiedContracts)> { + let targeted_senders = + self.call_sol_default(to, &IInvariantTest::targetSendersCall {}).targetedSenders; + let excluded_senders = + self.call_sol_default(to, &IInvariantTest::excludeSendersCall {}).excludedSenders; + let selected = + self.call_sol_default(to, &IInvariantTest::targetContractsCall {}).targetedContracts; + let excluded = + self.call_sol_default(to, &IInvariantTest::excludeContractsCall {}).excludedContracts; let mut contracts: TargetedContracts = self .setup_contracts .clone() .into_iter() .filter(|(addr, (identifier, _))| { - *addr != invariant_address && + *addr != to && *addr != CHEATCODE_ADDRESS && *addr != HARDHAT_CONSOLE_ADDRESS && (selected.is_empty() || selected.contains(addr)) && @@ -481,11 +512,19 @@ impl<'a> InvariantExecutor<'a> { .map(|(addr, (identifier, abi))| (addr, (identifier, abi, vec![]))) .collect(); - self.target_interfaces(invariant_address, abi, &mut contracts)?; + self.target_interfaces(to, &mut contracts)?; - self.select_selectors(invariant_address, abi, &mut contracts)?; + self.select_selectors(to, &mut contracts)?; + + // There should be at least one contract identified as target for fuzz runs. + if contracts.is_empty() { + eyre::bail!("No contracts to fuzz."); + } - Ok((SenderFilters::new(targeted_senders, excluded_senders), contracts)) + Ok(( + SenderFilters::new(targeted_senders, excluded_senders), + FuzzRunIdentifiedContracts::new(contracts, selected.is_empty()), + )) } /// Extends the contracts and selectors to fuzz with the addresses and ABIs specified in @@ -495,36 +534,11 @@ impl<'a> InvariantExecutor<'a> { pub fn target_interfaces( &self, invariant_address: Address, - abi: &Abi, targeted_contracts: &mut TargetedContracts, - ) -> eyre::Result<()> { - let interfaces = self.get_list::<(Address, Vec)>( - invariant_address, - abi, - "targetInterfaces", - |v| { - if let Some(l) = v.as_array() { - l.iter() - .map(|v| { - if let Some((_, _names, elements)) = v.as_custom_struct() { - let addr = elements[0].as_address().unwrap(); - let interfaces = elements[1] - .as_array() - .unwrap() - .iter() - .map(|v| v.as_str().unwrap().to_string()) - .collect::>(); - (addr, interfaces) - } else { - panic!("targetInterfaces should be a tuple array") - } - }) - .collect::>() - } else { - panic!("targetInterfaces should be a tuple array") - } - }, - ); + ) -> Result<()> { + let interfaces = self + .call_sol_default(invariant_address, &IInvariantTest::targetInterfacesCall {}) + .targetedInterfaces; // Since `targetInterfaces` returns a tuple array there is no guarantee // that the addresses are unique this map is used to merge functions of @@ -535,11 +549,11 @@ impl<'a> InvariantExecutor<'a> { // Loop through each address and its associated artifact identifiers. // We're borrowing here to avoid taking full ownership. - for (addr, identifiers) in &interfaces { + for IInvariantTest::FuzzInterface { addr, artifacts } in &interfaces { // Identifiers are specified as an array, so we loop through them. - for identifier in identifiers { + for identifier in artifacts { // Try to find the contract by name or identifier in the project's contracts. - if let Some((_, (abi, _))) = + if let Some((_, contract)) = self.project_contracts.find_by_name_or_identifier(identifier)? { combined @@ -550,10 +564,10 @@ impl<'a> InvariantExecutor<'a> { let (_, contract_abi, _) = entry; // Extend the ABI's function list with the new functions. - contract_abi.functions.extend(abi.functions.clone()); + contract_abi.functions.extend(contract.abi.functions.clone()); }) // Otherwise insert it into the map. - .or_insert_with(|| (identifier.to_string(), abi.clone(), vec![])); + .or_insert_with(|| (identifier.to_string(), contract.abi.clone(), vec![])); } } } @@ -568,10 +582,8 @@ impl<'a> InvariantExecutor<'a> { pub fn select_selectors( &self, address: Address, - abi: &Abi, targeted_contracts: &mut TargetedContracts, - ) -> eyre::Result<()> { - // `targetArtifactSelectors() -> (string, bytes4[])[]`. + ) -> Result<()> { let some_abi_selectors = self .artifact_filters .targeted @@ -589,37 +601,9 @@ impl<'a> InvariantExecutor<'a> { } } - // `targetSelectors() -> (address, bytes4[])[]`. - let selectors = - self.get_list::<(Address, Vec>)>(address, abi, "targetSelectors", |v| { - if let Some(l) = v.as_array() { - l.iter() - .map(|val| { - if let Some((_, _str, elements)) = val.as_custom_struct() { - let name = elements[0].as_address().unwrap(); - let selectors = elements[1] - .as_array() - .unwrap() - .iter() - .map(|selector| { - FixedBytes::<4>::from_slice( - &selector.as_fixed_bytes().unwrap().0[0..4], - ) - }) - .collect::>(); - (name, selectors) - } else { - panic!("targetSelectors should be a tuple array2") - } - }) - .collect::>() - } else { - panic!("targetSelectors should be a tuple array") - } - }); - - for (address, bytes4_array) in selectors.into_iter() { - self.add_address_with_functions(address, bytes4_array, targeted_contracts)?; + let selectors = self.call_sol_default(address, &IInvariantTest::targetSelectorsCall {}); + for IInvariantTest::FuzzSelector { addr, selectors } in selectors.targetedSelectors { + self.add_address_with_functions(addr, selectors, targeted_contracts)?; } Ok(()) } @@ -654,34 +638,15 @@ impl<'a> InvariantExecutor<'a> { Ok(()) } - /// Get the function output by calling the contract `method_name` function, encoded as a - /// [DynSolValue]. - fn get_list( - &self, - address: Address, - abi: &Abi, - method_name: &str, - f: fn(DynSolValue) -> Vec, - ) -> Vec { - if let Some(func) = abi.functions().find(|func| func.name == method_name) { - if let Ok(call_result) = self.executor.call::<_, _>( - CALLER, - address, - func.clone(), - vec![], - U256::ZERO, - Some(abi), - ) { - return f(call_result.result) - } else { - warn!( - "The function {} was found but there was an error querying its data.", - method_name - ); - } - }; - - Vec::new() + fn call_sol_default(&self, to: Address, args: &C) -> C::Return + where + C::Return: Default, + { + self.executor + .call_sol(CALLER, to, args, U256::ZERO, None) + .map(|c| c.decoded_result) + .inspect_err(|e| warn!(target: "forge::test", "failed calling {:?}: {e}", C::SIGNATURE)) + .unwrap_or_default() } } @@ -692,8 +657,7 @@ fn collect_data( state_changeset: &mut HashMap, sender: &Address, call_result: &RawCallResult, - fuzz_state: EvmFuzzState, - config: &FuzzDictionaryConfig, + fuzz_state: &EvmFuzzState, ) { // Verify it has no code. let mut has_code = false; @@ -708,63 +672,10 @@ fn collect_data( sender_changeset = state_changeset.remove(sender); } - collect_state_from_call(&call_result.logs, &*state_changeset, fuzz_state, config); + fuzz_state.collect_state_from_call(&call_result.logs, &*state_changeset); // Re-add changes if let Some(changed) = sender_changeset { state_changeset.insert(*sender, changed); } } - -/// Verifies that the invariant run execution can continue. -/// Returns the mapping of (Invariant Function Name -> Call Result, Logs, Traces) if invariants were -/// asserted. -#[allow(clippy::too_many_arguments)] -fn can_continue( - invariant_contract: &InvariantContract<'_>, - call_result: RawCallResult, - executor: &Executor, - calldata: &[BasicTxDetails], - failures: &mut InvariantFailures, - targeted_contracts: &FuzzRunIdentifiedContracts, - state_changeset: StateChangeset, - fail_on_revert: bool, - shrink_sequence: bool, -) -> RichInvariantResults { - let mut call_results = None; - - // Detect handler assertion failures first. - let handlers_failed = targeted_contracts - .lock() - .iter() - .any(|contract| !executor.is_success(*contract.0, false, state_changeset.clone(), false)); - - // Assert invariants IFF the call did not revert and the handlers did not fail. - if !call_result.reverted && !handlers_failed { - call_results = - assert_invariants(invariant_contract, executor, calldata, failures, shrink_sequence); - if call_results.is_none() { - return RichInvariantResults::new(false, None) - } - } else { - // Increase the amount of reverts. - failures.reverts += 1; - // If fail on revert is set, we must return immediately. - if fail_on_revert { - let error = InvariantFuzzError::new( - invariant_contract, - None, - calldata, - call_result, - &[], - shrink_sequence, - ); - - failures.revert_reason = Some(error.revert_reason.clone()); - failures.error = Some(error); - - return RichInvariantResults::new(false, None) - } - } - RichInvariantResults::new(true, call_results) -} diff --git a/crates/evm/evm/src/executors/invariant/replay.rs b/crates/evm/evm/src/executors/invariant/replay.rs new file mode 100644 index 0000000000000..941df781aacf8 --- /dev/null +++ b/crates/evm/evm/src/executors/invariant/replay.rs @@ -0,0 +1,134 @@ +use super::{error::FailedInvariantCaseData, shrink_sequence}; +use crate::executors::Executor; +use alloy_dyn_abi::JsonAbiExt; +use alloy_primitives::Log; +use eyre::Result; +use foundry_common::{ContractsByAddress, ContractsByArtifact}; +use foundry_evm_core::constants::CALLER; +use foundry_evm_coverage::HitMaps; +use foundry_evm_fuzz::{ + invariant::{BasicTxDetails, InvariantContract}, + BaseCounterExample, CounterExample, +}; +use foundry_evm_traces::{load_contracts, TraceKind, Traces}; +use parking_lot::RwLock; +use proptest::test_runner::TestError; +use revm::primitives::U256; +use std::sync::Arc; + +/// Replays a call sequence for collecting logs and traces. +/// Returns counterexample to be used when the call sequence is a failed scenario. +#[allow(clippy::too_many_arguments)] +pub fn replay_run( + invariant_contract: &InvariantContract<'_>, + mut executor: Executor, + known_contracts: &ContractsByArtifact, + mut ided_contracts: ContractsByAddress, + logs: &mut Vec, + traces: &mut Traces, + coverage: &mut Option, + inputs: Vec, +) -> Result> { + // We want traces for a failed case. + executor.set_tracing(true); + + let mut counterexample_sequence = vec![]; + + // Replay each call from the sequence, collect logs, traces and coverage. + for (sender, (addr, bytes)) in inputs.iter() { + let call_result = + executor.call_raw_committing(*sender, *addr, bytes.clone(), U256::ZERO)?; + logs.extend(call_result.logs); + traces.push((TraceKind::Execution, call_result.traces.clone().unwrap())); + + if let Some(new_coverage) = call_result.coverage { + if let Some(old_coverage) = coverage { + *coverage = Some(std::mem::take(old_coverage).merge(new_coverage)); + } else { + *coverage = Some(new_coverage); + } + } + + // Identify newly generated contracts, if they exist. + ided_contracts.extend(load_contracts( + vec![(TraceKind::Execution, call_result.traces.clone().unwrap())], + known_contracts, + )); + + // Create counter example to be used in failed case. + counterexample_sequence.push(BaseCounterExample::create( + *sender, + *addr, + bytes, + &ided_contracts, + call_result.traces, + )); + + // Replay invariant to collect logs and traces. + let error_call_result = executor.call_raw( + CALLER, + invariant_contract.address, + invariant_contract + .invariant_function + .abi_encode_input(&[]) + .expect("invariant should have no inputs") + .into(), + U256::ZERO, + )?; + traces.push((TraceKind::Execution, error_call_result.traces.clone().unwrap())); + logs.extend(error_call_result.logs); + } + + Ok((!counterexample_sequence.is_empty()) + .then_some(CounterExample::Sequence(counterexample_sequence))) +} + +/// Replays the error case, shrinks the failing sequence and collects all necessary traces. +#[allow(clippy::too_many_arguments)] +pub fn replay_error( + failed_case: &FailedInvariantCaseData, + invariant_contract: &InvariantContract<'_>, + mut executor: Executor, + known_contracts: &ContractsByArtifact, + ided_contracts: ContractsByAddress, + logs: &mut Vec, + traces: &mut Traces, + coverage: &mut Option, +) -> Result> { + match failed_case.test_error { + // Don't use at the moment. + TestError::Abort(_) => Ok(None), + TestError::Fail(_, ref calls) => { + // Shrink sequence of failed calls. + let calls = if failed_case.shrink_sequence { + shrink_sequence(failed_case, calls, &executor)? + } else { + trace!(target: "forge::test", "Shrinking disabled."); + calls.clone() + }; + + set_up_inner_replay(&mut executor, &failed_case.inner_sequence); + // Replay calls to get the counterexample and to collect logs, traces and coverage. + replay_run( + invariant_contract, + executor, + known_contracts, + ided_contracts, + logs, + traces, + coverage, + calls, + ) + } + } +} + +/// Sets up the calls generated by the internal fuzzer, if they exist. +fn set_up_inner_replay(executor: &mut Executor, inner_sequence: &[Option]) { + if let Some(fuzzer) = &mut executor.inspector.fuzzer { + if let Some(call_generator) = &mut fuzzer.call_generator { + call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned())); + call_generator.set_replay(true); + } + } +} diff --git a/crates/evm/evm/src/executors/invariant/result.rs b/crates/evm/evm/src/executors/invariant/result.rs new file mode 100644 index 0000000000000..8b2acc56cbd23 --- /dev/null +++ b/crates/evm/evm/src/executors/invariant/result.rs @@ -0,0 +1,156 @@ +use super::{error::FailedInvariantCaseData, InvariantFailures, InvariantFuzzError}; +use crate::executors::{Executor, RawCallResult}; +use alloy_dyn_abi::JsonAbiExt; +use eyre::Result; +use foundry_config::InvariantConfig; +use foundry_evm_core::{constants::CALLER, utils::StateChangeset}; +use foundry_evm_fuzz::{ + invariant::{BasicTxDetails, FuzzRunIdentifiedContracts, InvariantContract}, + FuzzedCases, +}; +use revm::primitives::U256; +use revm_inspectors::tracing::CallTraceArena; +use std::borrow::Cow; + +/// The outcome of an invariant fuzz test +#[derive(Debug)] +pub struct InvariantFuzzTestResult { + pub error: Option, + /// Every successful fuzz test case + pub cases: Vec, + /// Number of reverted fuzz calls + pub reverts: usize, + /// The entire inputs of the last run of the invariant campaign, used for + /// replaying the run for collecting traces. + pub last_run_inputs: Vec, + /// Additional traces used for gas report construction. + pub gas_report_traces: Vec>, +} + +/// Enriched results of an invariant run check. +/// +/// Contains the success condition and call results of the last run +pub(crate) struct RichInvariantResults { + pub(crate) can_continue: bool, + pub(crate) call_result: Option, +} + +impl RichInvariantResults { + fn new(can_continue: bool, call_result: Option) -> Self { + Self { can_continue, call_result } + } +} + +/// Given the executor state, asserts that no invariant has been broken. Otherwise, it fills the +/// external `invariant_failures.failed_invariant` map and returns a generic error. +/// Either returns the call result if successful, or nothing if there was an error. +pub(crate) fn assert_invariants( + invariant_contract: &InvariantContract<'_>, + invariant_config: &InvariantConfig, + targeted_contracts: &FuzzRunIdentifiedContracts, + executor: &Executor, + calldata: &[BasicTxDetails], + invariant_failures: &mut InvariantFailures, +) -> Result> { + let mut inner_sequence = vec![]; + + if let Some(fuzzer) = &executor.inspector.fuzzer { + if let Some(call_generator) = &fuzzer.call_generator { + inner_sequence.extend(call_generator.last_sequence.read().iter().cloned()); + } + } + + let func = invariant_contract.invariant_function; + let mut call_result = executor.call_raw( + CALLER, + invariant_contract.address, + func.abi_encode_input(&[]).expect("invariant should have no inputs").into(), + U256::ZERO, + )?; + + let is_err = !executor.is_raw_call_success( + invariant_contract.address, + Cow::Owned(call_result.state_changeset.take().unwrap()), + &call_result, + false, + ); + if is_err { + // We only care about invariants which we haven't broken yet. + if invariant_failures.error.is_none() { + let case_data = FailedInvariantCaseData::new( + invariant_contract, + invariant_config, + targeted_contracts, + calldata, + call_result, + &inner_sequence, + ); + invariant_failures.error = Some(InvariantFuzzError::BrokenInvariant(case_data)); + return Ok(None); + } + } + + Ok(Some(call_result)) +} + +/// Verifies that the invariant run execution can continue. +/// Returns the mapping of (Invariant Function Name -> Call Result, Logs, Traces) if invariants were +/// asserted. +#[allow(clippy::too_many_arguments)] +pub(crate) fn can_continue( + invariant_contract: &InvariantContract<'_>, + invariant_config: &InvariantConfig, + call_result: RawCallResult, + executor: &Executor, + calldata: &[BasicTxDetails], + failures: &mut InvariantFailures, + targeted_contracts: &FuzzRunIdentifiedContracts, + state_changeset: &StateChangeset, + run_traces: &mut Vec, +) -> Result { + let mut call_results = None; + + // Detect handler assertion failures first. + let handlers_failed = targeted_contracts.targets.lock().iter().any(|contract| { + !executor.is_success(*contract.0, false, Cow::Borrowed(state_changeset), false) + }); + + // Assert invariants IF the call did not revert and the handlers did not fail. + if !call_result.reverted && !handlers_failed { + if let Some(traces) = call_result.traces { + run_traces.push(traces); + } + + call_results = assert_invariants( + invariant_contract, + invariant_config, + targeted_contracts, + executor, + calldata, + failures, + )?; + if call_results.is_none() { + return Ok(RichInvariantResults::new(false, None)); + } + } else { + // Increase the amount of reverts. + failures.reverts += 1; + // If fail on revert is set, we must return immediately. + if invariant_config.fail_on_revert { + let case_data = FailedInvariantCaseData::new( + invariant_contract, + invariant_config, + targeted_contracts, + calldata, + call_result, + &[], + ); + failures.revert_reason = Some(case_data.revert_reason.clone()); + let error = InvariantFuzzError::Revert(case_data); + failures.error = Some(error); + + return Ok(RichInvariantResults::new(false, None)); + } + } + Ok(RichInvariantResults::new(true, call_results)) +} diff --git a/crates/evm/evm/src/executors/invariant/shrink.rs b/crates/evm/evm/src/executors/invariant/shrink.rs new file mode 100644 index 0000000000000..bfa592136eafe --- /dev/null +++ b/crates/evm/evm/src/executors/invariant/shrink.rs @@ -0,0 +1,152 @@ +use crate::executors::{invariant::error::FailedInvariantCaseData, Executor}; +use alloy_primitives::U256; +use foundry_evm_core::constants::CALLER; +use foundry_evm_fuzz::invariant::BasicTxDetails; +use proptest::bits::{BitSetLike, VarBitSet}; +use std::borrow::Cow; + +#[derive(Clone, Copy, Debug)] +struct Shrink { + call_index: usize, +} + +/// Shrinker for a call sequence failure. +/// Iterates sequence call sequence top down and removes calls one by one. +/// If the failure is still reproducible with removed call then moves to the next one. +/// If the failure is not reproducible then restore removed call and moves to next one. +#[derive(Debug)] +struct CallSequenceShrinker { + /// Length of call sequence to be shrinked. + call_sequence_len: usize, + /// Call ids contained in current shrinked sequence. + included_calls: VarBitSet, + /// Current shrinked call id. + shrink: Shrink, + /// Previous shrinked call id. + prev_shrink: Option, +} + +impl CallSequenceShrinker { + fn new(call_sequence_len: usize) -> Self { + Self { + call_sequence_len, + included_calls: VarBitSet::saturated(call_sequence_len), + shrink: Shrink { call_index: 0 }, + prev_shrink: None, + } + } + + /// Return candidate shrink sequence to be tested, by removing ids from original sequence. + fn current(&self) -> impl Iterator + '_ { + (0..self.call_sequence_len).filter(|&call_id| self.included_calls.test(call_id)) + } + + /// Removes next call from sequence. + fn simplify(&mut self) -> bool { + if self.shrink.call_index >= self.call_sequence_len { + // We reached the end of call sequence, nothing left to simplify. + false + } else { + // Remove current call. + self.included_calls.clear(self.shrink.call_index); + // Record current call as previous call. + self.prev_shrink = Some(self.shrink); + // Remove next call index + self.shrink = Shrink { call_index: self.shrink.call_index + 1 }; + true + } + } + + /// Reverts removed call from sequence and tries to simplify next call. + fn complicate(&mut self) -> bool { + match self.prev_shrink { + Some(shrink) => { + // Undo the last call removed. + self.included_calls.set(shrink.call_index); + self.prev_shrink = None; + // Try to simplify next call. + self.simplify() + } + None => false, + } + } +} + +/// Shrinks the failure case to its smallest sequence of calls. +/// +/// Maximal shrinkage is guaranteed if the shrink_run_limit is not set to a value lower than the +/// length of failed call sequence. +/// +/// The shrinked call sequence always respect the order failure is reproduced as it is tested +/// top-down. +pub(crate) fn shrink_sequence( + failed_case: &FailedInvariantCaseData, + calls: &[BasicTxDetails], + executor: &Executor, +) -> eyre::Result> { + trace!(target: "forge::test", "Shrinking."); + + // Special case test: the invariant is *unsatisfiable* - it took 0 calls to + // break the invariant -- consider emitting a warning. + let error_call_result = + executor.call_raw(CALLER, failed_case.addr, failed_case.func.clone(), U256::ZERO)?; + if error_call_result.reverted { + return Ok(vec![]); + } + + let mut shrinker = CallSequenceShrinker::new(calls.len()); + for _ in 0..failed_case.shrink_run_limit { + // Check candidate sequence result. + match check_sequence(failed_case, executor.clone(), calls, shrinker.current().collect()) { + // If candidate sequence still fails then shrink more if possible. + Ok(false) if !shrinker.simplify() => break, + // If candidate sequence pass then restore last removed call and shrink other + // calls if possible. + Ok(true) if !shrinker.complicate() => break, + _ => {} + } + } + + Ok(shrinker.current().map(|idx| &calls[idx]).cloned().collect()) +} + +/// Checks if the shrinked sequence fails test, if it does then we can try simplifying more. +fn check_sequence( + failed_case: &FailedInvariantCaseData, + mut executor: Executor, + calls: &[BasicTxDetails], + sequence: Vec, +) -> eyre::Result { + let mut sequence_failed = false; + // Apply the shrinked candidate sequence. + for call_index in sequence { + let (sender, (addr, bytes)) = &calls[call_index]; + let call_result = + executor.call_raw_committing(*sender, *addr, bytes.clone(), U256::ZERO)?; + if call_result.reverted && failed_case.fail_on_revert { + // Candidate sequence fails test. + // We don't have to apply remaining calls to check sequence. + sequence_failed = true; + break; + } + } + // Return without checking the invariant if we already have failing sequence. + if sequence_failed { + return Ok(false); + }; + + // Check the invariant for candidate sequence. + // If sequence fails then we can continue with shrinking - the removed call does not affect + // failure. + // + // If sequence doesn't fail then we have to restore last removed call and continue with next + // call - removed call is a required step for reproducing the failure. + let mut call_result = + executor.call_raw(CALLER, failed_case.addr, failed_case.func.clone(), U256::ZERO)?; + Ok(executor.is_raw_call_success( + failed_case.addr, + Cow::Owned(call_result.state_changeset.take().unwrap()), + &call_result, + false, + )) +} diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index dcb1682883fa9..45abbfc9c823a 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -2,7 +2,7 @@ //! //! Used for running tests, scripts, and interacting with the inner backend which holds the state. -// TODO: The individual executors in this module should be moved into the respective craits, and the +// TODO: The individual executors in this module should be moved into the respective crates, and the // `Executor` struct should be accessed using a trait defined in `foundry-evm-core` instead of // the concrete `Executor` type. @@ -10,30 +10,29 @@ use crate::inspectors::{ cheatcodes::BroadcastableTransactions, Cheatcodes, InspectorData, InspectorStack, }; use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt}; -use alloy_json_abi::{Function, JsonAbi as Abi}; -use alloy_primitives::{Address, Bytes, U256}; -use ethers_core::types::Log; -use ethers_signers::LocalWallet; -use foundry_common::{abi::IntoFunction, evm::Breakpoints}; +use alloy_json_abi::Function; +use alloy_primitives::{Address, Bytes, Log, U256}; +use alloy_sol_types::{sol, SolCall}; use foundry_evm_core::{ - backend::{Backend, DatabaseError, DatabaseExt, DatabaseResult, FuzzBackendWrapper}, + backend::{Backend, CowBackend, DatabaseError, DatabaseExt, DatabaseResult}, constants::{ CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, DEFAULT_CREATE2_DEPLOYER_CODE, }, debug::DebugArena, - decode, - utils::{eval_to_instruction_result, halt_to_instruction_result, StateChangeset}, + decode::RevertDecoder, + utils::StateChangeset, }; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::CallTraceArena; use revm::{ db::{DatabaseCommit, DatabaseRef}, - interpreter::{return_ok, CreateScheme, InstructionResult, Stack}, + interpreter::{return_ok, CreateScheme, InstructionResult}, primitives::{ - BlockEnv, Bytecode, Env, ExecutionResult, Output, ResultAndState, SpecId, TransactTo, TxEnv, + BlockEnv, Bytecode, Env, EnvWithHandlerCfg, ExecutionResult, Output, ResultAndState, + SpecId, TransactTo, TxEnv, }, }; -use std::collections::BTreeMap; +use std::{borrow::Cow, collections::HashMap}; mod builder; pub use builder::ExecutorBuilder; @@ -47,6 +46,13 @@ pub use invariant::InvariantExecutor; mod tracing; pub use tracing::TracingExecutor; +sol! { + interface ITest { + function setUp() external; + function failed() external view returns (bool); + } +} + /// A type that can execute calls /// /// The executor can be configured with various `revm::Inspector`s, like `Cheatcodes`. @@ -55,7 +61,7 @@ pub use tracing::TracingExecutor; /// - `committing`: any state changes made during the call are recorded and are persisting /// - `raw`: state changes only exist for the duration of the call and are discarded afterwards, in /// other words: the state of the underlying database remains unchanged. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct Executor { /// The underlying `revm::Database` that contains the EVM storage. // Note: We do not store an EVM here, since we are really @@ -64,7 +70,7 @@ pub struct Executor { // so the performance difference should be negligible. pub backend: Backend, /// The EVM environment. - pub env: Env, + pub env: EnvWithHandlerCfg, /// The Revm inspector stack. pub inspector: InspectorStack, /// The gas limit for calls and deployments. This is different from the gas limit imposed by @@ -75,7 +81,12 @@ pub struct Executor { impl Executor { #[inline] - pub fn new(mut backend: Backend, env: Env, inspector: InspectorStack, gas_limit: U256) -> Self { + pub fn new( + mut backend: Backend, + env: EnvWithHandlerCfg, + inspector: InspectorStack, + gas_limit: U256, + ) -> Self { // Need to create a non-empty contract on the cheatcodes address so `extcodesize` checks // does not fail backend.insert_account_info( @@ -89,6 +100,11 @@ impl Executor { Executor { backend, env, inspector, gas_limit } } + /// Returns the spec id of the executor + pub fn spec_id(&self) -> SpecId { + self.env.handler_cfg.spec_id + } + /// Creates the default CREATE2 Contract Deployer for local tests and scripts. pub fn deploy_create2_deployer(&mut self) -> eyre::Result<()> { trace!("deploying local create2 deployer"); @@ -138,6 +154,11 @@ impl Executor { Ok(self) } + /// Gets the nonce of an account + pub fn get_nonce(&self, address: Address) -> DatabaseResult { + Ok(self.backend.basic_ref(address)?.map(|acc| acc.nonce).unwrap_or_default()) + } + #[inline] pub fn set_tracing(&mut self, tracing: bool) -> &mut Self { self.inspector.tracing(tracing); @@ -168,62 +189,52 @@ impl Executor { /// /// Ayn changes made during the setup call to env's block environment are persistent, for /// example `vm.chainId()` will change the `block.chainId` for all subsequent test calls. - pub fn setup(&mut self, from: Option
, to: Address) -> Result { + pub fn setup( + &mut self, + from: Option
, + to: Address, + rd: Option<&RevertDecoder>, + ) -> Result { trace!(?from, ?to, "setting up contract"); let from = from.unwrap_or(CALLER); self.backend.set_test_contract(to).set_caller(from); - let res = self.call_committing::<_, _>(from, to, "setUp()", vec![], U256::ZERO, None)?; + let calldata = Bytes::from_static(&ITest::setUpCall::SELECTOR); + let mut res = self.call_raw_committing(from, to, calldata, U256::ZERO)?; + res = res.into_result(rd)?; // record any changes made to the block's environment during setup self.env.block = res.env.block.clone(); // and also the chainid, which can be set manually self.env.cfg.chain_id = res.env.cfg.chain_id; - match res.state_changeset.as_ref() { - Some(changeset) => { - let success = self - .ensure_success(to, res.reverted, changeset.clone(), false) - .map_err(|err| EvmError::Eyre(eyre::eyre!(err.to_string())))?; - if success { - Ok(res) - } else { - Err(EvmError::Execution(Box::new(ExecutionErr { - reverted: res.reverted, - reason: "execution error".to_owned(), - traces: res.traces, - gas_used: res.gas_used, - gas_refunded: res.gas_refunded, - stipend: res.stipend, - logs: res.logs, - debug: res.debug, - labels: res.labels, - state_changeset: None, - transactions: None, - script_wallets: res.script_wallets, - }))) - } + if let Some(changeset) = res.state_changeset.as_ref() { + let success = self + .ensure_success(to, res.reverted, Cow::Borrowed(changeset), false) + .map_err(|err| EvmError::Eyre(eyre::eyre!(err)))?; + if !success { + return Err(res.into_execution_error("execution error".to_string()).into()); } - None => Ok(res), } + + Ok(res) } /// Performs a call to an account on the current state of the VM. /// /// The state after the call is persisted. - pub fn call_committing>, F: IntoFunction>( + pub fn call_committing( &mut self, from: Address, to: Address, - func: F, - args: T, + func: &Function, + args: &[DynSolValue], value: U256, - abi: Option<&Abi>, + rd: Option<&RevertDecoder>, ) -> Result { - let func = func.into(); - let calldata = Bytes::from(func.abi_encode_input(&args.into())?.to_vec()); + let calldata = Bytes::from(func.abi_encode_input(args)?); let result = self.call_raw_committing(from, to, calldata, value)?; - convert_call_result(abi, &func, result) + result.into_decoded_result(func, rd) } /// Performs a raw call to an account on the current state of the VM. @@ -243,40 +254,55 @@ impl Executor { } /// Executes the test function call - pub fn execute_test>, F: IntoFunction>( + pub fn execute_test( &mut self, from: Address, test_contract: Address, - func: F, - args: T, + func: &Function, + args: &[DynSolValue], value: U256, - abi: Option<&Abi>, + rd: Option<&RevertDecoder>, ) -> Result { - let func = func.into(); - let calldata = Bytes::from(func.abi_encode_input(&args.into())?.to_vec()); + let calldata = Bytes::from(func.abi_encode_input(args)?); // execute the call let env = self.build_test_env(from, TransactTo::Call(test_contract), calldata, value); - let call_result = self.call_raw_with_env(env)?; - convert_call_result(abi, &func, call_result) + let result = self.call_raw_with_env(env)?; + result.into_decoded_result(func, rd) } /// Performs a call to an account on the current state of the VM. /// /// The state after the call is not persisted. - pub fn call>, F: IntoFunction>( + pub fn call( &self, from: Address, to: Address, - func: F, - args: T, + func: &Function, + args: &[DynSolValue], value: U256, - abi: Option<&Abi>, + rd: Option<&RevertDecoder>, ) -> Result { - let func = func.into(); - let calldata = Bytes::from(func.abi_encode_input(&args.into())?.to_vec()); - let call_result = self.call_raw(from, to, calldata, value)?; - convert_call_result(abi, &func, call_result) + let calldata = Bytes::from(func.abi_encode_input(args)?); + let result = self.call_raw(from, to, calldata, value)?; + result.into_decoded_result(func, rd) + } + + /// Performs a call to an account on the current state of the VM. + /// + /// The state after the call is not persisted. + pub fn call_sol( + &self, + from: Address, + to: Address, + args: &C, + value: U256, + rd: Option<&RevertDecoder>, + ) -> Result, EvmError> { + let calldata = Bytes::from(args.abi_encode()); + let mut raw = self.call_raw(from, to, calldata, value)?; + raw = raw.into_result(rd)?; + Ok(CallResult { decoded_result: C::abi_decode_returns(&raw.result, false)?, raw }) } /// Performs a raw call to an account on the current state of the VM. @@ -296,8 +322,8 @@ impl Executor { let mut inspector = self.inspector.clone(); // Build VM let mut env = self.build_test_env(from, TransactTo::Call(to), calldata, value); - let mut db = FuzzBackendWrapper::new(&self.backend); - let result = db.inspect_ref(&mut env, &mut inspector)?; + let mut db = CowBackend::new(&self.backend); + let result = db.inspect(&mut env, &mut inspector)?; // Persist the snapshot failure recorded on the fuzz backend wrapper. let has_snapshot_failure = db.has_snapshot_failure(); @@ -305,29 +331,29 @@ impl Executor { } /// Execute the transaction configured in `env.tx` and commit the changes - pub fn commit_tx_with_env(&mut self, env: Env) -> eyre::Result { + pub fn commit_tx_with_env(&mut self, env: EnvWithHandlerCfg) -> eyre::Result { let mut result = self.call_raw_with_env(env)?; self.commit(&mut result); Ok(result) } /// Execute the transaction configured in `env.tx` - pub fn call_raw_with_env(&mut self, mut env: Env) -> eyre::Result { + pub fn call_raw_with_env(&mut self, mut env: EnvWithHandlerCfg) -> eyre::Result { // execute the call let mut inspector = self.inspector.clone(); - let result = self.backend.inspect_ref(&mut env, &mut inspector)?; + let result = self.backend.inspect(&mut env, &mut inspector)?; convert_executed_result(env, inspector, result, self.backend.has_snapshot_failure()) } /// Commit the changeset to the database and adjust `self.inspector_config` /// values according to the executed call result fn commit(&mut self, result: &mut RawCallResult) { - // Persist changes to db + // Persist changes to db. if let Some(changes) = &result.state_changeset { self.backend.commit(changes.clone()); } - // Persist cheatcode state + // Persist cheatcode state. let mut cheatcodes = result.cheatcodes.take(); if let Some(cheats) = cheatcodes.as_mut() { // Clear broadcastable transactions @@ -339,108 +365,57 @@ impl Executor { } self.inspector.cheatcodes = cheatcodes; - // Persist the changed environment + // Persist the changed environment. self.inspector.set_env(&result.env); } /// Deploys a contract using the given `env` and commits the new state to the underlying - /// database + /// database. + /// + /// # Panics + /// + /// Panics if `env.tx.transact_to` is not `TransactTo::Create(_)`. pub fn deploy_with_env( &mut self, - env: Env, - abi: Option<&Abi>, + env: EnvWithHandlerCfg, + rd: Option<&RevertDecoder>, ) -> Result { - debug_assert!( + assert!( matches!(env.tx.transact_to, TransactTo::Create(_)), - "Expect create transaction" + "Expected create transaction, got {:?}", + env.tx.transact_to ); - trace!(sender=?env.tx.caller, "deploying contract"); + trace!(sender=%env.tx.caller, "deploying contract"); let mut result = self.call_raw_with_env(env)?; self.commit(&mut result); - - let RawCallResult { - exit_reason, - out, - gas_used, - gas_refunded, - logs, - labels, - traces, - debug, - script_wallets, - env, - coverage, - .. - } = result; - - let result = match &out { - Some(Output::Create(data, _)) => data.to_owned(), - _ => Bytes::default(), - }; - - let address = match exit_reason { - return_ok!() => { - if let Some(Output::Create(_, Some(addr))) = out { - addr - } else { - return Err(EvmError::Execution(Box::new(ExecutionErr { - reverted: true, - reason: "Deployment succeeded, but no address was returned. This is a bug, please report it".to_string(), - traces, - gas_used, - gas_refunded: 0, - stipend: 0, - logs, - debug, - labels, - state_changeset: None, - transactions: None, - script_wallets - }))); - } - } - _ => { - let reason = decode::decode_revert(result.as_ref(), abi, Some(exit_reason)); - return Err(EvmError::Execution(Box::new(ExecutionErr { - reverted: true, - reason, - traces, - gas_used, - gas_refunded, - stipend: 0, - logs, - debug, - labels, - state_changeset: None, - transactions: None, - script_wallets, - }))) - } + result = result.into_result(rd)?; + let Some(Output::Create(_, Some(address))) = result.out else { + panic!("Deployment succeeded, but no address was returned: {result:#?}"); }; // also mark this library as persistent, this will ensure that the state of the library is // persistent across fork swaps in forking mode self.backend.add_persistent_account(address); - trace!(address=?address, "deployed contract"); + debug!(%address, "deployed contract"); - Ok(DeployResult { address, gas_used, gas_refunded, logs, traces, debug, env, coverage }) + Ok(DeployResult { raw: result, address }) } /// Deploys a contract and commits the new state to the underlying database. /// /// Executes a CREATE transaction with the contract `code` and persistent database state - /// modifications + /// modifications. pub fn deploy( &mut self, from: Address, code: Bytes, value: U256, - abi: Option<&Abi>, + rd: Option<&RevertDecoder>, ) -> Result { let env = self.build_test_env(from, TransactTo::Create(CreateScheme::Create), code, value); - self.deploy_with_env(env, abi) + self.deploy_with_env(env, rd) } /// Check if a call to a test contract was successful. @@ -462,7 +437,7 @@ impl Executor { &self, address: Address, reverted: bool, - state_changeset: StateChangeset, + state_changeset: Cow<'_, StateChangeset>, should_fail: bool, ) -> bool { self.ensure_success(address, reverted, state_changeset, should_fail).unwrap_or_default() @@ -473,19 +448,20 @@ impl Executor { /// /// ## Background /// - /// Executing and failure checking [Executor::ensure_success] are two steps, for ds-test + /// Executing and failure checking [`Executor::ensure_success`] are two steps, for ds-test /// legacy reasons failures can be stored in a global variables and needs to be called via a - /// solidity call `failed()(bool)`. For fuzz tests we’re using the - /// `FuzzBackendWrapper` which is a Cow of the executor’s backend which lazily clones the - /// backend when it’s mutated via cheatcodes like `snapshot`. Snapshots make it even - /// more complicated because now we also need to keep track of that global variable when we - /// revert to a snapshot (because it is stored in state). Now, the problem is that - /// the `FuzzBackendWrapper` is dropped after every call, so we need to keep track of the - /// snapshot failure in the [RawCallResult] instead. + /// solidity call `failed()(bool)`. + /// + /// For fuzz tests we’re using the `CowBackend` which is a Cow of the executor’s backend which + /// lazily clones the backend when it’s mutated via cheatcodes like `snapshot`. Snapshots + /// make it even more complicated because now we also need to keep track of that global + /// variable when we revert to a snapshot (because it is stored in state). Now, the problem + /// is that the `CowBackend` is dropped after every call, so we need to keep track of the + /// snapshot failure in the [`RawCallResult`] instead. pub fn is_raw_call_success( &self, address: Address, - state_changeset: StateChangeset, + state_changeset: Cow<'_, StateChangeset>, call_result: &RawCallResult, should_fail: bool, ) -> bool { @@ -500,7 +476,7 @@ impl Executor { &self, address: Address, reverted: bool, - state_changeset: StateChangeset, + state_changeset: Cow<'_, StateChangeset>, should_fail: bool, ) -> Result { if self.backend.has_snapshot_failure() { @@ -508,30 +484,33 @@ impl Executor { return Ok(should_fail) } - // Construct a new VM with the state changeset - let mut backend = self.backend.clone_empty(); - - // we only clone the test contract and cheatcode accounts, that's all we need to evaluate - // success - for addr in [address, CHEATCODE_ADDRESS] { - let acc = self.backend.basic_ref(addr)?.unwrap_or_default(); - backend.insert_account_info(addr, acc); - } - - // If this test failed any asserts, then this changeset will contain changes `false -> true` - // for the contract's `failed` variable and the `globalFailure` flag in the state of the - // cheatcode address which are both read when we call `"failed()(bool)"` in the next step - backend.commit(state_changeset); - let mut success = !reverted; if success { + // Construct a new bare-bones backend to evaluate success. + let mut backend = self.backend.clone_empty(); + + // We only clone the test contract and cheatcode accounts, + // that's all we need to evaluate success. + for addr in [address, CHEATCODE_ADDRESS] { + let acc = self.backend.basic_ref(addr)?.unwrap_or_default(); + backend.insert_account_info(addr, acc); + } + + // If this test failed any asserts, then this changeset will contain changes + // `false -> true` for the contract's `failed` variable and the `globalFailure` flag + // in the state of the cheatcode address, + // which are both read when we call `"failed()(bool)"` in the next step. + backend.commit(state_changeset.into_owned()); + // Check if a DSTest assertion failed let executor = Executor::new(backend, self.env.clone(), self.inspector.clone(), self.gas_limit); - let call = executor.call(CALLER, address, "failed()(bool)", vec![], U256::ZERO, None); - if let Ok(CallResult { result: failed, .. }) = call { - debug!(?failed, "DSTest"); - success = !failed.as_bool().unwrap(); + let call = executor.call_sol(CALLER, address, &ITest::failedCall {}, U256::ZERO, None); + if let Ok(CallResult { raw: _, decoded_result: ITest::failedReturn { _0: failed } }) = + call + { + debug!(failed, "DSTest::failed()"); + success = !failed; } } @@ -550,14 +529,14 @@ impl Executor { transact_to: TransactTo, data: Bytes, value: U256, - ) -> Env { - Env { + ) -> EnvWithHandlerCfg { + let env = Env { cfg: self.env.cfg.clone(), // We always set the gas price to 0 so we can execute the transaction regardless of // network conditions - the actual gas price is kept in `self.block` and is applied by // the cheatcode handler if it is enabled block: BlockEnv { - basefee: U256::from(0), + basefee: U256::ZERO, gas_limit: self.gas_limit, ..self.env.block.clone() }, @@ -567,38 +546,48 @@ impl Executor { data, value, // As above, we set the gas price to 0. - gas_price: U256::from(0), + gas_price: U256::ZERO, gas_priority_fee: None, gas_limit: self.gas_limit.to(), ..self.env.tx.clone() }, - } + }; + + EnvWithHandlerCfg::new_with_spec_id(Box::new(env), self.env.handler_cfg.spec_id) } } /// Represents the context after an execution error occurred. -#[derive(thiserror::Error, Debug)] -#[error("Execution reverted: {reason} (gas: {gas_used})")] +#[derive(Debug, thiserror::Error)] +#[error("execution reverted: {reason} (gas: {})", raw.gas_used)] pub struct ExecutionErr { - pub reverted: bool, + /// The raw result of the call. + pub raw: RawCallResult, + /// The revert reason. pub reason: String, - pub gas_used: u64, - pub gas_refunded: u64, - pub stipend: u64, - pub logs: Vec, - pub traces: Option, - pub debug: Option, - pub labels: BTreeMap, - pub transactions: Option, - pub state_changeset: Option, - pub script_wallets: Vec, } -#[derive(thiserror::Error, Debug)] +impl std::ops::Deref for ExecutionErr { + type Target = RawCallResult; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.raw + } +} + +impl std::ops::DerefMut for ExecutionErr { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.raw + } +} + +#[derive(Debug, thiserror::Error)] pub enum EvmError { /// Error which occurred during execution of a transaction #[error(transparent)] - Execution(Box), + Execution(#[from] Box), /// Error which occurred during ABI encoding/decoding #[error(transparent)] AbiError(#[from] alloy_dyn_abi::Error), @@ -610,64 +599,41 @@ pub enum EvmError { Eyre(#[from] eyre::Error), } +impl From for EvmError { + fn from(err: ExecutionErr) -> Self { + EvmError::Execution(Box::new(err)) + } +} + +impl From for EvmError { + fn from(err: alloy_sol_types::Error) -> Self { + EvmError::AbiError(err.into()) + } +} + /// The result of a deployment. #[derive(Debug)] pub struct DeployResult { + /// The raw result of the deployment. + pub raw: RawCallResult, /// The address of the deployed contract pub address: Address, - /// The gas cost of the deployment - pub gas_used: u64, - /// The refunded gas - pub gas_refunded: u64, - /// The logs emitted during the deployment - pub logs: Vec, - /// The traces of the deployment - pub traces: Option, - /// The debug nodes of the call - pub debug: Option, - /// The `revm::Env` after deployment - pub env: Env, - /// The coverage info collected during the deployment - pub coverage: Option, } -/// The result of a call. -#[derive(Debug)] -pub struct CallResult { - pub skipped: bool, - /// Whether the call reverted or not - pub reverted: bool, - /// The decoded result of the call - pub result: DynSolValue, - /// The gas used for the call - pub gas_used: u64, - /// The refunded gas for the call - pub gas_refunded: u64, - /// The initial gas stipend for the transaction - pub stipend: u64, - /// The logs emitted during the call - pub logs: Vec, - /// The labels assigned to addresses during the call - pub labels: BTreeMap, - /// The traces of the call - pub traces: Option, - /// The coverage info collected during the call - pub coverage: Option, - /// The debug nodes of the call - pub debug: Option, - /// Scripted transactions generated from this call - pub transactions: Option, - /// The changeset of the state. - /// - /// This is only present if the changed state was not committed to the database (i.e. if you - /// used `call` and `call_raw` not `call_committing` or `call_raw_committing`). - pub state_changeset: Option, - /// The wallets added during the call using the `rememberKey` cheatcode - pub script_wallets: Vec, - /// The `revm::Env` after the call - pub env: Env, - /// breakpoints - pub breakpoints: Breakpoints, +impl std::ops::Deref for DeployResult { + type Target = RawCallResult; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.raw + } +} + +impl std::ops::DerefMut for DeployResult { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.raw + } } /// The result of a raw call. @@ -682,7 +648,7 @@ pub struct RawCallResult { /// This is tracked separately from revert because a snapshot failure can occur without a /// revert, since assert failures are stored in a global variable (ds-test legacy) pub has_snapshot_failure: bool, - /// The raw result of the call + /// The raw result of the call. pub result: Bytes, /// The gas used for the call pub gas_used: u64, @@ -693,7 +659,7 @@ pub struct RawCallResult { /// The logs emitted during the call pub logs: Vec, /// The labels assigned to addresses during the call - pub labels: BTreeMap, + pub labels: HashMap, /// The traces of the call pub traces: Option, /// The coverage info collected during the call @@ -707,16 +673,14 @@ pub struct RawCallResult { /// This is only present if the changed state was not committed to the database (i.e. if you /// used `call` and `call_raw` not `call_committing` or `call_raw_committing`). pub state_changeset: Option, - /// The wallets added during the call using the `rememberKey` cheatcode - pub script_wallets: Vec, /// The `revm::Env` after the call - pub env: Env, + pub env: EnvWithHandlerCfg, /// The cheatcode states after execution pub cheatcodes: Option, /// The raw output of the execution pub out: Option, /// The chisel state - pub chisel_state: Option<(Stack, Vec, InstructionResult)>, + pub chisel_state: Option<(Vec, Vec, InstructionResult)>, } impl Default for RawCallResult { @@ -730,14 +694,13 @@ impl Default for RawCallResult { gas_refunded: 0, stipend: 0, logs: Vec::new(), - labels: BTreeMap::new(), + labels: HashMap::new(), traces: None, coverage: None, debug: None, transactions: None, state_changeset: None, - script_wallets: Vec::new(), - env: Default::default(), + env: EnvWithHandlerCfg::new_with_spec_id(Box::default(), SpecId::LATEST), cheatcodes: Default::default(), out: None, chisel_state: None, @@ -745,6 +708,77 @@ impl Default for RawCallResult { } } +impl RawCallResult { + /// Converts the result of the call into an `EvmError`. + pub fn into_evm_error(self, rd: Option<&RevertDecoder>) -> EvmError { + if self.result[..] == crate::constants::MAGIC_SKIP[..] { + return EvmError::SkipError; + } + let reason = rd.unwrap_or_default().decode(&self.result, Some(self.exit_reason)); + EvmError::Execution(Box::new(self.into_execution_error(reason))) + } + + /// Converts the result of the call into an `ExecutionErr`. + pub fn into_execution_error(self, reason: String) -> ExecutionErr { + ExecutionErr { raw: self, reason } + } + + /// Returns an `EvmError` if the call failed, otherwise returns `self`. + pub fn into_result(self, rd: Option<&RevertDecoder>) -> Result { + if self.exit_reason.is_ok() { + Ok(self) + } else { + Err(self.into_evm_error(rd)) + } + } + + /// Decodes the result of the call with the given function. + pub fn into_decoded_result( + mut self, + func: &Function, + rd: Option<&RevertDecoder>, + ) -> Result { + self = self.into_result(rd)?; + let mut result = func.abi_decode_output(&self.result, false)?; + let decoded_result = if result.len() == 1 { + result.pop().unwrap() + } else { + // combine results into a tuple + DynSolValue::Tuple(result) + }; + Ok(CallResult { raw: self, decoded_result }) + } + + /// Returns the transactions generated from this call. + pub fn transactions(&self) -> Option<&BroadcastableTransactions> { + self.cheatcodes.as_ref().map(|c| &c.broadcastable_transactions) + } +} + +/// The result of a call. +pub struct CallResult { + /// The raw result of the call. + pub raw: RawCallResult, + /// The decoded result of the call. + pub decoded_result: T, +} + +impl std::ops::Deref for CallResult { + type Target = RawCallResult; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.raw + } +} + +impl std::ops::DerefMut for CallResult { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.raw + } +} + /// Calculates the initial gas stipend for a transaction fn calc_stipend(calldata: &[u8], spec: SpecId) -> u64 { let non_zero_data_cost = if SpecId::enabled(spec, SpecId::ISTANBUL) { 16 } else { 68 }; @@ -753,7 +787,7 @@ fn calc_stipend(calldata: &[u8], spec: SpecId) -> u64 { /// Converts the data aggregated in the `inspector` and `call` to a `RawCallResult` fn convert_executed_result( - env: Env, + env: EnvWithHandlerCfg, inspector: InspectorStack, result: ResultAndState, has_snapshot_failure: bool, @@ -761,33 +795,23 @@ fn convert_executed_result( let ResultAndState { result: exec_result, state: state_changeset } = result; let (exit_reason, gas_refunded, gas_used, out) = match exec_result { ExecutionResult::Success { reason, gas_used, gas_refunded, output, .. } => { - (eval_to_instruction_result(reason), gas_refunded, gas_used, Some(output)) + (reason.into(), gas_refunded, gas_used, Some(output)) } ExecutionResult::Revert { gas_used, output } => { // Need to fetch the unused gas (InstructionResult::Revert, 0_u64, gas_used, Some(Output::Call(output))) } - ExecutionResult::Halt { reason, gas_used } => { - (halt_to_instruction_result(reason), 0_u64, gas_used, None) - } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), 0_u64, gas_used, None), }; - let stipend = calc_stipend(&env.tx.data, env.cfg.spec_id); + let stipend = calc_stipend(&env.tx.data, env.handler_cfg.spec_id); let result = match &out { Some(Output::Call(data)) => data.clone(), _ => Bytes::new(), }; - let InspectorData { - logs, - labels, - traces, - coverage, - debug, - cheatcodes, - script_wallets, - chisel_state, - } = inspector.collect(); + let InspectorData { logs, labels, traces, coverage, debug, cheatcodes, chisel_state } = + inspector.collect(); let transactions = match cheatcodes.as_ref() { Some(cheats) if !cheats.broadcastable_transactions.is_empty() => { @@ -811,91 +835,9 @@ fn convert_executed_result( debug, transactions, state_changeset: Some(state_changeset), - script_wallets, env, cheatcodes, out, chisel_state, }) } - -fn convert_call_result( - abi: Option<&Abi>, - func: &Function, - call_result: RawCallResult, -) -> Result { - let RawCallResult { - result, - exit_reason: status, - reverted, - gas_used, - gas_refunded, - stipend, - logs, - labels, - traces, - coverage, - debug, - transactions, - state_changeset, - script_wallets, - env, - .. - } = call_result; - - let breakpoints = if let Some(c) = call_result.cheatcodes { - c.breakpoints - } else { - std::collections::HashMap::new() - }; - - match status { - return_ok!() => { - let mut result = func.abi_decode_output(&result, false)?; - let res = if result.len() == 1 { - result.pop().unwrap() - } else { - // combine results into a tuple - DynSolValue::Tuple(result) - }; - Ok(CallResult { - reverted, - result: res, - gas_used, - gas_refunded, - stipend, - logs, - labels, - traces, - coverage, - debug, - transactions, - state_changeset, - script_wallets, - env, - breakpoints, - skipped: false, - }) - } - _ => { - if &result == crate::constants::MAGIC_SKIP { - return Err(EvmError::SkipError) - } - let reason = decode::decode_revert(&result, abi, Some(status)); - Err(EvmError::Execution(Box::new(ExecutionErr { - reverted, - reason, - gas_used, - gas_refunded, - stipend, - logs, - traces, - debug, - labels, - transactions, - state_changeset, - script_wallets, - }))) - } - } -} diff --git a/crates/evm/evm/src/executors/tracing.rs b/crates/evm/evm/src/executors/tracing.rs index 19db5fc57755d..08979bc168669 100644 --- a/crates/evm/evm/src/executors/tracing.rs +++ b/crates/evm/evm/src/executors/tracing.rs @@ -2,7 +2,7 @@ use crate::executors::{Executor, ExecutorBuilder}; use foundry_compilers::EvmVersion; use foundry_config::{utils::evm_spec_id, Chain, Config}; use foundry_evm_core::{backend::Backend, fork::CreateFork, opts::EvmOpts}; -use revm::primitives::Env; +use revm::primitives::{Env, SpecId}; use std::ops::{Deref, DerefMut}; /// A default executor with tracing enabled @@ -11,13 +11,13 @@ pub struct TracingExecutor { } impl TracingExecutor { - pub async fn new( + pub fn new( env: revm::primitives::Env, fork: Option, version: Option, debug: bool, ) -> Self { - let db = Backend::spawn(fork).await; + let db = Backend::spawn(fork); Self { // configures a bare version of the evm executor: no cheatcode inspector is enabled, // tracing will be enabled only for the targeted transaction @@ -28,6 +28,11 @@ impl TracingExecutor { } } + /// Returns the spec id of the executor + pub fn spec_id(&self) -> SpecId { + self.executor.spec_id() + } + /// uses the fork block number from the config pub async fn get_fork_material( config: &Config, diff --git a/crates/evm/evm/src/inspectors/access_list.rs b/crates/evm/evm/src/inspectors/access_list.rs deleted file mode 100644 index 102e19fb5dea9..0000000000000 --- a/crates/evm/evm/src/inspectors/access_list.rs +++ /dev/null @@ -1,85 +0,0 @@ -use alloy_primitives::{Address, B256}; -use ethers_core::types::transaction::eip2930::{AccessList, AccessListItem}; -use foundry_common::types::{ToAlloy, ToEthers}; -use hashbrown::{HashMap, HashSet}; -use revm::{ - interpreter::{opcode, Interpreter}, - Database, EVMData, Inspector, -}; - -/// An inspector that collects touched accounts and storage slots. -#[derive(Default, Debug)] -pub struct AccessListTracer { - excluded: HashSet
, - access_list: HashMap>, -} - -impl AccessListTracer { - pub fn new( - access_list: AccessList, - from: Address, - to: Address, - precompiles: Vec
, - ) -> Self { - AccessListTracer { - excluded: [from, to].iter().chain(precompiles.iter()).copied().collect(), - access_list: access_list - .0 - .iter() - .map(|v| { - ( - v.address.to_alloy(), - v.storage_keys.iter().copied().map(|v| v.to_alloy()).collect(), - ) - }) - .collect(), - } - } - - pub fn access_list(&self) -> AccessList { - AccessList::from( - self.access_list - .iter() - .map(|(address, slots)| AccessListItem { - address: address.to_ethers(), - storage_keys: slots.iter().copied().map(|k| k.to_ethers()).collect(), - }) - .collect::>(), - ) - } -} - -impl Inspector for AccessListTracer { - #[inline] - fn step(&mut self, interpreter: &mut Interpreter<'_>, _data: &mut EVMData<'_, DB>) { - match interpreter.current_opcode() { - opcode::SLOAD | opcode::SSTORE => { - if let Ok(slot) = interpreter.stack().peek(0) { - let cur_contract = interpreter.contract.address; - self.access_list.entry(cur_contract).or_default().insert(slot.into()); - } - } - opcode::EXTCODECOPY | - opcode::EXTCODEHASH | - opcode::EXTCODESIZE | - opcode::BALANCE | - opcode::SELFDESTRUCT => { - if let Ok(slot) = interpreter.stack().peek(0) { - let addr: Address = Address::from_word(slot.into()); - if !self.excluded.contains(&addr) { - self.access_list.entry(addr).or_default(); - } - } - } - opcode::DELEGATECALL | opcode::CALL | opcode::STATICCALL | opcode::CALLCODE => { - if let Ok(slot) = interpreter.stack().peek(1) { - let addr: Address = Address::from_word(slot.into()); - if !self.excluded.contains(&addr) { - self.access_list.entry(addr).or_default(); - } - } - } - _ => (), - } - } -} diff --git a/crates/evm/evm/src/inspectors/chisel_state.rs b/crates/evm/evm/src/inspectors/chisel_state.rs index a4d3a1895f24c..9aa933d388681 100644 --- a/crates/evm/evm/src/inspectors/chisel_state.rs +++ b/crates/evm/evm/src/inspectors/chisel_state.rs @@ -1,6 +1,7 @@ +use alloy_primitives::U256; use revm::{ - interpreter::{InstructionResult, Interpreter, Stack}, - Database, Inspector, + interpreter::{InstructionResult, Interpreter}, + Database, EvmContext, Inspector, }; /// An inspector for Chisel @@ -9,7 +10,7 @@ pub struct ChiselState { /// The PC of the final instruction pub final_pc: usize, /// The final state of the REPL contract call - pub state: Option<(Stack, Vec, InstructionResult)>, + pub state: Option<(Vec, Vec, InstructionResult)>, } impl ChiselState { @@ -22,12 +23,12 @@ impl ChiselState { impl Inspector for ChiselState { #[inline] - fn step_end(&mut self, interp: &mut Interpreter<'_>, _: &mut revm::EVMData<'_, DB>) { + fn step_end(&mut self, interp: &mut Interpreter, _context: &mut EvmContext) { // If we are at the final pc of the REPL contract execution, set the state. // Subtraction can't overflow because `pc` is always at least 1 in `step_end`. if self.final_pc == interp.program_counter() - 1 { self.state = Some(( - interp.stack().clone(), + interp.stack.data().clone(), interp.shared_memory.context_memory().to_vec(), interp.instruction_result, )) diff --git a/crates/evm/evm/src/inspectors/debugger.rs b/crates/evm/evm/src/inspectors/debugger.rs index f66fdbc9beaf9..d68a14bf6dc43 100644 --- a/crates/evm/evm/src/inspectors/debugger.rs +++ b/crates/evm/evm/src/inspectors/debugger.rs @@ -1,21 +1,23 @@ -use alloy_primitives::{Address, Bytes}; -use foundry_common::{ErrorExt, SELECTOR_LEN}; +use alloy_primitives::Address; +use arrayvec::ArrayVec; +use foundry_common::ErrorExt; use foundry_evm_core::{ backend::DatabaseExt, - constants::CHEATCODE_ADDRESS, - debug::{DebugArena, DebugNode, DebugStep, Instruction}, - utils::{gas_used, get_create_address, CallKind}, + debug::{DebugArena, DebugNode, DebugStep}, + utils::gas_used, }; use revm::{ interpreter::{ opcode::{self, spec_opcode_gas}, - CallInputs, CreateInputs, Gas, InstructionResult, Interpreter, + CallInputs, CallOutcome, CreateInputs, CreateOutcome, Gas, InstructionResult, Interpreter, + InterpreterResult, }, - EVMData, Inspector, + EvmContext, Inspector, }; +use revm_inspectors::tracing::types::CallKind; /// An inspector that collects debug nodes on every step of the interpreter. -#[derive(Clone, Default, Debug)] +#[derive(Clone, Debug, Default)] pub struct Debugger { /// The arena of [DebugNode]s pub arena: DebugArena, @@ -44,114 +46,108 @@ impl Debugger { } impl Inspector for Debugger { - #[inline] - fn step(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - let pc = interpreter.program_counter(); - let op = interpreter.current_opcode(); + fn step(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { + let pc = interp.program_counter(); + let op = interp.current_opcode(); // Get opcode information - let opcode_infos = spec_opcode_gas(data.env.cfg.spec_id); + let opcode_infos = spec_opcode_gas(ecx.spec_id()); let opcode_info = &opcode_infos[op as usize]; // Extract the push bytes - let push_size = if opcode_info.is_push() { (op - opcode::PUSH1 + 1) as usize } else { 0 }; - let push_bytes = match push_size { - 0 => None, - n => { - let start = pc + 1; - let end = start + n; - Some(interpreter.contract.bytecode.bytecode()[start..end].to_vec()) - } - }; + let push_size = if opcode_info.is_push() { (op - opcode::PUSH0) as usize } else { 0 }; + let push_bytes = (push_size > 0).then(|| { + let start = pc + 1; + let end = start + push_size; + let slice = &interp.contract.bytecode.bytecode()[start..end]; + assert!(slice.len() <= 32); + let mut array = ArrayVec::new(); + array.try_extend_from_slice(slice).unwrap(); + array + }); let total_gas_used = gas_used( - data.env.cfg.spec_id, - interpreter.gas.limit().saturating_sub(interpreter.gas.remaining()), - interpreter.gas.refunded() as u64, + ecx.spec_id(), + interp.gas.limit().saturating_sub(interp.gas.remaining()), + interp.gas.refunded() as u64, ); + // Reuse the memory from the previous step if the previous opcode did not modify it. + let memory = self.arena.arena[self.head] + .steps + .last() + .filter(|step| !step.opcode_modifies_memory()) + .map(|step| step.memory.clone()) + .unwrap_or_else(|| interp.shared_memory.context_memory().to_vec().into()); + self.arena.arena[self.head].steps.push(DebugStep { pc, - stack: interpreter.stack().data().clone(), - memory: interpreter.shared_memory.context_memory().to_vec(), - instruction: Instruction::OpCode(op), - push_bytes, + stack: interp.stack().data().clone(), + memory, + calldata: interp.contract().input.clone(), + returndata: interp.return_data_buffer.clone(), + instruction: op, + push_bytes: push_bytes.unwrap_or_default(), total_gas_used, }); } - #[inline] - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { + fn call(&mut self, ecx: &mut EvmContext, inputs: &mut CallInputs) -> Option { self.enter( - data.journaled_state.depth() as usize, - call.context.code_address, - call.context.scheme.into(), + ecx.journaled_state.depth() as usize, + inputs.context.code_address, + inputs.context.scheme.into(), ); - if call.contract == CHEATCODE_ADDRESS { - if let Some(selector) = call.input.get(..SELECTOR_LEN) { - self.arena.arena[self.head].steps.push(DebugStep { - instruction: Instruction::Cheatcode(selector.try_into().unwrap()), - ..Default::default() - }); - } - } - - (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) + None } - #[inline] fn call_end( &mut self, - _: &mut EVMData<'_, DB>, - _: &CallInputs, - gas: Gas, - status: InstructionResult, - retdata: Bytes, - ) -> (InstructionResult, Gas, Bytes) { + _context: &mut EvmContext, + _inputs: &CallInputs, + outcome: CallOutcome, + ) -> CallOutcome { self.exit(); - (status, gas, retdata) + outcome } - #[inline] fn create( &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { - // TODO: Does this increase gas cost? - if let Err(err) = data.journaled_state.load_account(call.caller, data.db) { - let gas = Gas::new(call.gas_limit); - return (InstructionResult::Revert, None, gas, err.abi_encode_revert()) + ecx: &mut EvmContext, + inputs: &mut CreateInputs, + ) -> Option { + if let Err(err) = ecx.load_account(inputs.caller) { + let gas = Gas::new(inputs.gas_limit); + return Some(CreateOutcome::new( + InterpreterResult { + result: InstructionResult::Revert, + output: err.abi_encode_revert(), + gas, + }, + None, + )); } - let nonce = data.journaled_state.account(call.caller).info.nonce; + let nonce = ecx.journaled_state.account(inputs.caller).info.nonce; self.enter( - data.journaled_state.depth() as usize, - get_create_address(call, nonce), + ecx.journaled_state.depth() as usize, + inputs.created_address(nonce), CallKind::Create, ); - (InstructionResult::Continue, None, Gas::new(call.gas_limit), Bytes::new()) + None } - #[inline] fn create_end( &mut self, - _: &mut EVMData<'_, DB>, - _: &CreateInputs, - status: InstructionResult, - address: Option
, - gas: Gas, - retdata: Bytes, - ) -> (InstructionResult, Option
, Gas, Bytes) { + _context: &mut EvmContext, + _inputs: &CreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { self.exit(); - (status, address, gas, retdata) + outcome } } diff --git a/crates/evm/evm/src/inspectors/logs.rs b/crates/evm/evm/src/inspectors/logs.rs index 466cf9fee7b80..a8e4a063c352a 100644 --- a/crates/evm/evm/src/inspectors/logs.rs +++ b/crates/evm/evm/src/inspectors/logs.rs @@ -1,20 +1,19 @@ -use alloy_primitives::{Address, Bytes, B256}; +use alloy_primitives::{Address, Bytes, Log}; use alloy_sol_types::{SolEvent, SolInterface, SolValue}; -use ethers_core::types::Log; -use foundry_common::{fmt::ConsoleFmt, types::ToEthers, ErrorExt}; +use foundry_common::{fmt::ConsoleFmt, ErrorExt}; use foundry_evm_core::{ - abi::{patch_hardhat_console_selector, Console, HardhatConsole}, + abi::{patch_hh_console_selector, Console, HardhatConsole}, constants::HARDHAT_CONSOLE_ADDRESS, }; use revm::{ - interpreter::{CallInputs, Gas, InstructionResult}, - Database, EVMData, Inspector, + interpreter::{CallInputs, CallOutcome, Gas, InstructionResult, InterpreterResult}, + Database, EvmContext, Inspector, }; /// An inspector that collects logs during execution. /// /// The inspector collects logs from the `LOG` opcodes as well as Hardhat-style logs. -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct LogCollector { /// The collected logs. Includes both `LOG` opcodes and Hardhat-style logs. pub logs: Vec, @@ -23,7 +22,7 @@ pub struct LogCollector { impl LogCollector { fn hardhat_log(&mut self, mut input: Vec) -> (InstructionResult, Bytes) { // Patch the Hardhat-style selector (`uint` instead of `uint256`) - patch_hardhat_console_selector(&mut input); + patch_hh_console_selector(&mut input); // Decode the call let decoded = match HardhatConsole::HardhatConsoleCalls::abi_decode(&input, false) { @@ -39,26 +38,31 @@ impl LogCollector { } impl Inspector for LogCollector { - fn log(&mut self, _: &mut EVMData<'_, DB>, address: &Address, topics: &[B256], data: &Bytes) { - self.logs.push(Log { - address: address.to_ethers(), - topics: topics.iter().copied().map(|t| t.to_ethers()).collect(), - data: data.clone().to_ethers(), - ..Default::default() - }); + fn log(&mut self, _context: &mut EvmContext, log: &Log) { + self.logs.push(log.clone()); } + #[inline] fn call( &mut self, - _: &mut EVMData<'_, DB>, - call: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { - let (status, reason) = if call.contract == HARDHAT_CONSOLE_ADDRESS { - self.hardhat_log(call.input.to_vec()) - } else { - (InstructionResult::Continue, Bytes::new()) - }; - (status, Gas::new(call.gas_limit), reason) + _context: &mut EvmContext, + inputs: &mut CallInputs, + ) -> Option { + if inputs.contract == HARDHAT_CONSOLE_ADDRESS { + let (res, out) = self.hardhat_log(inputs.input.to_vec()); + if res != InstructionResult::Continue { + return Some(CallOutcome { + result: InterpreterResult { + result: res, + output: out, + gas: Gas::new(inputs.gas_limit), + }, + memory_offset: inputs.return_memory_offset.clone(), + }) + } + } + + None } } @@ -66,9 +70,6 @@ impl Inspector for LogCollector { fn convert_hh_log_to_event(call: HardhatConsole::HardhatConsoleCalls) -> Log { // Convert the parameters of the call to their string representation using `ConsoleFmt`. let fmt = call.fmt(Default::default()); - Log { - topics: vec![Console::log::SIGNATURE_HASH.to_ethers()], - data: fmt.abi_encode().into(), - ..Default::default() - } + Log::new(Address::default(), vec![Console::log::SIGNATURE_HASH], fmt.abi_encode().into()) + .unwrap_or_else(|| Log { ..Default::default() }) } diff --git a/crates/evm/evm/src/inspectors/mod.rs b/crates/evm/evm/src/inspectors/mod.rs index b781ce96c8dd2..786786b28e926 100644 --- a/crates/evm/evm/src/inspectors/mod.rs +++ b/crates/evm/evm/src/inspectors/mod.rs @@ -3,10 +3,9 @@ pub use foundry_cheatcodes::{self as cheatcodes, Cheatcodes, CheatsConfig}; pub use foundry_evm_coverage::CoverageCollector; pub use foundry_evm_fuzz::Fuzzer; -pub use foundry_evm_traces::Tracer; +pub use foundry_evm_traces::{StackSnapshotType, TracingInspector, TracingInspectorConfig}; -mod access_list; -pub use access_list::AccessListTracer; +pub use revm_inspectors::access_list::AccessListInspector; mod chisel_state; pub use chisel_state::ChiselState; @@ -17,8 +16,5 @@ pub use debugger::Debugger; mod logs; pub use logs::LogCollector; -mod printer; -pub use printer::TracePrinter; - mod stack; pub use stack::{InspectorData, InspectorStack, InspectorStackBuilder}; diff --git a/crates/evm/evm/src/inspectors/printer.rs b/crates/evm/evm/src/inspectors/printer.rs deleted file mode 100644 index 02a47fda178aa..0000000000000 --- a/crates/evm/evm/src/inspectors/printer.rs +++ /dev/null @@ -1,65 +0,0 @@ -use alloy_primitives::{Address, Bytes}; -use revm::{ - interpreter::{opcode, CallInputs, CreateInputs, Gas, InstructionResult, Interpreter}, - Database, EVMData, Inspector, -}; - -#[derive(Clone, Debug, Default)] -#[non_exhaustive] -pub struct TracePrinter; - -impl Inspector for TracePrinter { - // get opcode by calling `interp.contract.opcode(interp.program_counter())`. - // all other information can be obtained from interp. - fn step(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - let opcode = interp.current_opcode(); - let opcode_str = opcode::OPCODE_JUMPMAP[opcode as usize]; - let gas_remaining = interp.gas.remaining(); - println!( - "depth:{}, PC:{}, gas:{:#x}({}), OPCODE: {:?}({:?}) refund:{:#x}({}) Stack:{:?}, Data size:{}, Data: 0x{}", - data.journaled_state.depth(), - interp.program_counter(), - gas_remaining, - gas_remaining, - opcode_str.unwrap_or(""), - opcode, - interp.gas.refunded(), - interp.gas.refunded(), - interp.stack.data(), - interp.shared_memory.len(), - hex::encode(interp.shared_memory.context_memory()), - ); - } - - fn call( - &mut self, - _data: &mut EVMData<'_, DB>, - inputs: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { - println!( - "SM CALL: {},context:{:?}, is_static:{:?}, transfer:{:?}, input_size:{:?}", - inputs.contract, - inputs.context, - inputs.is_static, - inputs.transfer, - inputs.input.len(), - ); - (InstructionResult::Continue, Gas::new(0), Bytes::new()) - } - - fn create( - &mut self, - _data: &mut EVMData<'_, DB>, - inputs: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { - println!( - "CREATE CALL: caller:{}, scheme:{:?}, value:{:?}, init_code:{:?}, gas:{:?}", - inputs.caller, - inputs.scheme, - inputs.value, - hex::encode(&inputs.init_code), - inputs.gas_limit - ); - (InstructionResult::Continue, None, Gas::new(0), Bytes::new()) - } -} diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index d72d2ef0eed55..bcb1e5517ead2 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -1,21 +1,25 @@ use super::{ Cheatcodes, CheatsConfig, ChiselState, CoverageCollector, Debugger, Fuzzer, LogCollector, - TracePrinter, Tracer, + StackSnapshotType, TracingInspector, TracingInspectorConfig, +}; +use alloy_primitives::{Address, Bytes, Log, U256}; +use foundry_evm_core::{ + backend::{update_state, DatabaseExt}, + debug::DebugArena, + InspectorExt, }; -use alloy_primitives::{Address, Bytes, B256, U256}; -use ethers_core::types::Log; -use ethers_signers::LocalWallet; -use foundry_evm_core::{backend::DatabaseExt, debug::DebugArena}; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::CallTraceArena; use revm::{ + inspectors::CustomPrintTracer, interpreter::{ - return_revert, CallInputs, CreateInputs, Gas, InstructionResult, Interpreter, Stack, + CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, Gas, InstructionResult, + Interpreter, InterpreterResult, }, - primitives::{BlockEnv, Env}, - EVMData, Inspector, + primitives::{BlockEnv, Env, EnvWithHandlerCfg, ExecutionResult, Output, TransactTo}, + DatabaseCommit, EvmContext, Inspector, }; -use std::{collections::BTreeMap, sync::Arc}; +use std::{collections::HashMap, sync::Arc}; #[derive(Clone, Debug, Default)] #[must_use = "builders do nothing unless you call `build` on them"] @@ -46,6 +50,10 @@ pub struct InspectorStackBuilder { pub print: Option, /// The chisel state inspector. pub chisel_state: Option, + /// Whether to enable call isolation. + /// In isolation mode all top-level calls are executed as a separate transaction in a separate + /// EVM context, enabling more precise gas accounting and transaction state changes. + pub enable_isolation: bool, } impl InspectorStackBuilder { @@ -125,6 +133,14 @@ impl InspectorStackBuilder { self } + /// Set whether to enable the call isolation. + /// For description of call isolation, see [`InspectorStack::enable_isolation`]. + #[inline] + pub fn enable_isolation(mut self, yes: bool) -> Self { + self.enable_isolation = yes; + self + } + /// Builds the stack of inspectors to use when transacting/committing on the EVM. /// /// See also [`revm::Evm::inspect_ref`] and [`revm::Evm::commit_ref`]. @@ -140,6 +156,7 @@ impl InspectorStackBuilder { coverage, print, chisel_state, + enable_isolation, } = self; let mut stack = InspectorStack::new(); @@ -159,6 +176,8 @@ impl InspectorStackBuilder { stack.print(print.unwrap_or(false)); stack.tracing(trace.unwrap_or(false)); + stack.enable_isolation(enable_isolation); + // environment, must come after all of the inspectors if let Some(block) = block { stack.set_block(&block); @@ -182,23 +201,83 @@ macro_rules! call_inspectors { )+}} } +/// Same as [call_inspectors] macro, but with depth adjustment for isolated execution. +macro_rules! call_inspectors_adjust_depth { + (#[no_ret] [$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr, $self:ident, $data:ident $(,)?) => { + if $self.in_inner_context { + $data.journaled_state.depth += 1; + $( + if let Some($id) = $inspector { + $call + } + )+ + $data.journaled_state.depth -= 1; + } else { + $( + if let Some($id) = $inspector { + $call + } + )+ + } + }; + ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr, $self:ident, $data:ident $(,)?) => { + if $self.in_inner_context { + $data.journaled_state.depth += 1; + $( + if let Some($id) = $inspector { + if let Some(result) = $call { + $data.journaled_state.depth -= 1; + return result; + } + } + )+ + $data.journaled_state.depth -= 1; + } else { + $( + if let Some($id) = $inspector { + if let Some(result) = $call { + return result; + } + } + )+ + } + }; +} + /// The collected results of [`InspectorStack`]. pub struct InspectorData { pub logs: Vec, - pub labels: BTreeMap, + pub labels: HashMap, pub traces: Option, pub debug: Option, pub coverage: Option, pub cheatcodes: Option, - pub script_wallets: Vec, - pub chisel_state: Option<(Stack, Vec, InstructionResult)>, + pub chisel_state: Option<(Vec, Vec, InstructionResult)>, +} + +/// Contains data about the state of outer/main EVM which created and invoked the inner EVM context. +/// Used to adjust EVM state while in inner context. +/// +/// We need this to avoid breaking changes due to EVM behavior differences in isolated vs +/// non-isolated mode. For descriptions and workarounds for those changes see: https://github.com/foundry-rs/foundry/pull/7186#issuecomment-1959102195 +#[derive(Debug, Clone)] +pub struct InnerContextData { + /// The sender of the inner EVM context. + /// It is also an origin of the transaction that created the inner EVM context. + sender: Address, + /// Nonce of the sender before invocation of the inner EVM context. + original_sender_nonce: u64, + /// Origin of the transaction in the outer EVM context. + original_origin: Address, + /// Whether the inner context was created by a CREATE transaction. + is_create: bool, } /// An inspector that calls multiple inspectors in sequence. /// /// If a call to an inspector returns a value other than [InstructionResult::Continue] (or /// equivalent) the remaining inspectors are not called. -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct InspectorStack { pub cheatcodes: Option, pub chisel_state: Option, @@ -206,8 +285,13 @@ pub struct InspectorStack { pub debugger: Option, pub fuzzer: Option, pub log_collector: Option, - pub printer: Option, - pub tracer: Option, + pub printer: Option, + pub tracer: Option, + pub enable_isolation: bool, + + /// Flag marking if we are in the inner EVM context. + pub in_inner_context: bool, + pub inner_context_data: Option, } impl InspectorStack { @@ -274,6 +358,12 @@ impl InspectorStack { self.debugger = yes.then(Default::default); } + /// Set whether to enable call isolation. + #[inline] + pub fn enable_isolation(&mut self, yes: bool) { + self.enable_isolation = yes; + } + /// Set whether to enable the log collector. #[inline] pub fn collect_logs(&mut self, yes: bool) { @@ -289,7 +379,16 @@ impl InspectorStack { /// Set whether to enable the tracer. #[inline] pub fn tracing(&mut self, yes: bool) { - self.tracer = yes.then(Default::default); + self.tracer = yes.then(|| { + TracingInspector::new(TracingInspectorConfig { + record_steps: false, + record_memory_snapshots: false, + record_stack_snapshots: StackSnapshotType::None, + record_state_diff: false, + exclude_precompile_calls: false, + record_logs: true, + }) + }); } /// Collects all the data gathered during inspection into a single struct. @@ -304,14 +403,9 @@ impl InspectorStack { cheatcodes.labels.clone().into_iter().map(|l| (l.0, l.1)).collect() }) .unwrap_or_default(), - traces: self.tracer.map(|tracer| tracer.traces), + traces: self.tracer.map(|tracer| tracer.get_traces().clone()), debug: self.debugger.map(|debugger| debugger.arena), coverage: self.coverage.map(|coverage| coverage.maps), - script_wallets: self - .cheatcodes - .as_ref() - .map(|cheatcodes| cheatcodes.script_wallets.clone()) - .unwrap_or_default(), cheatcodes: self.cheatcodes, chisel_state: self.chisel_state.and_then(|state| state.state), } @@ -319,254 +413,399 @@ impl InspectorStack { fn do_call_end( &mut self, - data: &mut EVMData<'_, DB>, - call: &CallInputs, - remaining_gas: Gas, - status: InstructionResult, - retdata: Bytes, - ) -> (InstructionResult, Gas, Bytes) { - call_inspectors!( + ecx: &mut EvmContext<&mut DB>, + inputs: &CallInputs, + outcome: CallOutcome, + ) -> CallOutcome { + let result = outcome.result.result; + call_inspectors_adjust_depth!( [ &mut self.fuzzer, &mut self.debugger, &mut self.tracer, - &mut self.coverage, - &mut self.log_collector, &mut self.cheatcodes, - &mut self.printer + &mut self.printer, ], |inspector| { - let (new_status, new_gas, new_retdata) = - inspector.call_end(data, call, remaining_gas, status, retdata.clone()); + let new_outcome = inspector.call_end(ecx, inputs, outcome.clone()); // If the inspector returns a different status or a revert with a non-empty message, // we assume it wants to tell us something - if new_status != status || - (new_status == InstructionResult::Revert && new_retdata != retdata) - { - return (new_status, new_gas, new_retdata) - } - } + let different = new_outcome.result.result != result || + (new_outcome.result.result == InstructionResult::Revert && + new_outcome.output() != outcome.output()); + different.then_some(new_outcome) + }, + self, + ecx ); - (status, remaining_gas, retdata) + outcome } -} -impl Inspector for InspectorStack { - fn initialize_interp(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - let res = interpreter.instruction_result; - call_inspectors!( - [ - &mut self.debugger, - &mut self.coverage, - &mut self.tracer, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer - ], - |inspector| { - inspector.initialize_interp(interpreter, data); + fn transact_inner( + &mut self, + ecx: &mut EvmContext<&mut DB>, + transact_to: TransactTo, + caller: Address, + input: Bytes, + gas_limit: u64, + value: U256, + ) -> (InterpreterResult, Option
) { + let ecx = &mut ecx.inner; + + ecx.db.commit(ecx.journaled_state.state.clone()); + + let nonce = ecx + .journaled_state + .load_account(caller, &mut ecx.db) + .expect("failed to load caller") + .0 + .info + .nonce; + + let cached_env = ecx.env.clone(); + + ecx.env.block.basefee = U256::ZERO; + ecx.env.tx.caller = caller; + ecx.env.tx.transact_to = transact_to.clone(); + ecx.env.tx.data = input; + ecx.env.tx.value = value; + ecx.env.tx.nonce = Some(nonce); + // Add 21000 to the gas limit to account for the base cost of transaction. + ecx.env.tx.gas_limit = gas_limit + 21000; + // If we haven't disabled gas limit checks, ensure that transaction gas limit will not + // exceed block gas limit. + if !ecx.env.cfg.disable_block_gas_limit { + ecx.env.tx.gas_limit = + std::cmp::min(ecx.env.tx.gas_limit, ecx.env.block.gas_limit.to()); + } + ecx.env.tx.gas_price = U256::ZERO; + + self.inner_context_data = Some(InnerContextData { + sender: ecx.env.tx.caller, + original_origin: cached_env.tx.caller, + original_sender_nonce: nonce, + is_create: matches!(transact_to, TransactTo::Create(_)), + }); + self.in_inner_context = true; + + let env = EnvWithHandlerCfg::new_with_spec_id(ecx.env.clone(), ecx.spec_id()); + let res = { + let mut evm = crate::utils::new_evm_with_inspector(&mut *ecx.db, env, &mut *self); + let res = evm.transact(); + + // need to reset the env in case it was modified via cheatcodes during execution + ecx.env = evm.context.evm.inner.env; + res + }; + + self.in_inner_context = false; + self.inner_context_data = None; + + ecx.env.tx = cached_env.tx; + ecx.env.block.basefee = cached_env.block.basefee; + + let mut gas = Gas::new(gas_limit); + + let Ok(mut res) = res else { + // Should we match, encode and propagate error as a revert reason? + let result = + InterpreterResult { result: InstructionResult::Revert, output: Bytes::new(), gas }; + return (result, None) + }; + + // Commit changes after transaction + ecx.db.commit(res.state.clone()); + + // Update both states with new DB data after commit. + if let Err(e) = update_state(&mut ecx.journaled_state.state, &mut ecx.db) { + let res = InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from(e.to_string()), + gas, + }; + return (res, None) + } + if let Err(e) = update_state(&mut res.state, &mut ecx.db) { + let res = InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from(e.to_string()), + gas, + }; + return (res, None) + } - // Allow inspectors to exit early - if interpreter.instruction_result != res { - #[allow(clippy::needless_return)] - return + // Merge transaction journal into the active journal. + for (addr, acc) in res.state { + if let Some(acc_mut) = ecx.journaled_state.state.get_mut(&addr) { + acc_mut.status |= acc.status; + for (key, val) in acc.storage { + acc_mut.storage.entry(key).or_insert(val); } + } else { + ecx.journaled_state.state.insert(addr, acc); } - ); - } - - fn step(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - let res = interpreter.instruction_result; - call_inspectors!( - [ - &mut self.fuzzer, - &mut self.debugger, - &mut self.tracer, - &mut self.coverage, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer - ], - |inspector| { - inspector.step(interpreter, data); + } - // Allow inspectors to exit early - if interpreter.instruction_result != res { - #[allow(clippy::needless_return)] - return - } + let (result, address, output) = match res.result { + ExecutionResult::Success { reason, gas_used, gas_refunded, logs: _, output } => { + gas.set_refund(gas_refunded as i64); + gas.record_cost(gas_used); + let address = match output { + Output::Create(_, address) => address, + Output::Call(_) => None, + }; + (reason.into(), address, output.into_data()) } - ); + ExecutionResult::Halt { reason, gas_used } => { + gas.record_cost(gas_used); + (reason.into(), None, Bytes::new()) + } + ExecutionResult::Revert { gas_used, output } => { + gas.record_cost(gas_used); + (InstructionResult::Revert, None, output) + } + }; + (InterpreterResult { result, output, gas }, address) } - fn log( + /// Adjusts the EVM data for the inner EVM context. + /// Should be called on the top-level call of inner context (depth == 0 && + /// self.in_inner_context) Decreases sender nonce for CALLs to keep backwards compatibility + /// Updates tx.origin to the value before entering inner context + fn adjust_evm_data_for_inner_context( &mut self, - evm_data: &mut EVMData<'_, DB>, - address: &Address, - topics: &[B256], - data: &Bytes, + ecx: &mut EvmContext<&mut DB>, ) { - call_inspectors!( - [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer], - |inspector| { - inspector.log(evm_data, address, topics, data); - } + let inner_context_data = + self.inner_context_data.as_ref().expect("should be called in inner context"); + let sender_acc = ecx + .journaled_state + .state + .get_mut(&inner_context_data.sender) + .expect("failed to load sender"); + if !inner_context_data.is_create { + sender_acc.info.nonce = inner_context_data.original_sender_nonce; + } + ecx.env.tx.caller = inner_context_data.original_origin; + } +} + +// NOTE: `&mut DB` is required because we recurse inside of `transact_inner` and we need to use the +// same reference to the DB, otherwise there's infinite recursion and Rust fails to instatiate this +// implementation. This currently works because internally we only use `&mut DB` anyways, but if +// this ever needs to be changed, this can be reverted back to using just `DB`, and instead using +// dynamic dispatch (`&mut dyn ...`) in `transact_inner`. +impl Inspector<&mut DB> for InspectorStack { + fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut DB>) { + call_inspectors_adjust_depth!( + #[no_ret] + [&mut self.coverage, &mut self.tracer, &mut self.cheatcodes, &mut self.printer], + |inspector| inspector.initialize_interp(interpreter, ecx), + self, + ecx ); } - fn step_end(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - let res = interpreter.instruction_result; - call_inspectors!( + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut DB>) { + call_inspectors_adjust_depth!( + #[no_ret] [ + &mut self.fuzzer, &mut self.debugger, &mut self.tracer, - &mut self.log_collector, + &mut self.coverage, &mut self.cheatcodes, &mut self.printer, - &mut self.chisel_state ], - |inspector| { - inspector.step_end(interpreter, data); + |inspector| inspector.step(interpreter, ecx), + self, + ecx + ); + } - // Allow inspectors to exit early - if interpreter.instruction_result != res { - #[allow(clippy::needless_return)] - return - } - } + fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut DB>) { + call_inspectors_adjust_depth!( + #[no_ret] + [&mut self.tracer, &mut self.cheatcodes, &mut self.chisel_state, &mut self.printer], + |inspector| inspector.step_end(interpreter, ecx), + self, + ecx + ); + } + + fn log(&mut self, ecx: &mut EvmContext<&mut DB>, log: &Log) { + call_inspectors_adjust_depth!( + #[no_ret] + [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer], + |inspector| inspector.log(ecx, log), + self, + ecx ); } fn call( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext<&mut DB>, call: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { - call_inspectors!( + ) -> Option { + if self.in_inner_context && ecx.journaled_state.depth == 0 { + self.adjust_evm_data_for_inner_context(ecx); + return None; + } + + call_inspectors_adjust_depth!( [ &mut self.fuzzer, &mut self.debugger, &mut self.tracer, - &mut self.coverage, &mut self.log_collector, &mut self.cheatcodes, - &mut self.printer + &mut self.printer, ], |inspector| { - let (status, gas, retdata) = inspector.call(data, call); - - // Allow inspectors to exit early - #[allow(clippy::needless_return)] - if status != InstructionResult::Continue { - return (status, gas, retdata) + let mut out = None; + if let Some(output) = inspector.call(ecx, call) { + if output.result.result != InstructionResult::Continue { + out = Some(Some(output)); + } } - } + out + }, + self, + ecx ); - (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) + if self.enable_isolation && + call.context.scheme == CallScheme::Call && + !self.in_inner_context && + ecx.journaled_state.depth == 1 + { + let (result, _) = self.transact_inner( + ecx, + TransactTo::Call(call.contract), + call.context.caller, + call.input.clone(), + call.gas_limit, + call.transfer.value, + ); + return Some(CallOutcome { result, memory_offset: call.return_memory_offset.clone() }) + } + + None } fn call_end( &mut self, - data: &mut EVMData<'_, DB>, - call: &CallInputs, - remaining_gas: Gas, - status: InstructionResult, - retdata: Bytes, - ) -> (InstructionResult, Gas, Bytes) { - let res = self.do_call_end(data, call, remaining_gas, status, retdata); - - if matches!(res.0, return_revert!()) { + ecx: &mut EvmContext<&mut DB>, + inputs: &CallInputs, + outcome: CallOutcome, + ) -> CallOutcome { + // Inner context calls with depth 0 are being dispatched as top-level calls with depth 1. + // Avoid processing twice. + if self.in_inner_context && ecx.journaled_state.depth == 0 { + return outcome + } + + let outcome = self.do_call_end(ecx, inputs, outcome); + if outcome.result.is_revert() { // Encountered a revert, since cheatcodes may have altered the evm state in such a way // that violates some constraints, e.g. `deal`, we need to manually roll back on revert // before revm reverts the state itself if let Some(cheats) = self.cheatcodes.as_mut() { - cheats.on_revert(data); + cheats.on_revert(ecx); } } - res + outcome } fn create( &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { - call_inspectors!( - [ - &mut self.debugger, - &mut self.tracer, - &mut self.coverage, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer - ], - |inspector| { - let (status, addr, gas, retdata) = inspector.create(data, call); + ecx: &mut EvmContext<&mut DB>, + create: &mut CreateInputs, + ) -> Option { + if self.in_inner_context && ecx.journaled_state.depth == 0 { + self.adjust_evm_data_for_inner_context(ecx); + return None; + } - // Allow inspectors to exit early - if status != InstructionResult::Continue { - return (status, addr, gas, retdata) - } - } + call_inspectors_adjust_depth!( + [&mut self.debugger, &mut self.tracer, &mut self.coverage, &mut self.cheatcodes], + |inspector| inspector.create(ecx, create).map(Some), + self, + ecx ); - (InstructionResult::Continue, None, Gas::new(call.gas_limit), Bytes::new()) + if self.enable_isolation && !self.in_inner_context && ecx.journaled_state.depth == 1 { + let (result, address) = self.transact_inner( + ecx, + TransactTo::Create(create.scheme), + create.caller, + create.init_code.clone(), + create.gas_limit, + create.value, + ); + return Some(CreateOutcome { result, address }) + } + + None } fn create_end( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext<&mut DB>, call: &CreateInputs, - status: InstructionResult, - address: Option
, - remaining_gas: Gas, - retdata: Bytes, - ) -> (InstructionResult, Option
, Gas, Bytes) { - call_inspectors!( - [ - &mut self.debugger, - &mut self.tracer, - &mut self.coverage, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer - ], + outcome: CreateOutcome, + ) -> CreateOutcome { + // Inner context calls with depth 0 are being dispatched as top-level calls with depth 1. + // Avoid processing twice. + if self.in_inner_context && ecx.journaled_state.depth == 0 { + return outcome + } + + let result = outcome.result.result; + + call_inspectors_adjust_depth!( + [&mut self.debugger, &mut self.tracer, &mut self.cheatcodes, &mut self.printer], |inspector| { - let (new_status, new_address, new_gas, new_retdata) = inspector.create_end( - data, - call, - status, - address, - remaining_gas, - retdata.clone(), - ); - - if new_status != status { - return (new_status, new_address, new_gas, new_retdata) - } - } + let new_outcome = inspector.create_end(ecx, call, outcome.clone()); + + // If the inspector returns a different status or a revert with a non-empty message, + // we assume it wants to tell us something + let different = new_outcome.result.result != result || + (new_outcome.result.result == InstructionResult::Revert && + new_outcome.output() != outcome.output()); + different.then_some(new_outcome) + }, + self, + ecx ); - (status, address, remaining_gas, retdata) + outcome } fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { - call_inspectors!( - [ - &mut self.debugger, - &mut self.tracer, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer, - &mut self.chisel_state - ], - |inspector| { - Inspector::::selfdestruct(inspector, contract, target, value); - } + call_inspectors!([&mut self.tracer, &mut self.printer], |inspector| { + Inspector::::selfdestruct(inspector, contract, target, value) + }); + } +} + +impl InspectorExt<&mut DB> for InspectorStack { + fn should_use_create2_factory( + &mut self, + ecx: &mut EvmContext<&mut DB>, + inputs: &mut CreateInputs, + ) -> bool { + call_inspectors_adjust_depth!( + [&mut self.cheatcodes], + |inspector| { inspector.should_use_create2_factory(ecx, inputs).then_some(true) }, + self, + ecx ); + + false } } diff --git a/crates/evm/evm/src/lib.rs b/crates/evm/evm/src/lib.rs index 7c69c1823831d..a699b6bf80447 100644 --- a/crates/evm/evm/src/lib.rs +++ b/crates/evm/evm/src/lib.rs @@ -10,11 +10,17 @@ extern crate tracing; pub mod executors; pub mod inspectors; -pub use foundry_evm_core::{backend, constants, debug, decode, fork, opts, utils}; +pub use foundry_evm_core::{backend, constants, debug, decode, fork, opts, utils, InspectorExt}; pub use foundry_evm_coverage as coverage; pub use foundry_evm_fuzz as fuzz; pub use foundry_evm_traces as traces; // TODO: We should probably remove these, but it's a pretty big breaking change. #[doc(hidden)] -pub use {hashbrown, revm}; +pub use revm; + +#[doc(hidden)] +#[deprecated = "use `{hash_map, hash_set, HashMap, HashSet}` in `std::collections` or `revm::primitives` instead"] +pub mod hashbrown { + pub use revm::primitives::{hash_map, hash_set, HashMap, HashSet}; +} diff --git a/crates/evm/fuzz/Cargo.toml b/crates/evm/fuzz/Cargo.toml index 49b10fc9d21a5..5fd6db65db49f 100644 --- a/crates/evm/fuzz/Cargo.toml +++ b/crates/evm/fuzz/Cargo.toml @@ -21,7 +21,7 @@ foundry-evm-traces.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-json-abi.workspace = true alloy-primitives = { workspace = true, features = ["serde", "getrandom", "arbitrary", "rlp"] } -revm = { workspace = true, default-features = false, features = [ +revm = { workspace = true, features = [ "std", "serde", "memory_limit", @@ -31,11 +31,7 @@ revm = { workspace = true, default-features = false, features = [ "arbitrary", ] } -ethers-core.workspace = true - -arbitrary = "1.3.1" eyre = "0.6" -hashbrown = { version = "0.14", features = ["serde"] } itertools.workspace = true parking_lot = "0.12" proptest = "1" @@ -43,3 +39,4 @@ rand.workspace = true serde = "1" thiserror = "1" tracing = "0.1" +indexmap.workspace = true diff --git a/crates/evm/fuzz/src/error.rs b/crates/evm/fuzz/src/error.rs index 145afa5e72743..1371f49692adf 100644 --- a/crates/evm/fuzz/src/error.rs +++ b/crates/evm/fuzz/src/error.rs @@ -8,8 +8,6 @@ pub enum FuzzError { UnknownContract, #[error("Failed contract call")] FailedContractCall, - #[error("Empty state changeset")] - EmptyChangeset, #[error("`vm.assume` reject")] AssumeReject, #[error("The `vm.assume` cheatcode rejected too many inputs ({0} allowed)")] diff --git a/crates/evm/fuzz/src/inspector.rs b/crates/evm/fuzz/src/inspector.rs index 6190c073dfc6d..5e7e644b11d60 100644 --- a/crates/evm/fuzz/src/inspector.rs +++ b/crates/evm/fuzz/src/inspector.rs @@ -1,10 +1,8 @@ use crate::{invariant::RandomCallGenerator, strategies::EvmFuzzState}; -use alloy_primitives::Bytes; -use foundry_common::types::ToEthers; -use foundry_evm_core::utils; +use alloy_primitives::U256; use revm::{ - interpreter::{CallInputs, CallScheme, Gas, InstructionResult, Interpreter}, - Database, EVMData, Inspector, + interpreter::{CallInputs, CallOutcome, CallScheme, Interpreter}, + Database, EvmContext, Inspector, }; /// An inspector that can fuzz and collect data for that effect. @@ -20,41 +18,35 @@ pub struct Fuzzer { impl Inspector for Fuzzer { #[inline] - fn step(&mut self, interpreter: &mut Interpreter<'_>, _: &mut EVMData<'_, DB>) { + fn step(&mut self, interp: &mut Interpreter, _context: &mut EvmContext) { // We only collect `stack` and `memory` data before and after calls. if self.collect { - self.collect_data(interpreter); + self.collect_data(interp); self.collect = false; } } #[inline] - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { + fn call(&mut self, ecx: &mut EvmContext, inputs: &mut CallInputs) -> Option { // We don't want to override the very first call made to the test contract. - if self.call_generator.is_some() && data.env.tx.caller != call.context.caller { - self.override_call(call); + if self.call_generator.is_some() && ecx.env.tx.caller != inputs.context.caller { + self.override_call(inputs); } // We only collect `stack` and `memory` data before and after calls. // this will be turned off on the next `step` self.collect = true; - (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) + None } #[inline] fn call_end( &mut self, - _: &mut EVMData<'_, DB>, - _: &CallInputs, - remaining_gas: Gas, - status: InstructionResult, - retdata: Bytes, - ) -> (InstructionResult, Gas, Bytes) { + _context: &mut EvmContext, + _inputs: &CallInputs, + outcome: CallOutcome, + ) -> CallOutcome { if let Some(ref mut call_generator) = self.call_generator { call_generator.used = false; } @@ -63,18 +55,14 @@ impl Inspector for Fuzzer { // this will be turned off on the next `step` self.collect = true; - (status, remaining_gas, retdata) + outcome } } impl Fuzzer { /// Collects `stack` and `memory` values into the fuzz dictionary. - fn collect_data(&mut self, interpreter: &Interpreter<'_>) { - let mut state = self.fuzz_state.write(); - - for slot in interpreter.stack().data() { - state.values_mut().insert(utils::u256_to_h256_be(slot.to_ethers()).into()); - } + fn collect_data(&mut self, interpreter: &Interpreter) { + self.fuzz_state.collect_values(interpreter.stack().data().iter().map(U256::to_be_bytes)); // TODO: disabled for now since it's flooding the dictionary // for index in 0..interpreter.shared_memory.len() / 32 { diff --git a/crates/evm/fuzz/src/invariant/call_override.rs b/crates/evm/fuzz/src/invariant/call_override.rs index c9a0b4ff10357..1d9edba5c3db1 100644 --- a/crates/evm/fuzz/src/invariant/call_override.rs +++ b/crates/evm/fuzz/src/invariant/call_override.rs @@ -10,7 +10,7 @@ use std::sync::Arc; /// Given a TestRunner and a strategy, it generates calls. Used inside the Fuzzer inspector to /// override external calls to test for potential reentrancy vulnerabilities.. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct RandomCallGenerator { /// Address of the test contract. pub test_address: Address, diff --git a/crates/evm/fuzz/src/invariant/filters.rs b/crates/evm/fuzz/src/invariant/filters.rs index 3561ef9d66728..c56450c27065d 100644 --- a/crates/evm/fuzz/src/invariant/filters.rs +++ b/crates/evm/fuzz/src/invariant/filters.rs @@ -1,4 +1,4 @@ -use alloy_json_abi::{Function, JsonAbi as Abi}; +use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Selector}; use foundry_compilers::ArtifactId; use foundry_evm_core::utils::get_function; @@ -23,7 +23,7 @@ impl ArtifactFilters { pub fn get_targeted_functions( &self, artifact: &ArtifactId, - abi: &Abi, + abi: &JsonAbi, ) -> eyre::Result>> { if let Some(selectors) = self.targeted.get(&artifact.identifier()) { let functions = selectors diff --git a/crates/evm/fuzz/src/invariant/mod.rs b/crates/evm/fuzz/src/invariant/mod.rs index 6a0a6ec7543c2..d682041e97065 100644 --- a/crates/evm/fuzz/src/invariant/mod.rs +++ b/crates/evm/fuzz/src/invariant/mod.rs @@ -1,4 +1,4 @@ -use alloy_json_abi::{Function, JsonAbi as Abi}; +use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Bytes}; use parking_lot::Mutex; use std::{collections::BTreeMap, sync::Arc}; @@ -9,19 +9,35 @@ pub use call_override::RandomCallGenerator; mod filters; pub use filters::{ArtifactFilters, SenderFilters}; -pub type TargetedContracts = BTreeMap)>; -pub type FuzzRunIdentifiedContracts = Arc>; +pub type TargetedContracts = BTreeMap)>; + +/// Contracts identified as targets during a fuzz run. +/// During execution, any newly created contract is added as target and used through the rest of +/// the fuzz run if the collection is updatable (no `targetContract` specified in `setUp`). +#[derive(Clone, Debug)] +pub struct FuzzRunIdentifiedContracts { + /// Contracts identified as targets during a fuzz run. + pub targets: Arc>, + /// Whether target contracts are updatable or not. + pub is_updatable: bool, +} + +impl FuzzRunIdentifiedContracts { + pub fn new(targets: TargetedContracts, is_updatable: bool) -> Self { + Self { targets: Arc::new(Mutex::new(targets)), is_updatable } + } +} /// (Sender, (TargetContract, Calldata)) pub type BasicTxDetails = (Address, (Address, Bytes)); /// Test contract which is testing its invariants. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct InvariantContract<'a> { /// Address of the test contract. pub address: Address, /// Invariant function present in the test contract. pub invariant_function: &'a Function, - /// Abi of the test contract. - pub abi: &'a Abi, + /// ABI of the test contract. + pub abi: &'a JsonAbi, } diff --git a/crates/evm/fuzz/src/lib.rs b/crates/evm/fuzz/src/lib.rs index 62181c2300c9b..b2a058e5bb022 100644 --- a/crates/evm/fuzz/src/lib.rs +++ b/crates/evm/fuzz/src/lib.rs @@ -8,14 +8,13 @@ extern crate tracing; use alloy_dyn_abi::{DynSolValue, JsonAbiExt}; -use alloy_primitives::{Address, Bytes, U256}; -use ethers_core::types::Log; +use alloy_primitives::{Address, Bytes, Log}; use foundry_common::{calc, contracts::ContractsByAddress}; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::CallTraceArena; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt}; +use std::{collections::HashMap, fmt, sync::Arc}; pub use proptest::test_runner::{Config as FuzzConfig, Reason}; @@ -49,6 +48,7 @@ pub struct BaseCounterExample { /// Contract name if it exists pub contract_name: Option, /// Traces + #[serde(skip)] pub traces: Option, #[serde(skip)] pub args: Vec, @@ -74,7 +74,7 @@ impl BaseCounterExample { contract_name: Some(name.clone()), traces, args, - } + }; } } } @@ -142,7 +142,7 @@ pub struct FuzzTestResult { pub decoded_logs: Vec, /// Labeled addresses - pub labeled_addresses: BTreeMap, + pub labeled_addresses: HashMap, /// Exemplary traces for a fuzz run of the test function /// @@ -150,6 +150,10 @@ pub struct FuzzTestResult { /// `num(fuzz_cases)` traces, one for each run, which is neither helpful nor performant. pub traces: Option, + /// Additional traces used for gas report construction. + /// Those traces should not be displayed. + pub gas_report_traces: Vec, + /// Raw coverage info pub coverage: Option, } @@ -157,18 +161,16 @@ pub struct FuzzTestResult { impl FuzzTestResult { /// Returns the median gas of all test cases pub fn median_gas(&self, with_stipend: bool) -> u64 { - let mut values = - self.gas_values(with_stipend).into_iter().map(U256::from).collect::>(); + let mut values = self.gas_values(with_stipend); values.sort_unstable(); - calc::median_sorted(&values).to::() + calc::median_sorted(&values) } /// Returns the average gas use of all test cases pub fn mean_gas(&self, with_stipend: bool) -> u64 { - let mut values = - self.gas_values(with_stipend).into_iter().map(U256::from).collect::>(); + let mut values = self.gas_values(with_stipend); values.sort_unstable(); - calc::mean(&values).to::() + calc::mean(&values) } fn gas_values(&self, with_stipend: bool) -> Vec { @@ -223,19 +225,17 @@ impl FuzzedCases { /// Returns the median gas of all test cases #[inline] pub fn median_gas(&self, with_stipend: bool) -> u64 { - let mut values = - self.gas_values(with_stipend).into_iter().map(U256::from).collect::>(); + let mut values = self.gas_values(with_stipend); values.sort_unstable(); - calc::median_sorted(&values).to::() + calc::median_sorted(&values) } /// Returns the average gas use of all test cases #[inline] pub fn mean_gas(&self, with_stipend: bool) -> u64 { - let mut values = - self.gas_values(with_stipend).into_iter().map(U256::from).collect::>(); + let mut values = self.gas_values(with_stipend); values.sort_unstable(); - calc::mean(&values).to::() + calc::mean(&values) } #[inline] @@ -272,3 +272,42 @@ impl FuzzedCases { self.lowest().map(|c| c.gas).unwrap_or_default() } } + +/// Fixtures to be used for fuzz tests. +/// The key represents name of the fuzzed parameter, value holds possible fuzzed values. +/// For example, for a fixture function declared as +/// `function fixture_sender() external returns (address[] memory senders)` +/// the fuzz fixtures will contain `sender` key with `senders` array as value +#[derive(Clone, Default, Debug)] +pub struct FuzzFixtures { + inner: Arc>, +} + +impl FuzzFixtures { + pub fn new(fixtures: HashMap) -> FuzzFixtures { + Self { inner: Arc::new(fixtures) } + } + + /// Returns configured fixtures for `param_name` fuzzed parameter. + pub fn param_fixtures(&self, param_name: &str) -> Option<&[DynSolValue]> { + if let Some(param_fixtures) = self.inner.get(&normalize_fixture(param_name)) { + match param_fixtures { + DynSolValue::FixedArray(_) => param_fixtures.as_fixed_array(), + _ => param_fixtures.as_array(), + } + } else { + None + } + } +} + +/// Extracts fixture name from a function name. +/// For example: fixtures defined in `fixture_Owner` function will be applied for `owner` parameter. +pub fn fixture_name(function_name: String) -> String { + normalize_fixture(function_name.strip_prefix("fixture").unwrap()) +} + +/// Normalize fixture parameter name, for example `_Owner` to `owner`. +fn normalize_fixture(param_name: &str) -> String { + param_name.trim_matches(&['_']).to_ascii_lowercase() +} diff --git a/crates/evm/fuzz/src/strategies/calldata.rs b/crates/evm/fuzz/src/strategies/calldata.rs index 7a1566243cdea..760d661754813 100644 --- a/crates/evm/fuzz/src/strategies/calldata.rs +++ b/crates/evm/fuzz/src/strategies/calldata.rs @@ -1,24 +1,86 @@ -use super::fuzz_param; +use crate::{ + strategies::{fuzz_param, fuzz_param_from_state, EvmFuzzState}, + FuzzFixtures, +}; use alloy_dyn_abi::JsonAbiExt; use alloy_json_abi::Function; use alloy_primitives::Bytes; use proptest::prelude::{BoxedStrategy, Strategy}; /// Given a function, it returns a strategy which generates valid calldata -/// for that function's input types. -pub fn fuzz_calldata(func: Function) -> BoxedStrategy { +/// for that function's input types, following declared test fixtures. +pub fn fuzz_calldata(func: Function, fuzz_fixtures: &FuzzFixtures) -> impl Strategy { // We need to compose all the strategies generated for each parameter in all - // possible combinations + // possible combinations, accounting any parameter declared fixture let strats = func .inputs .iter() - .map(|input| fuzz_param(&input.selector_type().parse().unwrap())) + .map(|input| { + fuzz_param( + &input.selector_type().parse().unwrap(), + fuzz_fixtures.param_fixtures(&input.name), + ) + }) .collect::>(); + strats.prop_map(move |values| { + func.abi_encode_input(&values) + .unwrap_or_else(|_| { + panic!( + "Fuzzer generated invalid arguments for function `{}` with inputs {:?}: {:?}", + func.name, func.inputs, values + ) + }) + .into() + }) +} +/// Given a function and some state, it returns a strategy which generated valid calldata for the +/// given function's input types, based on state taken from the EVM. +pub fn fuzz_calldata_from_state(func: Function, state: &EvmFuzzState) -> BoxedStrategy { + let strats = func + .inputs + .iter() + .map(|input| fuzz_param_from_state(&input.selector_type().parse().unwrap(), state)) + .collect::>(); strats - .prop_map(move |tokens| { - trace!(input=?tokens); - func.abi_encode_input(&tokens).unwrap().into() + .prop_map(move |values| { + func.abi_encode_input(&values) + .unwrap_or_else(|_| { + panic!( + "Fuzzer generated invalid arguments for function `{}` with inputs {:?}: {:?}", + func.name, func.inputs, values + ) + }) + .into() }) + .no_shrink() .boxed() } + +#[cfg(test)] +mod tests { + use crate::{strategies::fuzz_calldata, FuzzFixtures}; + use alloy_dyn_abi::{DynSolValue, JsonAbiExt}; + use alloy_json_abi::Function; + use alloy_primitives::Address; + use proptest::prelude::Strategy; + use std::collections::HashMap; + + #[test] + fn can_fuzz_with_fixtures() { + let function = Function::parse("test_fuzzed_address(address addressFixture)").unwrap(); + + let address_fixture = DynSolValue::Address(Address::random()); + let mut fixtures = HashMap::new(); + fixtures.insert( + "addressFixture".to_string(), + DynSolValue::Array(vec![address_fixture.clone()]), + ); + + let expected = function.abi_encode_input(&[address_fixture]).unwrap(); + let strategy = fuzz_calldata(function, &FuzzFixtures::new(fixtures)); + let _ = strategy.prop_map(move |fuzzed| { + assert_eq!(expected, fuzzed); + }); + } +} diff --git a/crates/evm/fuzz/src/strategies/int.rs b/crates/evm/fuzz/src/strategies/int.rs index 5c95235f2a3a7..b27f62f2854d9 100644 --- a/crates/evm/fuzz/src/strategies/int.rs +++ b/crates/evm/fuzz/src/strategies/int.rs @@ -1,3 +1,4 @@ +use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_primitives::{Sign, I256, U256}; use proptest::{ strategy::{NewTree, Strategy, ValueTree}, @@ -17,6 +18,7 @@ pub struct IntValueTree { /// If true cannot be simplified or complexified fixed: bool, } + impl IntValueTree { /// Create a new tree /// # Arguments @@ -37,6 +39,7 @@ impl IntValueTree { true } } + fn magnitude_greater(lhs: I256, rhs: I256) -> bool { if lhs.is_zero() { return false @@ -44,11 +47,14 @@ impl IntValueTree { (lhs > rhs) ^ (lhs.is_negative()) } } + impl ValueTree for IntValueTree { type Value = I256; + fn current(&self) -> Self::Value { self.curr } + fn simplify(&mut self) -> bool { if self.fixed || !IntValueTree::magnitude_greater(self.hi, self.lo) { return false @@ -56,28 +62,42 @@ impl ValueTree for IntValueTree { self.hi = self.curr; self.reposition() } + fn complicate(&mut self) -> bool { if self.fixed || !IntValueTree::magnitude_greater(self.hi, self.lo) { return false } - self.lo = self.curr + if self.hi.is_negative() { I256::MINUS_ONE } else { I256::ONE }; + self.lo = if self.curr != I256::MIN && self.curr != I256::MAX { + self.curr + if self.hi.is_negative() { I256::MINUS_ONE } else { I256::ONE } + } else { + self.curr + }; self.reposition() } } + /// Value tree for signed ints (up to int256). /// The strategy combines 3 different strategies, each assigned a specific weight: /// 1. Generate purely random value in a range. This will first choose bit size uniformly (up `bits` /// param). Then generate a value for this bit size. /// 2. Generate a random value around the edges (+/- 3 around min, 0 and max possible value) /// 3. Generate a value from a predefined fixtures set +/// +/// To define int fixtures: +/// - return an array of possible values for a parameter named `amount` declare a function +/// `function fixture_amount() public returns (int32[] memory)`. +/// - use `amount` named parameter in fuzzed test in order to include fixtures in fuzzed values +/// `function testFuzz_int32(int32 amount)`. +/// +/// If fixture is not a valid int type then error is raised and random value generated. #[derive(Debug)] pub struct IntStrategy { /// Bit size of int (e.g. 256) bits: usize, /// A set of fixtures to be generated - fixtures: Vec, + fixtures: Vec, /// The weight for edge cases (+/- 3 around 0 and max possible value) edge_weight: usize, /// The weight for fixtures @@ -85,20 +105,22 @@ pub struct IntStrategy { /// The weight for purely random values random_weight: usize, } + impl IntStrategy { /// Create a new strategy. /// #Arguments /// * `bits` - Size of uint in bits /// * `fixtures` - A set of fixed values to be generated (according to fixtures weight) - pub fn new(bits: usize, fixtures: Vec) -> Self { + pub fn new(bits: usize, fixtures: Option<&[DynSolValue]>) -> Self { Self { bits, - fixtures, + fixtures: Vec::from(fixtures.unwrap_or_default()), edge_weight: 10usize, fixtures_weight: 40usize, random_weight: 50usize, } } + fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); @@ -117,16 +139,29 @@ impl IntStrategy { }; Ok(IntValueTree::new(start, false)) } + fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { - // generate edge cases if there's no fixtures + // generate random cases if there's no fixtures if self.fixtures.is_empty() { - return self.generate_edge_tree(runner) + return self.generate_random_tree(runner) + } + + // Generate value tree from fixture. + let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())]; + if let Some(int_fixture) = fixture.as_int() { + if int_fixture.1 == self.bits { + return Ok(IntValueTree::new(int_fixture.0, false)); + } } - let idx = runner.rng().gen_range(0..self.fixtures.len()); - Ok(IntValueTree::new(self.fixtures[idx], false)) + + // If fixture is not a valid type, raise error and generate random value. + error!("{:?} is not a valid {} fixture", fixture, DynSolType::Int(self.bits)); + self.generate_random_tree(runner) } + fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); + // generate random number of bits uniformly let bits = rng.gen_range(0..=self.bits); @@ -137,6 +172,7 @@ impl IntStrategy { // init 2 128-bit randoms let mut higher: u128 = rng.gen_range(0..=u128::MAX); let mut lower: u128 = rng.gen_range(0..=u128::MAX); + // cut 2 randoms according to bits size match bits - 1 { x if x < 128 => { @@ -154,17 +190,20 @@ impl IntStrategy { inner[1] = (lower >> 64) as u64; inner[2] = (higher & mask64) as u64; inner[3] = (higher >> 64) as u64; - let sign = if rng.gen_bool(0.5) { Sign::Positive } else { Sign::Negative }; + // we have a small bias here, i.e. intN::min will never be generated // but it's ok since it's generated in `fn generate_edge_tree(...)` + let sign = if rng.gen_bool(0.5) { Sign::Positive } else { Sign::Negative }; let (start, _) = I256::overflowing_from_sign_and_abs(sign, U256::from_limbs(inner)); Ok(IntValueTree::new(start, false)) } } + impl Strategy for IntStrategy { type Tree = IntValueTree; type Value = I256; + fn new_tree(&self, runner: &mut TestRunner) -> NewTree { let total_weight = self.random_weight + self.fixtures_weight + self.edge_weight; let bias = runner.rng().gen_range(0..total_weight); @@ -176,3 +215,25 @@ impl Strategy for IntStrategy { } } } + +#[cfg(test)] +mod tests { + use crate::strategies::int::IntValueTree; + use alloy_primitives::I256; + use proptest::strategy::ValueTree; + + #[test] + fn test_int_tree_complicate_should_not_overflow() { + let mut int_tree = IntValueTree::new(I256::MAX, false); + assert_eq!(int_tree.hi, I256::MAX); + assert_eq!(int_tree.curr, I256::MAX); + int_tree.complicate(); + assert_eq!(int_tree.lo, I256::MAX); + + let mut int_tree = IntValueTree::new(I256::MIN, false); + assert_eq!(int_tree.hi, I256::MIN); + assert_eq!(int_tree.curr, I256::MIN); + int_tree.complicate(); + assert_eq!(int_tree.lo, I256::MIN); + } +} diff --git a/crates/evm/fuzz/src/strategies/invariants.rs b/crates/evm/fuzz/src/strategies/invariants.rs index 3534ffea17533..08e53b2a0bf5e 100644 --- a/crates/evm/fuzz/src/strategies/invariants.rs +++ b/crates/evm/fuzz/src/strategies/invariants.rs @@ -1,9 +1,10 @@ -use super::fuzz_param_from_state; +use super::{fuzz_calldata, fuzz_param_from_state}; use crate::{ invariant::{BasicTxDetails, FuzzRunIdentifiedContracts, SenderFilters}, - strategies::{fuzz_calldata, fuzz_calldata_from_state, fuzz_param, EvmFuzzState}, + strategies::{fuzz_calldata_from_state, fuzz_param, EvmFuzzState}, + FuzzFixtures, }; -use alloy_json_abi::{Function, JsonAbi as Abi}; +use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Bytes}; use parking_lot::RwLock; use proptest::prelude::*; @@ -14,23 +15,33 @@ pub fn override_call_strat( fuzz_state: EvmFuzzState, contracts: FuzzRunIdentifiedContracts, target: Arc>, + fuzz_fixtures: FuzzFixtures, ) -> SBoxedStrategy<(Address, Bytes)> { - let contracts_ref = contracts.clone(); - - let random_contract = any::() - .prop_map(move |selector| *selector.select(contracts_ref.lock().keys())); - let target = any::().prop_map(move |_| *target.read()); - - proptest::strategy::Union::new_weighted(vec![ - (80, target.sboxed()), - (20, random_contract.sboxed()), - ]) + let contracts_ref = contracts.targets.clone(); + proptest::prop_oneof![ + 80 => proptest::strategy::LazyJust::new(move || *target.read()), + 20 => any::() + .prop_map(move |selector| *selector.select(contracts_ref.lock().keys())), + ] .prop_flat_map(move |target_address| { let fuzz_state = fuzz_state.clone(); - let (_, abi, functions) = contracts.lock().get(&target_address).unwrap().clone(); - let func = select_random_function(abi, functions); + let fuzz_fixtures = fuzz_fixtures.clone(); + + let func = { + let contracts = contracts.targets.lock(); + let (_, abi, functions) = contracts.get(&target_address).unwrap_or_else(|| { + // Choose a random contract if target selected by lazy strategy is not in fuzz run + // identified contracts. This can happen when contract is created in `setUp` call + // but is not included in targetContracts. + let rand_index = rand::thread_rng().gen_range(0..contracts.len()); + let (_, contract_specs) = contracts.iter().nth(rand_index).unwrap(); + contract_specs + }); + select_random_function(abi, functions) + }; + func.prop_flat_map(move |func| { - fuzz_contract_with_calldata(fuzz_state.clone(), target_address, func) + fuzz_contract_with_calldata(&fuzz_state, &fuzz_fixtures, target_address, func) }) }) .sboxed() @@ -51,10 +62,11 @@ pub fn invariant_strat( senders: SenderFilters, contracts: FuzzRunIdentifiedContracts, dictionary_weight: u32, -) -> impl Strategy> { + fuzz_fixtures: FuzzFixtures, +) -> impl Strategy { // We only want to seed the first value, since we want to generate the rest as we mutate the // state - generate_call(fuzz_state, senders, contracts, dictionary_weight).prop_map(|x| vec![x]) + generate_call(fuzz_state, senders, contracts, dictionary_weight, fuzz_fixtures) } /// Strategy to generate a transaction where the `sender`, `target` and `calldata` are all generated @@ -64,18 +76,29 @@ fn generate_call( senders: SenderFilters, contracts: FuzzRunIdentifiedContracts, dictionary_weight: u32, + fuzz_fixtures: FuzzFixtures, ) -> BoxedStrategy { - let random_contract = select_random_contract(contracts); let senders = Rc::new(senders); - random_contract - .prop_flat_map(move |(contract, abi, functions)| { - let func = select_random_function(abi, functions); + any::() + .prop_flat_map(move |selector| { + let (contract, func) = { + let contracts = contracts.targets.lock(); + let contracts = + contracts.iter().filter(|(_, (_, abi, _))| !abi.functions.is_empty()); + let (&contract, (_, abi, functions)) = selector.select(contracts); + + let func = select_random_function(abi, functions); + (contract, func) + }; + let senders = senders.clone(); let fuzz_state = fuzz_state.clone(); + let fuzz_fixtures = fuzz_fixtures.clone(); func.prop_flat_map(move |func| { - let sender = - select_random_sender(fuzz_state.clone(), senders.clone(), dictionary_weight); - (sender, fuzz_contract_with_calldata(fuzz_state.clone(), contract, func)) + let sender = select_random_sender(&fuzz_state, senders.clone(), dictionary_weight); + let contract = + fuzz_contract_with_calldata(&fuzz_state, &fuzz_fixtures, contract, func); + (sender, contract) }) }) .boxed() @@ -85,75 +108,55 @@ fn generate_call( /// * If `senders` is empty, then it's either a random address (10%) or from the dictionary (90%). /// * If `senders` is not empty, a random address is chosen from the list of senders. fn select_random_sender( - fuzz_state: EvmFuzzState, + fuzz_state: &EvmFuzzState, senders: Rc, dictionary_weight: u32, ) -> BoxedStrategy
{ - let senders_ref = senders.clone(); - let fuzz_strategy = proptest::strategy::Union::new_weighted(vec![ - ( - 100 - dictionary_weight, - fuzz_param(&alloy_dyn_abi::DynSolType::Address) - .prop_map(move |addr| addr.as_address().unwrap()) - .boxed(), - ), - ( - dictionary_weight, - fuzz_param_from_state(&alloy_dyn_abi::DynSolType::Address, fuzz_state) - .prop_map(move |addr| addr.as_address().unwrap()) - .boxed(), - ), - ]) - // Too many exclusions can slow down testing. - .prop_filter("senders not allowed", move |addr| !senders_ref.excluded.contains(addr)) - .boxed(); if !senders.targeted.is_empty() { any::() - .prop_map(move |selector| *selector.select(&*senders.targeted)) + .prop_map(move |selector| *selector.select(&senders.targeted)) .boxed() } else { - fuzz_strategy + proptest::prop_oneof![ + 100 - dictionary_weight => fuzz_param(&alloy_dyn_abi::DynSolType::Address, None) + .prop_map(move |addr| addr.as_address().unwrap()) + .boxed(), + dictionary_weight => fuzz_param_from_state(&alloy_dyn_abi::DynSolType::Address, fuzz_state) + .prop_map(move |addr| addr.as_address().unwrap()) + .boxed(), + ] + // Too many exclusions can slow down testing. + .prop_filter("excluded sender", move |addr| !senders.excluded.contains(addr)) + .boxed() } } -/// Strategy to randomly select a contract from the `contracts` list that has at least 1 function -fn select_random_contract( - contracts: FuzzRunIdentifiedContracts, -) -> impl Strategy)> { - let selectors = any::(); - selectors.prop_map(move |selector| { - let contracts = contracts.lock(); - let (addr, (_, abi, functions)) = - selector.select(contracts.iter().filter(|(_, (_, abi, _))| !abi.functions.is_empty())); - (*addr, abi.clone(), functions.clone()) - }) -} - /// Strategy to select a random mutable function from the abi. /// /// If `targeted_functions` is not empty, select one from it. Otherwise, take any /// of the available abi functions. -fn select_random_function(abi: Abi, targeted_functions: Vec) -> BoxedStrategy { - let selectors = any::(); - let possible_funcs: Vec = abi - .functions() - .filter(|func| { - !matches!( - func.state_mutability, - alloy_json_abi::StateMutability::Pure | alloy_json_abi::StateMutability::View - ) - }) - .cloned() - .collect(); - let total_random = selectors.prop_map(move |selector| { - let func = selector.select(&possible_funcs); - func.clone() - }); +fn select_random_function( + abi: &JsonAbi, + targeted_functions: &[Function], +) -> BoxedStrategy { if !targeted_functions.is_empty() { + let targeted_functions = targeted_functions.to_vec(); let selector = any::() - .prop_map(move |selector| selector.select(targeted_functions.clone())); + .prop_map(move |selector| selector.select(&targeted_functions).clone()); selector.boxed() } else { + let possible_funcs: Vec = abi + .functions() + .filter(|&func| { + !matches!( + func.state_mutability, + alloy_json_abi::StateMutability::Pure | alloy_json_abi::StateMutability::View + ) + }) + .cloned() + .collect(); + let total_random = any::() + .prop_map(move |selector| selector.select(&possible_funcs).clone()); total_random.boxed() } } @@ -161,19 +164,20 @@ fn select_random_function(abi: Abi, targeted_functions: Vec) -> BoxedS /// Given a function, it returns a proptest strategy which generates valid abi-encoded calldata /// for that function's input types. pub fn fuzz_contract_with_calldata( - fuzz_state: EvmFuzzState, + fuzz_state: &EvmFuzzState, + fuzz_fixtures: &FuzzFixtures, contract: Address, func: Function, ) -> impl Strategy { - // We need to compose all the strategies generated for each parameter in all - // possible combinations - // `prop_oneof!` / `TupleUnion` `Arc`s for cheap cloning + // We need to compose all the strategies generated for each parameter in all possible + // combinations. + // `prop_oneof!` / `TupleUnion` `Arc`s for cheap cloning. #[allow(clippy::arc_with_non_send_sync)] - let strats = prop_oneof![ - 60 => fuzz_calldata(func.clone()), + prop_oneof![ + 60 => fuzz_calldata(func.clone(), fuzz_fixtures), 40 => fuzz_calldata_from_state(func, fuzz_state), - ]; - strats.prop_map(move |calldata| { + ] + .prop_map(move |calldata| { trace!(input=?calldata); (contract, calldata) }) diff --git a/crates/evm/fuzz/src/strategies/mod.rs b/crates/evm/fuzz/src/strategies/mod.rs index a795905de95ac..74cefca2ab371 100644 --- a/crates/evm/fuzz/src/strategies/mod.rs +++ b/crates/evm/fuzz/src/strategies/mod.rs @@ -8,13 +8,38 @@ mod param; pub use param::{fuzz_param, fuzz_param_from_state}; mod calldata; -pub use calldata::fuzz_calldata; +pub use calldata::{fuzz_calldata, fuzz_calldata_from_state}; mod state; -pub use state::{ - build_initial_state, collect_created_contracts, collect_state_from_call, - fuzz_calldata_from_state, EvmFuzzState, -}; +pub use state::{build_initial_state, collect_created_contracts, EvmFuzzState}; mod invariants; pub use invariants::{fuzz_contract_with_calldata, invariant_strat, override_call_strat}; + +/// Macro to create strategy with fixtures. +/// 1. A default strategy if no fixture defined for current parameter. +/// 2. A weighted strategy that use fixtures and default strategy values for current parameter. +/// If fixture is not of the same type as fuzzed parameter then value is rejected and error raised. +macro_rules! fixture_strategy { + ($fixtures:ident, $strategy_value:expr, $default_strategy:expr) => { + if let Some(fixtures) = $fixtures { + proptest::prop_oneof![ + 50 => { + let custom_fixtures: Vec = + fixtures.iter().enumerate().map(|(_, value)| value.to_owned()).collect(); + let custom_fixtures_len = custom_fixtures.len(); + any::() + .prop_filter_map("invalid fixture", move |index| { + let index = index.index(custom_fixtures_len); + $strategy_value(custom_fixtures.get(index)) + }) + }, + 50 => $default_strategy + ].boxed() + } else { + $default_strategy.boxed() + } + }; +} + +pub(crate) use fixture_strategy; diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs index 09fbfe590bc2f..12904cb005699 100644 --- a/crates/evm/fuzz/src/strategies/param.rs +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -1,63 +1,115 @@ use super::state::EvmFuzzState; +use crate::strategies::fixture_strategy; use alloy_dyn_abi::{DynSolType, DynSolValue}; -use alloy_primitives::{Address, FixedBytes, I256, U256}; -use arbitrary::Unstructured; +use alloy_primitives::{Address, B256, I256, U256}; use proptest::prelude::*; /// The max length of arrays we fuzz for is 256. const MAX_ARRAY_LEN: usize = 256; -/// Given a parameter type, returns a strategy for generating values for that type. +/// Given a parameter type and configured fixtures for param name, returns a strategy for generating +/// values for that type. Fixtures can be currently generated for uint, int, address, bytes and +/// string types and are defined for parameter name. +/// +/// For example, fixtures for parameter `owner` of type `address` can be defined in a function with +/// a `function fixture_owner() public returns (address[] memory)` signature. +/// +/// Fixtures are matched on parameter name, hence fixtures defined in +/// `fixture_owner` function can be used in a fuzzed test function with a signature like +/// `function testFuzz_ownerAddress(address owner, uint amount)`. +/// +/// Fuzzer will reject value and raise error if the fixture type is not of the same type as +/// parameter to fuzz. /// /// Works with ABI Encoder v2 tuples. -pub fn fuzz_param(param: &DynSolType) -> BoxedStrategy { - let param = param.to_owned(); - match param { - DynSolType::Address => any::<[u8; 32]>() - .prop_map(|x| DynSolValue::Address(Address::from_word(x.into()))) - .boxed(), - DynSolType::Int(n) => { - let strat = super::IntStrategy::new(n, vec![]); - let strat = strat.prop_map(move |x| DynSolValue::Int(x, n)); - strat.boxed() - } - DynSolType::Uint(n) => { - let strat = super::UintStrategy::new(n, vec![]); - let strat = strat.prop_map(move |x| DynSolValue::Uint(x, n)); - strat.boxed() +pub fn fuzz_param( + param: &DynSolType, + fuzz_fixtures: Option<&[DynSolValue]>, +) -> BoxedStrategy { + match *param { + DynSolType::Address => { + fixture_strategy!( + fuzz_fixtures, + |fixture: Option<&DynSolValue>| { + if let Some(val @ DynSolValue::Address(_)) = fixture { + Some(val.clone()) + } else { + error!("{:?} is not a valid address fixture", fixture.unwrap()); + None + } + }, + DynSolValue::type_strategy(&DynSolType::Address) + ) } - DynSolType::Function | DynSolType::Bool | DynSolType::Bytes => { - DynSolValue::type_strategy(¶m).boxed() + DynSolType::Int(n @ 8..=256) => super::IntStrategy::new(n, fuzz_fixtures) + .prop_map(move |x| DynSolValue::Int(x, n)) + .boxed(), + DynSolType::Uint(n @ 8..=256) => super::UintStrategy::new(n, fuzz_fixtures) + .prop_map(move |x| DynSolValue::Uint(x, n)) + .boxed(), + DynSolType::Function | DynSolType::Bool => DynSolValue::type_strategy(param).boxed(), + DynSolType::Bytes => { + fixture_strategy!( + fuzz_fixtures, + |fixture: Option<&DynSolValue>| { + if let Some(val @ DynSolValue::Bytes(_)) = fixture { + Some(val.clone()) + } else { + error!("{:?} is not a valid bytes fixture", fixture.unwrap()); + None + } + }, + DynSolValue::type_strategy(&DynSolType::Bytes) + ) } - DynSolType::FixedBytes(size) => prop::collection::vec(any::(), size) - .prop_map(move |mut v| { - v.reverse(); - while v.len() < 32 { - v.push(0); + DynSolType::FixedBytes(size @ 1..=32) => fixture_strategy!( + fuzz_fixtures, + |fixture: Option<&DynSolValue>| { + if let Some(val @ DynSolValue::FixedBytes(_, _)) = fixture { + if let Some(val) = val.as_fixed_bytes() { + if val.1 == size { + return Some(DynSolValue::FixedBytes(B256::from_slice(val.0), val.1)) + } + } } - DynSolValue::FixedBytes(FixedBytes::from_slice(&v), size) - }) - .boxed(), - DynSolType::String => DynSolValue::type_strategy(¶m) - .prop_map(move |value| { + error!("{:?} is not a valid fixed bytes fixture", fixture.unwrap()); + None + }, + DynSolValue::type_strategy(&DynSolType::FixedBytes(size)) + ), + DynSolType::String => fixture_strategy!( + fuzz_fixtures, + |fixture: Option<&DynSolValue>| { + if let Some(val @ DynSolValue::String(_)) = fixture { + Some(val.clone()) + } else { + error!("{:?} is not a valid string fixture", fixture.unwrap()); + None + } + }, + DynSolValue::type_strategy(&DynSolType::String).prop_map(move |value| { DynSolValue::String( - String::from_utf8_lossy(value.as_str().unwrap().as_bytes()) - .trim() - .trim_end_matches('\0') - .to_string(), + value.as_str().unwrap().trim().trim_end_matches('\0').to_string(), ) }) + ), + DynSolType::Tuple(ref params) => params + .iter() + .map(|p| fuzz_param(p, None)) + .collect::>() + .prop_map(DynSolValue::Tuple) .boxed(), - DynSolType::Tuple(params) => { - params.iter().map(fuzz_param).collect::>().prop_map(DynSolValue::Tuple).boxed() + DynSolType::FixedArray(ref param, size) => { + proptest::collection::vec(fuzz_param(param, None), size) + .prop_map(DynSolValue::FixedArray) + .boxed() } - DynSolType::FixedArray(param, size) => proptest::collection::vec(fuzz_param(¶m), size) - .prop_map(DynSolValue::FixedArray) - .boxed(), - DynSolType::Array(param) => proptest::collection::vec(fuzz_param(¶m), 0..MAX_ARRAY_LEN) - .prop_map(DynSolValue::Array) - .boxed(), - DynSolType::CustomStruct { .. } => panic!("unsupported type"), + DynSolType::Array(ref param) => { + proptest::collection::vec(fuzz_param(param, None), 0..MAX_ARRAY_LEN) + .prop_map(DynSolValue::Array) + .boxed() + } + _ => panic!("unsupported fuzz param type: {param}"), } } @@ -67,119 +119,116 @@ pub fn fuzz_param(param: &DynSolType) -> BoxedStrategy { /// Works with ABI Encoder v2 tuples. pub fn fuzz_param_from_state( param: &DynSolType, - arc_state: EvmFuzzState, + state: &EvmFuzzState, ) -> BoxedStrategy { - // These are to comply with lifetime requirements - let state_len = arc_state.read().values().len(); - - // Select a value from the state - let st = arc_state.clone(); - let value = any::() - .prop_map(move |index| index.index(state_len)) - .prop_map(move |index| *st.read().values().iter().nth(index).unwrap()); - let param = param.to_owned(); + // Value strategy that uses the state. + let value = || { + let state = state.clone(); + // Use `Index` instead of `Selector` to not iterate over the entire dictionary. + any::().prop_map(move |index| { + let state = state.dictionary_read(); + let values = state.values(); + let index = index.index(values.len()); + *values.iter().nth(index).unwrap() + }) + }; // Convert the value based on the parameter type - match param { - DynSolType::Address => value + match *param { + DynSolType::Address => value() .prop_map(move |value| DynSolValue::Address(Address::from_word(value.into()))) .boxed(), - DynSolType::FixedBytes(size) => value - .prop_map(move |v| { - let mut buf: [u8; 32] = [0; 32]; - - for b in v[..size].iter().enumerate() { - buf[b.0] = *b.1 - } - - let mut unstructured_v = Unstructured::new(v.as_slice()); - DynSolValue::arbitrary_from_type(¶m, &mut unstructured_v) - .unwrap_or(DynSolValue::FixedBytes(FixedBytes::from_slice(&buf), size)) + DynSolType::Function => value() + .prop_map(move |value| { + DynSolValue::Function(alloy_primitives::Function::from_word(value.into())) }) .boxed(), - DynSolType::Function | DynSolType::Bool => DynSolValue::type_strategy(¶m).boxed(), - DynSolType::String => DynSolValue::type_strategy(¶m) + DynSolType::FixedBytes(size @ 1..=32) => value() + .prop_map(move |mut v| { + v[size..].fill(0); + DynSolValue::FixedBytes(B256::from(v), size) + }) + .boxed(), + DynSolType::Bool => DynSolValue::type_strategy(param).boxed(), + DynSolType::String => DynSolValue::type_strategy(param) .prop_map(move |value| { DynSolValue::String( - String::from_utf8_lossy(value.as_str().unwrap().as_bytes()) - .trim() - .trim_end_matches('\0') - .to_string(), + value.as_str().unwrap().trim().trim_end_matches('\0').to_string(), ) }) .boxed(), - DynSolType::Int(n) => match n / 8 { - 32 => value + DynSolType::Bytes => { + value().prop_map(move |value| DynSolValue::Bytes(value.into())).boxed() + } + DynSolType::Int(n @ 8..=256) => match n / 8 { + 32 => value() .prop_map(move |value| { DynSolValue::Int(I256::from_raw(U256::from_be_bytes(value)), 256) }) .boxed(), - y @ 1..=31 => value + 1..=31 => value() .prop_map(move |value| { // Generate a uintN in the correct range, then shift it to the range of intN // by subtracting 2^(N-1) - let uint = - U256::from_be_bytes(value) % U256::from(2usize).pow(U256::from(y * 8)); - let max_int_plus1 = U256::from(2usize).pow(U256::from(y * 8 - 1)); - let num = I256::from_raw(uint.overflowing_sub(max_int_plus1).0); - DynSolValue::Int(num, y * 8) + let uint = U256::from_be_bytes(value) % U256::from(1).wrapping_shl(n); + let max_int_plus1 = U256::from(1).wrapping_shl(n - 1); + let num = I256::from_raw(uint.wrapping_sub(max_int_plus1)); + DynSolValue::Int(num, n) }) .boxed(), - _ => panic!("unsupported solidity type int{n}"), + _ => unreachable!(), }, - DynSolType::Uint(n) => match n / 8 { - 32 => value + DynSolType::Uint(n @ 8..=256) => match n / 8 { + 32 => value() .prop_map(move |value| DynSolValue::Uint(U256::from_be_bytes(value), 256)) .boxed(), - y @ 1..=31 => value + 1..=31 => value() .prop_map(move |value| { - DynSolValue::Uint( - U256::from_be_bytes(value) % U256::from(2).pow(U256::from(y * 8)), - y * 8, - ) + DynSolValue::Uint(U256::from_be_bytes(value) % U256::from(1).wrapping_shl(n), n) }) .boxed(), - _ => panic!("unsupported solidity type uint{n}"), + _ => unreachable!(), }, - DynSolType::Tuple(params) => params + DynSolType::Tuple(ref params) => params .iter() - .map(|p| fuzz_param_from_state(p, arc_state.clone())) + .map(|p| fuzz_param_from_state(p, state)) .collect::>() .prop_map(DynSolValue::Tuple) .boxed(), - DynSolType::Bytes => value.prop_map(move |value| DynSolValue::Bytes(value.into())).boxed(), - DynSolType::FixedArray(param, size) => { - let fixed_size = size; - proptest::collection::vec(fuzz_param_from_state(¶m, arc_state), fixed_size) + DynSolType::FixedArray(ref param, size) => { + proptest::collection::vec(fuzz_param_from_state(param, state), size) .prop_map(DynSolValue::FixedArray) .boxed() } - DynSolType::Array(param) => { - proptest::collection::vec(fuzz_param_from_state(¶m, arc_state), 0..MAX_ARRAY_LEN) + DynSolType::Array(ref param) => { + proptest::collection::vec(fuzz_param_from_state(param, state), 0..MAX_ARRAY_LEN) .prop_map(DynSolValue::Array) .boxed() } - DynSolType::CustomStruct { .. } => panic!("unsupported type"), + _ => panic!("unsupported fuzz param type: {param}"), } } #[cfg(test)] mod tests { - use crate::strategies::{build_initial_state, fuzz_calldata, fuzz_calldata_from_state}; - use alloy_json_abi::Function; + use crate::{ + strategies::{build_initial_state, fuzz_calldata, fuzz_calldata_from_state}, + FuzzFixtures, + }; + use foundry_common::abi::get_func; use foundry_config::FuzzDictionaryConfig; use revm::db::{CacheDB, EmptyDB}; #[test] fn can_fuzz_array() { let f = "testArray(uint64[2] calldata values)"; - let func = Function::parse(f).unwrap(); + let func = get_func(f).unwrap(); let db = CacheDB::new(EmptyDB::default()); - let state = build_initial_state(&db, &FuzzDictionaryConfig::default()); - let strat = proptest::strategy::Union::new_weighted(vec![ - (60, fuzz_calldata(func.clone())), - (40, fuzz_calldata_from_state(func, state)), - ]); + let state = build_initial_state(&db, FuzzDictionaryConfig::default()); + let strat = proptest::prop_oneof![ + 60 => fuzz_calldata(func.clone(), &FuzzFixtures::default()), + 40 => fuzz_calldata_from_state(func, &state), + ]; let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; let mut runner = proptest::test_runner::TestRunner::new(cfg); let _ = runner.run(&strat, |_| Ok(())); diff --git a/crates/evm/fuzz/src/strategies/state.rs b/crates/evm/fuzz/src/strategies/state.rs index 33a0ddf329284..2ee3f7fca1817 100644 --- a/crates/evm/fuzz/src/strategies/state.rs +++ b/crates/evm/fuzz/src/strategies/state.rs @@ -1,36 +1,117 @@ -use super::fuzz_param_from_state; use crate::invariant::{ArtifactFilters, FuzzRunIdentifiedContracts}; -use alloy_dyn_abi::{DynSolType, JsonAbiExt}; -use alloy_json_abi::Function; -use alloy_primitives::{Address, Bytes, B256, U256}; -use ethers_core::types::Log; -use foundry_common::{ - contracts::{ContractsByAddress, ContractsByArtifact}, - types::ToEthers, -}; +use alloy_primitives::{Address, Log, B256, U256}; +use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; use foundry_config::FuzzDictionaryConfig; use foundry_evm_core::utils::StateChangeset; -use hashbrown::HashSet; -use parking_lot::RwLock; -use proptest::prelude::{BoxedStrategy, Strategy}; +use indexmap::IndexSet; +use parking_lot::{lock_api::RwLockReadGuard, RawRwLock, RwLock}; use revm::{ db::{CacheDB, DatabaseRef}, interpreter::opcode::{self, spec_opcode_gas}, primitives::SpecId, }; -use std::{fmt, io::Write, str::FromStr, sync::Arc}; +use std::{fmt, sync::Arc}; /// A set of arbitrary 32 byte data from the VM used to generate values for the strategy. /// /// Wrapped in a shareable container. -pub type EvmFuzzState = Arc>; +#[derive(Clone, Debug)] +pub struct EvmFuzzState { + inner: Arc>, +} + +impl EvmFuzzState { + pub fn new(dictionary: FuzzDictionary) -> Self { + Self { inner: Arc::new(RwLock::new(dictionary)) } + } + + pub fn collect_values(&self, values: impl IntoIterator) { + let mut dict = self.inner.write(); + for value in values { + dict.insert_value(value); + } + } + + /// Collects state changes from a [StateChangeset] and logs into an [EvmFuzzState] according to + /// the given [FuzzDictionaryConfig]. + pub fn collect_state_from_call(&self, logs: &[Log], state_changeset: &StateChangeset) { + let mut dict = self.inner.write(); + + // Insert log topics and data. + for log in logs { + for topic in log.topics() { + dict.insert_value(topic.0); + } + let chunks = log.data.data.chunks_exact(32); + let rem = chunks.remainder(); + for chunk in chunks { + dict.insert_value(chunk.try_into().unwrap()); + } + if !rem.is_empty() { + dict.insert_value(B256::right_padding_from(rem).0); + } + } + + for (address, account) in state_changeset { + // Insert basic account information + dict.insert_value(address.into_word().into()); + + if dict.config.include_push_bytes { + // Insert push bytes + if let Some(code) = &account.info.code { + dict.insert_address(*address); + for push_byte in collect_push_bytes(code.bytes()) { + dict.insert_value(push_byte); + } + } + } -#[derive(Default)] + if dict.config.include_storage { + // Insert storage + for (slot, value) in &account.storage { + let value = value.present_value; + dict.insert_value(B256::from(*slot).0); + dict.insert_value(B256::from(value).0); + // also add the value below and above the storage value to the dictionary. + if value != U256::ZERO { + let below_value = value - U256::from(1); + dict.insert_value(B256::from(below_value).0); + } + if value != U256::MAX { + let above_value = value + U256::from(1); + dict.insert_value(B256::from(above_value).0); + } + } + } + } + } + + /// Removes all newly added entries from the dictionary. + /// + /// Should be called between fuzz/invariant runs to avoid accumumlating data derived from fuzz + /// inputs. + pub fn revert(&self) { + self.inner.write().revert(); + } + + pub fn dictionary_read(&self) -> RwLockReadGuard<'_, RawRwLock, FuzzDictionary> { + self.inner.read() + } +} + +// We're using `IndexSet` to have a stable element order when restoring persisted state, as well as +// for performance when iterating over the sets. pub struct FuzzDictionary { /// Collected state values. - state_values: HashSet<[u8; 32]>, + state_values: IndexSet<[u8; 32]>, /// Addresses that already had their PUSH bytes collected. - addresses: HashSet
, + addresses: IndexSet
, + /// Configuration for the dictionary. + config: FuzzDictionaryConfig, + /// New keys added to the dictionary since container initialization. + new_values: IndexSet<[u8; 32]>, + /// New addresses added to the dictionary since container initialization. + new_addreses: IndexSet
, } impl fmt::Debug for FuzzDictionary { @@ -43,75 +124,82 @@ impl fmt::Debug for FuzzDictionary { } impl FuzzDictionary { - #[inline] - pub fn values(&self) -> &HashSet<[u8; 32]> { - &self.state_values + pub fn new( + initial_values: IndexSet<[u8; 32]>, + initial_addresses: IndexSet
, + config: FuzzDictionaryConfig, + ) -> Self { + Self { + state_values: initial_values, + addresses: initial_addresses, + config, + new_values: IndexSet::new(), + new_addreses: IndexSet::new(), + } } - #[inline] - pub fn values_mut(&mut self) -> &mut HashSet<[u8; 32]> { - &mut self.state_values + pub fn insert_value(&mut self, value: [u8; 32]) { + if self.state_values.len() < self.config.max_fuzz_dictionary_values && + self.state_values.insert(value) + { + self.new_values.insert(value); + } + } + + pub fn insert_address(&mut self, address: Address) { + if self.addresses.len() < self.config.max_fuzz_dictionary_addresses && + self.addresses.insert(address) + { + self.new_addreses.insert(address); + } } #[inline] - pub fn addresses(&mut self) -> &HashSet
{ - &self.addresses + pub fn values(&self) -> &IndexSet<[u8; 32]> { + &self.state_values } #[inline] - pub fn addresses_mut(&mut self) -> &mut HashSet
{ - &mut self.addresses + pub fn addresses(&self) -> &IndexSet
{ + &self.addresses } -} -/// Given a function and some state, it returns a strategy which generated valid calldata for the -/// given function's input types, based on state taken from the EVM. -pub fn fuzz_calldata_from_state(func: Function, state: EvmFuzzState) -> BoxedStrategy { - let strats = func - .inputs - .iter() - .map(|input| { - fuzz_param_from_state( - &DynSolType::from_str(&input.selector_type()).unwrap(), - state.clone(), - ) - }) - .collect::>(); + pub fn revert(&mut self) { + for key in self.new_values.iter() { + self.state_values.swap_remove(key); + } + for address in self.new_addreses.iter() { + self.addresses.swap_remove(address); + } - strats - .prop_map(move |tokens| { - func.abi_encode_input(&tokens) - .unwrap_or_else(|_| { - panic!( - "Fuzzer generated invalid tokens for function `{}` with inputs {:?}: {:?}", - func.name, func.inputs, tokens - ) - }) - .into() - }) - .no_shrink() - .boxed() + self.new_values.clear(); + self.new_addreses.clear(); + } } /// Builds the initial [EvmFuzzState] from a database. pub fn build_initial_state( db: &CacheDB, - config: &FuzzDictionaryConfig, + config: FuzzDictionaryConfig, ) -> EvmFuzzState { - let mut state = FuzzDictionary::default(); + let mut values = IndexSet::new(); + let mut addresses = IndexSet::new(); + + // Sort accounts to ensure deterministic dictionary generation from the same setUp state. + let mut accs = db.accounts.iter().collect::>(); + accs.sort_by_key(|(address, _)| *address); - for (address, account) in db.accounts.iter() { + for (address, account) in accs { let address: Address = *address; // Insert basic account information - state.values_mut().insert(address.into_word().into()); + values.insert(address.into_word().into()); // Insert push bytes if config.include_push_bytes { if let Some(code) = &account.info.code { - if state.addresses_mut().insert(address) { - for push_byte in collect_push_bytes(code.bytes()) { - state.values_mut().insert(push_byte); - } + addresses.insert(address); + for push_byte in collect_push_bytes(code.bytes()) { + values.insert(push_byte); } } } @@ -119,16 +207,16 @@ pub fn build_initial_state( if config.include_storage { // Insert storage for (slot, value) in &account.storage { - state.values_mut().insert(B256::from(*slot).0); - state.values_mut().insert(B256::from(*value).0); + values.insert(B256::from(*slot).0); + values.insert(B256::from(*value).0); // also add the value below and above the storage value to the dictionary. if *value != U256::ZERO { let below_value = value - U256::from(1); - state.values_mut().insert(B256::from(below_value).0); + values.insert(B256::from(below_value).0); } if *value != U256::MAX { let above_value = value + U256::from(1); - state.values_mut().insert(B256::from(above_value).0); + values.insert(B256::from(above_value).0); } } } @@ -136,74 +224,12 @@ pub fn build_initial_state( // need at least some state data if db is empty otherwise we can't select random data for state // fuzzing - if state.values().is_empty() { + if values.is_empty() { // prefill with a random addresses - state.values_mut().insert(Address::random().into_word().into()); + values.insert(Address::random().into_word().into()); } - Arc::new(RwLock::new(state)) -} - -/// Collects state changes from a [StateChangeset] and logs into an [EvmFuzzState] according to the -/// given [FuzzDictionaryConfig]. -pub fn collect_state_from_call( - logs: &[Log], - state_changeset: &StateChangeset, - state: EvmFuzzState, - config: &FuzzDictionaryConfig, -) { - let mut state = state.write(); - - for (address, account) in state_changeset { - // Insert basic account information - state.values_mut().insert(address.into_word().into()); - - if config.include_push_bytes && state.addresses.len() < config.max_fuzz_dictionary_addresses - { - // Insert push bytes - if let Some(code) = &account.info.code { - if state.addresses_mut().insert(*address) { - for push_byte in collect_push_bytes(code.bytes()) { - state.values_mut().insert(push_byte); - } - } - } - } - - if config.include_storage && state.state_values.len() < config.max_fuzz_dictionary_values { - // Insert storage - for (slot, value) in &account.storage { - let value = value.present_value; - state.values_mut().insert(B256::from(*slot).0); - state.values_mut().insert(B256::from(value).0); - // also add the value below and above the storage value to the dictionary. - if value != U256::ZERO { - let below_value = value - U256::from(1); - state.values_mut().insert(B256::from(below_value).0); - } - if value != U256::MAX { - let above_value = value + U256::from(1); - state.values_mut().insert(B256::from(above_value).0); - } - } - } else { - return - } - - // Insert log topics and data - for log in logs { - log.topics.iter().for_each(|topic| { - state.values_mut().insert(topic.0); - }); - log.data.0.chunks(32).for_each(|chunk| { - let mut buffer: [u8; 32] = [0; 32]; - let _ = (&mut buffer[..]) - .write(chunk) - .expect("log data chunk was larger than 32 bytes"); - state.values_mut().insert(buffer); - }); - } - } + EvmFuzzState::new(FuzzDictionary::new(values, addresses, config)) } /// The maximum number of bytes we will look at in bytecodes to find push bytes (24 KiB). @@ -228,11 +254,11 @@ fn collect_push_bytes(code: &[u8]) -> Vec<[u8; 32]> { // As a precaution, if a fuzz test deploys malformed bytecode (such as using `CREATE2`) // this will terminate the loop early. if push_start > code.len() || push_end > code.len() { - return bytes + return bytes; } let push_value = U256::try_from_be_slice(&code[push_start..push_end]).unwrap(); - bytes.push(push_value.to_ethers().into()); + bytes.push(push_value.to_be_bytes()); // also add the value below and above the push value to the dictionary. if push_value != U256::ZERO { bytes.push((push_value - U256::from(1)).to_be_bytes()); @@ -255,22 +281,25 @@ pub fn collect_created_contracts( project_contracts: &ContractsByArtifact, setup_contracts: &ContractsByAddress, artifact_filters: &ArtifactFilters, - targeted_contracts: FuzzRunIdentifiedContracts, + targeted_contracts: &FuzzRunIdentifiedContracts, created_contracts: &mut Vec
, ) -> eyre::Result<()> { - let mut writable_targeted = targeted_contracts.lock(); + let mut writable_targeted = targeted_contracts.targets.lock(); for (address, account) in state_changeset { if !setup_contracts.contains_key(address) { if let (true, Some(code)) = (&account.is_touched(), &account.info.code) { if !code.is_empty() { - if let Some((artifact, (abi, _))) = project_contracts.find_by_code(code.bytes()) + if let Some((artifact, contract)) = + project_contracts.find_by_deployed_code(&code.original_bytes()) { if let Some(functions) = - artifact_filters.get_targeted_functions(artifact, abi)? + artifact_filters.get_targeted_functions(artifact, &contract.abi)? { created_contracts.push(*address); - writable_targeted - .insert(*address, (artifact.name.clone(), abi.clone(), functions)); + writable_targeted.insert( + *address, + (artifact.name.clone(), contract.abi.clone(), functions), + ); } } } diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index 5f53d838754aa..1b1eb2540499a 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -1,3 +1,4 @@ +use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_primitives::U256; use proptest::{ strategy::{NewTree, Strategy, ValueTree}, @@ -17,6 +18,7 @@ pub struct UintValueTree { /// If true cannot be simplified or complexified fixed: bool, } + impl UintValueTree { /// Create a new tree /// # Arguments @@ -38,11 +40,14 @@ impl UintValueTree { } } } + impl ValueTree for UintValueTree { type Value = U256; + fn current(&self) -> Self::Value { self.curr } + fn simplify(&mut self) -> bool { if self.fixed || (self.hi <= self.lo) { return false @@ -50,6 +55,7 @@ impl ValueTree for UintValueTree { self.hi = self.curr; self.reposition() } + fn complicate(&mut self) -> bool { if self.fixed || (self.hi <= self.lo) { return false @@ -59,18 +65,27 @@ impl ValueTree for UintValueTree { self.reposition() } } + /// Value tree for unsigned ints (up to uint256). /// The strategy combines 3 different strategies, each assigned a specific weight: /// 1. Generate purely random value in a range. This will first choose bit size uniformly (up `bits` /// param). Then generate a value for this bit size. /// 2. Generate a random value around the edges (+/- 3 around 0 and max possible value) /// 3. Generate a value from a predefined fixtures set +/// +/// To define uint fixtures: +/// - return an array of possible values for a parameter named `amount` declare a function +/// `function fixture_amount() public returns (uint32[] memory)`. +/// - use `amount` named parameter in fuzzed test in order to include fixtures in fuzzed values +/// `function testFuzz_uint32(uint32 amount)`. +/// +/// If fixture is not a valid uint type then error is raised and random value generated. #[derive(Debug)] pub struct UintStrategy { /// Bit size of uint (e.g. 256) bits: usize, /// A set of fixtures to be generated - fixtures: Vec, + fixtures: Vec, /// The weight for edge cases (+/- 3 around 0 and max possible value) edge_weight: usize, /// The weight for fixtures @@ -78,45 +93,60 @@ pub struct UintStrategy { /// The weight for purely random values random_weight: usize, } + impl UintStrategy { /// Create a new strategy. /// #Arguments /// * `bits` - Size of uint in bits /// * `fixtures` - A set of fixed values to be generated (according to fixtures weight) - pub fn new(bits: usize, fixtures: Vec) -> Self { + pub fn new(bits: usize, fixtures: Option<&[DynSolValue]>) -> Self { Self { bits, - fixtures, + fixtures: Vec::from(fixtures.unwrap_or_default()), edge_weight: 10usize, fixtures_weight: 40usize, random_weight: 50usize, } } + fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); // Choose if we want values around 0 or max let is_min = rng.gen_bool(0.5); let offset = U256::from(rng.gen_range(0..4)); - let max = - if self.bits < 256 { (U256::from(1) << self.bits) - U256::from(1) } else { U256::MAX }; - let start = if is_min { offset } else { max.saturating_sub(offset) }; + let start = if is_min { offset } else { self.type_max().saturating_sub(offset) }; Ok(UintValueTree::new(start, false)) } + fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { - // generate edge cases if there's no fixtures + // generate random cases if there's no fixtures if self.fixtures.is_empty() { - return self.generate_edge_tree(runner) + return self.generate_random_tree(runner) } - let idx = runner.rng().gen_range(0..self.fixtures.len()); - Ok(UintValueTree::new(self.fixtures[idx], false)) + + // Generate value tree from fixture. + let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())]; + if let Some(uint_fixture) = fixture.as_uint() { + if uint_fixture.1 == self.bits { + return Ok(UintValueTree::new(uint_fixture.0, false)); + } + } + + // If fixture is not a valid type, raise error and generate random value. + error!("{:?} is not a valid {} fixture", fixture, DynSolType::Uint(self.bits)); + self.generate_random_tree(runner) } + fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); + // generate random number of bits uniformly let bits = rng.gen_range(0..=self.bits); + // init 2 128-bit randoms let mut higher: u128 = rng.gen_range(0..=u128::MAX); let mut lower: u128 = rng.gen_range(0..=u128::MAX); + // cut 2 randoms according to bits size match bits { x if x < 128 => { @@ -126,6 +156,7 @@ impl UintStrategy { x if (128..256).contains(&x) => higher &= (1u128 << (x - 128)) - 1, _ => {} }; + // init U256 from 2 randoms let mut inner: [u64; 4] = [0; 4]; let mask64 = (1 << 65) - 1; @@ -137,7 +168,16 @@ impl UintStrategy { Ok(UintValueTree::new(start, false)) } + + fn type_max(&self) -> U256 { + if self.bits < 256 { + (U256::from(1) << self.bits) - U256::from(1) + } else { + U256::MAX + } + } } + impl Strategy for UintStrategy { type Tree = UintValueTree; type Value = U256; @@ -152,3 +192,19 @@ impl Strategy for UintStrategy { } } } + +#[cfg(test)] +mod tests { + use crate::strategies::uint::UintValueTree; + use alloy_primitives::U256; + use proptest::strategy::ValueTree; + + #[test] + fn test_uint_tree_complicate_max() { + let mut uint_tree = UintValueTree::new(U256::MAX, false); + assert_eq!(uint_tree.hi, U256::MAX); + assert_eq!(uint_tree.curr, U256::MAX); + uint_tree.complicate(); + assert_eq!(uint_tree.lo, U256::MIN); + } +} diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml index 7e11d23f8f543..a4bcb0a0b33fb 100644 --- a/crates/evm/traces/Cargo.toml +++ b/crates/evm/traces/Cargo.toml @@ -21,21 +21,17 @@ alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-json-abi.workspace = true alloy-primitives = { workspace = true, features = ["serde", "getrandom", "arbitrary", "rlp"] } alloy-sol-types.workspace = true -revm.workspace = true - -ethers-core.workspace = true +revm-inspectors.workspace = true eyre = "0.6" futures = "0.3" -hashbrown = "0.14" hex.workspace = true itertools.workspace = true once_cell = "1" -ordered-float = "4" serde = "1" tokio = { version = "1", features = ["time", "macros"] } tracing = "0.1" -yansi = "0.5" +yansi.workspace = true [dev-dependencies] -tempfile = "3" +tempfile.workspace = true diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index ece2f13b41bce..4d677653a253f 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -1,18 +1,22 @@ use crate::{ - identifier::{AddressIdentity, SingleSignaturesIdentifier, TraceIdentifier}, - CallTrace, CallTraceArena, TraceCallData, TraceLog, TraceRetData, + identifier::{ + AddressIdentity, LocalTraceIdentifier, SingleSignaturesIdentifier, TraceIdentifier, + }, + CallTrace, CallTraceArena, CallTraceNode, DecodedCallData, DecodedCallLog, DecodedCallTrace, }; use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt, FunctionExt, JsonAbiExt}; -use alloy_json_abi::{Event, Function, JsonAbi as Abi}; -use alloy_primitives::{Address, Selector, B256}; -use foundry_common::{abi::get_indexed_event, fmt::format_token, SELECTOR_LEN}; +use alloy_json_abi::{Error, Event, Function, JsonAbi}; +use alloy_primitives::{Address, LogData, Selector, B256}; +use foundry_common::{ + abi::get_indexed_event, fmt::format_token, ContractsByArtifact, SELECTOR_LEN, +}; use foundry_evm_core::{ - abi::{Console, HardhatConsole, Vm}, + abi::{Console, HardhatConsole, Vm, HARDHAT_CONSOLE_SELECTOR_PATCHES}, constants::{ CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS, }, - decode, + decode::RevertDecoder, }; use itertools::Itertools; use once_cell::sync::OnceCell; @@ -41,19 +45,29 @@ impl CallTraceDecoderBuilder { self } - /// Add known events to the decoder. + /// Add known errors to the decoder. #[inline] - pub fn with_events(mut self, events: impl IntoIterator) -> Self { - for event in events { - self.decoder - .events - .entry((event.selector(), indexed_inputs(&event))) - .or_default() - .push(event); + pub fn with_abi(mut self, abi: &JsonAbi) -> Self { + self.decoder.collect_abi(abi, None); + self + } + + /// Add known contracts to the decoder. + #[inline] + pub fn with_known_contracts(mut self, contracts: &ContractsByArtifact) -> Self { + trace!(target: "evm::traces", len=contracts.len(), "collecting known contract ABIs"); + for contract in contracts.values() { + self.decoder.collect_abi(&contract.abi, None); } self } + /// Add known contracts to the decoder from a `LocalTraceIdentifier`. + #[inline] + pub fn with_local_identifier_abis(self, identifier: &LocalTraceIdentifier<'_>) -> Self { + self.with_known_contracts(identifier.contracts()) + } + /// Sets the verbosity level of the decoder. #[inline] pub fn with_verbosity(mut self, level: u8) -> Self { @@ -82,7 +96,7 @@ impl CallTraceDecoderBuilder { /// /// Note that a call trace decoder is required for each new set of traces, since addresses in /// different sets might overlap. -#[derive(Clone, Default, Debug)] +#[derive(Clone, Debug, Default)] pub struct CallTraceDecoder { /// Addresses identified to be a specific contract. /// @@ -92,12 +106,14 @@ pub struct CallTraceDecoder { pub labels: HashMap, /// Contract addresses that have a receive function. pub receive_contracts: Vec
, + /// All known functions. pub functions: HashMap>, /// All known events. pub events: BTreeMap<(B256, usize), Vec>, - /// All known errors. - pub errors: Abi, + /// Revert decoder. Contains all known custom errors. + pub revert_decoder: RevertDecoder, + /// A signature identifier for events and functions. pub signature_identifier: Option, /// Verbosity level @@ -117,9 +133,25 @@ impl CallTraceDecoder { } fn init() -> Self { + /// All functions from the Hardhat console ABI. + /// + /// See [`HARDHAT_CONSOLE_SELECTOR_PATCHES`] for more details. + fn hh_funcs() -> impl Iterator { + let functions = HardhatConsole::abi::functions(); + let mut functions: Vec<_> = + functions.into_values().flatten().map(|func| (func.selector(), func)).collect(); + let len = functions.len(); + // `functions` is the list of all patched functions; duplicate the unpatched ones + for (unpatched, patched) in HARDHAT_CONSOLE_SELECTOR_PATCHES.iter() { + if let Some((_, func)) = functions[..len].iter().find(|(sel, _)| sel == patched) { + functions.push((unpatched.into(), func.clone())); + } + } + functions.into_iter() + } + Self { contracts: Default::default(), - labels: [ (CHEATCODE_ADDRESS, "VM".to_string()), (HARDHAT_CONSOLE_ADDRESS, "console".to_string()), @@ -128,125 +160,160 @@ impl CallTraceDecoder { (TEST_CONTRACT_ADDRESS, "DefaultTestContract".to_string()), ] .into(), + receive_contracts: Default::default(), - functions: HardhatConsole::abi::functions() - .into_values() - .chain(Vm::abi::functions().into_values()) - .flatten() - .map(|func| (func.selector(), vec![func])) + functions: hh_funcs() + .chain( + Vm::abi::functions() + .into_values() + .flatten() + .map(|func| (func.selector(), func)), + ) + .map(|(selector, func)| (selector, vec![func])) .collect(), - events: Console::abi::events() .into_values() .flatten() .map(|event| ((event.selector(), indexed_inputs(&event)), vec![event])) .collect(), + revert_decoder: Default::default(), - errors: Default::default(), signature_identifier: None, - receive_contracts: Default::default(), verbosity: 0, } } + /// Clears all known addresses. + pub fn clear_addresses(&mut self) { + self.contracts.clear(); + + let default_labels = &Self::new().labels; + if self.labels.len() > default_labels.len() { + self.labels.clone_from(default_labels); + } + + self.receive_contracts.clear(); + } + /// Identify unknown addresses in the specified call trace using the specified identifier. /// /// Unknown contracts are contracts that either lack a label or an ABI. - #[inline] pub fn identify(&mut self, trace: &CallTraceArena, identifier: &mut impl TraceIdentifier) { self.collect_identities(identifier.identify_addresses(self.addresses(trace))); } - #[inline(always)] + /// Adds a single event to the decoder. + pub fn push_event(&mut self, event: Event) { + self.events.entry((event.selector(), indexed_inputs(&event))).or_default().push(event); + } + + /// Adds a single function to the decoder. + pub fn push_function(&mut self, function: Function) { + match self.functions.entry(function.selector()) { + Entry::Occupied(entry) => { + // This shouldn't happen that often. + if entry.get().contains(&function) { + return; + } + trace!(target: "evm::traces", selector=%entry.key(), new=%function.signature(), "duplicate function selector"); + entry.into_mut().push(function); + } + Entry::Vacant(entry) => { + entry.insert(vec![function]); + } + } + } + + /// Adds a single error to the decoder. + pub fn push_error(&mut self, error: Error) { + self.revert_decoder.push_error(error); + } + fn addresses<'a>( &'a self, - trace: &'a CallTraceArena, - ) -> impl Iterator)> + 'a { - trace.addresses().into_iter().filter(|&(address, _)| { - !self.labels.contains_key(address) || !self.contracts.contains_key(address) - }) + arena: &'a CallTraceArena, + ) -> impl Iterator)> + Clone + 'a { + arena + .nodes() + .iter() + .map(|node| { + ( + &node.trace.address, + node.trace.kind.is_any_create().then_some(&node.trace.output[..]), + ) + }) + .filter(|(address, _)| { + !self.labels.contains_key(*address) || !self.contracts.contains_key(*address) + }) } fn collect_identities(&mut self, identities: Vec>) { - for identity in identities { - let address = identity.address; + // Skip logging if there are no identities. + if identities.is_empty() { + return; + } - if let Some(contract) = &identity.contract { - self.contracts.entry(address).or_insert_with(|| contract.to_string()); - } + trace!(target: "evm::traces", len=identities.len(), "collecting address identities"); + for AddressIdentity { address, label, contract, abi, artifact_id: _ } in identities { + let _span = trace_span!(target: "evm::traces", "identity", ?contract, ?label).entered(); - if let Some(label) = &identity.label { - self.labels.entry(address).or_insert_with(|| label.to_string()); + if let Some(contract) = contract { + self.contracts.entry(address).or_insert(contract); } - if let Some(abi) = &identity.abi { - // Store known functions for the address - for function in abi.functions() { - match self.functions.entry(function.selector()) { - Entry::Occupied(entry) => { - // This shouldn't happen that often - debug!(target: "evm::traces", selector=%entry.key(), old=?entry.get(), new=?function, "Duplicate function"); - entry.into_mut().push(function.clone()); - } - Entry::Vacant(entry) => { - entry.insert(vec![function.clone()]); - } - } - } - - // Flatten events from all ABIs - for event in abi.events() { - let sig = (event.selector(), indexed_inputs(event)); - self.events.entry(sig).or_default().push(event.clone()); - } - - // Flatten errors from all ABIs - for error in abi.errors() { - self.errors.errors.entry(error.name.clone()).or_default().push(error.clone()); - } + if let Some(label) = label { + self.labels.entry(address).or_insert(label); + } - if abi.receive.is_some() { - self.receive_contracts.push(address); - } + if let Some(abi) = abi { + self.collect_abi(&abi, Some(&address)); } } } - /// Decodes all nodes in the specified call trace. - pub async fn decode(&self, traces: &mut CallTraceArena) { - for node in &mut traces.arena { - self.decode_function(&mut node.trace).await; - for log in node.logs.iter_mut() { - self.decode_event(log).await; + fn collect_abi(&mut self, abi: &JsonAbi, address: Option<&Address>) { + trace!(target: "evm::traces", len=abi.len(), ?address, "collecting ABI"); + for function in abi.functions() { + self.push_function(function.clone()); + } + for event in abi.events() { + self.push_event(event.clone()); + } + for error in abi.errors() { + self.push_error(error.clone()); + } + if let Some(address) = address { + if abi.receive.is_some() { + self.receive_contracts.push(*address); } } } - async fn decode_function(&self, trace: &mut CallTrace) { + pub async fn decode_function(&self, trace: &CallTrace) -> DecodedCallTrace { // Decode precompile - if precompiles::decode(trace, 1) { - return + if let Some((label, func)) = precompiles::decode(trace, 1) { + return DecodedCallTrace { + label: Some(label), + return_data: None, + contract: None, + func: Some(func), + }; } // Set label - if trace.label.is_none() { - if let Some(label) = self.labels.get(&trace.address) { - trace.label = Some(label.clone()); - } - } + let label = self.labels.get(&trace.address).cloned(); // Set contract name - if trace.contract.is_none() { - if let Some(contract) = self.contracts.get(&trace.address) { - trace.contract = Some(contract.clone()); - } - } - - let TraceCallData::Raw(cdata) = &trace.data else { return }; + let contract = self.contracts.get(&trace.address).cloned(); + let cdata = &trace.data; if trace.address == DEFAULT_CREATE2_DEPLOYER { - trace.data = TraceCallData::Decoded { signature: "create2".to_string(), args: vec![] }; - return + return DecodedCallTrace { + label, + return_data: None, + contract, + func: Some(DecodedCallData { signature: "create2".to_string(), args: vec![] }), + }; } if cdata.len() >= SELECTOR_LEN { @@ -265,73 +332,94 @@ impl CallTraceDecoder { &functions } }; - let [func, ..] = &functions[..] else { return }; - self.decode_function_input(trace, func); - self.decode_function_output(trace, functions); + let [func, ..] = &functions[..] else { + return DecodedCallTrace { label, return_data: None, contract, func: None }; + }; + + DecodedCallTrace { + label, + func: Some(self.decode_function_input(trace, func)), + return_data: self.decode_function_output(trace, functions), + contract, + } } else { let has_receive = self.receive_contracts.contains(&trace.address); let signature = if cdata.is_empty() && has_receive { "receive()" } else { "fallback()" }.into(); let args = if cdata.is_empty() { Vec::new() } else { vec![cdata.to_string()] }; - trace.data = TraceCallData::Decoded { signature, args }; - - if let TraceRetData::Raw(rdata) = &trace.output { - if !trace.success { - trace.output = TraceRetData::Decoded(decode::decode_revert( - rdata, - Some(&self.errors), - Some(trace.status), - )); - } + DecodedCallTrace { + label, + return_data: if !trace.success { + Some(self.revert_decoder.decode(&trace.output, Some(trace.status))) + } else { + None + }, + contract, + func: Some(DecodedCallData { signature, args }), } } } /// Decodes a function's input into the given trace. - fn decode_function_input(&self, trace: &mut CallTrace, func: &Function) { - let TraceCallData::Raw(data) = &trace.data else { return }; + fn decode_function_input(&self, trace: &CallTrace, func: &Function) -> DecodedCallData { let mut args = None; - if data.len() >= SELECTOR_LEN { + if trace.data.len() >= SELECTOR_LEN { if trace.address == CHEATCODE_ADDRESS { // Try to decode cheatcode inputs in a more custom way - if let Some(v) = self.decode_cheatcode_inputs(func, data) { + if let Some(v) = self.decode_cheatcode_inputs(func, &trace.data) { args = Some(v); } } if args.is_none() { - if let Ok(v) = func.abi_decode_input(&data[SELECTOR_LEN..], false) { + if let Ok(v) = func.abi_decode_input(&trace.data[SELECTOR_LEN..], false) { args = Some(v.iter().map(|value| self.apply_label(value)).collect()); } } } - trace.data = - TraceCallData::Decoded { signature: func.signature(), args: args.unwrap_or_default() }; + + DecodedCallData { signature: func.signature(), args: args.unwrap_or_default() } } /// Custom decoding for cheatcode inputs. fn decode_cheatcode_inputs(&self, func: &Function, data: &[u8]) -> Option> { match func.name.as_str() { - "expectRevert" => Some(vec![decode::decode_revert(data, Some(&self.errors), None)]), - "rememberKey" | "addr" | "startBroadcast" | "broadcast" => { - // these functions accept a private key as uint256, which should not be - // converted to plain text - if !func.inputs.is_empty() && func.inputs[0].ty != "uint256" { - // redact private key input + "expectRevert" => Some(vec![self.revert_decoder.decode(data, None)]), + "addr" | "createWallet" | "deriveKey" | "rememberKey" => { + // Redact private key in all cases + Some(vec!["".to_string()]) + } + "broadcast" | "startBroadcast" => { + // Redact private key if defined + // broadcast(uint256) / startBroadcast(uint256) + if !func.inputs.is_empty() && func.inputs[0].ty == "uint256" { Some(vec!["".to_string()]) } else { None } } - "sign" => { - // sign(uint256,bytes32) + "getNonce" => { + // Redact private key if defined + // getNonce(Wallet) + if !func.inputs.is_empty() && func.inputs[0].ty == "tuple" { + Some(vec!["".to_string()]) + } else { + None + } + } + "sign" | "signP256" => { let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?; - if !decoded.is_empty() && func.inputs[0].ty != "uint256" { + + // Redact private key and replace in trace + // sign(uint256,bytes32) / signP256(uint256,bytes32) / sign(Wallet,bytes32) + if !decoded.is_empty() && + (func.inputs[0].ty == "uint256" || func.inputs[0].ty == "tuple") + { decoded[0] = DynSolValue::String("".to_string()); } + Some(decoded.iter().map(format_token).collect()) } - "deriveKey" => Some(vec!["".to_string()]), "parseJson" | "parseJsonUint" | "parseJsonUintArray" | @@ -348,9 +436,12 @@ impl CallTraceDecoder { "parseJsonBytes32" | "parseJsonBytes32Array" | "writeJson" | - "keyExists" | + // `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions. + "keyExists" | + "keyExistsJson" | "serializeBool" | "serializeUint" | + "serializeUintToHex" | "serializeInt" | "serializeAddress" | "serializeBytes32" | @@ -360,12 +451,31 @@ impl CallTraceDecoder { None } else { let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?; - let token = - if func.name.as_str() == "parseJson" || func.name.as_str() == "keyExists" { - "" - } else { - "" - }; + let token = if func.name.as_str() == "parseJson" || + // `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions. + func.name.as_str() == "keyExists" || + func.name.as_str() == "keyExistsJson" + { + "" + } else { + "" + }; + decoded[0] = DynSolValue::String(token.to_string()); + Some(decoded.iter().map(format_token).collect()) + } + } + s if s.contains("Toml") => { + if self.verbosity >= 5 { + None + } else { + let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?; + let token = if func.name.as_str() == "parseToml" || + func.name.as_str() == "keyExistsToml" + { + "" + } else { + "" + }; decoded[0] = DynSolValue::String(token.to_string()); Some(decoded.iter().map(format_token).collect()) } @@ -375,15 +485,14 @@ impl CallTraceDecoder { } /// Decodes a function's output into the given trace. - fn decode_function_output(&self, trace: &mut CallTrace, funcs: &[Function]) { - let TraceRetData::Raw(data) = &trace.output else { return }; + fn decode_function_output(&self, trace: &CallTrace, funcs: &[Function]) -> Option { + let data = &trace.output; if trace.success { if trace.address == CHEATCODE_ADDRESS { if let Some(decoded) = funcs.iter().find_map(|func| self.decode_cheatcode_outputs(func)) { - trace.output = TraceRetData::Decoded(decoded); - return + return Some(decoded); } } @@ -393,18 +502,17 @@ impl CallTraceDecoder { // Functions coming from an external database do not have any outputs specified, // and will lead to returning an empty list of values. if values.is_empty() { - return + return None; } - trace.output = TraceRetData::Decoded( + + return Some( values.iter().map(|value| self.apply_label(value)).format(", ").to_string(), ); } + + None } else { - trace.output = TraceRetData::Decoded(decode::decode_revert( - data, - Some(&self.errors), - Some(trace.status), - )); + Some(self.revert_decoder.decode(data, Some(trace.status))) } } @@ -412,7 +520,8 @@ impl CallTraceDecoder { fn decode_cheatcode_outputs(&self, func: &Function) -> Option { match func.name.as_str() { s if s.starts_with("env") => Some(""), - "deriveKey" => Some(""), + "createWallet" | "deriveKey" => Some(""), + "promptSecret" => Some(""), "parseJson" if self.verbosity < 5 => Some(""), "readFile" if self.verbosity < 5 => Some(""), _ => None, @@ -421,26 +530,25 @@ impl CallTraceDecoder { } /// Decodes an event. - async fn decode_event(&self, log: &mut TraceLog) { - let TraceLog::Raw(raw_log) = log else { return }; - let &[t0, ..] = raw_log.topics() else { return }; + pub async fn decode_event<'a>(&self, log: &'a LogData) -> DecodedCallLog<'a> { + let &[t0, ..] = log.topics() else { return DecodedCallLog::Raw(log) }; let mut events = Vec::new(); - let events = match self.events.get(&(t0, raw_log.topics().len() - 1)) { + let events = match self.events.get(&(t0, log.topics().len() - 1)) { Some(es) => es, None => { if let Some(identifier) = &self.signature_identifier { if let Some(event) = identifier.write().await.identify_event(&t0[..]).await { - events.push(get_indexed_event(event, raw_log)); + events.push(get_indexed_event(event, log)); } } &events } }; for event in events { - if let Ok(decoded) = event.decode_log(raw_log, false) { + if let Ok(decoded) = event.decode_log(log, false) { let params = reconstruct_params(event, &decoded); - *log = TraceLog::Decoded( + return DecodedCallLog::Decoded( event.name.clone(), params .into_iter() @@ -452,9 +560,33 @@ impl CallTraceDecoder { }) .collect(), ); - break } } + + DecodedCallLog::Raw(log) + } + + /// Prefetches function and event signatures into the identifier cache + pub async fn prefetch_signatures(&self, nodes: &[CallTraceNode]) { + let Some(identifier) = &self.signature_identifier else { return }; + + let events_it = nodes + .iter() + .flat_map(|node| node.logs.iter().filter_map(|log| log.topics().first())) + .unique(); + identifier.write().await.identify_events(events_it).await; + + const DEFAULT_CREATE2_DEPLOYER_BYTES: [u8; 20] = DEFAULT_CREATE2_DEPLOYER.0 .0; + let funcs_it = nodes + .iter() + .filter_map(|n| match n.trace.address.0 .0 { + DEFAULT_CREATE2_DEPLOYER_BYTES => None, + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01..=0x0a] => None, + _ => n.trace.data.get(..SELECTOR_LEN), + }) + .filter(|v| !self.functions.contains_key(*v)) + .unique(); + identifier.write().await.identify_functions(funcs_it).await; } fn apply_label(&self, value: &DynSolValue) -> String { @@ -489,3 +621,88 @@ fn reconstruct_params(event: &Event, decoded: &DecodedEvent) -> Vec fn indexed_inputs(event: &Event) -> usize { event.inputs.iter().filter(|param| param.indexed).count() } + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::hex; + + #[test] + fn test_should_redact_pk() { + let decoder = CallTraceDecoder::new(); + + // [function_signature, data, expected] + let cheatcode_input_test_cases = vec![ + // Should redact private key from traces in all cases: + ("addr(uint256)", vec![], Some(vec!["".to_string()])), + ("createWallet(string)", vec![], Some(vec!["".to_string()])), + ("createWallet(uint256)", vec![], Some(vec!["".to_string()])), + ("deriveKey(string,uint32)", vec![], Some(vec!["".to_string()])), + ("deriveKey(string,string,uint32)", vec![], Some(vec!["".to_string()])), + ("deriveKey(string,uint32,string)", vec![], Some(vec!["".to_string()])), + ("deriveKey(string,string,uint32,string)", vec![], Some(vec!["".to_string()])), + ("rememberKey(uint256)", vec![], Some(vec!["".to_string()])), + // + // Should redact private key from traces in specific cases with exceptions: + ("broadcast(uint256)", vec![], Some(vec!["".to_string()])), + ("broadcast()", vec![], None), // Ignore: `private key` is not passed. + ("startBroadcast(uint256)", vec![], Some(vec!["".to_string()])), + ("startBroadcast()", vec![], None), // Ignore: `private key` is not passed. + ("getNonce((address,uint256,uint256,uint256))", vec![], Some(vec!["".to_string()])), + ("getNonce(address)", vec![], None), // Ignore: `address` is public. + // + // Should redact private key and replace in trace in cases: + ( + "sign(uint256,bytes32)", + hex!( + " + e341eaa4 + 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 + 0000000000000000000000000000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec![ + "\"\"".to_string(), + "0x0000000000000000000000000000000000000000000000000000000000000000" + .to_string(), + ]), + ), + ( + "signP256(uint256,bytes32)", + hex!( + " + 83211b40 + 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 + 0000000000000000000000000000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec![ + "\"\"".to_string(), + "0x0000000000000000000000000000000000000000000000000000000000000000" + .to_string(), + ]), + ), + ]; + + // [function_signature, expected] + let cheatcode_output_test_cases = vec![ + // Should redact private key on output in all cases: + ("createWallet(string)", Some("".to_string())), + ("deriveKey(string,uint32)", Some("".to_string())), + ]; + + for (function_signature, data, expected) in cheatcode_input_test_cases { + let function = Function::parse(function_signature).unwrap(); + let result = decoder.decode_cheatcode_inputs(&function, &data); + assert_eq!(result, expected, "Input case failed for: {}", function_signature); + } + + for (function_signature, expected) in cheatcode_output_test_cases { + let function = Function::parse(function_signature).unwrap(); + let result = Some(decoder.decode_cheatcode_outputs(&function).unwrap_or_default()); + assert_eq!(result, expected, "Output case failed for: {}", function_signature); + } + } +} diff --git a/crates/evm/traces/src/decoder/precompiles.rs b/crates/evm/traces/src/decoder/precompiles.rs index a1b3f94250343..1475208f40c88 100644 --- a/crates/evm/traces/src/decoder/precompiles.rs +++ b/crates/evm/traces/src/decoder/precompiles.rs @@ -1,4 +1,4 @@ -use crate::{CallTrace, TraceCallData}; +use crate::{CallTrace, DecodedCallData}; use alloy_primitives::{B256, U256}; use alloy_sol_types::{abi, sol, SolCall}; use itertools::Itertools; @@ -36,20 +36,20 @@ macro_rules! tri { ($e:expr) => { match $e { Ok(x) => x, - Err(_) => return false, + Err(_) => return None, } }; } -/// Tries to decode a precompile call. Returns `true` if successful. -pub(super) fn decode(trace: &mut CallTrace, _chain_id: u64) -> bool { +/// Tries to decode a precompile call. Returns `Some` if successful. +pub(super) fn decode(trace: &CallTrace, _chain_id: u64) -> Option<(String, DecodedCallData)> { let [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, x @ 0x01..=0x0a] = trace.address.0 .0 else { - return false + return None }; - let TraceCallData::Raw(data) = &trace.data else { return false }; + let data = &trace.data; let (signature, args) = match x { 0x01 => { @@ -74,12 +74,7 @@ pub(super) fn decode(trace: &mut CallTrace, _chain_id: u64) -> bool { 0x00 | 0x0b.. => unreachable!(), }; - // TODO: Other chain precompiles - - trace.data = TraceCallData::Decoded { signature: signature.to_string(), args }; - trace.label = Some("PRECOMPILES".into()); - - true + Some(("PRECOMPILES".into(), DecodedCallData { signature: signature.to_string(), args })) } // Note: we use the ABI decoder, but this is not necessarily ABI-encoded data. It's just a diff --git a/crates/evm/traces/src/identifier/etherscan.rs b/crates/evm/traces/src/identifier/etherscan.rs index e6e566cc8a4d1..11996be3a5d65 100644 --- a/crates/evm/traces/src/identifier/etherscan.rs +++ b/crates/evm/traces/src/identifier/etherscan.rs @@ -25,10 +25,9 @@ use std::{ use tokio::time::{Duration, Interval}; /// A trace identifier that tries to identify addresses using Etherscan. -#[derive(Default)] pub struct EtherscanIdentifier { /// The Etherscan client - client: Option>, + client: Arc, /// Tracks whether the API key provides was marked as invalid /// /// After the first [EtherscanError::InvalidApiKey] this will get set to true, so we can @@ -40,18 +39,21 @@ pub struct EtherscanIdentifier { impl EtherscanIdentifier { /// Creates a new Etherscan identifier with the given client - pub fn new(config: &Config, chain: Option) -> eyre::Result { - if let Some(config) = config.get_etherscan_config_with_chain(chain)? { - trace!(target: "etherscanidentifier", chain=?config.chain, url=?config.api_url, "using etherscan identifier"); - Ok(Self { - client: Some(Arc::new(config.into_client()?)), - invalid_api_key: Arc::new(Default::default()), - contracts: BTreeMap::new(), - sources: BTreeMap::new(), - }) - } else { - Ok(Default::default()) + pub fn new(config: &Config, chain: Option) -> eyre::Result> { + // In offline mode, don't use Etherscan. + if config.offline { + return Ok(None); } + let Some(config) = config.get_etherscan_config_with_chain(chain)? else { + return Ok(None); + }; + trace!(target: "traces::etherscan", chain=?config.chain, url=?config.api_url, "using etherscan identifier"); + Ok(Some(Self { + client: Arc::new(config.into_client()?), + invalid_api_key: Arc::new(AtomicBool::new(false)), + contracts: BTreeMap::new(), + sources: BTreeMap::new(), + })) } /// Goes over the list of contracts we have pulled from the traces, clones their source from @@ -83,11 +85,7 @@ impl EtherscanIdentifier { for (results, (_, metadata)) in artifacts.into_iter().zip(contracts_iter) { // get the inner type let (artifact_id, file_id, bytecode) = results?; - sources - .0 - .entry(artifact_id.clone().name) - .or_default() - .insert(file_id, (metadata.source_code(), bytecode)); + sources.insert(&artifact_id, file_id, metadata.source_code(), bytecode); } Ok(sources) @@ -99,48 +97,58 @@ impl TraceIdentifier for EtherscanIdentifier { where A: Iterator)>, { - trace!(target: "etherscanidentifier", "identify {:?} addresses", addresses.size_hint().1); - - let Some(client) = self.client.clone() else { - // no client was configured - return Vec::new() - }; + trace!(target: "evm::traces", "identify {:?} addresses", addresses.size_hint().1); if self.invalid_api_key.load(Ordering::Relaxed) { // api key was marked as invalid return Vec::new() } + let mut identities = Vec::new(); let mut fetcher = EtherscanFetcher::new( - client, + self.client.clone(), Duration::from_secs(1), 5, Arc::clone(&self.invalid_api_key), ); for (addr, _) in addresses { - if !self.contracts.contains_key(addr) { - fetcher.push(*addr); - } - } - - let fut = fetcher - .map(|(address, metadata)| { + if let Some(metadata) = self.contracts.get(addr) { let label = metadata.contract_name.clone(); let abi = metadata.abi().ok().map(Cow::Owned); - self.contracts.insert(address, metadata); - AddressIdentity { - address, + identities.push(AddressIdentity { + address: *addr, label: Some(label.clone()), contract: Some(label), abi, artifact_id: None, - } - }) - .collect(); + }); + } else { + fetcher.push(*addr); + } + } - RuntimeOrHandle::new().block_on(fut) + let fetched_identities = RuntimeOrHandle::new().block_on( + fetcher + .map(|(address, metadata)| { + let label = metadata.contract_name.clone(); + let abi = metadata.abi().ok().map(Cow::Owned); + self.contracts.insert(address, metadata); + + AddressIdentity { + address, + label: Some(label.clone()), + contract: Some(label), + abi, + artifact_id: None, + } + }) + .collect::>>(), + ); + + identities.extend(fetched_identities); + identities } } @@ -191,16 +199,13 @@ impl EtherscanFetcher { fn queue_next_reqs(&mut self) { while self.in_progress.len() < self.concurrency { - if let Some(addr) = self.queue.pop() { - let client = Arc::clone(&self.client); - trace!(target: "etherscanidentifier", "fetching info for {:?}", addr); - self.in_progress.push(Box::pin(async move { - let res = client.contract_source_code(addr).await; - (addr, res) - })); - } else { - break - } + let Some(addr) = self.queue.pop() else { break }; + let client = Arc::clone(&self.client); + self.in_progress.push(Box::pin(async move { + trace!(target: "traces::etherscan", ?addr, "fetching info"); + let res = client.contract_source_code(addr).await; + (addr, res) + })); } } } @@ -234,24 +239,24 @@ impl Stream for EtherscanFetcher { } } Err(EtherscanError::RateLimitExceeded) => { - warn!(target: "etherscanidentifier", "rate limit exceeded on attempt"); + warn!(target: "traces::etherscan", "rate limit exceeded on attempt"); pin.backoff = Some(tokio::time::interval(pin.timeout)); pin.queue.push(addr); } Err(EtherscanError::InvalidApiKey) => { - warn!(target: "etherscanidentifier", "invalid api key"); + warn!(target: "traces::etherscan", "invalid api key"); // mark key as invalid pin.invalid_api_key.store(true, Ordering::Relaxed); return Poll::Ready(None) } Err(EtherscanError::BlockedByCloudflare) => { - warn!(target: "etherscanidentifier", "blocked by cloudflare"); + warn!(target: "traces::etherscan", "blocked by cloudflare"); // mark key as invalid pin.invalid_api_key.store(true, Ordering::Relaxed); return Poll::Ready(None) } Err(err) => { - warn!(target: "etherscanidentifier", "could not get etherscan info: {:?}", err); + warn!(target: "traces::etherscan", "could not get etherscan info: {:?}", err); } } } diff --git a/crates/evm/traces/src/identifier/local.rs b/crates/evm/traces/src/identifier/local.rs index f0bc851562ea3..129656b959212 100644 --- a/crates/evm/traces/src/identifier/local.rs +++ b/crates/evm/traces/src/identifier/local.rs @@ -1,23 +1,108 @@ use super::{AddressIdentity, TraceIdentifier}; -use alloy_json_abi::Event; +use alloy_json_abi::JsonAbi; use alloy_primitives::Address; -use foundry_common::contracts::{diff_score, ContractsByArtifact}; -use ordered_float::OrderedFloat; +use foundry_common::contracts::{bytecode_diff_score, ContractsByArtifact}; +use foundry_compilers::ArtifactId; use std::borrow::Cow; /// A trace identifier that tries to identify addresses using local contracts. pub struct LocalTraceIdentifier<'a> { + /// Known contracts to search through. known_contracts: &'a ContractsByArtifact, + /// Vector of pairs of artifact ID and the runtime code length of the given artifact. + ordered_ids: Vec<(&'a ArtifactId, usize)>, } impl<'a> LocalTraceIdentifier<'a> { + /// Creates a new local trace identifier. + #[inline] pub fn new(known_contracts: &'a ContractsByArtifact) -> Self { - Self { known_contracts } + let mut ordered_ids = known_contracts + .iter() + .filter_map(|(id, contract)| Some((id, contract.deployed_bytecode.as_ref()?))) + .map(|(id, bytecode)| (id, bytecode.len())) + .collect::>(); + ordered_ids.sort_by_key(|(_, len)| *len); + Self { known_contracts, ordered_ids } } - /// Get all the events of the local contracts. - pub fn events(&self) -> impl Iterator { - self.known_contracts.iter().flat_map(|(_, (abi, _))| abi.events()) + /// Returns the known contracts. + #[inline] + pub fn contracts(&self) -> &'a ContractsByArtifact { + self.known_contracts + } + + /// Tries to the bytecode most similar to the given one. + pub fn identify_code(&self, code: &[u8]) -> Option<(&'a ArtifactId, &'a JsonAbi)> { + let len = code.len(); + + let mut min_score = f64::MAX; + let mut min_score_id = None; + + let mut check = |id| { + let contract = self.known_contracts.get(id)?; + if let Some(deployed_bytecode) = &contract.deployed_bytecode { + let score = bytecode_diff_score(deployed_bytecode, code); + if score == 0.0 { + trace!(target: "evm::traces", "found exact match"); + return Some((id, &contract.abi)); + } + if score < min_score { + min_score = score; + min_score_id = Some((id, &contract.abi)); + } + } + None + }; + + // Check `[len * 0.9, ..., len * 1.1]`. + let max_len = (len * 11) / 10; + + // Start at artifacts with the same code length: `len..len*1.1`. + let same_length_idx = self.find_index(len); + for idx in same_length_idx..self.ordered_ids.len() { + let (id, len) = self.ordered_ids[idx]; + if len > max_len { + break; + } + if let found @ Some(_) = check(id) { + return found; + } + } + + // Iterate over the remaining artifacts with less code length: `len*0.9..len`. + let min_len = (len * 9) / 10; + let idx = self.find_index(min_len); + for i in idx..same_length_idx { + let (id, _) = self.ordered_ids[i]; + if let found @ Some(_) = check(id) { + return found; + } + } + + trace!(target: "evm::traces", %min_score, "no exact match found"); + + // Note: the diff score can be inaccurate for small contracts so we're using a relatively + // high threshold here to avoid filtering out too many contracts. + if min_score < 0.85 { + min_score_id + } else { + None + } + } + + /// Returns the index of the artifact with the given code length, or the index of the first + /// artifact with a greater code length if the exact code length is not found. + fn find_index(&self, len: usize) -> usize { + let (Ok(mut idx) | Err(mut idx)) = + self.ordered_ids.binary_search_by(|(_, probe)| probe.cmp(&len)); + + // In case of multiple artifacts with the same code length, we need to find the first one. + while idx > 0 && self.ordered_ids[idx - 1].1 == len { + idx -= 1; + } + + idx } } @@ -26,24 +111,15 @@ impl TraceIdentifier for LocalTraceIdentifier<'_> { where A: Iterator)>, { + trace!(target: "evm::traces", "identify {:?} addresses", addresses.size_hint().1); + addresses .filter_map(|(address, code)| { - let code = code?; - let (_, id, abi) = self - .known_contracts - .iter() - .filter_map(|(id, (abi, known_code))| { - let score = diff_score(known_code, code); - // Note: the diff score can be inaccurate for small contracts so we're using - // a relatively high threshold here to avoid filtering out too many - // contracts. - if score < 0.85 { - Some((OrderedFloat(score), id, abi)) - } else { - None - } - }) - .min_by_key(|(score, _, _)| *score)?; + let _span = trace_span!(target: "evm::traces", "identify", %address).entered(); + + trace!(target: "evm::traces", "identifying"); + let (id, abi) = self.identify_code(code?)?; + trace!(target: "evm::traces", id=%id.identifier(), "identified"); Some(AddressIdentity { address: *address, diff --git a/crates/evm/traces/src/identifier/mod.rs b/crates/evm/traces/src/identifier/mod.rs index 3743cde8d6b64..a16b108d8537f 100644 --- a/crates/evm/traces/src/identifier/mod.rs +++ b/crates/evm/traces/src/identifier/mod.rs @@ -1,6 +1,8 @@ -use alloy_json_abi::JsonAbi as Abi; +use alloy_json_abi::JsonAbi; use alloy_primitives::Address; +use foundry_common::ContractsByArtifact; use foundry_compilers::ArtifactId; +use foundry_config::{Chain, Config}; use std::borrow::Cow; mod local; @@ -23,7 +25,7 @@ pub struct AddressIdentity<'a> { /// Note: This may be in the format `":"`. pub contract: Option, /// The ABI of the contract at this address - pub abi: Option>, + pub abi: Option>, /// The artifact ID of the contract, if any. pub artifact_id: Option, } @@ -33,5 +35,59 @@ pub trait TraceIdentifier { /// Attempts to identify an address in one or more call traces. fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec> where - A: Iterator)>; + A: Iterator)> + Clone; +} + +/// A collection of trace identifiers. +pub struct TraceIdentifiers<'a> { + /// The local trace identifier. + pub local: Option>, + /// The optional Etherscan trace identifier. + pub etherscan: Option, +} + +impl Default for TraceIdentifiers<'_> { + fn default() -> Self { + Self::new() + } +} + +impl TraceIdentifier for TraceIdentifiers<'_> { + fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec> + where + A: Iterator)> + Clone, + { + let mut identities = Vec::new(); + if let Some(local) = &mut self.local { + identities.extend(local.identify_addresses(addresses.clone())); + } + if let Some(etherscan) = &mut self.etherscan { + identities.extend(etherscan.identify_addresses(addresses)); + } + identities + } +} + +impl<'a> TraceIdentifiers<'a> { + /// Creates a new, empty instance. + pub const fn new() -> Self { + Self { local: None, etherscan: None } + } + + /// Sets the local identifier. + pub fn with_local(mut self, known_contracts: &'a ContractsByArtifact) -> Self { + self.local = Some(LocalTraceIdentifier::new(known_contracts)); + self + } + + /// Sets the etherscan identifier. + pub fn with_etherscan(mut self, config: &Config, chain: Option) -> eyre::Result { + self.etherscan = EtherscanIdentifier::new(config, chain)?; + Ok(self) + } + + /// Returns `true` if there are no set identifiers. + pub fn is_empty(&self) -> bool { + self.local.is_none() && self.etherscan.is_none() + } } diff --git a/crates/evm/traces/src/identifier/signatures.rs b/crates/evm/traces/src/identifier/signatures.rs index 0364133b9760c..976e9ea6979cd 100644 --- a/crates/evm/traces/src/identifier/signatures.rs +++ b/crates/evm/traces/src/identifier/signatures.rs @@ -4,14 +4,17 @@ use foundry_common::{ fs, selectors::{SelectorType, SignEthClient}, }; -use hashbrown::HashSet; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, path::PathBuf, sync::Arc}; +use std::{ + collections::{BTreeMap, HashSet}, + path::PathBuf, + sync::Arc, +}; use tokio::sync::RwLock; pub type SingleSignaturesIdentifier = Arc>; -#[derive(Debug, Deserialize, Serialize, Default)] +#[derive(Debug, Default, Serialize, Deserialize)] struct CachedSignatures { events: BTreeMap, functions: BTreeMap, @@ -26,7 +29,7 @@ pub struct SignaturesIdentifier { /// Location where to save `CachedSignatures` cached_path: Option, /// Selectors that were unavailable during the session. - unavailable: HashSet>, + unavailable: HashSet, /// The API client to fetch signatures from sign_eth_api: SignEthClient, /// whether traces should be decoded via `sign_eth_api` @@ -34,7 +37,7 @@ pub struct SignaturesIdentifier { } impl SignaturesIdentifier { - #[instrument(target = "forge::signatures")] + #[instrument(target = "evm::traces")] pub fn new( cache_path: Option, offline: bool, @@ -43,14 +46,14 @@ impl SignaturesIdentifier { let identifier = if let Some(cache_path) = cache_path { let path = cache_path.join("signatures"); - trace!(?path, "reading signature cache"); + trace!(target: "evm::traces", ?path, "reading signature cache"); let cached = if path.is_file() { fs::read_json_file(&path) - .map_err(|err| warn!(?path, ?err, "failed to read cache file")) + .map_err(|err| warn!(target: "evm::traces", ?path, ?err, "failed to read cache file")) .unwrap_or_default() } else { if let Err(err) = std::fs::create_dir_all(cache_path) { - warn!("could not create signatures cache dir: {:?}", err); + warn!(target: "evm::traces", "could not create signatures cache dir: {:?}", err); } CachedSignatures::default() }; @@ -74,18 +77,18 @@ impl SignaturesIdentifier { Ok(Arc::new(RwLock::new(identifier))) } - #[instrument(target = "forge::signatures", skip(self))] + #[instrument(target = "evm::traces", skip(self))] pub fn save(&self) { if let Some(cached_path) = &self.cached_path { if let Some(parent) = cached_path.parent() { if let Err(err) = std::fs::create_dir_all(parent) { - warn!(?parent, ?err, "failed to create cache"); + warn!(target: "evm::traces", ?parent, ?err, "failed to create cache"); } } if let Err(err) = fs::write_json_file(cached_path, &self.cached) { - warn!(?cached_path, ?err, "failed to flush signature cache"); + warn!(target: "evm::traces", ?cached_path, ?err, "failed to flush signature cache"); } else { - trace!(?cached_path, "flushed signature cache") + trace!(target: "evm::traces", ?cached_path, "flushed signature cache") } } } @@ -95,59 +98,68 @@ impl SignaturesIdentifier { async fn identify( &mut self, selector_type: SelectorType, - identifier: &[u8], + identifiers: impl IntoIterator>, get_type: impl Fn(&str) -> eyre::Result, - ) -> Option { - // Exit early if we have unsuccessfully queried it before. - if self.unavailable.contains(identifier) { - return None - } - - let map = match selector_type { + ) -> Vec> { + let cache = match selector_type { SelectorType::Function => &mut self.cached.functions, SelectorType::Event => &mut self.cached.events, }; - let hex_identifier = hex::encode_prefixed(identifier); + let hex_identifiers: Vec = + identifiers.into_iter().map(hex::encode_prefixed).collect(); + + if !self.offline { + let query: Vec<_> = hex_identifiers + .iter() + .filter(|v| !cache.contains_key(v.as_str())) + .filter(|v| !self.unavailable.contains(v.as_str())) + .collect(); - if !self.offline && !map.contains_key(&hex_identifier) { - if let Ok(signatures) = - self.sign_eth_api.decode_selector(&hex_identifier, selector_type).await + if let Ok(res) = self.sign_eth_api.decode_selectors(selector_type, query.clone()).await { - if let Some(signature) = signatures.into_iter().next() { - map.insert(hex_identifier.clone(), signature); + for (hex_id, selector_result) in query.into_iter().zip(res.into_iter()) { + let mut found = false; + if let Some(decoded_results) = selector_result { + if let Some(decoded_result) = decoded_results.into_iter().next() { + cache.insert(hex_id.clone(), decoded_result); + found = true; + } + } + if !found { + self.unavailable.insert(hex_id.clone()); + } } } } - if let Some(signature) = map.get(&hex_identifier) { - return get_type(signature).ok() - } - - self.unavailable.insert(identifier.to_vec()); - - None + hex_identifiers.iter().map(|v| cache.get(v).and_then(|v| get_type(v).ok())).collect() } - /// Returns `None` if in offline mode - fn ensure_not_offline(&self) -> Option<()> { - if self.offline { - None - } else { - Some(()) - } + /// Identifies `Function`s from its cache or `https://api.openchain.xyz` + pub async fn identify_functions( + &mut self, + identifiers: impl IntoIterator>, + ) -> Vec> { + self.identify(SelectorType::Function, identifiers, get_func).await } /// Identifies `Function` from its cache or `https://api.openchain.xyz` pub async fn identify_function(&mut self, identifier: &[u8]) -> Option { - self.ensure_not_offline()?; - self.identify(SelectorType::Function, identifier, get_func).await + self.identify_functions(&[identifier]).await.pop().unwrap() + } + + /// Identifies `Event`s from its cache or `https://api.openchain.xyz` + pub async fn identify_events( + &mut self, + identifiers: impl IntoIterator>, + ) -> Vec> { + self.identify(SelectorType::Event, identifiers, get_event).await } /// Identifies `Event` from its cache or `https://api.openchain.xyz` pub async fn identify_event(&mut self, identifier: &[u8]) -> Option { - self.ensure_not_offline()?; - self.identify(SelectorType::Event, identifier, get_event).await + self.identify_events(&[identifier]).await.pop().unwrap() } } diff --git a/crates/evm/traces/src/inspector.rs b/crates/evm/traces/src/inspector.rs deleted file mode 100644 index 09a6cbfdd259e..0000000000000 --- a/crates/evm/traces/src/inspector.rs +++ /dev/null @@ -1,250 +0,0 @@ -use crate::{ - CallTrace, CallTraceArena, CallTraceStep, LogCallOrder, TraceCallData, TraceLog, TraceRetData, -}; -use alloy_primitives::{Address, Bytes, Log as RawLog, B256, U256}; -use foundry_evm_core::{ - debug::Instruction::OpCode, - utils::{gas_used, get_create_address, CallKind}, -}; -use revm::{ - interpreter::{ - opcode, return_ok, CallInputs, CallScheme, CreateInputs, Gas, InstructionResult, - Interpreter, - }, - Database, EVMData, Inspector, JournalEntry, -}; - -/// An inspector that collects call traces. -#[derive(Default, Debug, Clone)] -pub struct Tracer { - pub traces: CallTraceArena, - trace_stack: Vec, - step_stack: Vec<(usize, usize)>, // (trace_idx, step_idx) - record_steps: bool, -} - -impl Tracer { - /// Enables step recording. - pub fn record_steps(&mut self) { - self.record_steps = true; - } - - fn start_trace( - &mut self, - depth: usize, - address: Address, - data: Vec, - value: U256, - kind: CallKind, - caller: Address, - ) { - self.trace_stack.push(self.traces.push_trace( - 0, - CallTrace { - depth, - address, - kind, - data: TraceCallData::Raw(data.into()), - value, - status: InstructionResult::Continue, - caller, - ..Default::default() - }, - )); - } - - fn fill_trace( - &mut self, - status: InstructionResult, - cost: u64, - output: Vec, - address: Option
, - ) { - let success = matches!(status, return_ok!()); - let trace = &mut self.traces.arena - [self.trace_stack.pop().expect("more traces were filled than started")] - .trace; - trace.status = status; - trace.success = success; - trace.gas_cost = cost; - trace.output = TraceRetData::Raw(output.into()); - - if let Some(address) = address { - trace.address = address; - } - } - - fn start_step(&mut self, interp: &Interpreter<'_>, data: &EVMData<'_, DB>) { - let trace_idx = - *self.trace_stack.last().expect("can't start step without starting a trace first"); - let node = &mut self.traces.arena[trace_idx]; - - self.step_stack.push((trace_idx, node.trace.steps.len())); - - node.trace.steps.push(CallTraceStep { - depth: data.journaled_state.depth(), - pc: interp.program_counter(), - op: OpCode(interp.current_opcode()), - contract: interp.contract.address, - stack: interp.stack.clone(), - memory: interp.shared_memory.context_memory().to_vec(), - gas: interp.gas.remaining(), - gas_refund_counter: interp.gas.refunded() as u64, - gas_cost: 0, - state_diff: None, - error: None, - }); - } - - fn fill_step(&mut self, interp: &Interpreter<'_>, data: &EVMData<'_, DB>) { - let (trace_idx, step_idx) = - self.step_stack.pop().expect("can't fill step without starting a step first"); - let step = &mut self.traces.arena[trace_idx].trace.steps[step_idx]; - - let op = interp.current_opcode(); - let journal_entry = data - .journaled_state - .journal - .last() - // This should always work because revm initializes it as `vec![vec![]]` - .unwrap() - .last(); - step.state_diff = match (op, journal_entry) { - ( - opcode::SLOAD | opcode::SSTORE, - Some(JournalEntry::StorageChange { address, key, .. }), - ) => { - let value = data.journaled_state.state[address].storage[key].present_value(); - Some((*key, value)) - } - _ => None, - }; - - step.gas_cost = step.gas - interp.gas.remaining(); - - // Error codes only - if interp.instruction_result.is_error() { - step.error = Some(format!("{:?}", interp.instruction_result)); - } - } -} - -impl Inspector for Tracer { - #[inline] - fn step(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - if self.record_steps { - self.start_step(interp, data); - } - } - - #[inline] - fn step_end(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - if self.record_steps { - self.fill_step(interp, data); - } - } - - #[inline] - fn log(&mut self, _: &mut EVMData<'_, DB>, _: &Address, topics: &[B256], data: &Bytes) { - let node = &mut self.traces.arena[*self.trace_stack.last().expect("no ongoing trace")]; - node.ordering.push(LogCallOrder::Log(node.logs.len())); - let data = data.clone(); - node.logs - .push(TraceLog::Raw(RawLog::new(topics.to_vec(), data).expect("Received invalid log"))); - } - - #[inline] - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { - let (from, to) = match inputs.context.scheme { - CallScheme::DelegateCall | CallScheme::CallCode => { - (inputs.context.address, inputs.context.code_address) - } - _ => (inputs.context.caller, inputs.context.address), - }; - - self.start_trace( - data.journaled_state.depth() as usize, - to, - inputs.input.to_vec(), - inputs.transfer.value, - inputs.context.scheme.into(), - from, - ); - - (InstructionResult::Continue, Gas::new(inputs.gas_limit), Bytes::new()) - } - - #[inline] - fn call_end( - &mut self, - data: &mut EVMData<'_, DB>, - _inputs: &CallInputs, - gas: Gas, - status: InstructionResult, - retdata: Bytes, - ) -> (InstructionResult, Gas, Bytes) { - self.fill_trace( - status, - gas_used(data.env.cfg.spec_id, gas.spend(), gas.refunded() as u64), - retdata.to_vec(), - None, - ); - - (status, gas, retdata) - } - - #[inline] - fn create( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { - // TODO: Does this increase gas cost? - let _ = data.journaled_state.load_account(inputs.caller, data.db); - let nonce = data.journaled_state.account(inputs.caller).info.nonce; - self.start_trace( - data.journaled_state.depth() as usize, - get_create_address(inputs, nonce), - inputs.init_code.to_vec(), - inputs.value, - inputs.scheme.into(), - inputs.caller, - ); - - (InstructionResult::Continue, None, Gas::new(inputs.gas_limit), Bytes::new()) - } - - #[inline] - fn create_end( - &mut self, - data: &mut EVMData<'_, DB>, - _inputs: &CreateInputs, - status: InstructionResult, - address: Option
, - gas: Gas, - retdata: Bytes, - ) -> (InstructionResult, Option
, Gas, Bytes) { - let code = match address { - Some(address) => data - .journaled_state - .account(address) - .info - .code - .as_ref() - .map_or(vec![], |code| code.bytes()[..code.len()].to_vec()), - None => vec![], - }; - self.fill_trace( - status, - gas_used(data.env.cfg.spec_id, gas.spend(), gas.refunded() as u64), - code, - address, - ); - - (status, address, gas, retdata) - } -} diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index 4dd1e5ae6e771..31d92ecd60c01 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -7,21 +7,12 @@ #[macro_use] extern crate tracing; -use alloy_primitives::{Address, Bytes, Log as RawLog, B256, U256}; -use ethers_core::types::{DefaultFrame, GethDebugTracingOptions, StructLog}; -use foundry_common::{ - contracts::{ContractsByAddress, ContractsByArtifact}, - types::ToEthers, -}; -use foundry_evm_core::{constants::CHEATCODE_ADDRESS, debug::Instruction, utils::CallKind}; -use hashbrown::HashMap; -use itertools::Itertools; -use revm::interpreter::{opcode, CallContext, InstructionResult, Stack}; +use alloy_primitives::LogData; +use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; +use foundry_evm_core::constants::CHEATCODE_ADDRESS; +use futures::{future::BoxFuture, FutureExt}; use serde::{Deserialize, Serialize}; -use std::{ - collections::{BTreeMap, HashSet}, - fmt, -}; +use std::fmt::Write; use yansi::{Color, Paint}; /// Call trace address identifiers. @@ -33,160 +24,38 @@ use identifier::LocalTraceIdentifier; mod decoder; pub use decoder::{CallTraceDecoder, CallTraceDecoderBuilder}; -mod inspector; -pub use inspector::Tracer; - -mod node; -pub use node::CallTraceNode; +use revm_inspectors::tracing::types::LogCallOrder; +pub use revm_inspectors::tracing::{ + types::{CallKind, CallTrace, CallTraceNode}, + CallTraceArena, GethTraceBuilder, ParityTraceBuilder, StackSnapshotType, TracingInspector, + TracingInspectorConfig, +}; pub type Traces = Vec<(TraceKind, CallTraceArena)>; -/// An arena of [CallTraceNode]s -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct CallTraceArena { - /// The arena of nodes - pub arena: Vec, +#[derive(Default, Debug, Eq, PartialEq)] +pub struct DecodedCallData { + pub signature: String, + pub args: Vec, } -impl Default for CallTraceArena { - fn default() -> Self { - CallTraceArena { arena: vec![Default::default()] } - } +#[derive(Default, Debug)] +pub struct DecodedCallTrace { + pub label: Option, + pub return_data: Option, + pub func: Option, + pub contract: Option, } -impl CallTraceArena { - /// Pushes a new trace into the arena, returning the trace ID - pub fn push_trace(&mut self, entry: usize, new_trace: CallTrace) -> usize { - match new_trace.depth { - // The entry node, just update it - 0 => { - self.arena[0].trace = new_trace; - 0 - } - // We found the parent node, add the new trace as a child - _ if self.arena[entry].trace.depth == new_trace.depth - 1 => { - let id = self.arena.len(); - - let trace_location = self.arena[entry].children.len(); - self.arena[entry].ordering.push(LogCallOrder::Call(trace_location)); - let node = CallTraceNode { - parent: Some(entry), - trace: new_trace, - idx: id, - ..Default::default() - }; - self.arena.push(node); - self.arena[entry].children.push(id); - - id - } - // We haven't found the parent node, go deeper - _ => self.push_trace( - *self.arena[entry].children.last().expect("Disconnected trace"), - new_trace, - ), - } - } - - pub fn addresses(&self) -> HashSet<(&Address, Option<&[u8]>)> { - self.arena - .iter() - .map(|node| { - if node.trace.created() { - if let TraceRetData::Raw(bytes) = &node.trace.output { - return (&node.trace.address, Some(bytes.as_ref())) - } - } - - (&node.trace.address, None) - }) - .collect() - } - - // Recursively fill in the geth trace by going through the traces - fn add_to_geth_trace( - &self, - storage: &mut HashMap>, - trace_node: &CallTraceNode, - struct_logs: &mut Vec, - opts: &GethDebugTracingOptions, - ) { - let mut child_id = 0; - // Iterate over the steps inside the given trace - for step in trace_node.trace.steps.iter() { - let mut log: StructLog = step.into(); - - // Fill in memory and storage depending on the options - if !opts.disable_storage.unwrap_or_default() { - let contract_storage = storage.entry(step.contract).or_default(); - if let Some((key, value)) = step.state_diff { - contract_storage.insert(B256::from(key), B256::from(value)); - log.storage = Some( - contract_storage - .iter_mut() - .map(|t| (t.0.to_ethers(), t.1.to_ethers())) - .collect(), - ); - } - } - if opts.disable_stack.unwrap_or_default() { - log.stack = None; - } - if !opts.enable_memory.unwrap_or_default() { - log.memory = None; - } - - // Add step to geth trace - struct_logs.push(log); - - // Descend into a child trace if the step was a call - if let Instruction::OpCode( - opcode::CREATE | - opcode::CREATE2 | - opcode::DELEGATECALL | - opcode::CALL | - opcode::STATICCALL | - opcode::CALLCODE, - ) = step.op - { - self.add_to_geth_trace( - storage, - &self.arena[trace_node.children[child_id]], - struct_logs, - opts, - ); - child_id += 1; - } - } - } - - /// Generate a geth-style trace e.g. for debug_traceTransaction - pub fn geth_trace( - &self, - receipt_gas_used: U256, - opts: GethDebugTracingOptions, - ) -> DefaultFrame { - if self.arena.is_empty() { - return Default::default() - } - - let mut storage = HashMap::new(); - // Fetch top-level trace - let main_trace_node = &self.arena[0]; - let main_trace = &main_trace_node.trace; - // Start geth trace - let mut acc = DefaultFrame { - // If the top-level trace succeeded, then it was a success - failed: !main_trace.success, - gas: receipt_gas_used.to_ethers(), - return_value: main_trace.output.to_bytes().to_ethers(), - ..Default::default() - }; - - self.add_to_geth_trace(&mut storage, main_trace_node, &mut acc.struct_logs, &opts); - - acc - } +#[derive(Debug)] +pub enum DecodedCallLog<'a> { + /// A raw log. + Raw(&'a LogData), + /// A decoded log. + /// + /// The first member of the tuple is the event name, and the second is a vector of decoded + /// parameters. + Decoded(String, Vec<(String, String)>), } const PIPE: &str = " │ "; @@ -195,19 +64,29 @@ const BRANCH: &str = " ├─ "; const CALL: &str = "→ "; const RETURN: &str = "← "; -impl fmt::Display for CallTraceArena { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fn inner( - arena: &CallTraceArena, - f: &mut fmt::Formatter<'_>, - idx: usize, - left: &str, - child: &str, - ) -> fmt::Result { - let node = &arena.arena[idx]; +/// Render a collection of call traces. +/// +/// The traces will be decoded using the given decoder, if possible. +pub async fn render_trace_arena( + arena: &CallTraceArena, + decoder: &CallTraceDecoder, +) -> Result { + decoder.prefetch_signatures(arena.nodes()).await; + + fn inner<'a>( + arena: &'a [CallTraceNode], + decoder: &'a CallTraceDecoder, + s: &'a mut String, + idx: usize, + left: &'a str, + child: &'a str, + ) -> BoxFuture<'a, Result<(), std::fmt::Error>> { + async move { + let node = &arena[idx]; // Display trace header - writeln!(f, "{left}{}", node.trace)?; + let (trace, return_data) = render_trace(&node.trace, decoder).await?; + writeln!(s, "{left}{}", trace)?; // Display logs and subcalls let left_prefix = format!("{child}{BRANCH}"); @@ -215,11 +94,12 @@ impl fmt::Display for CallTraceArena { for child in &node.ordering { match child { LogCallOrder::Log(index) => { - let log = node.logs[*index].to_string(); + let log = render_trace_log(&node.logs[*index], decoder).await?; + // Prepend our tree structure symbols to each line of the displayed log log.lines().enumerate().try_for_each(|(i, line)| { writeln!( - f, + s, "{}{}", if i == 0 { &left_prefix } else { &right_prefix }, line @@ -227,333 +107,145 @@ impl fmt::Display for CallTraceArena { })?; } LogCallOrder::Call(index) => { - inner(arena, f, node.children[*index], &left_prefix, &right_prefix)?; + inner( + arena, + decoder, + s, + node.children[*index], + &left_prefix, + &right_prefix, + ) + .await?; } } } // Display trace return data let color = trace_color(&node.trace); - write!(f, "{child}{EDGE}{}", color.paint(RETURN))?; - if node.trace.created() { - match &node.trace.output { - TraceRetData::Raw(bytes) => { - writeln!(f, "{} bytes of code", bytes.len())?; - } - TraceRetData::Decoded(val) => { - writeln!(f, "{val}")?; - } + write!( + s, + "{child}{EDGE}{}{}", + RETURN.fg(color), + format!("[{:?}] ", node.trace.status).fg(color) + )?; + match return_data { + Some(val) => write!(s, "{val}"), + None if node.trace.kind.is_any_create() => { + write!(s, "{} bytes of code", node.trace.output.len()) } - } else { - writeln!(f, "{}", node.trace.output)?; - } + None if node.trace.output.is_empty() => Ok(()), + None => write!(s, "{}", node.trace.output), + }?; + writeln!(s)?; Ok(()) } - - inner(self, f, 0, " ", " ") + .boxed() } -} - -/// A raw or decoded log. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum TraceLog { - /// A raw log - Raw(RawLog), - /// A decoded log. - /// - /// The first member of the tuple is the event name, and the second is a vector of decoded - /// parameters. - Decoded(String, Vec<(String, String)>), -} -impl fmt::Display for TraceLog { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - TraceLog::Raw(log) => { - for (i, topic) in log.topics().iter().enumerate() { - writeln!( - f, - "{:>13}: {}", - if i == 0 { "emit topic 0".to_string() } else { format!("topic {i}") }, - Paint::cyan(format!("{topic:?}")) - )?; - } - - write!(f, " data: {}", Paint::cyan(hex::encode_prefixed(&log.data))) - } - TraceLog::Decoded(name, params) => { - let params = params - .iter() - .map(|(name, value)| format!("{name}: {value}")) - .collect::>() - .join(", "); - - write!(f, "emit {}({params})", Paint::cyan(name.clone())) - } - } - } + let mut s = String::new(); + inner(arena.nodes(), decoder, &mut s, 0, " ", " ").await?; + Ok(s) } -/// Ordering enum for calls and logs +/// Render a call trace. /// -/// i.e. if Call 0 occurs before Log 0, it will be pushed into the `CallTraceNode`'s ordering before -/// the log. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum LogCallOrder { - Log(usize), - Call(usize), -} - -/// Raw or decoded calldata. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] -pub enum TraceCallData { - /// Raw calldata bytes. - Raw(Bytes), - /// Decoded calldata. - Decoded { - /// The function signature. - signature: String, - /// The function arguments. - args: Vec, - }, -} - -impl Default for TraceCallData { - fn default() -> Self { - Self::Raw(Bytes::new()) - } -} - -impl TraceCallData { - pub fn as_bytes(&self) -> &[u8] { - match self { - TraceCallData::Raw(raw) => raw, - TraceCallData::Decoded { .. } => &[], - } - } -} - -/// Raw or decoded return data. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] -pub enum TraceRetData { - /// Raw return data. - Raw(Bytes), - /// Decoded return data. - Decoded(String), -} - -impl Default for TraceRetData { - fn default() -> Self { - Self::Raw(Bytes::new()) - } -} - -impl TraceRetData { - /// Returns the data as [`Bytes`] - pub fn to_bytes(&self) -> Bytes { - match self { - TraceRetData::Raw(raw) => raw.clone(), - TraceRetData::Decoded(val) => val.as_bytes().to_vec().into(), - } - } - - pub fn to_raw(&self) -> Vec { - self.to_bytes().to_vec() - } -} - -impl fmt::Display for TraceRetData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self { - TraceRetData::Raw(bytes) => { - if bytes.is_empty() { - write!(f, "()") +/// The trace will be decoded using the given decoder, if possible. +pub async fn render_trace( + trace: &CallTrace, + decoder: &CallTraceDecoder, +) -> Result<(String, Option), std::fmt::Error> { + let mut s = String::new(); + write!(&mut s, "[{}] ", trace.gas_used)?; + let address = trace.address.to_checksum(None); + + let decoded = decoder.decode_function(trace).await; + if trace.kind.is_any_create() { + write!( + &mut s, + "{}{} {}@{}", + CALL.yellow(), + "new".yellow(), + decoded.label.as_deref().unwrap_or(""), + address + )?; + } else { + let (func_name, inputs) = match &decoded.func { + Some(DecodedCallData { signature, args }) => { + let name = signature.split('(').next().unwrap(); + (name.to_string(), args.join(", ")) + } + None => { + debug!(target: "evm::traces", trace=?trace, "unhandled raw calldata"); + if trace.data.len() < 4 { + ("fallback".to_string(), hex::encode(&trace.data)) } else { - bytes.fmt(f) + let (selector, data) = trace.data.split_at(4); + (hex::encode(selector), hex::encode(data)) } } - TraceRetData::Decoded(decoded) => f.write_str(decoded), - } - } -} + }; -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct CallTraceStep { - // Fields filled in `step` - /// Call depth - pub depth: u64, - /// Program counter before step execution - pub pc: usize, - /// Opcode to be executed - pub op: Instruction, - /// Current contract address - pub contract: Address, - /// Stack before step execution - pub stack: Stack, - /// Memory before step execution - pub memory: Vec, - /// Remaining gas before step execution - pub gas: u64, - /// Gas refund counter before step execution - pub gas_refund_counter: u64, - - // Fields filled in `step_end` - /// Gas cost of step execution - pub gas_cost: u64, - /// Change of the contract state after step execution (effect of the SLOAD/SSTORE instructions) - pub state_diff: Option<(U256, U256)>, - /// Error (if any) after step execution - pub error: Option, -} + let action = match trace.kind { + CallKind::AuthCall => " [authcall]", + CallKind::Call => "", + CallKind::StaticCall => " [staticcall]", + CallKind::CallCode => " [callcode]", + CallKind::DelegateCall => " [delegatecall]", + CallKind::Create | CallKind::Create2 => unreachable!(), + }; -impl From<&CallTraceStep> for StructLog { - fn from(step: &CallTraceStep) -> Self { - StructLog { - depth: step.depth, - error: step.error.clone(), - gas: step.gas, - gas_cost: step.gas_cost, - memory: Some(convert_memory(&step.memory)), - op: step.op.to_string(), - pc: step.pc as u64, - refund_counter: if step.gas_refund_counter > 0 { - Some(step.gas_refund_counter) + let color = trace_color(trace); + write!( + &mut s, + "{addr}::{func_name}{opt_value}({inputs}){action}", + addr = decoded.label.as_deref().unwrap_or(&address).fg(color), + func_name = func_name.fg(color), + opt_value = if trace.value.is_zero() { + String::new() } else { - None + format!("{{value: {}}}", trace.value) }, - stack: Some(step.stack.data().iter().copied().map(|v| v.to_ethers()).collect_vec()), - // Filled in `CallTraceArena::geth_trace` as a result of compounding all slot changes - storage: None, - return_data: None, - mem_size: None, - } + action = action.yellow(), + )?; } -} -/// A trace of a call. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct CallTrace { - /// The depth of the call - pub depth: usize, - /// Whether the call was successful - pub success: bool, - /// The name of the contract, if any. - /// - /// The format is `":"` for easy lookup in local contracts. - /// - /// This member is not used by the core call tracing functionality (decoding/displaying). The - /// intended use case is for other components that may want to process traces by specific - /// contracts (e.g. gas reports). - pub contract: Option, - /// The label for the destination address, if any - pub label: Option, - /// caller of this call - pub caller: Address, - /// The destination address of the call or the address from the created contract - pub address: Address, - /// The kind of call this is - pub kind: CallKind, - /// The value transferred in the call - pub value: U256, - /// The calldata for the call, or the init code for contract creations - pub data: TraceCallData, - /// The return data of the call if this was not a contract creation, otherwise it is the - /// runtime bytecode of the created contract - pub output: TraceRetData, - /// The gas cost of the call - pub gas_cost: u64, - /// The status of the trace's call - pub status: InstructionResult, - /// call context of the runtime - pub call_context: Option, - /// Opcode-level execution steps - pub steps: Vec, + Ok((s, decoded.return_data)) } -impl CallTrace { - /// Whether this is a contract creation or not - pub fn created(&self) -> bool { - matches!(self.kind, CallKind::Create | CallKind::Create2) - } -} +/// Render a trace log. +async fn render_trace_log( + log: &LogData, + decoder: &CallTraceDecoder, +) -> Result { + let mut s = String::new(); + let decoded = decoder.decode_event(log).await; + + match decoded { + DecodedCallLog::Raw(log) => { + for (i, topic) in log.topics().iter().enumerate() { + writeln!( + s, + "{:>13}: {}", + if i == 0 { "emit topic 0".to_string() } else { format!("topic {i}") }, + format!("{topic:?}").cyan() + )?; + } -impl Default for CallTrace { - fn default() -> Self { - Self { - depth: Default::default(), - success: Default::default(), - contract: Default::default(), - label: Default::default(), - caller: Default::default(), - address: Default::default(), - kind: Default::default(), - value: Default::default(), - data: Default::default(), - output: Default::default(), - gas_cost: Default::default(), - status: InstructionResult::Continue, - call_context: Default::default(), - steps: Default::default(), + write!(s, " data: {}", hex::encode_prefixed(&log.data).cyan())?; } - } -} - -impl fmt::Display for CallTrace { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "[{}] ", self.gas_cost)?; - let address = self.address.to_checksum(None); - if self.created() { - write!( - f, - "{}{} {}@{}", - Paint::yellow(CALL), - Paint::yellow("new"), - self.label.as_deref().unwrap_or(""), - address - ) - } else { - let (func_name, inputs) = match &self.data { - TraceCallData::Raw(bytes) => { - debug!(target: "evm::traces", trace=?self, "unhandled raw calldata"); - if bytes.len() < 4 { - ("fallback".into(), hex::encode(bytes)) - } else { - let (selector, data) = bytes.split_at(4); - (hex::encode(selector), hex::encode(data)) - } - } - TraceCallData::Decoded { signature, args } => { - let name = signature.split('(').next().unwrap(); - (name.to_string(), args.join(", ")) - } - }; - - let action = match self.kind { - // do not show anything for CALLs - CallKind::Call => "", - CallKind::StaticCall => " [staticcall]", - CallKind::CallCode => " [callcode]", - CallKind::DelegateCall => " [delegatecall]", - CallKind::AuthCall => " [authcall]", - CallKind::Create | CallKind::Create2 => unreachable!(), - }; - - let color = trace_color(self); - write!( - f, - "{addr}::{func_name}{opt_value}({inputs}){action}", - addr = color.paint(self.label.as_deref().unwrap_or(&address)), - func_name = color.paint(func_name), - opt_value = if self.value == U256::ZERO { - String::new() - } else { - format!("{{value: {}}}", self.value) - }, - action = Paint::yellow(action), - ) + DecodedCallLog::Decoded(name, params) => { + let params = params + .iter() + .map(|(name, value)| format!("{name}: {value}")) + .collect::>() + .join(", "); + + write!(s, "emit {}({params})", name.clone().cyan())?; } } + + Ok(s) } /// Specifies the kind of trace. @@ -602,12 +294,8 @@ fn trace_color(trace: &CallTrace) -> Color { } /// Given a list of traces and artifacts, it returns a map connecting address to abi -pub fn load_contracts( - traces: Traces, - known_contracts: Option<&ContractsByArtifact>, -) -> ContractsByAddress { - let Some(contracts) = known_contracts else { return BTreeMap::new() }; - let mut local_identifier = LocalTraceIdentifier::new(contracts); +pub fn load_contracts(traces: Traces, known_contracts: &ContractsByArtifact) -> ContractsByAddress { + let mut local_identifier = LocalTraceIdentifier::new(known_contracts); let mut decoder = CallTraceDecoderBuilder::new().build(); for (_, trace) in &traces { decoder.identify(trace, &mut local_identifier); @@ -617,43 +305,10 @@ pub fn load_contracts( .contracts .iter() .filter_map(|(addr, name)| { - if let Ok(Some((_, (abi, _)))) = contracts.find_by_name_or_identifier(name) { - return Some((*addr, (name.clone(), abi.clone()))) + if let Ok(Some((_, contract))) = known_contracts.find_by_name_or_identifier(name) { + return Some((*addr, (name.clone(), contract.abi.clone()))); } None }) .collect() } - -/// creates the memory data in 32byte chunks -/// see -fn convert_memory(data: &[u8]) -> Vec { - let mut memory = Vec::with_capacity((data.len() + 31) / 32); - for idx in (0..data.len()).step_by(32) { - let len = std::cmp::min(idx + 32, data.len()); - memory.push(hex::encode(&data[idx..len])); - } - memory -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_convert_memory() { - let mut data = vec![0u8; 32]; - assert_eq!( - convert_memory(&data), - vec!["0000000000000000000000000000000000000000000000000000000000000000".to_string()] - ); - data.extend(data.clone()); - assert_eq!( - convert_memory(&data), - vec![ - "0000000000000000000000000000000000000000000000000000000000000000".to_string(), - "0000000000000000000000000000000000000000000000000000000000000000".to_string() - ] - ); - } -} diff --git a/crates/evm/traces/src/node.rs b/crates/evm/traces/src/node.rs deleted file mode 100644 index b753902706978..0000000000000 --- a/crates/evm/traces/src/node.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::{CallTrace, LogCallOrder, TraceLog}; -use ethers_core::types::{Action, Call, CallResult, Create, CreateResult, Res, Suicide}; -use foundry_common::types::ToEthers; -use foundry_evm_core::utils::CallKind; -use revm::interpreter::InstructionResult; -use serde::{Deserialize, Serialize}; - -/// A node in the arena -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct CallTraceNode { - /// Parent node index in the arena - pub parent: Option, - /// Children node indexes in the arena - pub children: Vec, - /// This node's index in the arena - pub idx: usize, - /// The call trace - pub trace: CallTrace, - /// Logs - #[serde(skip)] - pub logs: Vec, - /// Ordering of child calls and logs - pub ordering: Vec, -} - -impl CallTraceNode { - /// Returns the kind of call the trace belongs to - pub fn kind(&self) -> CallKind { - self.trace.kind - } - - /// Returns the status of the call - pub fn status(&self) -> InstructionResult { - self.trace.status - } - - /// Returns the `Res` for a parity trace - pub fn parity_result(&self) -> Res { - match self.kind() { - CallKind::Call | - CallKind::StaticCall | - CallKind::CallCode | - CallKind::DelegateCall | - CallKind::AuthCall => Res::Call(CallResult { - gas_used: self.trace.gas_cost.into(), - output: self.trace.output.to_raw().into(), - }), - CallKind::Create | CallKind::Create2 => Res::Create(CreateResult { - gas_used: self.trace.gas_cost.into(), - code: self.trace.output.to_raw().into(), - address: self.trace.address.to_ethers(), - }), - } - } - - /// Returns the `Action` for a parity trace - pub fn parity_action(&self) -> Action { - if self.status() == InstructionResult::SelfDestruct { - return Action::Suicide(Suicide { - address: self.trace.address.to_ethers(), - // TODO deserialize from calldata here? - refund_address: Default::default(), - balance: self.trace.value.to_ethers(), - }) - } - match self.kind() { - CallKind::Call | - CallKind::StaticCall | - CallKind::CallCode | - CallKind::DelegateCall | - CallKind::AuthCall => Action::Call(Call { - from: self.trace.caller.to_ethers(), - to: self.trace.address.to_ethers(), - value: self.trace.value.to_ethers(), - gas: self.trace.gas_cost.into(), - input: self.trace.data.as_bytes().to_vec().into(), - call_type: self.kind().into(), - }), - CallKind::Create | CallKind::Create2 => Action::Create(Create { - from: self.trace.caller.to_ethers(), - value: self.trace.value.to_ethers(), - gas: self.trace.gas_cost.into(), - init: self.trace.data.as_bytes().to_vec().into(), - }), - } - } -} diff --git a/crates/fmt/Cargo.toml b/crates/fmt/Cargo.toml index 0f49a10a1b82e..c8937a281a5f3 100644 --- a/crates/fmt/Cargo.toml +++ b/crates/fmt/Cargo.toml @@ -14,7 +14,7 @@ foundry-config.workspace = true alloy-primitives.workspace = true -ariadne = "0.3" +ariadne = "0.4" itertools.workspace = true solang-parser.workspace = true thiserror = "1" diff --git a/crates/fmt/src/buffer.rs b/crates/fmt/src/buffer.rs index 2f26f650b72bb..11c0838ecd370 100644 --- a/crates/fmt/src/buffer.rs +++ b/crates/fmt/src/buffer.rs @@ -7,7 +7,7 @@ use crate::{ use std::fmt::Write; /// An indent group. The group may optionally skip the first line -#[derive(Default, Clone, Debug)] +#[derive(Clone, Debug, Default)] struct IndentGroup { skip_line: bool, } diff --git a/crates/fmt/src/comments.rs b/crates/fmt/src/comments.rs index f0316aa2b9c9a..03f4e41813c19 100644 --- a/crates/fmt/src/comments.rs +++ b/crates/fmt/src/comments.rs @@ -4,7 +4,7 @@ use solang_parser::pt::*; use std::collections::VecDeque; /// The type of a Comment -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CommentType { /// A Line comment (e.g. `// ...`) Line, @@ -17,7 +17,7 @@ pub enum CommentType { } /// The comment position -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CommentPosition { /// Comes before the code it describes Prefix, @@ -26,7 +26,7 @@ pub enum CommentPosition { } /// Comment with additional metadata -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct CommentWithMetadata { pub ty: CommentType, pub loc: Loc, @@ -152,6 +152,10 @@ impl CommentWithMetadata { matches!(self.ty, CommentType::Line | CommentType::DocLine) } + pub fn is_doc_block(&self) -> bool { + matches!(self.ty, CommentType::DocBlock) + } + pub fn is_prefix(&self) -> bool { matches!(self.position, CommentPosition::Prefix) } @@ -208,7 +212,7 @@ impl CommentWithMetadata { } /// A list of comments -#[derive(Default, Debug, Clone)] +#[derive(Clone, Debug, Default)] pub struct Comments { prefixes: VecDeque, postfixes: VecDeque, @@ -295,7 +299,7 @@ impl Comments { } /// The state of a character in a string with possible comments -#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum CommentState { /// character not in a comment #[default] @@ -394,13 +398,13 @@ impl Iterator for CommentStateCharIndices<'_> { } #[inline] - fn count(self) -> usize { - self.iter.count() + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() } #[inline] - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() + fn count(self) -> usize { + self.iter.count() } } diff --git a/crates/fmt/src/formatter.rs b/crates/fmt/src/formatter.rs index 02cf851697fe4..d10cf4b382359 100644 --- a/crates/fmt/src/formatter.rs +++ b/crates/fmt/src/formatter.rs @@ -16,14 +16,13 @@ use crate::{ use alloy_primitives::Address; use foundry_config::fmt::{HexUnderscore, MultilineFuncHeaderStyle, SingleLineBlockStyle}; use itertools::{Either, Itertools}; -use solang_parser::pt::ImportPath; use std::{fmt::Write, str::FromStr}; use thiserror::Error; type Result = std::result::Result; /// A custom Error thrown by the Formatter -#[derive(Error, Debug)] +#[derive(Debug, Error)] pub enum FormatterError { /// Error thrown by `std::fmt::Write` interfaces #[error(transparent)] @@ -73,13 +72,20 @@ macro_rules! bail { // TODO: store context entities as references without copying /// Current context of the Formatter (e.g. inside Contract or Function definition) -#[derive(Default, Debug)] +#[derive(Debug, Default)] struct Context { contract: Option, function: Option, if_stmt_single_line: Option, } +impl Context { + /// Returns true if the current function context is the constructor + pub(crate) fn is_constructor_function(&self) -> bool { + self.function.as_ref().map_or(false, |f| matches!(f.ty, FunctionTy::Constructor)) + } +} + /// A Solidity formatter #[derive(Debug)] pub struct Formatter<'a, W> { @@ -92,46 +98,6 @@ pub struct Formatter<'a, W> { inline_config: InlineConfig, } -/// An action which may be committed to a Formatter -struct Transaction<'f, 'a, W> { - fmt: &'f mut Formatter<'a, W>, - buffer: String, - comments: Comments, -} - -impl<'f, 'a, W> std::ops::Deref for Transaction<'f, 'a, W> { - type Target = Formatter<'a, W>; - fn deref(&self) -> &Self::Target { - self.fmt - } -} - -impl<'f, 'a, W> std::ops::DerefMut for Transaction<'f, 'a, W> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.fmt - } -} - -impl<'f, 'a, W: Write> Transaction<'f, 'a, W> { - /// Create a new transaction from a callback - fn new( - fmt: &'f mut Formatter<'a, W>, - fun: impl FnMut(&mut Formatter<'a, W>) -> Result<()>, - ) -> Result { - let mut comments = fmt.comments.clone(); - let buffer = fmt.with_temp_buf(fun)?.w; - comments = std::mem::replace(&mut fmt.comments, comments); - Ok(Self { fmt, buffer, comments }) - } - - /// Commit the transaction to the Formatter - fn commit(self) -> Result { - self.fmt.comments = self.comments; - write_chunk!(self.fmt, "{}", self.buffer)?; - Ok(self.buffer) - } -} - impl<'a, W: Write> Formatter<'a, W> { pub fn new( w: W, @@ -276,6 +242,10 @@ impl<'a, W: Write> Formatter<'a, W> { /// Returns number of blank lines in source between two byte indexes fn blank_lines(&self, start: usize, end: usize) -> usize { + // because of sorting import statements, start can be greater than end + if start > end { + return 0 + } self.source[start..end].trim_comments().matches('\n').count() } @@ -557,8 +527,12 @@ impl<'a, W: Write> Formatter<'a, W> { .take_while(|(idx, ch)| ch.is_whitespace() && *idx <= self.buf.current_indent_len()) .count(); let to_skip = indent_whitespace_count - indent_whitespace_count % self.config.tab_width; - write!(self.buf(), " * ")?; - self.write_comment_line(comment, &line[to_skip..])?; + write!(self.buf(), " *")?; + let content = &line[to_skip..]; + if !content.trim().is_empty() { + write!(self.buf(), " ")?; + self.write_comment_line(comment, &line[to_skip..])?; + } self.write_whitespace_separator(true)?; Ok(()) } @@ -846,7 +820,7 @@ impl<'a, W: Write> Formatter<'a, W> { Ok(self.transact(fun)?.buffer) } - /// Turn a chunk and its surrounding comments into a a string + /// Turn a chunk and its surrounding comments into a string fn chunk_to_string(&mut self, chunk: &Chunk) -> Result { self.simulate_to_string(|fmt| fmt.write_chunk(chunk)) } @@ -921,7 +895,7 @@ impl<'a, W: Write> Formatter<'a, W> { write_chunk!(fmt, "{}", stringified.trim_start()) })?; if !last.content.trim_start().is_empty() { - self.write_whitespace_separator(true)?; + self.indented(1, |fmt| fmt.write_whitespace_separator(true))?; } let last_chunk = self.chunk_at(last.loc_before(), last.loc_next(), last.spaced, &last.content); @@ -989,8 +963,14 @@ impl<'a, W: Write> Formatter<'a, W> { (None, None) => return Ok(()), } .start(); + let mut last_loc: Option = None; + let mut visited_locs: Vec = Vec::new(); + + // marker for whether the next item needs additional space let mut needs_space = false; + let mut last_comment = None; + while let Some(mut line_item) = pop_next(self, &mut items) { let loc = line_item.as_ref().either(|c| c.loc, |i| i.loc()); let (unwritten_whitespace_loc, unwritten_whitespace) = @@ -1019,8 +999,32 @@ impl<'a, W: Write> Formatter<'a, W> { Either::Right(item) => { if !ignore_whitespace { self.write_whitespace_separator(true)?; - if let Some(last_loc) = last_loc { - if needs_space || self.blank_lines(last_loc.end(), loc.start()) > 1 { + if let Some(mut last_loc) = last_loc { + // here's an edge case when we reordered items so the last_loc isn't + // necessarily the item that directly precedes the current item because + // the order might have changed, so we need to find the last item that + // is before the current item by checking the recorded locations + if let Some(last_item) = visited_locs + .iter() + .rev() + .find(|prev_item| prev_item.start() > last_loc.end()) + { + last_loc = *last_item; + } + + // The blank lines check is susceptible additional trailing new lines + // because the block docs can contain + // multiple lines, but the function def should follow directly after the + // block comment + let is_last_doc_comment = matches!( + last_comment, + Some(CommentWithMetadata { ty: CommentType::DocBlock, .. }) + ); + + if needs_space || + (!is_last_doc_comment && + self.blank_lines(last_loc.end(), loc.start()) > 1) + { writeln!(self.buf())?; } } @@ -1037,12 +1041,17 @@ impl<'a, W: Write> Formatter<'a, W> { } last_loc = Some(loc); + visited_locs.push(loc); + + last_comment = None; + last_byte_written = loc.end(); - if let Some(comment) = line_item.as_ref().left() { + if let Some(comment) = line_item.left() { if comment.is_line() { last_byte_written = self.find_next_line(last_byte_written).unwrap_or(last_byte_written); } + last_comment = Some(comment); } } @@ -1110,7 +1119,7 @@ impl<'a, W: Write> Formatter<'a, W> { fn visit_list( &mut self, prefix: &str, - items: &mut Vec, + items: &mut [T], start_offset: Option, end_offset: Option, paren_required: bool, @@ -1153,7 +1162,7 @@ impl<'a, W: Write> Formatter<'a, W> { fn visit_block( &mut self, loc: Loc, - statements: &mut Vec, + statements: &mut [T], attempt_single_line: bool, attempt_omit_braces: bool, ) -> Result @@ -1207,7 +1216,7 @@ impl<'a, W: Write> Formatter<'a, W> { Statement::Block { loc, statements, .. } => { self.visit_block(*loc, statements, attempt_single_line, true) } - _ => self.visit_block(stmt.loc(), &mut vec![stmt], attempt_single_line, true), + _ => self.visit_block(stmt.loc(), &mut [stmt], attempt_single_line, true), } } @@ -1633,6 +1642,69 @@ impl<'a, W: Write> Formatter<'a, W> { } Ok(()) } + + /// Sorts grouped import statement alphabetically. + fn sort_imports(&self, source_unit: &mut SourceUnit) { + // first we need to find the grouped import statements + // A group is defined as a set of import statements that are separated by a blank line + let mut import_groups = Vec::new(); + let mut current_group = Vec::new(); + let mut source_unit_parts = source_unit.0.iter().enumerate().peekable(); + while let Some((i, part)) = source_unit_parts.next() { + if let SourceUnitPart::ImportDirective(_) = part { + current_group.push(i); + let current_loc = part.loc(); + if let Some((_, next_part)) = source_unit_parts.peek() { + let next_loc = next_part.loc(); + // import statements are followed by a new line, so if there are more than one + // we have a group + if self.blank_lines(current_loc.end(), next_loc.start()) > 1 { + import_groups.push(std::mem::take(&mut current_group)); + } + } + } else if !current_group.is_empty() { + import_groups.push(std::mem::take(&mut current_group)); + } + } + + if !current_group.is_empty() { + import_groups.push(current_group); + } + + if import_groups.is_empty() { + // nothing to sort + return + } + + // order all groups alphabetically + for group in import_groups.iter() { + // SAFETY: group is not empty + let first = group[0]; + let last = group.last().copied().expect("group is not empty"); + let import_directives = &mut source_unit.0[first..=last]; + + // sort rename style imports alphabetically based on the actual import and not the + // rename + for source_unit_part in import_directives.iter_mut() { + if let SourceUnitPart::ImportDirective(Import::Rename(_, renames, _)) = + source_unit_part + { + renames.sort_by_cached_key(|(og_ident, _)| og_ident.name.clone()); + } + } + + import_directives.sort_by_cached_key(|item| match item { + SourceUnitPart::ImportDirective(import) => match import { + Import::Plain(path, _) => path.to_string(), + Import::GlobalSymbol(path, _, _) => path.to_string(), + Import::Rename(path, _, _) => path.to_string(), + }, + _ => { + unreachable!("import group contains non-import statement") + } + }); + } + } } // Traverse the Solidity Parse Tree and write to the code formatter @@ -1659,6 +1731,9 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { #[instrument(name = "SU", skip_all)] fn visit_source_unit(&mut self, source_unit: &mut SourceUnit) -> Result<()> { + if self.config.sort_imports { + self.sort_imports(source_unit); + } // TODO: do we need to put pragma and import directives at the top of the file? // source_unit.0.sort_by_key(|item| match item { // SourceUnitPart::PragmaDirective(_, _, _) => 0, @@ -1826,6 +1901,18 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Ok(()) } + // Support extension for Solana/Substrate + #[instrument(name = "annotation", skip_all)] + fn visit_annotation(&mut self, annotation: &mut Annotation) -> Result<()> { + return_source_if_disabled!(self, annotation.loc); + let id = self.simulate_to_string(|fmt| annotation.id.visit(fmt))?; + write!(self.buf(), "@{id}")?; + write!(self.buf(), "(")?; + annotation.value.visit(self)?; + write!(self.buf(), ")")?; + Ok(()) + } + #[instrument(name = "pragma", skip_all)] fn visit_pragma( &mut self, @@ -1961,32 +2048,136 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { let enum_name = enumeration.name.safe_unwrap_mut(); let mut name = self.visit_to_chunk(enum_name.loc.start(), Some(enum_name.loc.end()), enum_name)?; - name.content = format!("enum {}", name.content); - self.write_chunk(&name)?; - + name.content = format!("enum {} ", name.content); if enumeration.values.is_empty() { + self.write_chunk(&name)?; self.write_empty_brackets()?; } else { - self.surrounded( - SurroundingChunk::new( - "{", - Some(enumeration.values.first_mut().unwrap().safe_unwrap().loc.start()), - None, - ), - SurroundingChunk::new("}", None, Some(enumeration.loc.end())), - |fmt, _multiline| { - let values = fmt.items_to_chunks( - Some(enumeration.loc.end()), - enumeration.values.iter_mut().map(|ident| { - let ident = ident.safe_unwrap_mut(); - (ident.loc, ident) - }), - )?; - fmt.write_chunks_separated(&values, ",", true)?; - Ok(()) - }, - )?; + name.content.push('{'); + self.write_chunk(&name)?; + + self.indented(1, |fmt| { + let values = fmt.items_to_chunks( + Some(enumeration.loc.end()), + enumeration.values.iter_mut().map(|ident| { + let ident = ident.safe_unwrap_mut(); + (ident.loc, ident) + }), + )?; + fmt.write_chunks_separated(&values, ",", true)?; + writeln!(fmt.buf())?; + Ok(()) + })?; + write_chunk!(self, "}}")?; + } + + Ok(()) + } + + #[instrument(name = "assembly", skip_all)] + fn visit_assembly( + &mut self, + loc: Loc, + dialect: &mut Option, + block: &mut YulBlock, + flags: &mut Option>, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + + write_chunk!(self, loc.start(), "assembly")?; + if let Some(StringLiteral { loc, string, .. }) = dialect { + write_chunk!(self, loc.start(), loc.end(), "\"{string}\"")?; + } + if let Some(flags) = flags { + if !flags.is_empty() { + let loc_start = flags.first().unwrap().loc.start(); + self.surrounded( + SurroundingChunk::new("(", Some(loc_start), None), + SurroundingChunk::new(")", None, Some(block.loc.start())), + |fmt, _| { + let mut flags = flags.iter_mut().peekable(); + let mut chunks = vec![]; + while let Some(flag) = flags.next() { + let next_byte_offset = + flags.peek().map(|next_flag| next_flag.loc.start()); + chunks.push(fmt.chunked( + flag.loc.start(), + next_byte_offset, + |fmt| { + write!(fmt.buf(), "\"{}\"", flag.string)?; + Ok(()) + }, + )?); + } + fmt.write_chunks_separated(&chunks, ",", false)?; + Ok(()) + }, + )?; + } + } + + block.visit(self) + } + + #[instrument(name = "block", skip_all)] + fn visit_block( + &mut self, + loc: Loc, + unchecked: bool, + statements: &mut Vec, + ) -> Result<()> { + return_source_if_disabled!(self, loc); + if unchecked { + write_chunk!(self, loc.start(), "unchecked ")?; + } + + self.visit_block(loc, statements, false, false)?; + Ok(()) + } + + #[instrument(name = "args", skip_all)] + fn visit_args(&mut self, loc: Loc, args: &mut Vec) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + + write!(self.buf(), "{{")?; + + let mut args_iter = args.iter_mut().peekable(); + let mut chunks = Vec::new(); + while let Some(NamedArgument { loc: arg_loc, name, expr }) = args_iter.next() { + let next_byte_offset = args_iter + .peek() + .map(|NamedArgument { loc: arg_loc, .. }| arg_loc.start()) + .unwrap_or_else(|| loc.end()); + chunks.push(self.chunked(arg_loc.start(), Some(next_byte_offset), |fmt| { + fmt.grouped(|fmt| { + write_chunk!(fmt, name.loc.start(), "{}: ", name.name)?; + expr.visit(fmt) + })?; + Ok(()) + })?); + } + + if let Some(first) = chunks.first_mut() { + if first.prefixes.is_empty() && + first.postfixes_before.is_empty() && + !self.config.bracket_spacing + { + first.needs_space = Some(false); + } } + let multiline = self.are_chunks_separated_multiline("{}}", &chunks, ",")?; + self.indented_if(multiline, 1, |fmt| fmt.write_chunks_separated(&chunks, ",", multiline))?; + + let prefix = if multiline && !self.is_beginning_of_line() { + "\n" + } else if self.config.bracket_spacing { + " " + } else { + "" + }; + let closing_bracket = format!("{prefix}{}", "}"); + let closing_bracket_loc = args.last().unwrap().loc.end(); + write_chunk!(self, closing_bracket_loc, "{closing_bracket}")?; Ok(()) } @@ -2361,540 +2552,391 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Ok(()) } - #[instrument(name = "var_declaration", skip_all)] - fn visit_var_declaration(&mut self, var: &mut VariableDeclaration) -> Result<()> { - return_source_if_disabled!(self, var.loc); - self.grouped(|fmt| { - var.ty.visit(fmt)?; - if let Some(storage) = &var.storage { - write_chunk!(fmt, storage.loc().end(), "{storage}")?; + #[instrument(name = "var_definition", skip_all)] + fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<()> { + return_source_if_disabled!(self, var.loc, ';'); + + var.ty.visit(self)?; + + let multiline = self.grouped(|fmt| { + let var_name = var.name.safe_unwrap_mut(); + let name_start = var_name.loc.start(); + + let attrs = fmt.items_to_chunks_sorted(Some(name_start), var.attrs.iter_mut())?; + if !fmt.try_on_single_line(|fmt| fmt.write_chunks_separated(&attrs, "", false))? { + fmt.write_chunks_separated(&attrs, "", true)?; } - let var_name = var.name.safe_unwrap(); - write_chunk!(fmt, var_name.loc.end(), "{var_name}") + + let mut name = fmt.visit_to_chunk(name_start, Some(var_name.loc.end()), var_name)?; + if var.initializer.is_some() { + name.content.push_str(" ="); + } + fmt.write_chunk(&name)?; + + Ok(()) })?; + + var.initializer + .as_mut() + .map(|init| self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(init))) + .transpose()?; + + self.write_semicolon()?; + Ok(()) } - #[instrument(name = "break", skip_all)] - fn visit_break(&mut self, loc: Loc, semicolon: bool) -> Result<()> { - if semicolon { - return_source_if_disabled!(self, loc, ';'); - } else { - return_source_if_disabled!(self, loc); + #[instrument(name = "var_definition_stmt", skip_all)] + fn visit_var_definition_stmt( + &mut self, + loc: Loc, + declaration: &mut VariableDeclaration, + expr: &mut Option, + ) -> Result<()> { + return_source_if_disabled!(self, loc, ';'); + + let declaration = self + .chunked(declaration.loc.start(), None, |fmt| fmt.visit_var_declaration(declaration))?; + let multiline = declaration.content.contains('\n'); + self.write_chunk(&declaration)?; + + if let Some(expr) = expr { + write!(self.buf(), " =")?; + self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(expr))?; } - write_chunk!(self, loc.start(), loc.end(), "break{}", if semicolon { ";" } else { "" }) + + self.write_semicolon() } - #[instrument(name = "continue", skip_all)] - fn visit_continue(&mut self, loc: Loc, semicolon: bool) -> Result<()> { - if semicolon { - return_source_if_disabled!(self, loc, ';'); - } else { - return_source_if_disabled!(self, loc); - } - write_chunk!(self, loc.start(), loc.end(), "continue{}", if semicolon { ";" } else { "" }) + #[instrument(name = "var_declaration", skip_all)] + fn visit_var_declaration(&mut self, var: &mut VariableDeclaration) -> Result<()> { + return_source_if_disabled!(self, var.loc); + self.grouped(|fmt| { + var.ty.visit(fmt)?; + if let Some(storage) = &var.storage { + write_chunk!(fmt, storage.loc().end(), "{storage}")?; + } + let var_name = var.name.safe_unwrap(); + write_chunk!(fmt, var_name.loc.end(), "{var_name}") + })?; + Ok(()) } - #[instrument(name = "function", skip_all)] - fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<()> { - if func.body.is_some() { - return_source_if_disabled!(self, func.loc()); - } else { - return_source_if_disabled!(self, func.loc(), ';'); + #[instrument(name = "return", skip_all)] + fn visit_return(&mut self, loc: Loc, expr: &mut Option) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc, ';'); + + self.write_postfix_comments_before(loc.start())?; + self.write_prefix_comments_before(loc.start())?; + + if expr.is_none() { + write_chunk!(self, loc.end(), "return;")?; + return Ok(()) } - self.with_function_context(func.clone(), |fmt| { - fmt.write_postfix_comments_before(func.loc.start())?; - fmt.write_prefix_comments_before(func.loc.start())?; + let expr = expr.as_mut().unwrap(); + let expr_loc_start = expr.loc().start(); + let write_return = |fmt: &mut Self| -> Result<()> { + write_chunk!(fmt, loc.start(), "return")?; + fmt.write_postfix_comments_before(expr_loc_start)?; + Ok(()) + }; - let body_loc = func.body.as_ref().map(CodeLocation::loc); - let mut attrs_multiline = false; + let mut write_return_with_expr = |fmt: &mut Self| -> Result<()> { let fits_on_single = fmt.try_on_single_line(|fmt| { - fmt.write_function_header(func, body_loc, false)?; - Ok(()) + write_return(fmt)?; + expr.visit(fmt) })?; - if !fits_on_single { - attrs_multiline = fmt.write_function_header(func, body_loc, true)?; + if fits_on_single { + return Ok(()) } - // write function body - match &mut func.body { - Some(body) => { - let body_loc = body.loc(); - let byte_offset = body_loc.start(); - let body = fmt.visit_to_chunk(byte_offset, Some(body_loc.end()), body)?; - fmt.write_whitespace_separator( - attrs_multiline && !(func.attributes.is_empty() && func.returns.is_empty()), - )?; - fmt.write_chunk(&body)?; - } - None => fmt.write_semicolon()?, + let mut fit_on_next_line = false; + let tx = fmt.transact(|fmt| { + fmt.grouped(|fmt| { + write_return(fmt)?; + if !fmt.is_beginning_of_line() { + fmt.write_whitespace_separator(true)?; + } + fit_on_next_line = fmt.try_on_single_line(|fmt| expr.visit(fmt))?; + Ok(()) + })?; + Ok(()) + })?; + if fit_on_next_line { + tx.commit()?; + return Ok(()) } + write_return(fmt)?; + expr.visit(fmt)?; Ok(()) - })?; + }; + write_return_with_expr(self)?; + write_chunk!(self, loc.end(), ";")?; Ok(()) } - #[instrument(name = "function_attribute", skip_all)] - fn visit_function_attribute(&mut self, attribute: &mut FunctionAttribute) -> Result<()> { - return_source_if_disabled!(self, attribute.loc()); - - match attribute { - FunctionAttribute::Mutability(mutability) => { - write_chunk!(self, mutability.loc().end(), "{mutability}")? - } - FunctionAttribute::Visibility(visibility) => { - // Visibility will always have a location in a Function attribute - write_chunk!(self, visibility.loc_opt().unwrap().end(), "{visibility}")? - } - FunctionAttribute::Virtual(loc) => write_chunk!(self, loc.end(), "virtual")?, - FunctionAttribute::Immutable(loc) => write_chunk!(self, loc.end(), "immutable")?, - FunctionAttribute::Override(loc, args) => { - write_chunk!(self, loc.start(), "override")?; - if !args.is_empty() && self.config.override_spacing { - self.write_whitespace_separator(false)?; - } - self.visit_list("", args, None, Some(loc.end()), false)? - } - FunctionAttribute::BaseOrModifier(loc, base) => { - let is_contract_base = self.context.contract.as_ref().map_or(false, |contract| { - contract.base.iter().any(|contract_base| { - contract_base - .name - .identifiers - .iter() - .zip(&base.name.identifiers) - .all(|(l, r)| l.name == r.name) - }) - }); - - if is_contract_base { - base.visit(self)?; - } else { - let mut base_or_modifier = - self.visit_to_chunk(loc.start(), Some(loc.end()), base)?; - if base_or_modifier.content.ends_with("()") { - base_or_modifier.content.truncate(base_or_modifier.content.len() - 2); - } - self.write_chunk(&base_or_modifier)?; - } - } - FunctionAttribute::Error(loc) => self.visit_parser_error(*loc)?, - }; + #[instrument(name = "revert", skip_all)] + fn visit_revert( + &mut self, + loc: Loc, + error: &mut Option, + args: &mut Vec, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc, ';'); + write_chunk!(self, loc.start(), "revert")?; + if let Some(error) = error { + error.visit(self)?; + } + self.visit_list("", args, None, Some(loc.end()), true)?; + self.write_semicolon()?; Ok(()) } - #[instrument(name = "base", skip_all)] - fn visit_base(&mut self, base: &mut Base) -> Result<()> { - return_source_if_disabled!(self, base.loc); - - let name_loc = &base.name.loc; - let mut name = self.chunked(name_loc.start(), Some(name_loc.end()), |fmt| { - fmt.visit_ident_path(&mut base.name)?; - Ok(()) - })?; + #[instrument(name = "revert_named_args", skip_all)] + fn visit_revert_named_args( + &mut self, + loc: Loc, + error: &mut Option, + args: &mut Vec, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc, ';'); - if base.args.is_none() || base.args.as_ref().unwrap().is_empty() { - if self.context.function.is_some() { - name.content.push_str("()"); + write_chunk!(self, loc.start(), "revert")?; + let mut error_indented = false; + if let Some(error) = error { + if !self.try_on_single_line(|fmt| error.visit(fmt))? { + error.visit(self)?; + error_indented = true; } - self.write_chunk(&name)?; - return Ok(()) } - let args = base.args.as_mut().unwrap(); - let args_start = CodeLocation::loc(args.first().unwrap()).start(); - - name.content.push('('); - let formatted_name = self.chunk_to_string(&name)?; - - let multiline = !self.will_it_fit(&formatted_name); + if args.is_empty() { + write!(self.buf(), "({{}});")?; + return Ok(()) + } - self.surrounded( - SurroundingChunk::new(&formatted_name, Some(args_start), None), - SurroundingChunk::new(")", None, Some(base.loc.end())), - |fmt, multiline_hint| { - let args = fmt.items_to_chunks( - Some(base.loc.end()), - args.iter_mut().map(|arg| (arg.loc(), arg)), - )?; - let multiline = multiline || - multiline_hint || - fmt.are_chunks_separated_multiline("{}", &args, ",")?; - fmt.write_chunks_separated(&args, ",", multiline)?; - Ok(()) - }, - )?; + write!(self.buf(), "(")?; + self.indented_if(error_indented, 1, |fmt| fmt.visit_args(loc, args))?; + write!(self.buf(), ")")?; + self.write_semicolon()?; Ok(()) } - #[instrument(name = "parameter", skip_all)] - fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<()> { - return_source_if_disabled!(self, parameter.loc); - self.grouped(|fmt| { - parameter.ty.visit(fmt)?; - if let Some(storage) = ¶meter.storage { - write_chunk!(fmt, storage.loc().end(), "{storage}")?; - } - if let Some(name) = ¶meter.name { - write_chunk!(fmt, parameter.loc.end(), "{}", name.name)?; - } - Ok(()) - })?; - Ok(()) + #[instrument(name = "break", skip_all)] + fn visit_break(&mut self, loc: Loc, semicolon: bool) -> Result<()> { + if semicolon { + return_source_if_disabled!(self, loc, ';'); + } else { + return_source_if_disabled!(self, loc); + } + write_chunk!(self, loc.start(), loc.end(), "break{}", if semicolon { ";" } else { "" }) } - #[instrument(name = "struct", skip_all)] - fn visit_struct(&mut self, structure: &mut StructDefinition) -> Result<()> { - return_source_if_disabled!(self, structure.loc); - self.grouped(|fmt| { - let struct_name = structure.name.safe_unwrap_mut(); - write_chunk!(fmt, struct_name.loc.start(), "struct")?; - struct_name.visit(fmt)?; - if structure.fields.is_empty() { - return fmt.write_empty_brackets() - } - - write!(fmt.buf(), " {{")?; - fmt.surrounded( - SurroundingChunk::new("", Some(struct_name.loc.end()), None), - SurroundingChunk::new("}", None, Some(structure.loc.end())), - |fmt, _multiline| { - let chunks = fmt.items_to_chunks( - Some(structure.loc.end()), - structure.fields.iter_mut().map(|ident| (ident.loc, ident)), - )?; - for mut chunk in chunks { - chunk.content.push(';'); - fmt.write_chunk(&chunk)?; - fmt.write_whitespace_separator(true)?; - } - Ok(()) - }, - ) - })?; - - Ok(()) - } - - #[instrument(name = "type_definition", skip_all)] - fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<()> { - return_source_if_disabled!(self, def.loc, ';'); - self.grouped(|fmt| { - write_chunk!(fmt, def.loc.start(), def.name.loc.start(), "type")?; - def.name.visit(fmt)?; - write_chunk!(fmt, def.name.loc.end(), CodeLocation::loc(&def.ty).start(), "is")?; - def.ty.visit(fmt)?; - fmt.write_semicolon()?; - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "stray_semicolon", skip_all)] - fn visit_stray_semicolon(&mut self) -> Result<()> { - self.write_semicolon() + #[instrument(name = "continue", skip_all)] + fn visit_continue(&mut self, loc: Loc, semicolon: bool) -> Result<()> { + if semicolon { + return_source_if_disabled!(self, loc, ';'); + } else { + return_source_if_disabled!(self, loc); + } + write_chunk!(self, loc.start(), loc.end(), "continue{}", if semicolon { ";" } else { "" }) } - #[instrument(name = "block", skip_all)] - fn visit_block( + #[instrument(name = "try", skip_all)] + fn visit_try( &mut self, loc: Loc, - unchecked: bool, - statements: &mut Vec, - ) -> Result<()> { + expr: &mut Expression, + returns: &mut Option<(Vec<(Loc, Option)>, Box)>, + clauses: &mut Vec, + ) -> Result<(), Self::Error> { return_source_if_disabled!(self, loc); - if unchecked { - write_chunk!(self, loc.start(), "unchecked ")?; - } - - self.visit_block(loc, statements, false, false)?; - Ok(()) - } - - #[instrument(name = "opening_paren", skip_all)] - fn visit_opening_paren(&mut self) -> Result<()> { - write_chunk!(self, "(")?; - Ok(()) - } - #[instrument(name = "closing_paren", skip_all)] - fn visit_closing_paren(&mut self) -> Result<()> { - write_chunk!(self, ")")?; - Ok(()) - } - - #[instrument(name = "newline", skip_all)] - fn visit_newline(&mut self) -> Result<()> { - writeln_chunk!(self)?; - Ok(()) - } - - #[instrument(name = "event", skip_all)] - fn visit_event(&mut self, event: &mut EventDefinition) -> Result<()> { - return_source_if_disabled!(self, event.loc, ';'); - - let event_name = event.name.safe_unwrap_mut(); - let mut name = - self.visit_to_chunk(event_name.loc.start(), Some(event.loc.end()), event_name)?; - name.content = format!("event {}(", name.content); - - let last_chunk = if event.anonymous { ") anonymous;" } else { ");" }; - if event.fields.is_empty() { - name.content.push_str(last_chunk); - self.write_chunk(&name)?; - } else { - let byte_offset = event.fields.first().unwrap().loc.start(); - let first_chunk = self.chunk_to_string(&name)?; - self.surrounded( - SurroundingChunk::new(first_chunk, Some(byte_offset), None), - SurroundingChunk::new(last_chunk, None, Some(event.loc.end())), - |fmt, multiline| { - let params = fmt - .items_to_chunks(None, event.fields.iter_mut().map(|arg| (arg.loc, arg)))?; - - let multiline = - multiline && fmt.are_chunks_separated_multiline("{}", ¶ms, ",")?; - fmt.write_chunks_separated(¶ms, ",", multiline) - }, - )?; - } - - Ok(()) - } - - #[instrument(name = "event_parameter", skip_all)] - fn visit_event_parameter(&mut self, param: &mut EventParameter) -> Result<()> { - return_source_if_disabled!(self, param.loc); - - self.grouped(|fmt| { - param.ty.visit(fmt)?; - if param.indexed { - write_chunk!(fmt, param.loc.start(), "indexed")?; - } - if let Some(name) = ¶m.name { - write_chunk!(fmt, name.loc.end(), "{}", name.name)?; - } - Ok(()) - })?; - Ok(()) - } - - #[instrument(name = "error", skip_all)] - fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result<()> { - return_source_if_disabled!(self, error.loc, ';'); - - let error_name = error.name.safe_unwrap_mut(); - let mut name = self.visit_to_chunk(error_name.loc.start(), None, error_name)?; - name.content = format!("error {}", name.content); - - let formatted_name = self.chunk_to_string(&name)?; - write!(self.buf(), "{formatted_name}")?; - let start_offset = error.fields.first().map(|f| f.loc.start()); - self.visit_list("", &mut error.fields, start_offset, Some(error.loc.end()), true)?; - self.write_semicolon()?; - - Ok(()) - } - - #[instrument(name = "error_parameter", skip_all)] - fn visit_error_parameter(&mut self, param: &mut ErrorParameter) -> Result<()> { - return_source_if_disabled!(self, param.loc); - self.grouped(|fmt| { - param.ty.visit(fmt)?; - if let Some(name) = ¶m.name { - write_chunk!(fmt, name.loc.end(), "{}", name.name)?; + let try_next_byte = clauses.first().map(|c| match c { + CatchClause::Simple(loc, ..) => loc.start(), + CatchClause::Named(loc, ..) => loc.start(), + }); + let try_chunk = self.chunked(loc.start(), try_next_byte, |fmt| { + write_chunk!(fmt, loc.start(), expr.loc().start(), "try")?; + expr.visit(fmt)?; + if let Some((params, stmt)) = returns { + let mut params = + params.iter_mut().filter(|(_, param)| param.is_some()).collect::>(); + let byte_offset = params.first().map_or(stmt.loc().start(), |p| p.0.start()); + fmt.surrounded( + SurroundingChunk::new("returns (", Some(byte_offset), None), + SurroundingChunk::new(")", None, params.last().map(|p| p.0.end())), + |fmt, _| { + let chunks = fmt.items_to_chunks( + Some(stmt.loc().start()), + params.iter_mut().map(|(loc, ref mut ident)| (*loc, ident)), + )?; + let multiline = fmt.are_chunks_separated_multiline("{})", &chunks, ",")?; + fmt.write_chunks_separated(&chunks, ",", multiline)?; + Ok(()) + }, + )?; + stmt.visit(fmt)?; } Ok(()) })?; - Ok(()) - } - #[instrument(name = "using", skip_all)] - fn visit_using(&mut self, using: &mut Using) -> Result<()> { - return_source_if_disabled!(self, using.loc, ';'); - - write_chunk!(self, using.loc.start(), "using")?; - - let ty_start = using.ty.as_mut().map(|ty| CodeLocation::loc(&ty).start()); - let global_start = using.global.as_mut().map(|global| global.loc.start()); - let loc_end = using.loc.end(); - - let (is_library, mut list_chunks) = match &mut using.list { - UsingList::Library(library) => { - (true, vec![self.visit_to_chunk(library.loc.start(), None, library)?]) - } - UsingList::Functions(funcs) => { - let mut funcs = funcs.iter_mut().peekable(); - let mut chunks = Vec::new(); - while let Some(func) = funcs.next() { - let next_byte_end = funcs.peek().map(|func| func.loc.start()); - chunks.push(self.chunked(func.loc.start(), next_byte_end, |fmt| { - fmt.visit_ident_path(&mut func.path)?; - if let Some(op) = func.oper { - write!(fmt.buf(), " as {op}")?; - } - Ok(()) - })?); + let mut chunks = vec![try_chunk]; + for clause in clauses { + let (loc, ident, mut param, stmt) = match clause { + CatchClause::Simple(loc, param, stmt) => (loc, None, param.as_mut(), stmt), + CatchClause::Named(loc, ident, param, stmt) => { + (loc, Some(ident), Some(param), stmt) } - (false, chunks) - } - UsingList::Error => return self.visit_parser_error(using.loc), - }; - - let for_chunk = self.chunk_at( - using.loc.start(), - Some(ty_start.or(global_start).unwrap_or(loc_end)), - None, - "for", - ); - let ty_chunk = if let Some(ty) = &mut using.ty { - self.visit_to_chunk(ty.loc().start(), Some(global_start.unwrap_or(loc_end)), ty)? - } else { - self.chunk_at(using.loc.start(), Some(global_start.unwrap_or(loc_end)), None, "*") - }; - let global_chunk = using - .global - .as_mut() - .map(|global| self.visit_to_chunk(global.loc.start(), Some(using.loc.end()), global)) - .transpose()?; + }; - let write_for_def = |fmt: &mut Self| { - fmt.grouped(|fmt| { - fmt.write_chunk(&for_chunk)?; - fmt.write_chunk(&ty_chunk)?; - if let Some(global_chunk) = global_chunk.as_ref() { - fmt.write_chunk(global_chunk)?; + let chunk = self.chunked(loc.start(), Some(stmt.loc().start()), |fmt| { + write_chunk!(fmt, "catch")?; + if let Some(ident) = ident.as_ref() { + fmt.write_postfix_comments_before( + param.as_ref().map(|p| p.loc.start()).unwrap_or_else(|| ident.loc.end()), + )?; + write_chunk!(fmt, ident.loc.start(), "{}", ident.name)?; } + if let Some(param) = param.as_mut() { + write_chunk_spaced!(fmt, param.loc.start(), Some(ident.is_none()), "(")?; + fmt.surrounded( + SurroundingChunk::new("", Some(param.loc.start()), None), + SurroundingChunk::new(")", None, Some(stmt.loc().start())), + |fmt, _| param.visit(fmt), + )?; + } + + stmt.visit(fmt)?; Ok(()) })?; - Ok(()) - }; - let simulated_for_def = self.simulate_to_string(write_for_def)?; + chunks.push(chunk); + } - if is_library { - let chunk = list_chunks.pop().unwrap(); - if self.will_chunk_fit(&format!("{{}} {simulated_for_def};"), &chunk)? { - self.write_chunk(&chunk)?; - write_for_def(self)?; - } else { - self.write_whitespace_separator(true)?; - self.grouped(|fmt| { - fmt.write_chunk(&chunk)?; - Ok(()) - })?; - self.write_whitespace_separator(true)?; - write_for_def(self)?; - } - } else { - self.surrounded( - SurroundingChunk::new("{", Some(using.loc.start()), None), - SurroundingChunk::new( - "}", - None, - Some(ty_start.or(global_start).unwrap_or(loc_end)), - ), - |fmt, _multiline| { - let multiline = fmt.are_chunks_separated_multiline( - &format!("{{ {{}} }} {simulated_for_def};"), - &list_chunks, - ",", - )?; - fmt.write_chunks_separated(&list_chunks, ",", multiline)?; - Ok(()) - }, - )?; - write_for_def(self)?; + let multiline = self.are_chunks_separated_multiline("{}", &chunks, "")?; + if !multiline { + self.write_chunks_separated(&chunks, "", false)?; + return Ok(()) } - self.write_semicolon()?; - - Ok(()) - } + let mut chunks = chunks.iter_mut().peekable(); + let mut prev_multiline = false; - #[instrument(name = "var_attribute", skip_all)] - fn visit_var_attribute(&mut self, attribute: &mut VariableAttribute) -> Result<()> { - return_source_if_disabled!(self, attribute.loc()); + // write try chunk first + if let Some(chunk) = chunks.next() { + let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; + write!(self.buf(), "{chunk_str}")?; + prev_multiline = chunk_str.contains('\n'); + } - let token = match attribute { - VariableAttribute::Visibility(visibility) => Some(visibility.to_string()), - VariableAttribute::Constant(_) => Some("constant".to_string()), - VariableAttribute::Immutable(_) => Some("immutable".to_string()), - VariableAttribute::Override(loc, idents) => { - write_chunk!(self, loc.start(), "override")?; - if !idents.is_empty() && self.config.override_spacing { - self.write_whitespace_separator(false)?; - } - self.visit_list("", idents, Some(loc.start()), Some(loc.end()), false)?; - None - } - }; - if let Some(token) = token { - let loc = attribute.loc(); - write_chunk!(self, loc.start(), loc.end(), "{}", token)?; + while let Some(chunk) = chunks.next() { + let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; + let multiline = chunk_str.contains('\n'); + self.indented_if(!multiline, 1, |fmt| { + chunk.needs_space = Some(false); + let on_same_line = prev_multiline && (multiline || chunks.peek().is_none()); + let prefix = if fmt.is_beginning_of_line() { + "" + } else if on_same_line { + " " + } else { + "\n" + }; + let chunk_str = format!("{prefix}{chunk_str}"); + write!(fmt.buf(), "{chunk_str}")?; + Ok(()) + })?; + prev_multiline = multiline; } Ok(()) } - #[instrument(name = "var_definition", skip_all)] - fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<()> { - return_source_if_disabled!(self, var.loc, ';'); - - var.ty.visit(self)?; - - let multiline = self.grouped(|fmt| { - let var_name = var.name.safe_unwrap_mut(); - let name_start = var_name.loc.start(); - - let attrs = fmt.items_to_chunks_sorted(Some(name_start), var.attrs.iter_mut())?; - if !fmt.try_on_single_line(|fmt| fmt.write_chunks_separated(&attrs, "", false))? { - fmt.write_chunks_separated(&attrs, "", true)?; - } + #[instrument(name = "if", skip_all)] + fn visit_if( + &mut self, + loc: Loc, + cond: &mut Expression, + if_branch: &mut Box, + else_branch: &mut Option>, + is_first_stmt: bool, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); - let mut name = fmt.visit_to_chunk(name_start, Some(var_name.loc.end()), var_name)?; - if var.initializer.is_some() { - name.content.push_str(" ="); - } - fmt.write_chunk(&name)?; + if !is_first_stmt { + self.write_if_stmt(loc, cond, if_branch, else_branch)?; + return Ok(()) + } + self.context.if_stmt_single_line = Some(true); + let mut stmt_fits_on_single = false; + let tx = self.transact(|fmt| { + stmt_fits_on_single = match fmt.write_if_stmt(loc, cond, if_branch, else_branch) { + Ok(()) => true, + Err(FormatterError::Fmt(_)) => false, + Err(err) => bail!(err), + }; Ok(()) })?; - var.initializer - .as_mut() - .map(|init| self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(init))) - .transpose()?; - - self.write_semicolon()?; + if stmt_fits_on_single { + tx.commit()?; + } else { + self.context.if_stmt_single_line = Some(false); + self.write_if_stmt(loc, cond, if_branch, else_branch)?; + } + self.context.if_stmt_single_line = None; Ok(()) } - #[instrument(name = "var_definition_stmt", skip_all)] - fn visit_var_definition_stmt( + #[instrument(name = "do_while", skip_all)] + fn visit_do_while( &mut self, loc: Loc, - declaration: &mut VariableDeclaration, - expr: &mut Option, - ) -> Result<()> { + body: &mut Statement, + cond: &mut Expression, + ) -> Result<(), Self::Error> { return_source_if_disabled!(self, loc, ';'); + write_chunk!(self, loc.start(), "do ")?; + self.visit_stmt_as_block(body, false)?; + visit_source_if_disabled_else!(self, loc.with_start(body.loc().end()), { + self.surrounded( + SurroundingChunk::new("while (", Some(cond.loc().start()), None), + SurroundingChunk::new(");", None, Some(loc.end())), + |fmt, _| cond.visit(fmt), + )?; + }); + Ok(()) + } - let declaration = self - .chunked(declaration.loc.start(), None, |fmt| fmt.visit_var_declaration(declaration))?; - let multiline = declaration.content.contains('\n'); - self.write_chunk(&declaration)?; - - if let Some(expr) = expr { - write!(self.buf(), " =")?; - self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(expr))?; - } + #[instrument(name = "while", skip_all)] + fn visit_while( + &mut self, + loc: Loc, + cond: &mut Expression, + body: &mut Statement, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + self.surrounded( + SurroundingChunk::new("while (", Some(loc.start()), None), + SurroundingChunk::new(")", None, Some(cond.loc().end())), + |fmt, _| { + cond.visit(fmt)?; + fmt.write_postfix_comments_before(body.loc().start()) + }, + )?; - self.write_semicolon() + let cond_close_paren_loc = + self.find_next_in_src(cond.loc().end(), ')').unwrap_or_else(|| cond.loc().end()); + let attempt_single_line = self.should_attempt_block_single_line(body, cond_close_paren_loc); + self.visit_stmt_as_block(body, attempt_single_line)?; + Ok(()) } #[instrument(name = "for", skip_all)] @@ -2928,416 +2970,485 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { fmt.write_whitespace_separator(true)?; } - match update { - Some(expr) => expr.visit(fmt), - None => Ok(()), + match update { + Some(expr) => expr.visit(fmt), + None => Ok(()), + } + }; + let multiline = !fmt.try_on_single_line(|fmt| write_for_loop_header(fmt, false))?; + if multiline { + write_for_loop_header(fmt, true)?; + } + Ok(()) + }, + )?; + match body { + Some(body) => { + self.visit_stmt_as_block(body, false)?; + } + None => { + self.write_empty_brackets()?; + } + }; + Ok(()) + } + + #[instrument(name = "function", skip_all)] + fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<()> { + if func.body.is_some() { + return_source_if_disabled!(self, func.loc()); + } else { + return_source_if_disabled!(self, func.loc(), ';'); + } + + self.with_function_context(func.clone(), |fmt| { + fmt.write_postfix_comments_before(func.loc.start())?; + fmt.write_prefix_comments_before(func.loc.start())?; + + let body_loc = func.body.as_ref().map(CodeLocation::loc); + let mut attrs_multiline = false; + let fits_on_single = fmt.try_on_single_line(|fmt| { + fmt.write_function_header(func, body_loc, false)?; + Ok(()) + })?; + if !fits_on_single { + attrs_multiline = fmt.write_function_header(func, body_loc, true)?; + } + + // write function body + match &mut func.body { + Some(body) => { + let body_loc = body.loc(); + let byte_offset = body_loc.start(); + let body = fmt.visit_to_chunk(byte_offset, Some(body_loc.end()), body)?; + fmt.write_whitespace_separator( + attrs_multiline && !(func.attributes.is_empty() && func.returns.is_empty()), + )?; + fmt.write_chunk(&body)?; + } + None => fmt.write_semicolon()?, + } + + Ok(()) + })?; + + Ok(()) + } + + #[instrument(name = "function_attribute", skip_all)] + fn visit_function_attribute(&mut self, attribute: &mut FunctionAttribute) -> Result<()> { + return_source_if_disabled!(self, attribute.loc()); + + match attribute { + FunctionAttribute::Mutability(mutability) => { + write_chunk!(self, mutability.loc().end(), "{mutability}")? + } + FunctionAttribute::Visibility(visibility) => { + // Visibility will always have a location in a Function attribute + write_chunk!(self, visibility.loc_opt().unwrap().end(), "{visibility}")? + } + FunctionAttribute::Virtual(loc) => write_chunk!(self, loc.end(), "virtual")?, + FunctionAttribute::Immutable(loc) => write_chunk!(self, loc.end(), "immutable")?, + FunctionAttribute::Override(loc, args) => { + write_chunk!(self, loc.start(), "override")?; + if !args.is_empty() && self.config.override_spacing { + self.write_whitespace_separator(false)?; + } + self.visit_list("", args, None, Some(loc.end()), false)? + } + FunctionAttribute::BaseOrModifier(loc, base) => { + // here we need to find out if this attribute belongs to the constructor because the + // modifier need to include the trailing parenthesis + // This is very ambiguous because the modifier can either by an inherited contract + // or a modifier here: e.g.: This is valid constructor: + // `constructor() public Ownable() OnlyOwner {}` + let is_constructor = self.context.is_constructor_function(); + // we can't make any decisions here regarding trailing `()` because we'd need to + // find out if the `base` is a solidity modifier or an + // interface/contract therefor we we its raw content. + + // we can however check if the contract `is` the `base`, this however also does + // not cover all cases + let is_contract_base = self.context.contract.as_ref().map_or(false, |contract| { + contract.base.iter().any(|contract_base| { + contract_base + .name + .identifiers + .iter() + .zip(&base.name.identifiers) + .all(|(l, r)| l.name == r.name) + }) + }); + + if is_contract_base { + base.visit(self)?; + } else if is_constructor { + // This is ambiguous because the modifier can either by an inherited + // contract modifiers with empty parenthesis are + // valid, but not required so we make the assumption + // here that modifiers are lowercase + let mut base_or_modifier = + self.visit_to_chunk(loc.start(), Some(loc.end()), base)?; + let is_lowercase = + base_or_modifier.content.chars().next().map_or(false, |c| c.is_lowercase()); + if is_lowercase && base_or_modifier.content.ends_with("()") { + base_or_modifier.content.truncate(base_or_modifier.content.len() - 2); + } + + self.write_chunk(&base_or_modifier)?; + } else { + let mut base_or_modifier = + self.visit_to_chunk(loc.start(), Some(loc.end()), base)?; + if base_or_modifier.content.ends_with("()") { + base_or_modifier.content.truncate(base_or_modifier.content.len() - 2); } - }; - let multiline = !fmt.try_on_single_line(|fmt| write_for_loop_header(fmt, false))?; - if multiline { - write_for_loop_header(fmt, true)?; + self.write_chunk(&base_or_modifier)?; } - Ok(()) - }, - )?; - match body { - Some(body) => { - self.visit_stmt_as_block(body, false)?; - } - None => { - self.write_empty_brackets()?; } + FunctionAttribute::Error(loc) => self.visit_parser_error(*loc)?, }; - Ok(()) - } - #[instrument(name = "while", skip_all)] - fn visit_while( - &mut self, - loc: Loc, - cond: &mut Expression, - body: &mut Statement, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - self.surrounded( - SurroundingChunk::new("while (", Some(loc.start()), None), - SurroundingChunk::new(")", None, Some(cond.loc().end())), - |fmt, _| { - cond.visit(fmt)?; - fmt.write_postfix_comments_before(body.loc().start()) - }, - )?; - - let cond_close_paren_loc = - self.find_next_in_src(cond.loc().end(), ')').unwrap_or_else(|| cond.loc().end()); - let attempt_single_line = self.should_attempt_block_single_line(body, cond_close_paren_loc); - self.visit_stmt_as_block(body, attempt_single_line)?; Ok(()) } - #[instrument(name = "do_while", skip_all)] - fn visit_do_while( - &mut self, - loc: Loc, - body: &mut Statement, - cond: &mut Expression, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); - write_chunk!(self, loc.start(), "do ")?; - self.visit_stmt_as_block(body, false)?; - visit_source_if_disabled_else!(self, loc.with_start(body.loc().end()), { - self.surrounded( - SurroundingChunk::new("while (", Some(cond.loc().start()), None), - SurroundingChunk::new(");", None, Some(loc.end())), - |fmt, _| cond.visit(fmt), - )?; - }); + #[instrument(name = "var_attribute", skip_all)] + fn visit_var_attribute(&mut self, attribute: &mut VariableAttribute) -> Result<()> { + return_source_if_disabled!(self, attribute.loc()); + + let token = match attribute { + VariableAttribute::Visibility(visibility) => Some(visibility.to_string()), + VariableAttribute::Constant(_) => Some("constant".to_string()), + VariableAttribute::Immutable(_) => Some("immutable".to_string()), + VariableAttribute::Override(loc, idents) => { + write_chunk!(self, loc.start(), "override")?; + if !idents.is_empty() && self.config.override_spacing { + self.write_whitespace_separator(false)?; + } + self.visit_list("", idents, Some(loc.start()), Some(loc.end()), false)?; + None + } + }; + if let Some(token) = token { + let loc = attribute.loc(); + write_chunk!(self, loc.start(), loc.end(), "{}", token)?; + } Ok(()) } - #[instrument(name = "if", skip_all)] - fn visit_if( - &mut self, - loc: Loc, - cond: &mut Expression, - if_branch: &mut Box, - else_branch: &mut Option>, - is_first_stmt: bool, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - - if !is_first_stmt { - self.write_if_stmt(loc, cond, if_branch, else_branch)?; - return Ok(()) - } + #[instrument(name = "base", skip_all)] + fn visit_base(&mut self, base: &mut Base) -> Result<()> { + return_source_if_disabled!(self, base.loc); - self.context.if_stmt_single_line = Some(true); - let mut stmt_fits_on_single = false; - let tx = self.transact(|fmt| { - stmt_fits_on_single = match fmt.write_if_stmt(loc, cond, if_branch, else_branch) { - Ok(()) => true, - Err(FormatterError::Fmt(_)) => false, - Err(err) => bail!(err), - }; + let name_loc = &base.name.loc; + let mut name = self.chunked(name_loc.start(), Some(name_loc.end()), |fmt| { + fmt.visit_ident_path(&mut base.name)?; Ok(()) })?; - if stmt_fits_on_single { - tx.commit()?; - } else { - self.context.if_stmt_single_line = Some(false); - self.write_if_stmt(loc, cond, if_branch, else_branch)?; + if base.args.is_none() || base.args.as_ref().unwrap().is_empty() { + // This is ambiguous because the modifier can either by an inherited contract or a + // modifier + if self.context.function.is_some() { + name.content.push_str("()"); + } + self.write_chunk(&name)?; + return Ok(()) } - self.context.if_stmt_single_line = None; - Ok(()) - } + let args = base.args.as_mut().unwrap(); + let args_start = CodeLocation::loc(args.first().unwrap()).start(); - #[instrument(name = "args", skip_all)] - fn visit_args(&mut self, loc: Loc, args: &mut Vec) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); + name.content.push('('); + let formatted_name = self.chunk_to_string(&name)?; - write!(self.buf(), "{{")?; + let multiline = !self.will_it_fit(&formatted_name); - let mut args_iter = args.iter_mut().peekable(); - let mut chunks = Vec::new(); - while let Some(NamedArgument { loc: arg_loc, name, expr }) = args_iter.next() { - let next_byte_offset = args_iter - .peek() - .map(|NamedArgument { loc: arg_loc, .. }| arg_loc.start()) - .unwrap_or_else(|| loc.end()); - chunks.push(self.chunked(arg_loc.start(), Some(next_byte_offset), |fmt| { - fmt.grouped(|fmt| { - write_chunk!(fmt, name.loc.start(), "{}: ", name.name)?; - expr.visit(fmt) - })?; + self.surrounded( + SurroundingChunk::new(&formatted_name, Some(args_start), None), + SurroundingChunk::new(")", None, Some(base.loc.end())), + |fmt, multiline_hint| { + let args = fmt.items_to_chunks( + Some(base.loc.end()), + args.iter_mut().map(|arg| (arg.loc(), arg)), + )?; + let multiline = multiline || + multiline_hint || + fmt.are_chunks_separated_multiline("{}", &args, ",")?; + fmt.write_chunks_separated(&args, ",", multiline)?; Ok(()) - })?); - } + }, + )?; - if let Some(first) = chunks.first_mut() { - if first.prefixes.is_empty() && - first.postfixes_before.is_empty() && - !self.config.bracket_spacing - { - first.needs_space = Some(false); + Ok(()) + } + + #[instrument(name = "parameter", skip_all)] + fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<()> { + return_source_if_disabled!(self, parameter.loc); + self.grouped(|fmt| { + parameter.ty.visit(fmt)?; + if let Some(storage) = ¶meter.storage { + write_chunk!(fmt, storage.loc().end(), "{storage}")?; } - } - let multiline = self.are_chunks_separated_multiline("{}}", &chunks, ",")?; - self.indented_if(multiline, 1, |fmt| fmt.write_chunks_separated(&chunks, ",", multiline))?; + if let Some(name) = ¶meter.name { + write_chunk!(fmt, parameter.loc.end(), "{}", name.name)?; + } + Ok(()) + })?; + Ok(()) + } - let prefix = if multiline && !self.is_beginning_of_line() { - "\n" - } else if self.config.bracket_spacing { - " " - } else { - "" - }; - let closing_bracket = format!("{prefix}{}", "}"); - let closing_bracket_loc = args.last().unwrap().loc.end(); - write_chunk!(self, closing_bracket_loc, "{closing_bracket}")?; + #[instrument(name = "struct", skip_all)] + fn visit_struct(&mut self, structure: &mut StructDefinition) -> Result<()> { + return_source_if_disabled!(self, structure.loc); + self.grouped(|fmt| { + let struct_name = structure.name.safe_unwrap_mut(); + write_chunk!(fmt, struct_name.loc.start(), "struct")?; + struct_name.visit(fmt)?; + if structure.fields.is_empty() { + return fmt.write_empty_brackets() + } + + write!(fmt.buf(), " {{")?; + fmt.surrounded( + SurroundingChunk::new("", Some(struct_name.loc.end()), None), + SurroundingChunk::new("}", None, Some(structure.loc.end())), + |fmt, _multiline| { + let chunks = fmt.items_to_chunks( + Some(structure.loc.end()), + structure.fields.iter_mut().map(|ident| (ident.loc, ident)), + )?; + for mut chunk in chunks { + chunk.content.push(';'); + fmt.write_chunk(&chunk)?; + fmt.write_whitespace_separator(true)?; + } + Ok(()) + }, + ) + })?; Ok(()) } - #[instrument(name = "revert", skip_all)] - fn visit_revert( - &mut self, - loc: Loc, - error: &mut Option, - args: &mut Vec, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); - write_chunk!(self, loc.start(), "revert")?; - if let Some(error) = error { - error.visit(self)?; + #[instrument(name = "event", skip_all)] + fn visit_event(&mut self, event: &mut EventDefinition) -> Result<()> { + return_source_if_disabled!(self, event.loc, ';'); + + let event_name = event.name.safe_unwrap_mut(); + let mut name = + self.visit_to_chunk(event_name.loc.start(), Some(event.loc.end()), event_name)?; + name.content = format!("event {}(", name.content); + + let last_chunk = if event.anonymous { ") anonymous;" } else { ");" }; + if event.fields.is_empty() { + name.content.push_str(last_chunk); + self.write_chunk(&name)?; + } else { + let byte_offset = event.fields.first().unwrap().loc.start(); + let first_chunk = self.chunk_to_string(&name)?; + self.surrounded( + SurroundingChunk::new(first_chunk, Some(byte_offset), None), + SurroundingChunk::new(last_chunk, None, Some(event.loc.end())), + |fmt, multiline| { + let params = fmt + .items_to_chunks(None, event.fields.iter_mut().map(|arg| (arg.loc, arg)))?; + + let multiline = + multiline && fmt.are_chunks_separated_multiline("{}", ¶ms, ",")?; + fmt.write_chunks_separated(¶ms, ",", multiline) + }, + )?; } - self.visit_list("", args, None, Some(loc.end()), true)?; - self.write_semicolon()?; Ok(()) } - #[instrument(name = "revert_named_args", skip_all)] - fn visit_revert_named_args( - &mut self, - loc: Loc, - error: &mut Option, - args: &mut Vec, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); + #[instrument(name = "event_parameter", skip_all)] + fn visit_event_parameter(&mut self, param: &mut EventParameter) -> Result<()> { + return_source_if_disabled!(self, param.loc); - write_chunk!(self, loc.start(), "revert")?; - let mut error_indented = false; - if let Some(error) = error { - if !self.try_on_single_line(|fmt| error.visit(fmt))? { - error.visit(self)?; - error_indented = true; + self.grouped(|fmt| { + param.ty.visit(fmt)?; + if param.indexed { + write_chunk!(fmt, param.loc.start(), "indexed")?; } - } - - if args.is_empty() { - write!(self.buf(), "({{}});")?; - return Ok(()) - } - - write!(self.buf(), "(")?; - self.indented_if(error_indented, 1, |fmt| fmt.visit_args(loc, args))?; - write!(self.buf(), ")")?; - self.write_semicolon()?; - + if let Some(name) = ¶m.name { + write_chunk!(fmt, name.loc.end(), "{}", name.name)?; + } + Ok(()) + })?; Ok(()) } - #[instrument(name = "return", skip_all)] - fn visit_return(&mut self, loc: Loc, expr: &mut Option) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); - - self.write_postfix_comments_before(loc.start())?; - self.write_prefix_comments_before(loc.start())?; + #[instrument(name = "error", skip_all)] + fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result<()> { + return_source_if_disabled!(self, error.loc, ';'); - if expr.is_none() { - write_chunk!(self, loc.end(), "return;")?; - return Ok(()) - } + let error_name = error.name.safe_unwrap_mut(); + let mut name = self.visit_to_chunk(error_name.loc.start(), None, error_name)?; + name.content = format!("error {}", name.content); - let expr = expr.as_mut().unwrap(); - let expr_loc_start = expr.loc().start(); - let write_return = |fmt: &mut Self| -> Result<()> { - write_chunk!(fmt, loc.start(), "return")?; - fmt.write_postfix_comments_before(expr_loc_start)?; - Ok(()) - }; + let formatted_name = self.chunk_to_string(&name)?; + write!(self.buf(), "{formatted_name}")?; + let start_offset = error.fields.first().map(|f| f.loc.start()); + self.visit_list("", &mut error.fields, start_offset, Some(error.loc.end()), true)?; + self.write_semicolon()?; - let mut write_return_with_expr = |fmt: &mut Self| -> Result<()> { - let fits_on_single = fmt.try_on_single_line(|fmt| { - write_return(fmt)?; - expr.visit(fmt) - })?; - if fits_on_single { - return Ok(()) - } + Ok(()) + } - let mut fit_on_next_line = false; - let tx = fmt.transact(|fmt| { - fmt.grouped(|fmt| { - write_return(fmt)?; - if !fmt.is_beginning_of_line() { - fmt.write_whitespace_separator(true)?; - } - fit_on_next_line = fmt.try_on_single_line(|fmt| expr.visit(fmt))?; - Ok(()) - })?; - Ok(()) - })?; - if fit_on_next_line { - tx.commit()?; - return Ok(()) + #[instrument(name = "error_parameter", skip_all)] + fn visit_error_parameter(&mut self, param: &mut ErrorParameter) -> Result<()> { + return_source_if_disabled!(self, param.loc); + self.grouped(|fmt| { + param.ty.visit(fmt)?; + if let Some(name) = ¶m.name { + write_chunk!(fmt, name.loc.end(), "{}", name.name)?; } - - write_return(fmt)?; - expr.visit(fmt)?; Ok(()) - }; - - write_return_with_expr(self)?; - write_chunk!(self, loc.end(), ";")?; + })?; Ok(()) } - #[instrument(name = "try", skip_all)] - fn visit_try( - &mut self, - loc: Loc, - expr: &mut Expression, - returns: &mut Option<(Vec<(Loc, Option)>, Box)>, - clauses: &mut Vec, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - - let try_next_byte = clauses.first().map(|c| match c { - CatchClause::Simple(loc, ..) => loc.start(), - CatchClause::Named(loc, ..) => loc.start(), - }); - let try_chunk = self.chunked(loc.start(), try_next_byte, |fmt| { - write_chunk!(fmt, loc.start(), expr.loc().start(), "try")?; - expr.visit(fmt)?; - if let Some((params, stmt)) = returns { - let mut params = - params.iter_mut().filter(|(_, param)| param.is_some()).collect::>(); - let byte_offset = params.first().map_or(stmt.loc().start(), |p| p.0.start()); - fmt.surrounded( - SurroundingChunk::new("returns (", Some(byte_offset), None), - SurroundingChunk::new(")", None, params.last().map(|p| p.0.end())), - |fmt, _| { - let chunks = fmt.items_to_chunks( - Some(stmt.loc().start()), - params.iter_mut().map(|(loc, ref mut ident)| (*loc, ident)), - )?; - let multiline = fmt.are_chunks_separated_multiline("{})", &chunks, ",")?; - fmt.write_chunks_separated(&chunks, ",", multiline)?; - Ok(()) - }, - )?; - stmt.visit(fmt)?; - } + #[instrument(name = "type_definition", skip_all)] + fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<()> { + return_source_if_disabled!(self, def.loc, ';'); + self.grouped(|fmt| { + write_chunk!(fmt, def.loc.start(), def.name.loc.start(), "type")?; + def.name.visit(fmt)?; + write_chunk!(fmt, def.name.loc.end(), CodeLocation::loc(&def.ty).start(), "is")?; + def.ty.visit(fmt)?; + fmt.write_semicolon()?; Ok(()) })?; + Ok(()) + } - let mut chunks = vec![try_chunk]; - for clause in clauses { - let (loc, ident, mut param, stmt) = match clause { - CatchClause::Simple(loc, param, stmt) => (loc, None, param.as_mut(), stmt), - CatchClause::Named(loc, ident, param, stmt) => { - (loc, Some(ident), Some(param), stmt) - } - }; - - let chunk = self.chunked(loc.start(), Some(stmt.loc().start()), |fmt| { - write_chunk!(fmt, "catch")?; - if let Some(ident) = ident.as_ref() { - fmt.write_postfix_comments_before( - param.as_ref().map(|p| p.loc.start()).unwrap_or_else(|| ident.loc.end()), - )?; - write_chunk!(fmt, ident.loc.start(), "{}", ident.name)?; - } - if let Some(param) = param.as_mut() { - write_chunk_spaced!(fmt, param.loc.start(), Some(ident.is_none()), "(")?; - fmt.surrounded( - SurroundingChunk::new("", Some(param.loc.start()), None), - SurroundingChunk::new(")", None, Some(stmt.loc().start())), - |fmt, _| param.visit(fmt), - )?; - } - - stmt.visit(fmt)?; - Ok(()) - })?; - - chunks.push(chunk); - } - - let multiline = self.are_chunks_separated_multiline("{}", &chunks, "")?; - if !multiline { - self.write_chunks_separated(&chunks, "", false)?; - return Ok(()) - } + #[instrument(name = "stray_semicolon", skip_all)] + fn visit_stray_semicolon(&mut self) -> Result<()> { + self.write_semicolon() + } - let mut chunks = chunks.iter_mut().peekable(); - let mut prev_multiline = false; + #[instrument(name = "opening_paren", skip_all)] + fn visit_opening_paren(&mut self) -> Result<()> { + write_chunk!(self, "(")?; + Ok(()) + } - // write try chunk first - if let Some(chunk) = chunks.next() { - let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; - write!(self.buf(), "{chunk_str}")?; - prev_multiline = chunk_str.contains('\n'); - } + #[instrument(name = "closing_paren", skip_all)] + fn visit_closing_paren(&mut self) -> Result<()> { + write_chunk!(self, ")")?; + Ok(()) + } - while let Some(chunk) = chunks.next() { - let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; - let multiline = chunk_str.contains('\n'); - self.indented_if(!multiline, 1, |fmt| { - chunk.needs_space = Some(false); - let on_same_line = prev_multiline && (multiline || chunks.peek().is_none()); - let prefix = if fmt.is_beginning_of_line() { - "" - } else if on_same_line { - " " - } else { - "\n" - }; - let chunk_str = format!("{prefix}{chunk_str}"); - write!(fmt.buf(), "{chunk_str}")?; - Ok(()) - })?; - prev_multiline = multiline; - } + #[instrument(name = "newline", skip_all)] + fn visit_newline(&mut self) -> Result<()> { + writeln_chunk!(self)?; Ok(()) } - #[instrument(name = "assembly", skip_all)] - fn visit_assembly( - &mut self, - loc: Loc, - dialect: &mut Option, - block: &mut YulBlock, - flags: &mut Option>, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); + #[instrument(name = "using", skip_all)] + fn visit_using(&mut self, using: &mut Using) -> Result<()> { + return_source_if_disabled!(self, using.loc, ';'); - write_chunk!(self, loc.start(), "assembly")?; - if let Some(StringLiteral { loc, string, .. }) = dialect { - write_chunk!(self, loc.start(), loc.end(), "\"{string}\"")?; - } - if let Some(flags) = flags { - if !flags.is_empty() { - let loc_start = flags.first().unwrap().loc.start(); - self.surrounded( - SurroundingChunk::new("(", Some(loc_start), None), - SurroundingChunk::new(")", None, Some(block.loc.start())), - |fmt, _| { - let mut flags = flags.iter_mut().peekable(); - let mut chunks = vec![]; - while let Some(flag) = flags.next() { - let next_byte_offset = - flags.peek().map(|next_flag| next_flag.loc.start()); - chunks.push(fmt.chunked( - flag.loc.start(), - next_byte_offset, - |fmt| { - write!(fmt.buf(), "\"{}\"", flag.string)?; - Ok(()) - }, - )?); + write_chunk!(self, using.loc.start(), "using")?; + + let ty_start = using.ty.as_mut().map(|ty| CodeLocation::loc(&ty).start()); + let global_start = using.global.as_mut().map(|global| global.loc.start()); + let loc_end = using.loc.end(); + + let (is_library, mut list_chunks) = match &mut using.list { + UsingList::Library(library) => { + (true, vec![self.visit_to_chunk(library.loc.start(), None, library)?]) + } + UsingList::Functions(funcs) => { + let mut funcs = funcs.iter_mut().peekable(); + let mut chunks = Vec::new(); + while let Some(func) = funcs.next() { + let next_byte_end = funcs.peek().map(|func| func.loc.start()); + chunks.push(self.chunked(func.loc.start(), next_byte_end, |fmt| { + fmt.visit_ident_path(&mut func.path)?; + if let Some(op) = func.oper { + write!(fmt.buf(), " as {op}")?; } - fmt.write_chunks_separated(&chunks, ",", false)?; Ok(()) - }, - )?; + })?); + } + (false, chunks) + } + UsingList::Error => return self.visit_parser_error(using.loc), + }; + + let for_chunk = self.chunk_at( + using.loc.start(), + Some(ty_start.or(global_start).unwrap_or(loc_end)), + None, + "for", + ); + let ty_chunk = if let Some(ty) = &mut using.ty { + self.visit_to_chunk(ty.loc().start(), Some(global_start.unwrap_or(loc_end)), ty)? + } else { + self.chunk_at(using.loc.start(), Some(global_start.unwrap_or(loc_end)), None, "*") + }; + let global_chunk = using + .global + .as_mut() + .map(|global| self.visit_to_chunk(global.loc.start(), Some(using.loc.end()), global)) + .transpose()?; + + let write_for_def = |fmt: &mut Self| { + fmt.grouped(|fmt| { + fmt.write_chunk(&for_chunk)?; + fmt.write_chunk(&ty_chunk)?; + if let Some(global_chunk) = global_chunk.as_ref() { + fmt.write_chunk(global_chunk)?; + } + Ok(()) + })?; + Ok(()) + }; + + let simulated_for_def = self.simulate_to_string(write_for_def)?; + + if is_library { + let chunk = list_chunks.pop().unwrap(); + if self.will_chunk_fit(&format!("{{}} {simulated_for_def};"), &chunk)? { + self.write_chunk(&chunk)?; + write_for_def(self)?; + } else { + self.write_whitespace_separator(true)?; + self.grouped(|fmt| { + fmt.write_chunk(&chunk)?; + Ok(()) + })?; + self.write_whitespace_separator(true)?; + write_for_def(self)?; } + } else { + self.surrounded( + SurroundingChunk::new("{", Some(using.loc.start()), None), + SurroundingChunk::new( + "}", + None, + Some(ty_start.or(global_start).unwrap_or(loc_end)), + ), + |fmt, _multiline| { + let multiline = fmt.are_chunks_separated_multiline( + &format!("{{ {{}} }} {simulated_for_def};"), + &list_chunks, + ",", + )?; + fmt.write_chunks_separated(&list_chunks, ",", multiline)?; + Ok(()) + }, + )?; + write_for_def(self)?; } - block.visit(self) + self.write_semicolon()?; + + Ok(()) } #[instrument(name = "yul_block", skip_all)] @@ -3352,38 +3463,6 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Ok(()) } - #[instrument(name = "yul_assignment", skip_all)] - fn visit_yul_assignment( - &mut self, - loc: Loc, - exprs: &mut Vec, - expr: &mut Option<&mut YulExpression>, - ) -> Result<(), Self::Error> - where - T: Visitable + CodeLocation, - { - return_source_if_disabled!(self, loc); - - self.grouped(|fmt| { - let chunks = - fmt.items_to_chunks(None, exprs.iter_mut().map(|expr| (expr.loc(), expr)))?; - - let multiline = fmt.are_chunks_separated_multiline("{} := ", &chunks, ",")?; - fmt.write_chunks_separated(&chunks, ",", multiline)?; - - if let Some(expr) = expr { - write_chunk!(fmt, expr.loc().start(), ":=")?; - let chunk = fmt.visit_to_chunk(expr.loc().start(), Some(loc.end()), expr)?; - if !fmt.will_chunk_fit("{}", &chunk)? { - fmt.write_whitespace_separator(true)?; - } - fmt.write_chunk(&chunk)?; - } - Ok(()) - })?; - Ok(()) - } - #[instrument(name = "yul_expr", skip_all)] fn visit_yul_expr(&mut self, expr: &mut YulExpression) -> Result<(), Self::Error> { return_source_if_disabled!(self, expr.loc()); @@ -3428,6 +3507,38 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { } } + #[instrument(name = "yul_assignment", skip_all)] + fn visit_yul_assignment( + &mut self, + loc: Loc, + exprs: &mut Vec, + expr: &mut Option<&mut YulExpression>, + ) -> Result<(), Self::Error> + where + T: Visitable + CodeLocation, + { + return_source_if_disabled!(self, loc); + + self.grouped(|fmt| { + let chunks = + fmt.items_to_chunks(None, exprs.iter_mut().map(|expr| (expr.loc(), expr)))?; + + let multiline = fmt.are_chunks_separated_multiline("{} := ", &chunks, ",")?; + fmt.write_chunks_separated(&chunks, ",", multiline)?; + + if let Some(expr) = expr { + write_chunk!(fmt, expr.loc().start(), ":=")?; + let chunk = fmt.visit_to_chunk(expr.loc().start(), Some(loc.end()), expr)?; + if !fmt.will_chunk_fit("{}", &chunk)? { + fmt.write_whitespace_separator(true)?; + } + fmt.write_chunk(&chunk)?; + } + Ok(()) + })?; + Ok(()) + } + #[instrument(name = "yul_for", skip_all)] fn visit_yul_for(&mut self, stmt: &mut YulFor) -> Result<(), Self::Error> { return_source_if_disabled!(self, stmt.loc); @@ -3446,12 +3557,6 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { self.visit_list("", &mut stmt.arguments, None, Some(stmt.loc.end()), true) } - #[instrument(name = "yul_typed_ident", skip_all)] - fn visit_yul_typed_ident(&mut self, ident: &mut YulTypedIdentifier) -> Result<(), Self::Error> { - return_source_if_disabled!(self, ident.loc); - self.visit_yul_string_with_ident(ident.loc, &ident.id.name, &mut ident.ty) - } - #[instrument(name = "yul_fun_def", skip_all)] fn visit_yul_fun_def(&mut self, stmt: &mut YulFunctionDefinition) -> Result<(), Self::Error> { return_source_if_disabled!(self, stmt.loc); @@ -3540,16 +3645,10 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Ok(()) } - // Support extension for Solana/Substrate - #[instrument(name = "annotation", skip_all)] - fn visit_annotation(&mut self, annotation: &mut Annotation) -> Result<()> { - return_source_if_disabled!(self, annotation.loc); - let id = self.simulate_to_string(|fmt| annotation.id.visit(fmt))?; - write!(self.buf(), "@{id}")?; - write!(self.buf(), "(")?; - annotation.value.visit(self)?; - write!(self.buf(), ")")?; - Ok(()) + #[instrument(name = "yul_typed_ident", skip_all)] + fn visit_yul_typed_ident(&mut self, ident: &mut YulTypedIdentifier) -> Result<(), Self::Error> { + return_source_if_disabled!(self, ident.loc); + self.visit_yul_string_with_ident(ident.loc, &ident.id.name, &mut ident.ty) } #[instrument(name = "parser_error", skip_all)] @@ -3557,3 +3656,43 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Err(FormatterError::InvalidParsedItem(loc)) } } + +/// An action which may be committed to a Formatter +struct Transaction<'f, 'a, W> { + fmt: &'f mut Formatter<'a, W>, + buffer: String, + comments: Comments, +} + +impl<'f, 'a, W> std::ops::Deref for Transaction<'f, 'a, W> { + type Target = Formatter<'a, W>; + fn deref(&self) -> &Self::Target { + self.fmt + } +} + +impl<'f, 'a, W> std::ops::DerefMut for Transaction<'f, 'a, W> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.fmt + } +} + +impl<'f, 'a, W: Write> Transaction<'f, 'a, W> { + /// Create a new transaction from a callback + fn new( + fmt: &'f mut Formatter<'a, W>, + fun: impl FnMut(&mut Formatter<'a, W>) -> Result<()>, + ) -> Result { + let mut comments = fmt.comments.clone(); + let buffer = fmt.with_temp_buf(fun)?.w; + comments = std::mem::replace(&mut fmt.comments, comments); + Ok(Self { fmt, buffer, comments }) + } + + /// Commit the transaction to the Formatter + fn commit(self) -> Result { + self.fmt.comments = self.comments; + write_chunk!(self.fmt, "{}", self.buffer)?; + Ok(self.buffer) + } +} diff --git a/crates/fmt/src/helpers.rs b/crates/fmt/src/helpers.rs index 9d2988a6342b8..9a31edeb0901b 100644 --- a/crates/fmt/src/helpers.rs +++ b/crates/fmt/src/helpers.rs @@ -46,7 +46,10 @@ pub fn format_to( /// Parse and format a string with default settings pub fn format(src: &str) -> Result { - let parsed = parse(src).map_err(|_| FormatterError::Fmt(std::fmt::Error))?; + let parsed = parse(src).map_err(|err| { + debug!(?err, "Parse error"); + FormatterError::Fmt(std::fmt::Error) + })?; let mut output = String::new(); format_to(&mut output, parsed, FormatterConfig::default())?; @@ -78,6 +81,10 @@ pub fn print_diagnostics_report( path: Option<&Path>, diagnostics: Vec, ) -> std::io::Result<()> { + if diagnostics.is_empty() { + return Ok(()); + } + let filename = path.map(|p| p.file_name().unwrap().to_string_lossy().to_string()).unwrap_or_default(); for diag in diagnostics { @@ -105,3 +112,15 @@ pub fn import_path_string(path: &ImportPath) -> String { ImportPath::Path(p) => p.to_string(), } } + +#[cfg(test)] +mod tests { + use super::*; + + // + #[test] + fn test_interface_format() { + let s = "interface I {\n function increment() external;\n function number() external view returns (uint256);\n function setNumber(uint256 newNumber) external;\n}"; + let _formatted = format(s).unwrap(); + } +} diff --git a/crates/fmt/src/inline_config.rs b/crates/fmt/src/inline_config.rs index b0e3893d50da4..702669fe518bd 100644 --- a/crates/fmt/src/inline_config.rs +++ b/crates/fmt/src/inline_config.rs @@ -5,7 +5,7 @@ use std::{fmt, str::FromStr}; /// An inline config item #[allow(clippy::enum_variant_names)] -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub enum InlineConfigItem { /// Disables the next code item regardless of newlines DisableNextItem, @@ -63,7 +63,7 @@ impl DisabledRange { /// This is a list of Inline Config items for locations in a source file. This is /// usually acquired by parsing the comments for an `forgefmt:` items. See /// [`Comments::parse_inline_config_items`] for details. -#[derive(Default, Debug)] +#[derive(Debug, Default)] pub struct InlineConfig { disabled_ranges: Vec, } diff --git a/crates/fmt/src/string.rs b/crates/fmt/src/string.rs index 76f3a0df6c7dc..607c890e7c7ca 100644 --- a/crates/fmt/src/string.rs +++ b/crates/fmt/src/string.rs @@ -4,7 +4,7 @@ /// This is a simplified version of the /// [actual parser](https://docs.soliditylang.org/en/v0.8.15/grammar.html#a4.SolidityLexer.EscapeSequence) /// as we don't care about hex or other character meanings -#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum QuoteState { /// Not currently in quoted string #[default] @@ -65,7 +65,7 @@ impl<'a> Iterator for QuoteStateCharIndices<'a> { } } -/// An iterator over the the indices of quoted string locations +/// An iterator over the indices of quoted string locations pub struct QuotedRanges<'a>(QuoteStateCharIndices<'a>); impl<'a> QuotedRanges<'a> { diff --git a/crates/fmt/testdata/BlockComments/fmt.sol b/crates/fmt/testdata/BlockComments/fmt.sol new file mode 100644 index 0000000000000..1d7025f2ad591 --- /dev/null +++ b/crates/fmt/testdata/BlockComments/fmt.sol @@ -0,0 +1,25 @@ +contract CounterTest is Test { + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev See {IERC721-balanceOf}. + */ + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } +} diff --git a/crates/fmt/testdata/BlockComments/original.sol b/crates/fmt/testdata/BlockComments/original.sol new file mode 100644 index 0000000000000..b91934bf7d12b --- /dev/null +++ b/crates/fmt/testdata/BlockComments/original.sol @@ -0,0 +1,26 @@ +contract CounterTest is Test { + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev See {IERC721-balanceOf}. + */ + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); +} + +} \ No newline at end of file diff --git a/crates/fmt/testdata/BlockCommentsFunction/fmt.sol b/crates/fmt/testdata/BlockCommentsFunction/fmt.sol new file mode 100644 index 0000000000000..368749bf4fdaf --- /dev/null +++ b/crates/fmt/testdata/BlockCommentsFunction/fmt.sol @@ -0,0 +1,20 @@ +contract A { + Counter public counter; + /** + * TODO: this fuzz use too much time to execute + * function testGetFuzz(bytes[2][] memory kvs) public { + * for (uint256 i = 0; i < kvs.length; i++) { + * bytes32 root = trie.update(kvs[i][0], kvs[i][1]); + * console.logBytes32(root); + * } + * + * for (uint256 i = 0; i < kvs.length; i++) { + * (bool exist, bytes memory value) = trie.get(kvs[i][0]); + * console.logBool(exist); + * console.logBytes(value); + * require(exist); + * require(BytesSlice.equal(value, trie.getRaw(kvs[i][0]))); + * } + * } + */ +} diff --git a/crates/fmt/testdata/BlockCommentsFunction/original.sol b/crates/fmt/testdata/BlockCommentsFunction/original.sol new file mode 100644 index 0000000000000..089f1bac430cd --- /dev/null +++ b/crates/fmt/testdata/BlockCommentsFunction/original.sol @@ -0,0 +1,20 @@ +contract A { + Counter public counter; + /** + * TODO: this fuzz use too much time to execute + function testGetFuzz(bytes[2][] memory kvs) public { + for (uint256 i = 0; i < kvs.length; i++) { + bytes32 root = trie.update(kvs[i][0], kvs[i][1]); + console.logBytes32(root); + } + + for (uint256 i = 0; i < kvs.length; i++) { + (bool exist, bytes memory value) = trie.get(kvs[i][0]); + console.logBool(exist); + console.logBytes(value); + require(exist); + require(BytesSlice.equal(value, trie.getRaw(kvs[i][0]))); + } + } + */ +} \ No newline at end of file diff --git a/crates/fmt/testdata/ConstructorModifierStyle/fmt.sol b/crates/fmt/testdata/ConstructorModifierStyle/fmt.sol new file mode 100644 index 0000000000000..88694860aded2 --- /dev/null +++ b/crates/fmt/testdata/ConstructorModifierStyle/fmt.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.5.2; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ERC1155} from "solmate/tokens/ERC1155.sol"; + +import {IAchievements} from "./interfaces/IAchievements.sol"; +import {SoulBound1155} from "./abstracts/SoulBound1155.sol"; + +contract Achievements is IAchievements, SoulBound1155, Ownable { + constructor(address owner) Ownable() ERC1155() {} +} diff --git a/crates/fmt/testdata/ConstructorModifierStyle/original.sol b/crates/fmt/testdata/ConstructorModifierStyle/original.sol new file mode 100644 index 0000000000000..88694860aded2 --- /dev/null +++ b/crates/fmt/testdata/ConstructorModifierStyle/original.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.5.2; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ERC1155} from "solmate/tokens/ERC1155.sol"; + +import {IAchievements} from "./interfaces/IAchievements.sol"; +import {SoulBound1155} from "./abstracts/SoulBound1155.sol"; + +contract Achievements is IAchievements, SoulBound1155, Ownable { + constructor(address owner) Ownable() ERC1155() {} +} diff --git a/crates/fmt/testdata/EnumVariants/fmt.sol b/crates/fmt/testdata/EnumVariants/fmt.sol new file mode 100644 index 0000000000000..b33b8846984d2 --- /dev/null +++ b/crates/fmt/testdata/EnumVariants/fmt.sol @@ -0,0 +1,19 @@ +interface I { + enum Empty {} + + /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. + enum CallerMode { + /// No caller modification is currently active. + None + } + + /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. + enum CallerMode2 { + /// No caller modification is currently active. + None, + /// No caller modification is currently active2. + Some + } + + function bar() public {} +} diff --git a/crates/fmt/testdata/EnumVariants/original.sol b/crates/fmt/testdata/EnumVariants/original.sol new file mode 100644 index 0000000000000..8e146ae0fb574 --- /dev/null +++ b/crates/fmt/testdata/EnumVariants/original.sol @@ -0,0 +1,23 @@ +interface I { + enum Empty { + + } + + /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. + enum CallerMode + {/// No caller modification is currently active. + None + } + + /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. + enum CallerMode2 + {/// No caller modification is currently active. + None,/// No caller modification is currently active2. + + Some + } + + function bar() public { + + } +} \ No newline at end of file diff --git a/crates/fmt/testdata/Repros/fmt.sol b/crates/fmt/testdata/Repros/fmt.sol index 8439563ab4e76..dc1ac24eb3a05 100644 --- a/crates/fmt/testdata/Repros/fmt.sol +++ b/crates/fmt/testdata/Repros/fmt.sol @@ -5,3 +5,15 @@ function errorIdentifier() { bytes memory error = bytes(""); if (error.length > 0) {} } + +// https://github.com/foundry-rs/foundry/issues/7549 +function one() external { + this.other({ + data: abi.encodeCall( + this.other, + ( + "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla" + ) + ) + }); +} diff --git a/crates/fmt/testdata/Repros/original.sol b/crates/fmt/testdata/Repros/original.sol index 8439563ab4e76..cee4fc97a6af8 100644 --- a/crates/fmt/testdata/Repros/original.sol +++ b/crates/fmt/testdata/Repros/original.sol @@ -5,3 +5,15 @@ function errorIdentifier() { bytes memory error = bytes(""); if (error.length > 0) {} } + +// https://github.com/foundry-rs/foundry/issues/7549 +function one() external { + this.other({ + data: abi.encodeCall( + this.other, + ( + "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla" + ) + ) + }); +} diff --git a/crates/fmt/testdata/SortedImports/fmt.sol b/crates/fmt/testdata/SortedImports/fmt.sol new file mode 100644 index 0000000000000..f9b2c0ee2a9c3 --- /dev/null +++ b/crates/fmt/testdata/SortedImports/fmt.sol @@ -0,0 +1,34 @@ +// config: sort_imports = true +import "SomeFile0.sol" as SomeOtherFile; +import "SomeFile1.sol" as SomeOtherFile; +import "SomeFile2.sol"; +import "SomeFile3.sol"; + +import "AnotherFile1.sol" as SomeSymbol; +import "AnotherFile2.sol" as SomeSymbol; + +import { + symbol1 as alias3, + symbol2 as alias2, + symbol3 as alias1, + symbol4 +} from "File0.sol"; +import {symbol1 as alias, symbol2} from "File2.sol"; +import {symbol1 as alias, symbol2} from "File3.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File6.sol"; + +uint256 constant someConstant = 10; + +import {Something2, Something3} from "someFile.sol"; + +// This is a comment +import {Something2, Something3} from "someFile.sol"; + +import {symbol1 as alias, symbol2} from "File3.sol"; +// comment inside group is treated as a separator for now +import {symbol1 as alias, symbol2} from "File2.sol"; diff --git a/crates/fmt/testdata/SortedImports/original.sol b/crates/fmt/testdata/SortedImports/original.sol new file mode 100644 index 0000000000000..54b3ca3b59cfb --- /dev/null +++ b/crates/fmt/testdata/SortedImports/original.sol @@ -0,0 +1,23 @@ +import "SomeFile3.sol"; +import "SomeFile2.sol"; +import "SomeFile1.sol" as SomeOtherFile; +import "SomeFile0.sol" as SomeOtherFile; + +import "AnotherFile2.sol" as SomeSymbol; +import "AnotherFile1.sol" as SomeSymbol; + +import {symbol2, symbol1 as alias} from "File3.sol"; +import {symbol2, symbol1 as alias} from "File2.sol"; +import {symbol2 as alias2, symbol1 as alias1, symbol3 as alias3, symbol4} from "File6.sol"; +import {symbol3 as alias1, symbol2 as alias2, symbol1 as alias3, symbol4} from "File0.sol"; + +uint256 constant someConstant = 10; + +import {Something3, Something2} from "someFile.sol"; + +// This is a comment +import {Something3, Something2} from "someFile.sol"; + +import {symbol2, symbol1 as alias} from "File3.sol"; +// comment inside group is treated as a separator for now +import {symbol2, symbol1 as alias} from "File2.sol"; \ No newline at end of file diff --git a/crates/fmt/tests/formatter.rs b/crates/fmt/tests/formatter.rs index 5ef7154cd047f..18b72a54512be 100644 --- a/crates/fmt/tests/formatter.rs +++ b/crates/fmt/tests/formatter.rs @@ -11,7 +11,7 @@ fn tracing() { let _ = tracing::subscriber::set_global_default(subscriber); } -fn test_directory(base_name: &str) { +fn test_directory(base_name: &str, test_config: TestConfig) { tracing(); let mut original = None; @@ -74,6 +74,7 @@ fn test_directory(base_name: &str) { config, original.as_ref().expect("original.sol not found"), &formatted, + test_config, ); } } @@ -82,7 +83,13 @@ fn assert_eof(content: &str) { assert!(content.ends_with('\n') && !content.ends_with("\n\n")); } -fn test_formatter(filename: &str, config: FormatterConfig, source: &str, expected_source: &str) { +fn test_formatter( + filename: &str, + config: FormatterConfig, + source: &str, + expected_source: &str, + test_config: TestConfig, +) { #[derive(Eq)] struct PrettyString(String); @@ -103,7 +110,7 @@ fn test_formatter(filename: &str, config: FormatterConfig, source: &str, expecte let source_parsed = parse(source).unwrap(); let expected_parsed = parse(expected_source).unwrap(); - if !source_parsed.pt.ast_eq(&expected_parsed.pt) { + if !test_config.skip_compare_ast_eq && !source_parsed.pt.ast_eq(&expected_parsed.pt) { pretty_assertions::assert_eq!( source_parsed.pt, expected_parsed.pt, @@ -118,7 +125,6 @@ fn test_formatter(filename: &str, config: FormatterConfig, source: &str, expecte format_to(&mut source_formatted, source_parsed, config.clone()).unwrap(); assert_eof(&source_formatted); - // println!("{}", source_formatted); let source_formatted = PrettyString(source_formatted); pretty_assertions::assert_eq!( @@ -142,18 +148,40 @@ fn test_formatter(filename: &str, config: FormatterConfig, source: &str, expecte ); } -macro_rules! test_directories { - ($($dir:ident),+ $(,)?) => {$( +#[derive(Clone, Copy, Default)] +struct TestConfig { + /// Whether to compare the formatted source code AST with the original AST + skip_compare_ast_eq: bool, +} + +impl TestConfig { + fn skip_compare_ast_eq() -> Self { + Self { skip_compare_ast_eq: true } + } +} + +macro_rules! test_dir { + ($dir:ident $(,)?) => { + test_dir!($dir, Default::default()); + }; + ($dir:ident, $config:expr $(,)?) => { #[allow(non_snake_case)] #[test] fn $dir() { - test_directory(stringify!($dir)); + test_directory(stringify!($dir), $config); } + }; +} + +macro_rules! test_directories { + ($($dir:ident),+ $(,)?) => {$( + test_dir!($dir); )+}; } test_directories! { ConstructorDefinition, + ConstructorModifierStyle, ContractDefinition, DocComments, EnumDefinition, @@ -201,4 +229,9 @@ test_directories! { MappingType, EmitStatement, Repros, + BlockComments, + BlockCommentsFunction, + EnumVariants, } + +test_dir!(SortedImports, TestConfig::skip_compare_ast_eq()); diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index f09ffa216ab98..9d7b2777d71e7 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -15,7 +15,11 @@ name = "forge" path = "bin/main.rs" [build-dependencies] -vergen = { version = "8", default-features = false, features = ["build", "git", "git2"] } +vergen = { workspace = true, default-features = false, features = [ + "build", + "git", + "gitcl", +] } [dependencies] # lib @@ -24,12 +28,12 @@ foundry-common.workspace = true foundry-compilers = { workspace = true, features = ["full"] } foundry-config.workspace = true foundry-evm.workspace = true +foundry-wallets.workspace = true +foundry-linking.workspace = true -ethers-contract.workspace = true -ethers-core.workspace = true -ethers-middleware.workspace = true -ethers-providers.workspace = true -ethers-signers.workspace = true +ethers-contract = { workspace = true, features = ["abigen"] } + +revm-inspectors.workspace = true comfy-table = "7" eyre.workspace = true @@ -37,17 +41,27 @@ proptest = "1" rayon = "1" serde.workspace = true tracing.workspace = true -yansi = "0.5" +yansi.workspace = true +humantime-serde = "1.1.1" # bin forge-doc.workspace = true forge-fmt.workspace = true +forge-verify.workspace = true +forge-script.workspace = true foundry-cli.workspace = true foundry-debugger.workspace = true alloy-dyn-abi.workspace = true alloy-json-abi.workspace = true alloy-primitives = { workspace = true, features = ["serde"] } +alloy-rpc-types.workspace = true +alloy-provider = { workspace = true, features = ["reqwest", "ws", "ipc"] } +alloy-network.workspace = true +alloy-transport.workspace = true +alloy-signer.workspace = true +alloy-consensus.workspace = true +alloy-chains.workspace = true async-trait = "0.1" clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } @@ -62,15 +76,19 @@ itertools.workspace = true once_cell = "1" parking_lot = "0.12" regex = { version = "1", default-features = false } -reqwest = { version = "0.11", default-features = false, features = ["json"] } +reqwest = { workspace = true, features = ["json"] } semver = "1" serde_json.workspace = true similar = { version = "2", features = ["inline"] } solang-parser.workspace = true -strum = { version = "0.25", features = ["derive"] } +strum = { workspace = true, features = ["derive"] } thiserror = "1" tokio = { version = "1", features = ["time"] } +toml = { version = "0.8", features = ["preserve_order"] } +toml_edit = "0.22.4" watchexec = "2.3.2" +evm-disassembler.workspace = true +rustc-hash.workspace = true # doc server axum = { workspace = true, features = ["ws"] } @@ -78,27 +96,38 @@ hyper.workspace = true tower-http = { workspace = true, features = ["fs"] } opener = "0.6" +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = { workspace = true, optional = true } + [dev-dependencies] anvil.workspace = true foundry-test-utils.workspace = true +mockall = "0.12" criterion = "0.5" globset = "0.4" paste = "1.0" path-slash = "0.2" pretty_assertions.workspace = true -serial_test = "2" -svm = { package = "svm-rs", version = "0.3", default-features = false, features = ["rustls"] } -tempfile = "3" +svm = { package = "svm-rs", version = "0.5", default-features = false, features = [ + "rustls", +] } +tempfile.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } +alloy-signer-wallet.workspace = true + [features] default = ["rustls"] -rustls = ["foundry-cli/rustls", "reqwest/rustls-tls", "reqwest/rustls-tls-native-roots"] +rustls = [ + "foundry-cli/rustls", + "foundry-wallets/rustls", + "reqwest/rustls-tls", + "reqwest/rustls-tls-native-roots", +] openssl = ["foundry-cli/openssl", "reqwest/default-tls"] - -# feature for heavy (long-running) integration tests -heavy-integration-tests = [] +asm-keccak = ["alloy-primitives/asm-keccak"] +jemalloc = ["dep:tikv-jemallocator"] [[bench]] name = "test" diff --git a/crates/forge/README.md b/crates/forge/README.md index 35cb196ced432..5ac9d57478b56 100644 --- a/crates/forge/README.md +++ b/crates/forge/README.md @@ -404,7 +404,7 @@ For example, if you have `@openzeppelin` imports, you would ## Github Actions CI -We recommend using the [Github Actions CI setup](https://book.getfoundry.sh/config/continous-integration.html) from the [📖 Foundry Book](https://book.getfoundry.sh/index.html). +We recommend using the [Github Actions CI setup](https://book.getfoundry.sh/config/continuous-integration) from the [📖 Foundry Book](https://book.getfoundry.sh/index.html). ## Future Features diff --git a/crates/forge/assets/CounterTemplate.s.sol b/crates/forge/assets/CounterTemplate.s.sol index 1a47b40b82aab..df9ee8b02ce4d 100644 --- a/crates/forge/assets/CounterTemplate.s.sol +++ b/crates/forge/assets/CounterTemplate.s.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {Script, console2} from "forge-std/Script.sol"; +import {Script, console} from "forge-std/Script.sol"; contract CounterScript is Script { function setUp() public {} diff --git a/crates/forge/assets/CounterTemplate.t.sol b/crates/forge/assets/CounterTemplate.t.sol index e9b9e6acf2b43..54b724f7ae766 100644 --- a/crates/forge/assets/CounterTemplate.t.sol +++ b/crates/forge/assets/CounterTemplate.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {Test, console2} from "forge-std/Test.sol"; +import {Test, console} from "forge-std/Test.sol"; import {Counter} from "../src/Counter.sol"; contract CounterTest is Test { diff --git a/crates/forge/assets/generated/TestTemplate.t.sol b/crates/forge/assets/generated/TestTemplate.t.sol index 468acba01069a..816a8c68fd0a9 100644 --- a/crates/forge/assets/generated/TestTemplate.t.sol +++ b/crates/forge/assets/generated/TestTemplate.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {Test, console2} from "forge-std/Test.sol"; +import {Test, console} from "forge-std/Test.sol"; import {{contract_name}} from "../src/{contract_name}.sol"; contract {contract_name}Test is Test { @@ -10,4 +10,4 @@ contract {contract_name}Test is Test { function setUp() public { {instance_name} = new {contract_name}(); } -} \ No newline at end of file +} diff --git a/crates/forge/benches/test.rs b/crates/forge/benches/test.rs index 7650077b116ac..7646a3c214a33 100644 --- a/crates/forge/benches/test.rs +++ b/crates/forge/benches/test.rs @@ -15,7 +15,7 @@ fn forge_test_benchmark(c: &mut Criterion) { let mut cmd = prj.forge_command(); cmd.arg("test"); b.iter(|| { - cmd.ensure_execute_success().unwrap(); + cmd.print_output(); }); }); } diff --git a/crates/forge/bin/cmd/bind.rs b/crates/forge/bin/cmd/bind.rs index 199c83f65df40..5be0f262a1a21 100644 --- a/crates/forge/bin/cmd/bind.rs +++ b/crates/forge/bin/cmd/bind.rs @@ -2,7 +2,7 @@ use clap::{Parser, ValueHint}; use ethers_contract::{Abigen, ContractFilter, ExcludeContracts, MultiAbigen, SelectContracts}; use eyre::{Result, WrapErr}; use foundry_cli::{opts::CoreBuildArgs, utils::LoadConfig}; -use foundry_common::{compile, fs::json_files}; +use foundry_common::{compile::ProjectCompiler, fs::json_files}; use foundry_config::impl_figment_convert; use std::{ fs, @@ -15,10 +15,10 @@ const DEFAULT_CRATE_NAME: &str = "foundry-contracts"; const DEFAULT_CRATE_VERSION: &str = "0.1.0"; /// CLI arguments for `forge bind`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct BindArgs { /// Path to where the contract artifacts are stored. - #[clap( + #[arg( long = "bindings-path", short, value_hint = ValueHint::DirPath, @@ -27,57 +27,61 @@ pub struct BindArgs { pub bindings: Option, /// Create bindings only for contracts whose names match the specified filter(s) - #[clap(long)] + #[arg(long)] pub select: Vec, /// Create bindings only for contracts whose names do not match the specified filter(s) - #[clap(long, conflicts_with = "select")] + #[arg(long, conflicts_with = "select")] pub skip: Vec, /// Explicitly generate bindings for all contracts /// /// By default all contracts ending with `Test` or `Script` are excluded. - #[clap(long, conflicts_with_all = &["select", "skip"])] + #[arg(long, conflicts_with_all = &["select", "skip"])] pub select_all: bool, /// The name of the Rust crate to generate. /// /// This should be a valid crates.io crate name, /// however, this is not currently validated by this command. - #[clap(long, default_value = DEFAULT_CRATE_NAME, value_name = "NAME")] + #[arg(long, default_value = DEFAULT_CRATE_NAME, value_name = "NAME")] crate_name: String, /// The version of the Rust crate to generate. /// /// This should be a standard semver version string, /// however, this is not currently validated by this command. - #[clap(long, default_value = DEFAULT_CRATE_VERSION, value_name = "VERSION")] + #[arg(long, default_value = DEFAULT_CRATE_VERSION, value_name = "VERSION")] crate_version: String, /// Generate the bindings as a module instead of a crate. - #[clap(long)] + #[arg(long)] module: bool, /// Overwrite existing generated bindings. /// /// By default, the command will check that the bindings are correct, and then exit. If /// --overwrite is passed, it will instead delete and overwrite the bindings. - #[clap(long)] + #[arg(long)] overwrite: bool, /// Generate bindings as a single file. - #[clap(long)] + #[arg(long)] single_file: bool, /// Skip Cargo.toml consistency checks. - #[clap(long)] + #[arg(long)] skip_cargo_toml: bool, /// Skips running forge build before generating binding - #[clap(long)] + #[arg(long)] skip_build: bool, - #[clap(flatten)] + /// Don't add any additional derives to generated bindings + #[arg(long)] + skip_extra_derives: bool, + + #[command(flatten)] build_args: CoreBuildArgs, } @@ -86,7 +90,7 @@ impl BindArgs { if !self.skip_build { // run `forge build` let project = self.build_args.project()?; - compile::compile(&project, false, false)?; + let _ = ProjectCompiler::new().compile(&project)?; } let artifacts = self.try_load_config_emit_warnings()?.out; @@ -139,7 +143,7 @@ impl BindArgs { "console[2]?", "CommonBase", "Components", - "[Ss]td(Chains|Math|Error|Json|Utils|Cheats|Style|Invariant|Assertions|Storage(Safe)?)", + "[Ss]td(Chains|Math|Error|Json|Utils|Cheats|Style|Invariant|Assertions|Toml|Storage(Safe)?)", "[Vv]m.*", ]) .extend_names(["IMulticall3"]) @@ -151,6 +155,10 @@ impl BindArgs { let abigens = json_files(artifacts.as_ref()) .into_iter() .filter_map(|path| { + if path.to_string_lossy().contains("/build-info/") { + // ignore the build info json + return None + } // we don't want `.metadata.json files let stem = path.file_stem()?; if stem.to_str()?.ends_with(".metadata") { @@ -161,10 +169,13 @@ impl BindArgs { }) .map(|path| { trace!(?path, "parsing Abigen from file"); - Abigen::from_file(&path) - .wrap_err_with(|| format!("failed to parse Abigen from file: {:?}", path))? - .add_derive("serde::Serialize")? - .add_derive("serde::Deserialize") + let abi = Abigen::from_file(&path) + .wrap_err_with(|| format!("failed to parse Abigen from file: {:?}", path)); + if !self.skip_extra_derives { + abi?.add_derive("serde::Serialize")?.add_derive("serde::Deserialize") + } else { + abi + } }) .collect::, _>>()?; let multi = MultiAbigen::from_abigens(abigens).with_filter(self.get_filter()); @@ -207,20 +218,22 @@ No contract artifacts found. Hint: Have you built your contracts yet? `forge bin /// Generate the bindings fn generate_bindings(&self, artifacts: impl AsRef) -> Result<()> { - let bindings = self.get_multi(&artifacts)?.build()?; + let mut bindings = self.get_multi(&artifacts)?.build()?; println!("Generating bindings for {} contracts", bindings.len()); if !self.module { trace!(single_file = self.single_file, "generating crate"); - bindings.dependencies([r#"serde = "1""#]).write_to_crate( + if !self.skip_extra_derives { + bindings = bindings.dependencies([r#"serde = "1""#]) + } + bindings.write_to_crate( &self.crate_name, &self.crate_version, self.bindings_root(&artifacts), self.single_file, - )?; + ) } else { trace!(single_file = self.single_file, "generating module"); - bindings.write_to_module(self.bindings_root(&artifacts), self.single_file)?; + bindings.write_to_module(self.bindings_root(&artifacts), self.single_file) } - Ok(()) } } diff --git a/crates/forge/bin/cmd/build.rs b/crates/forge/bin/cmd/build.rs index 2c583106e3a59..c2e6715a4615f 100644 --- a/crates/forge/bin/cmd/build.rs +++ b/crates/forge/bin/cmd/build.rs @@ -2,10 +2,7 @@ use super::{install, watch::WatchArgs}; use clap::Parser; use eyre::Result; use foundry_cli::{opts::CoreBuildArgs, utils::LoadConfig}; -use foundry_common::{ - compile, - compile::{ProjectCompiler, SkipBuildFilter}, -}; +use foundry_common::compile::{ProjectCompiler, SkipBuildFilter, SkipBuildFilters}; use foundry_compilers::{Project, ProjectCompileOutput}; use foundry_config::{ figment::{ @@ -42,37 +39,37 @@ foundry_config::merge_impl_figment_convert!(BuildArgs, args); /// /// Some arguments are marked as `#[serde(skip)]` and require manual processing in /// `figment::Provider` implementation -#[derive(Debug, Clone, Parser, Serialize, Default)] -#[clap(next_help_heading = "Build options", about = None, long_about = None)] // override doc +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Build options", about = None, long_about = None)] // override doc pub struct BuildArgs { /// Print compiled contract names. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub names: bool, /// Print compiled contract sizes. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub sizes: bool, /// Skip building files whose names contain the given filter. /// /// `test` and `script` are aliases for `.t.sol` and `.s.sol`. - #[clap(long, num_args(1..))] + #[arg(long, num_args(1..))] #[serde(skip)] pub skip: Option>, - #[clap(flatten)] + #[command(flatten)] #[serde(flatten)] pub args: CoreBuildArgs, - #[clap(flatten)] + #[command(flatten)] #[serde(skip)] pub watch: WatchArgs, /// Output the compilation errors in the json format. /// This is useful when you want to use the output in other tools. - #[clap(long, conflicts_with = "silent")] + #[arg(long, conflicts_with = "silent")] #[serde(skip)] pub format_json: bool, } @@ -90,19 +87,24 @@ impl BuildArgs { project = config.project()?; } - let filters = self.skip.unwrap_or_default(); + let mut compiler = ProjectCompiler::new() + .print_names(self.names) + .print_sizes(self.sizes) + .quiet(self.format_json) + .bail(!self.format_json); + if let Some(skip) = self.skip { + if !skip.is_empty() { + compiler = compiler + .filter(Box::new(SkipBuildFilters::new(skip, project.root().to_path_buf())?)); + } + } + let output = compiler.compile(&project)?; if self.format_json { - let output = compile::suppress_compile_with_filter_json(&project, filters)?; - let json = serde_json::to_string_pretty(&output.clone().output())?; - println!("{}", json); - Ok(output) - } else if self.args.silent { - compile::suppress_compile_with_filter(&project, filters) - } else { - let compiler = ProjectCompiler::with_filter(self.names, self.sizes, filters); - compiler.compile(&project) + println!("{}", serde_json::to_string_pretty(&output.clone().output())?); } + + Ok(output) } /// Returns the `Project` for the current workspace diff --git a/crates/forge/bin/cmd/cache.rs b/crates/forge/bin/cmd/cache.rs index 53dbeb9fbcb8a..ff3117d345f1e 100644 --- a/crates/forge/bin/cmd/cache.rs +++ b/crates/forge/bin/cmd/cache.rs @@ -11,7 +11,7 @@ use strum::VariantNames; /// CLI arguments for `forge cache`. #[derive(Debug, Parser)] pub struct CacheArgs { - #[clap(subcommand)] + #[command(subcommand)] pub sub: CacheSubcommands, } @@ -26,12 +26,12 @@ pub enum CacheSubcommands { /// CLI arguments for `forge clean`. #[derive(Debug, Parser)] -#[clap(group = clap::ArgGroup::new("etherscan-blocks").multiple(false))] +#[command(group = clap::ArgGroup::new("etherscan-blocks").multiple(false))] pub struct CleanArgs { /// The chains to clean the cache for. /// /// Can also be "all" to clean all chains. - #[clap( + #[arg( env = "CHAIN", default_value = "all", value_parser = ChainOrAllValueParser::default(), @@ -39,18 +39,17 @@ pub struct CleanArgs { chains: Vec, /// The blocks to clean the cache for. - #[clap( + #[arg( short, long, num_args(1..), - use_value_delimiter(true), value_delimiter(','), group = "etherscan-blocks" )] blocks: Vec, /// Whether to clean the Etherscan cache. - #[clap(long, group = "etherscan-blocks")] + #[arg(long, group = "etherscan-blocks")] etherscan: bool, } @@ -82,7 +81,7 @@ pub struct LsArgs { /// The chains to list the cache for. /// /// Can also be "all" to list all chains. - #[clap( + #[arg( env = "CHAIN", default_value = "all", value_parser = ChainOrAllValueParser::default(), @@ -107,7 +106,7 @@ impl LsArgs { } } -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub enum ChainOrAll { NamedChain(NamedChain), All, diff --git a/crates/forge/bin/cmd/clone.rs b/crates/forge/bin/cmd/clone.rs new file mode 100644 index 0000000000000..ca6fdf7be767b --- /dev/null +++ b/crates/forge/bin/cmd/clone.rs @@ -0,0 +1,803 @@ +use super::{init::InitArgs, install::DependencyInstallOpts}; +use alloy_primitives::{Address, Bytes, ChainId, TxHash}; +use clap::{Parser, ValueHint}; +use eyre::Result; +use foundry_block_explorers::{ + contract::{ContractCreationData, ContractMetadata, Metadata}, + errors::EtherscanError, + Client, +}; +use foundry_cli::{opts::EtherscanOpts, p_println, utils::Git}; +use foundry_common::{compile::ProjectCompiler, fs}; +use foundry_compilers::{ + artifacts::{output_selection::ContractOutputSelection, Settings, StorageLayout}, + remappings::{RelativeRemapping, Remapping}, + ConfigurableContractArtifact, ProjectCompileOutput, ProjectPathsConfig, +}; +use foundry_config::{Chain, Config}; +use std::{ + fs::read_dir, + path::{Path, PathBuf}, + time::Duration, +}; + +/// CloneMetadata stores the metadata that are not included by `foundry.toml` but necessary for a +/// cloned contract. The metadata can be serialized to a metadata file in the cloned project root. +#[derive(Debug, Clone, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloneMetadata { + /// The path to the source file that contains the contract declaration. + /// The path is relative to the root directory of the project. + pub path: PathBuf, + /// The name of the contract in the file. + pub target_contract: String, + /// The address of the contract on the blockchain. + pub address: Address, + /// The chain id. + pub chain_id: ChainId, + /// The transaction hash of the creation transaction. + pub creation_transaction: TxHash, + /// The address of the deployer, i.e., sender of the creation transaction. + pub deployer: Address, + /// The constructor arguments of the contract on chain. + pub constructor_arguments: Bytes, + /// The storage layout of the contract on chain. + pub storage_layout: StorageLayout, +} + +/// CLI arguments for `forge clone`. +/// +/// `forge clone` clones an on-chain contract from block explorers (e.g., Etherscan) in the +/// following steps: +/// 1. Fetch the contract source code from the block explorer. +/// 2. Initialize a empty foundry project at the `root` directory specified in `CloneArgs`. +/// 3. Dump the contract sources to the source directory. +/// 4. Update the `foundry.toml` configuration file with the compiler settings from Etherscan. +/// 5. Try compile the cloned contract, so that we can get the original storage layout. This +/// original storage layout is preserved in the `CloneMetadata` so that if the user later +/// modifies the contract, it is possible to quickly check the storage layout compatibility with +/// the original on-chain contract. +/// 6. Dump the `CloneMetadata` to the root directory of the cloned project as `.clone.meta` file. +#[derive(Clone, Debug, Parser)] +pub struct CloneArgs { + /// The contract address to clone. + pub address: Address, + + /// The root directory of the cloned project. + #[arg(value_hint = ValueHint::DirPath, default_value = ".", value_name = "PATH")] + pub root: PathBuf, + + /// Do not generate the remappings.txt file. Instead, keep the remappings in the configuration. + #[arg(long)] + pub no_remappings_txt: bool, + + /// Keep the original directory structure collected from Etherscan. + /// + /// If this flag is set, the directory structure of the cloned project will be kept as is. + /// By default, the directory structure is re-orgnized to increase the readability, but may + /// risk some compilation failures. + #[arg(long)] + pub keep_directory_structure: bool, + + #[command(flatten)] + pub etherscan: EtherscanOpts, + + #[command(flatten)] + pub opts: DependencyInstallOpts, +} + +impl CloneArgs { + pub async fn run(self) -> Result<()> { + let CloneArgs { + address, + root, + opts, + etherscan, + no_remappings_txt, + keep_directory_structure, + } = self; + + // step 0. get the chain and api key from the config + let config = Config::from(ðerscan); + let chain = config.chain.unwrap_or_default(); + let etherscan_api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); + let client = Client::new(chain, etherscan_api_key.clone())?; + + // step 1. get the metadata from client + p_println!(!opts.quiet => "Downloading the source code of {} from Etherscan...", address); + let meta = Self::collect_metadata_from_client(address, &client).await?; + + // step 2. initialize an empty project + Self::init_an_empty_project(&root, opts)?; + // canonicalize the root path + // note that at this point, the root directory must have been created + let root = dunce::canonicalize(&root)?; + + // step 3. parse the metadata + Self::parse_metadata(&meta, chain, &root, no_remappings_txt, keep_directory_structure) + .await?; + + // step 4. collect the compilation metadata + // if the etherscan api key is not set, we need to wait for 3 seconds between calls + p_println!(!opts.quiet => "Collecting the creation information of {} from Etherscan...", address); + if etherscan_api_key.is_empty() { + p_println!(!opts.quiet => "Waiting for 5 seconds to avoid rate limit..."); + tokio::time::sleep(Duration::from_secs(5)).await; + } + Self::collect_compilation_metadata(&meta, chain, address, &root, &client, opts.quiet) + .await?; + + // step 5. git add and commit the changes if needed + if !opts.no_commit { + let git = Git::new(&root).quiet(opts.quiet); + git.add(Some("--all"))?; + let msg = format!("chore: forge clone {}", address); + git.commit(&msg)?; + } + + Ok(()) + } + + /// Collect the metadata of the contract from the block explorer. + /// + /// * `address` - the address of the contract to be cloned. + /// * `client` - the client of the block explorer. + pub(crate) async fn collect_metadata_from_client( + address: Address, + client: &C, + ) -> Result { + let mut meta = client.contract_source_code(address).await?; + eyre::ensure!(meta.items.len() == 1, "contract not found or ill-formed"); + let meta = meta.items.remove(0); + eyre::ensure!(!meta.is_vyper(), "Vyper contracts are not supported"); + Ok(meta) + } + + /// Initialize an empty project at the root directory. + /// + /// * `root` - the root directory of the project. + /// * `enable_git` - whether to enable git for the project. + /// * `quiet` - whether to print messages. + pub(crate) fn init_an_empty_project(root: &Path, opts: DependencyInstallOpts) -> Result<()> { + // let's try to init the project with default init args + let init_args = InitArgs { root: root.to_path_buf(), opts, ..Default::default() }; + init_args.run().map_err(|e| eyre::eyre!("Project init error: {:?}", e))?; + + // remove the unnecessary example contracts + // XXX (ZZ): this is a temporary solution until we have a proper way to remove contracts, + // e.g., add a field in the InitArgs to control the example contract generation + fs::remove_file(root.join("src/Counter.sol"))?; + fs::remove_file(root.join("test/Counter.t.sol"))?; + fs::remove_file(root.join("script/Counter.s.sol"))?; + + Ok(()) + } + + /// Collect the compilation metadata of the cloned contract. + /// This function compiles the cloned contract and collects the compilation metadata. + /// + /// * `meta` - the metadata of the contract (from Etherscan). + /// * `chain` - the chain where the contract to be cloned locates. + /// * `address` - the address of the contract to be cloned. + /// * `root` - the root directory of the cloned project. + /// * `client` - the client of the block explorer. + pub(crate) async fn collect_compilation_metadata( + meta: &Metadata, + chain: Chain, + address: Address, + root: &PathBuf, + client: &C, + quiet: bool, + ) -> Result<()> { + // compile the cloned contract + let compile_output = compile_project(root, quiet)?; + let (main_file, main_artifact) = find_main_contract(&compile_output, &meta.contract_name)?; + let main_file = main_file.strip_prefix(root)?.to_path_buf(); + let storage_layout = + main_artifact.storage_layout.to_owned().expect("storage layout not found"); + + // dump the metadata to the root directory + let creation_tx = client.contract_creation_data(address).await?; + let clone_meta = CloneMetadata { + path: main_file, + target_contract: meta.contract_name.clone(), + address, + chain_id: chain.id(), + creation_transaction: creation_tx.transaction_hash, + deployer: creation_tx.contract_creator, + constructor_arguments: meta.constructor_arguments.clone(), + storage_layout, + }; + let metadata_content = serde_json::to_string(&clone_meta)?; + let metadata_file = root.join(".clone.meta"); + fs::write(&metadata_file, metadata_content)?; + let mut perms = std::fs::metadata(&metadata_file)?.permissions(); + perms.set_readonly(true); + std::fs::set_permissions(&metadata_file, perms)?; + + Ok(()) + } + + /// Download and parse the source code from Etherscan. + /// + /// * `chain` - the chain where the contract to be cloned locates. + /// * `address` - the address of the contract to be cloned. + /// * `root` - the root directory to clone the contract into as a foundry project. + /// * `client` - the client of the block explorer. + /// * `no_remappings_txt` - whether to generate the remappings.txt file. + pub(crate) async fn parse_metadata( + meta: &Metadata, + chain: Chain, + root: &PathBuf, + no_remappings_txt: bool, + keep_directory_structure: bool, + ) -> Result<()> { + // dump sources and update the remapping in configuration + let remappings = dump_sources(meta, root, keep_directory_structure)?; + Config::update_at(root, |config, doc| { + let profile = config.profile.as_str().as_str(); + + // update the remappings in the configuration + let mut remapping_array = toml_edit::Array::new(); + for r in remappings { + remapping_array.push(r.to_string()); + } + doc[Config::PROFILE_SECTION][profile]["remappings"] = toml_edit::value(remapping_array); + + // make sure auto_detect_remappings is false (it is very important because cloned + // project may not follow the common remappings) + doc[Config::PROFILE_SECTION][profile]["auto_detect_remappings"] = + toml_edit::value(false); + true + })?; + + // update configuration + Config::update_at(root, |config, doc| { + update_config_by_metadata(config, doc, meta, chain).is_ok() + })?; + + // write remappings to remappings.txt if necessary + if !no_remappings_txt { + let remappings_txt = root.join("remappings.txt"); + eyre::ensure!( + !remappings_txt.exists(), + "remappings.txt already exists, please remove it first" + ); + + Config::update_at(root, |config, doc| { + let remappings_txt_content = + config.remappings.iter().map(|r| r.to_string()).collect::>().join("\n"); + if fs::write(&remappings_txt, remappings_txt_content).is_err() { + return false + } + + let profile = config.profile.as_str().as_str(); + if let Some(elem) = doc[Config::PROFILE_SECTION][profile].as_table_mut() { + elem.remove_entry("remappings"); + true + } else { + false + } + })?; + } + + Ok(()) + } +} + +/// Update the configuration file with the metadata. +/// This function will update the configuration file with the metadata from the contract. +/// It will update the following fields: +/// - `auto_detect_solc` to `false` +/// - `solc_version` to the value from the metadata +/// - `evm_version` to the value from the metadata +/// - `via_ir` to the value from the metadata +/// - `libraries` to the value from the metadata +/// - `metadata` to the value from the metadata +/// - `cbor_metadata`, `use_literal_content`, and `bytecode_hash` +/// - `optimizer` to the value from the metadata +/// - `optimizer_runs` to the value from the metadata +/// - `optimizer_details` to the value from the metadata +/// - `yul_details`, `yul`, etc. +/// - `simpleCounterForLoopUncheckedIncrement` is ignored for now +/// - `remappings` and `stop_after` are pre-validated to be empty and None, respectively +/// - `model_checker`, `debug`, and `output_selection` are ignored for now +/// +/// Detailed information can be found from the following link: +/// - https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +/// - https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description +fn update_config_by_metadata( + config: &Config, + doc: &mut toml_edit::DocumentMut, + meta: &Metadata, + chain: Chain, +) -> Result<()> { + let profile = config.profile.as_str().as_str(); + + // macro to update the config if the value exists + macro_rules! update_if_needed { + ([$($key:expr),+], $value:expr) => { + { + if let Some(value) = $value { + let mut current = &mut doc[Config::PROFILE_SECTION][profile]; + $( + if let Some(nested_doc) = current.get_mut(&$key) { + current = nested_doc; + } else { + return Err(eyre::eyre!("cannot find the key: {}", $key)); + } + )+ + *current = toml_edit::value(value); + } + } + }; + } + + // update the chain id + doc[Config::PROFILE_SECTION][profile]["chain_id"] = toml_edit::value(chain.id() as i64); + + // disable auto detect solc and set the solc version + doc[Config::PROFILE_SECTION][profile]["auto_detect_solc"] = toml_edit::value(false); + let version = meta.compiler_version()?; + doc[Config::PROFILE_SECTION][profile]["solc_version"] = + toml_edit::value(format!("{}.{}.{}", version.major, version.minor, version.patch)); + + // get optimizer settings + // we ignore `model_checker`, `debug`, and `output_selection` for now, + // it seems they do not have impacts on the actual compilation + let Settings { optimizer, libraries, evm_version, via_ir, stop_after, metadata, .. } = + meta.settings()?; + eyre::ensure!(stop_after.is_none(), "stop_after should be None"); + + update_if_needed!(["evm_version"], evm_version.map(|v| v.to_string())); + update_if_needed!(["via_ir"], via_ir); + + // update metadata if needed + if let Some(metadata) = metadata { + update_if_needed!(["cbor_metadata"], metadata.cbor_metadata); + update_if_needed!(["use_literal_content"], metadata.use_literal_content); + update_if_needed!(["bytecode_hash"], metadata.bytecode_hash.map(|v| v.to_string())); + } + + // update optimizer settings if needed + update_if_needed!(["optimizer"], optimizer.enabled); + update_if_needed!(["optimizer_runs"], optimizer.runs.map(|v| v as i64)); + // update optimizer details if needed + if let Some(detail) = optimizer.details { + doc[Config::PROFILE_SECTION][profile]["optimizer_details"] = toml_edit::table(); + + update_if_needed!(["optimizer_details", "peephole"], detail.peephole); + update_if_needed!(["optimizer_details", "inliner"], detail.inliner); + update_if_needed!(["optimizer_details", "jumpdestRemover"], detail.jumpdest_remover); + update_if_needed!(["optimizer_details", "orderLiterals"], detail.order_literals); + update_if_needed!(["optimizer_details", "deduplicate"], detail.deduplicate); + update_if_needed!(["optimizer_details", "cse"], detail.cse); + update_if_needed!(["optimizer_details", "constantOptimizer"], detail.constant_optimizer); + update_if_needed!( + ["optimizer_details", "simpleCounterForLoopUncheckedIncrement"], + detail.simple_counter_for_loop_unchecked_increment + ); + update_if_needed!(["optimizer_details", "yul"], detail.yul); + + if let Some(yul_detail) = detail.yul_details { + doc[Config::PROFILE_SECTION][profile]["optimizer_details"]["yulDetails"] = + toml_edit::table(); + update_if_needed!( + ["optimizer_details", "yulDetails", "stackAllocation"], + yul_detail.stack_allocation + ); + update_if_needed!( + ["optimizer_details", "yulDetails", "optimizerSteps"], + yul_detail.optimizer_steps + ); + } + } + + // apply remapping on libraries + let path_config = config.project_paths(); + let libraries = libraries + .with_applied_remappings(&path_config) + .with_stripped_file_prefixes(&path_config.root); + + // update libraries + let mut lib_array = toml_edit::Array::new(); + for (path_to_lib, info) in libraries.libs { + for (lib_name, address) in info { + lib_array.push(format!("{}:{}:{}", path_to_lib.to_str().unwrap(), lib_name, address)); + } + } + doc[Config::PROFILE_SECTION][profile]["libraries"] = toml_edit::value(lib_array); + + Ok(()) +} + +/// Dump the contract sources to the root directory. +/// The sources are dumped to the `src` directory. +/// IO errors may be returned. +/// A list of remappings is returned +fn dump_sources(meta: &Metadata, root: &PathBuf, no_reorg: bool) -> Result> { + // get config + let path_config = ProjectPathsConfig::builder().build_with_root(root); + // we will canonicalize the sources directory later + let src_dir = &path_config.sources; + let lib_dir = &path_config.libraries[0]; + let contract_name = &meta.contract_name; + let source_tree = meta.source_tree(); + + // then we move the sources to the correct directories + // we will first load existing remappings if necessary + // make sure this happens before dumping sources + let mut remappings: Vec = Remapping::find_many(root); + + // first we dump the sources to a temporary directory + let tmp_dump_dir = root.join("raw_sources"); + source_tree + .write_to(&tmp_dump_dir) + .map_err(|e| eyre::eyre!("failed to dump sources: {}", e))?; + + // check whether we need to re-organize directories in the original sources, since we do not + // want to put all the sources in the `src` directory if the original directory structure is + // well organized, e.g., a standard foundry project containing `src` and `lib` + // + // * if the user wants to keep the original directory structure, we should not re-organize. + // * if there is any other directory other than `src`, `contracts`, `lib`, `hardhat`, + // `forge-std`, + // or not started with `@`, we should not re-organize. + let to_reorg = !no_reorg && + std::fs::read_dir(tmp_dump_dir.join(contract_name))?.all(|e| { + let Ok(e) = e else { return false }; + let folder_name = e.file_name(); + folder_name == "src" || + folder_name == "lib" || + folder_name == "contracts" || + folder_name == "hardhat" || + folder_name == "forge-std" || + folder_name.to_string_lossy().starts_with('@') + }); + + // ensure `src` and `lib` directories exist + eyre::ensure!(Path::exists(&root.join(src_dir)), "`src` directory must exists"); + eyre::ensure!(Path::exists(&root.join(lib_dir)), "`lib` directory must exists"); + + // move source files + for entry in std::fs::read_dir(tmp_dump_dir.join(contract_name))? { + let entry = entry?; + let folder_name = entry.file_name(); + // special handling when we need to re-organize the directories: we flatten them. + if to_reorg { + if folder_name == "contracts" || folder_name == "src" || folder_name == "lib" { + // move all sub folders in contracts to src or lib + let new_dir = if folder_name == "lib" { lib_dir } else { src_dir }; + for e in read_dir(entry.path())? { + let e = e?; + let dest = new_dir.join(&e.file_name()); + eyre::ensure!(!Path::exists(&dest), "destination already exists: {:?}", dest); + std::fs::rename(e.path(), &dest)?; + remappings.push(Remapping { + context: None, + name: format!( + "{}/{}", + folder_name.to_string_lossy(), + e.file_name().to_string_lossy() + ), + path: dest.to_string_lossy().to_string(), + }); + } + } else { + assert!( + folder_name == "hardhat" || + folder_name == "forge-std" || + folder_name.to_string_lossy().starts_with('@') + ); + // move these other folders to lib + let dest = lib_dir.join(&folder_name); + if folder_name == "forge-std" { + // let's use the provided forge-std directory + std::fs::remove_dir_all(&dest)?; + } + eyre::ensure!(!Path::exists(&dest), "destination already exists: {:?}", dest); + std::fs::rename(entry.path(), &dest)?; + remappings.push(Remapping { + context: None, + name: folder_name.to_string_lossy().to_string(), + path: dest.to_string_lossy().to_string(), + }); + } + } else { + // directly move the all folders into src + let dest = src_dir.join(&folder_name); + eyre::ensure!(!Path::exists(&dest), "destination already exists: {:?}", dest); + std::fs::rename(entry.path(), &dest)?; + if folder_name != "src" { + remappings.push(Remapping { + context: None, + name: folder_name.to_string_lossy().to_string(), + path: dest.to_string_lossy().to_string(), + }); + } + } + } + + // remove the temporary directory + std::fs::remove_dir_all(tmp_dump_dir)?; + + // add remappings in the metedata + for mut r in meta.settings()?.remappings { + if to_reorg { + // we should update its remapped path in the same way as we dump sources + // i.e., remove prefix `contracts` (if any) and add prefix `src` + let new_path = if r.path.starts_with("contracts") { + PathBuf::from("src").join(PathBuf::from(&r.path).strip_prefix("contracts")?) + } else if r.path.starts_with('@') || + r.path.starts_with("hardhat/") || + r.path.starts_with("forge-std/") + { + PathBuf::from("lib").join(PathBuf::from(&r.path)) + } else { + PathBuf::from(&r.path) + }; + r.path = new_path.to_string_lossy().to_string(); + remappings.push(r); + } else { + remappings.push(r); + } + } + + Ok(remappings.into_iter().map(|r| r.into_relative(root)).collect()) +} + +/// Compile the project in the root directory, and return the compilation result. +pub fn compile_project(root: &PathBuf, quiet: bool) -> Result { + let mut config = Config::load_with_root(root).sanitized(); + config.extra_output.push(ContractOutputSelection::StorageLayout); + let project = config.project()?; + let compiler = ProjectCompiler::new().quiet_if(quiet); + compiler.compile(&project) +} + +/// Find the artifact of the contract with the specified name. +/// This function returns the path to the source file and the artifact. +pub fn find_main_contract<'a>( + compile_output: &'a ProjectCompileOutput, + contract: &str, +) -> Result<(PathBuf, &'a ConfigurableContractArtifact)> { + let mut rv = None; + for (f, c, a) in compile_output.artifacts_with_files() { + if contract == c { + // it is possible that we have multiple contracts with the same name + // in different files + // instead of throwing an error, we should handle this case in the future + if rv.is_some() { + return Err(eyre::eyre!("multiple contracts with the same name found")); + } + rv = Some((PathBuf::from(f), a)); + } + } + rv.ok_or(eyre::eyre!("contract not found")) +} + +#[cfg(test)] +use mockall::automock; +/// EtherscanClient is a trait that defines the methods to interact with Etherscan. +/// It is defined as a wrapper of the `foundry_block_explorers::Client` to allow mocking. +#[cfg_attr(test, automock)] +pub(crate) trait EtherscanClient { + async fn contract_source_code( + &self, + address: Address, + ) -> std::result::Result; + async fn contract_creation_data( + &self, + address: Address, + ) -> std::result::Result; +} + +impl EtherscanClient for Client { + #[inline] + async fn contract_source_code( + &self, + address: Address, + ) -> std::result::Result { + self.contract_source_code(address).await + } + + #[inline] + async fn contract_creation_data( + &self, + address: Address, + ) -> std::result::Result { + self.contract_creation_data(address).await + } +} + +#[cfg(test)] +mod tests { + use super::*; + use foundry_compilers::Artifact; + use foundry_test_utils::rpc::next_etherscan_api_key; + use hex::ToHex; + use std::collections::BTreeMap; + + fn assert_successful_compilation(root: &PathBuf) -> ProjectCompileOutput { + println!("project_root: {:#?}", root); + compile_project(root, false).expect("compilation failure") + } + + fn assert_compilation_result( + compiled: ProjectCompileOutput, + contract_name: &str, + stripped_creation_code: &str, + ) { + compiled.compiled_contracts_by_compiler_version().iter().for_each(|(_, contracts)| { + contracts.iter().for_each(|(name, contract)| { + if name == contract_name { + let compiled_creation_code = + contract.get_bytecode_object().expect("creation code not found"); + let compiled_creation_code: String = compiled_creation_code.encode_hex(); + assert!( + compiled_creation_code.starts_with(stripped_creation_code), + "inconsistent creation code" + ); + } + }); + }); + } + + fn mock_etherscan(address: Address) -> impl super::EtherscanClient { + // load mock data + let mut mocked_data = BTreeMap::new(); + let data_folder = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata/etherscan"); + // iterate each sub folder + for entry in std::fs::read_dir(data_folder).expect("failed to read test data folder") { + let entry = entry.expect("failed to read test data entry"); + let addr: Address = entry.file_name().to_string_lossy().parse().unwrap(); + let contract_data_dir = entry.path(); + // the metadata.json file contains the metadata of the contract + let metadata_file = contract_data_dir.join("metadata.json"); + let metadata: ContractMetadata = + serde_json::from_str(&std::fs::read_to_string(metadata_file).unwrap()) + .expect("failed to parse metadata.json"); + // the creation_data.json file contains the creation data of the contract + let creation_data_file = contract_data_dir.join("creation_data.json"); + let creation_data: ContractCreationData = + serde_json::from_str(&std::fs::read_to_string(creation_data_file).unwrap()) + .expect("failed to parse creation_data.json"); + // insert the data to the map + mocked_data.insert(addr, (metadata, creation_data)); + } + + let (metadata, creation_data) = mocked_data.get(&address).unwrap(); + let metadata = metadata.clone(); + let creation_data = *creation_data; + let mut mocked_client = super::MockEtherscanClient::new(); + mocked_client + .expect_contract_source_code() + .times(1) + .returning(move |_| Ok(metadata.clone())); + mocked_client + .expect_contract_creation_data() + .times(1) + .returning(move |_| Ok(creation_data)); + mocked_client + } + + /// Fetch the metadata and creation data from Etherscan and dump them to the testdata folder. + #[tokio::test(flavor = "multi_thread")] + #[ignore = "this test is used to dump mock data from Etherscan"] + async fn test_dump_mock_data() { + let address: Address = "0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F".parse().unwrap(); + let data_folder = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../testdata/etherscan") + .join(address.to_string()); + // create folder if not exists + std::fs::create_dir_all(&data_folder).unwrap(); + // create metadata.json and creation_data.json + let client = Client::new(Chain::mainnet(), next_etherscan_api_key()).unwrap(); + let meta = client.contract_source_code(address).await.unwrap(); + // dump json + let json = serde_json::to_string_pretty(&meta).unwrap(); + // write to metadata.json + std::fs::write(data_folder.join("metadata.json"), json).unwrap(); + let creation_data = client.contract_creation_data(address).await.unwrap(); + // dump json + let json = serde_json::to_string_pretty(&creation_data).unwrap(); + // write to creation_data.json + std::fs::write(data_folder.join("creation_data.json"), json).unwrap(); + } + + /// Run the clone command with the specified contract address and assert the compilation. + async fn one_test_case(address: Address, check_compilation_result: bool) { + let mut project_root = tempfile::tempdir().unwrap().path().to_path_buf(); + let client = mock_etherscan(address); + let meta = CloneArgs::collect_metadata_from_client(address, &client).await.unwrap(); + CloneArgs::init_an_empty_project(&project_root, DependencyInstallOpts::default()).unwrap(); + project_root = dunce::canonicalize(&project_root).unwrap(); + CloneArgs::parse_metadata(&meta, Chain::mainnet(), &project_root, false, false) + .await + .unwrap(); + CloneArgs::collect_compilation_metadata( + &meta, + Chain::mainnet(), + address, + &project_root, + &client, + false, + ) + .await + .unwrap(); + let rv = assert_successful_compilation(&project_root); + if check_compilation_result { + let (contract_name, stripped_creation_code) = + pick_creation_info(&address.to_string()).expect("creation code not found"); + assert_compilation_result(rv, contract_name, stripped_creation_code); + } + std::fs::remove_dir_all(project_root).unwrap(); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_single_file_contract() { + let address = "0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193".parse().unwrap(); + one_test_case(address, true).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_optimization_details() { + let address = "0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545".parse().unwrap(); + one_test_case(address, true).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_libraries() { + let address = "0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec".parse().unwrap(); + one_test_case(address, true).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_metadata() { + let address = "0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05".parse().unwrap(); + one_test_case(address, true).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_relative_import() { + let address = "0x3a23F943181408EAC424116Af7b7790c94Cb97a5".parse().unwrap(); + one_test_case(address, false).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_original_remappings() { + let address = "0x9ab6b21cdf116f611110b048987e58894786c244".parse().unwrap(); + one_test_case(address, false).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_relative_import2() { + let address = "0x044b75f554b886A065b9567891e45c79542d7357".parse().unwrap(); + one_test_case(address, false).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_nested_src() { + let address = "0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F".parse().unwrap(); + one_test_case(address, false).await + } + + fn pick_creation_info(address: &str) -> Option<(&'static str, &'static str)> { + for (addr, contract_name, creation_code) in CREATION_ARRAY.iter() { + if address == *addr { + return Some((contract_name, creation_code)); + } + } + + None + } + + // remember to remove CBOR metadata from the creation code + const CREATION_ARRAY: [(&str, &str, &str); 4] = [ + ("0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193", "BearXNFTStaking", "608060405234801561001057600080fd5b50613000806100206000396000f3fe608060405234801561001057600080fd5b50600436106102265760003560e01c80638129fc1c11610130578063bca35a71116100b8578063dada55011161007c578063dada550114610458578063f2fde38b1461046b578063f83d08ba1461047e578063fbb0022714610486578063fccd7f721461048e57600080fd5b8063bca35a71146103fa578063bf9befb11461040d578063c89d5b8b14610416578063d5d423001461041e578063d976e09f1461042657600080fd5b8063b1c92f95116100ff578063b1c92f95146103c5578063b549445c146103ce578063b81f8e89146103d6578063b9ade5b7146103de578063ba0848db146103e757600080fd5b80638129fc1c146103905780638da5cb5b14610398578063aaed083b146103a9578063b10dcc93146103b257600080fd5b8063367c164e116101b35780635923489b116101825780635923489b146103245780636e2751211461034f578063706ce3e114610362578063715018a614610375578063760a2e8a1461037d57600080fd5b8063367c164e146102bd57806338ff8a85146102d05780633a17f4f0146102f1578063426233601461030457600080fd5b8063206635e7116101fa578063206635e71461026d5780632afe761a146102805780632bd30f1114610289578063305f839a146102ab57806333ddacd1146102b457600080fd5b8062944f621461022b5780630d00368b146102405780630e8feed41461025c578063120957fd14610264575b600080fd5b61023e610239366004612aa4565b6104bc565b005b61024960735481565b6040519081526020015b60405180910390f35b61023e61053a565b610249606d5481565b61023e61027b366004612b2c565b61057e565b610249606f5481565b60785461029b90610100900460ff1681565b6040519015158152602001610253565b61024960715481565b61024960765481565b61023e6102cb366004612bc2565b6105d1565b6102e36102de366004612aa4565b610829565b604051610253929190612c16565b61023e6102ff366004612aa4565b6109e1565b610317610312366004612aa4565b610a56565b6040516102539190612c2f565b606a54610337906001600160a01b031681565b6040516001600160a01b039091168152602001610253565b6102e361035d366004612aa4565b610b4c565b606b54610337906001600160a01b031681565b61023e610cf8565b61029b61038b366004612aa4565b610d2e565b61023e610dc2565b6033546001600160a01b0316610337565b61024960705481565b61023e6103c0366004612b2c565b610fc0565b610249606e5481565b61023e611236565b61023e6112bb565b61024960725481565b6102e36103f5366004612aa4565b6112ef565b61023e610408366004612aa4565b61149b565b610249606c5481565b610249611510565b606f54610249565b610439610434366004612aa4565b611594565b6040805192151583526001600160a01b03909116602083015201610253565b606954610337906001600160a01b031681565b61023e610479366004612aa4565b6115cf565b61023e611667565b61023e6116a0565b6104a161049c366004612aa4565b6116e1565b60408051938452602084019290925290820152606001610253565b6033546001600160a01b031633146104ef5760405162461bcd60e51b81526004016104e690612c42565b60405180910390fd5b606b546001600160a01b03163b6105185760405162461bcd60e51b81526004016104e690612c77565b606b80546001600160a01b0319166001600160a01b0392909216919091179055565b600061054533611594565b509050806105655760405162461bcd60e51b81526004016104e690612cae565b61056e33610d2e565b61057b5761057b33611c73565b50565b610587336116e1565b505060765560005b81518110156105cd576105bb8282815181106105ad576105ad612cda565b602002602001015133611d7b565b806105c581612d06565b91505061058f565b5050565b606a54604051636eb1769f60e11b815233600482015273871770e3e03bfaefa3597056e540a1a9c9ac7f6b602482015282916001600160a01b03169063dd62ed3e90604401602060405180830381865afa158015610633573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106579190612d21565b10156106bb5760405162461bcd60e51b815260206004820152602d60248201527f596f75206861766520746f20617070726f766520726f6f747820746f2073746160448201526c1ada5b99c818dbdb9d1c9858dd609a1b60648201526084016104e6565b606a546040516323b872dd60e01b815233600482015273871770e3e03bfaefa3597056e540a1a9c9ac7f6b6024820152604481018390526001600160a01b03909116906323b872dd906064016020604051808303816000875af1158015610726573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061074a9190612d3a565b50606a546040516326c7e79d60e21b8152600481018390526001600160a01b0390911690639b1f9e7490602401600060405180830381600087803b15801561079157600080fd5b505af11580156107a5573d6000803e3d6000fd5b5050606b546001600160a01b031691506379c650689050336107c88460056120a1565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b15801561080e57600080fd5b505af1158015610822573d6000803e3d6000fd5b5050505050565b600060606000805b6001600160a01b0385166000908152607460205260409020548110156108bc576001600160a01b0385166000908152607460205260409020805461089791908390811061088057610880612cda565b906000526020600020906005020160000154612129565b156108aa576108a7600183612d5c565b91505b806108b481612d06565b915050610831565b5060008167ffffffffffffffff8111156108d8576108d8612ac1565b604051908082528060200260200182016040528015610901578160200160208202803683370190505b5090506000805b6001600160a01b0387166000908152607460205260409020548110156109d5576001600160a01b0387166000908152607460205260409020805461095791908390811061088057610880612cda565b156109c3576001600160a01b038716600090815260746020526040902080548290811061098657610986612cda565b9060005260206000209060050201600001548383815181106109aa576109aa612cda565b60209081029190910101526109c0826001612154565b91505b806109cd81612d06565b915050610908565b50919590945092505050565b6033546001600160a01b03163314610a0b5760405162461bcd60e51b81526004016104e690612c42565b6069546001600160a01b03163b610a345760405162461bcd60e51b81526004016104e690612c77565b606980546001600160a01b0319166001600160a01b0392909216919091179055565b6001600160a01b0381166000908152607460205260408120546060919067ffffffffffffffff811115610a8b57610a8b612ac1565b604051908082528060200260200182016040528015610ab4578160200160208202803683370190505b50905060005b6001600160a01b038416600090815260746020526040902054811015610b45576001600160a01b0384166000908152607460205260409020805482908110610b0457610b04612cda565b906000526020600020906005020160000154828281518110610b2857610b28612cda565b602090810291909101015280610b3d81612d06565b915050610aba565b5092915050565b600060606000805b6001600160a01b038516600090815260746020526040902054811015610bdf576001600160a01b03851660009081526074602052604090208054610bba919083908110610ba357610ba3612cda565b9060005260206000209060050201600001546121b3565b15610bcd57610bca826001612154565b91505b80610bd781612d06565b915050610b54565b5060008167ffffffffffffffff811115610bfb57610bfb612ac1565b604051908082528060200260200182016040528015610c24578160200160208202803683370190505b5090506000805b6001600160a01b0387166000908152607460205260409020548110156109d5576001600160a01b03871660009081526074602052604090208054610c7a919083908110610ba357610ba3612cda565b15610ce6576001600160a01b0387166000908152607460205260409020805482908110610ca957610ca9612cda565b906000526020600020906005020160000154838381518110610ccd57610ccd612cda565b6020908102919091010152610ce3826001612154565b91505b80610cf081612d06565b915050610c2b565b6033546001600160a01b03163314610d225760405162461bcd60e51b81526004016104e690612c42565b610d2c60006121d0565b565b60006001815b6001600160a01b038416600090815260746020526040902054811015610b45576001600160a01b03841660009081526074602052604081208054610d9a919084908110610d8357610d83612cda565b906000526020600020906005020160010154612222565b9050603c8111610daa5750610db0565b60009250505b80610dba81612d06565b915050610d34565b600054610100900460ff16610ddd5760005460ff1615610de1565b303b155b610e445760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016104e6565b600054610100900460ff16158015610e66576000805461ffff19166101011790555b610e6e61223c565b606580546001600160a01b0319908116737a250d5630b4cf539739df2c5dacb4c659f2488d1790915560668054821673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2179055620151806067556312cc030060685560698054821673e22e1e620dffb03065cd77db0162249c0c91bf01179055606a8054821673d718ad25285d65ef4d79262a6cd3aea6a8e01023179055606b80549091167399cfdf48d0ba4885a73786148a2f89d86c7021701790556000606c5568056bc75e2d63100000606d556802b5e3af16b1880000606e55690257058e269742680000606f819055681b1ae4d6e2ef5000006070819055610bb8607181905591610f709190612d74565b610f7a9190612d8b565b607255607154606e54606d54610f909190612d74565b610f9a9190612d8b565b60735560006076556078805460ff19169055801561057b576000805461ff001916905550565b6000610fcb33611594565b50905080610feb5760405162461bcd60e51b81526004016104e690612cae565b607854610100900460ff161561102c5760405162461bcd60e51b8152602060048201526006602482015265131bd8dad95960d21b60448201526064016104e6565b600061103733610a56565b90508051835111156110775760405162461bcd60e51b81526020600482015260096024820152684964206572726f727360b81b60448201526064016104e6565b6000805b84518110156110fd5760005b83518110156110ea578381815181106110a2576110a2612cda565b60200260200101518683815181106110bc576110bc612cda565b602002602001015114156110d8576110d5836001612154565b92505b806110e281612d06565b915050611087565b50806110f581612d06565b91505061107b565b50835181141561123057835161112761111e82678ac7230489e800006120a1565b606f5490612154565b606f55611132612273565b600060768190555b855181101561122d5760695486516001600160a01b03909116906323b872dd90309033908a908690811061117057611170612cda565b60209081029190910101516040516001600160e01b031960e086901b1681526001600160a01b0393841660048201529290911660248301526044820152606401600060405180830381600087803b1580156111ca57600080fd5b505af11580156111de573d6000803e3d6000fd5b5050606c80549250905060006111f383612dad565b919050555061121b3387838151811061120e5761120e612cda565b602002602001015161229b565b8061122581612d06565b91505061113a565b50505b50505050565b60785460ff166112ac5760005b60755481101561057b576001607760006075848154811061126657611266612cda565b6000918252602080832091909101546001600160a01b031683528201929092526040019020805460ff1916911515919091179055806112a481612d06565b915050611243565b6078805460ff19166001179055565b60006112c633611594565b509050806112e65760405162461bcd60e51b81526004016104e690612cae565b61057b33612470565b600060606000805b6001600160a01b038516600090815260746020526040902054811015611382576001600160a01b0385166000908152607460205260409020805461135d91908390811061134657611346612cda565b906000526020600020906005020160000154612574565b156113705761136d600183612d5c565b91505b8061137a81612d06565b9150506112f7565b5060008167ffffffffffffffff81111561139e5761139e612ac1565b6040519080825280602002602001820160405280156113c7578160200160208202803683370190505b5090506000805b6001600160a01b0387166000908152607460205260409020548110156109d5576001600160a01b0387166000908152607460205260409020805461141d91908390811061134657611346612cda565b15611489576001600160a01b038716600090815260746020526040902080548290811061144c5761144c612cda565b90600052602060002090600502016000015483838151811061147057611470612cda565b6020908102919091010152611486826001612154565b91505b8061149381612d06565b9150506113ce565b6033546001600160a01b031633146114c55760405162461bcd60e51b81526004016104e690612c42565b606a546001600160a01b03163b6114ee5760405162461bcd60e51b81526004016104e690612c77565b606a80546001600160a01b0319166001600160a01b0392909216919091179055565b6000806064606f5460016115249190612dc4565b61152e9190612d8b565b611539906001612d5c565b606d546115469190612dc4565b90506000606d54826115589190612d74565b905060006001606d548361156c9190612d8b565b6115769190612d8b565b611581906001612dc4565b61158c906064612dc4565b949350505050565b6001600160a01b038116600090815260776020526040812054819060ff161515600114156115c457506001929050565b506000928392509050565b6033546001600160a01b031633146115f95760405162461bcd60e51b81526004016104e690612c42565b6001600160a01b03811661165e5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016104e6565b61057b816121d0565b73d0d725208fd36be1561050fc1dd6a651d7ea7c89331415610d2c576078805461ff001981166101009182900460ff1615909102179055565b60006116ab33611594565b509050806116cb5760405162461bcd60e51b81526004016104e690612cae565b6116d433610d2e565b61057b5761057b336125aa565b600080808080808087816116f482610829565b509050600061170283610b4c565b50905060058110611a3b57600160005b6001600160a01b038516600090815260746020526040902054811015611a0e576001600160a01b0385166000908152607460205260408120805461177891908490811061176157611761612cda565b906000526020600020906005020160040154612222565b6001600160a01b038716600090815260746020526040902080549192506117a99184908110610ba357610ba3612cda565b806117de57506001600160a01b038616600090815260746020526040902080546117de91908490811061088057610880612cda565b80156117ea5750600181105b156117f457600092505b82801561181857506001600160a01b03861660009081526074602052604090205415155b156118715761186a8561182c83600a612dc4565b6118369190612dc4565b6118648661184585600a612dc4565b61184f9190612dc4565b60765461186490670de0b6b3a7640000612798565b90612154565b995061188e565b8261188e5760765461188b90670de0b6b3a7640000612798565b99505b6001600160a01b038616600090815260746020526040902080546118bd91908490811061088057610880612cda565b15611950576001600160a01b038616600090815260746020526040812080546119089190859081106118f1576118f1612cda565b906000526020600020906005020160020154612222565b90508061192861192182680ad78ebc5ac6200000612dc4565b8c90612154565b9a5061194761194082680ad78ebc5ac6200000612dc4565b8b90612154565b995050506119fb565b6001600160a01b0386166000908152607460205260409020805461197f919084908110610ba357610ba3612cda565b156119fb576001600160a01b038616600090815260746020526040812080546119b39190859081106118f1576118f1612cda565b905060008190506119d76002606d54846119cd9190612dc4565b6119219190612d8b565b9a506119f66002606d54836119ec9190612dc4565b6119409190612d8b565b995050505b5080611a0681612d06565b915050611712565b508515611a3557606b54606654611a32916001600160a01b039081169116886127da565b94505b50611c4f565b60005b6001600160a01b038416600090815260746020526040902054811015611c28576001600160a01b03841660009081526074602052604090208054611a8d91908390811061088057610880612cda565b15611b9f576001600160a01b03841660009081526074602052604081208054611ac191908490811061176157611761612cda565b9050611ad8611ad182600a612dc4565b8a90612154565b98506000611b1560746000886001600160a01b03166001600160a01b0316815260200190815260200160002084815481106118f1576118f1612cda565b9050611b2d611ad182680ad78ebc5ac6200000612dc4565b98506000611b8160746000896001600160a01b03166001600160a01b031681526020019081526020016000208581548110611b6a57611b6a612cda565b906000526020600020906005020160030154612222565b9050611b99611ad182680ad78ebc5ac6200000612dc4565b98505050505b6001600160a01b03841660009081526074602052604090208054611bce919083908110610ba357610ba3612cda565b15611c16576001600160a01b03841660009081526074602052604081208054611c0291908490811061176157611761612cda565b9050611c12611ad182600a612dc4565b9850505b80611c2081612d06565b915050611a3e565b508415611c4f57606b54606654611c4c916001600160a01b039081169116876127da565b93505b611c6187670de0b6b3a7640000612dc4565b9b959a50929850939650505050505050565b6000611c7e826116e1565b5091505080156105cd57606b5460405163a9059cbb60e01b81526001600160a01b038481166004830152602482018490529091169063a9059cbb906044016020604051808303816000875af1158015611cdb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cff9190612d3a565b5060005b6001600160a01b038316600090815260746020526040902054811015611d76576001600160a01b0383166000908152607460205260409020805442919083908110611d5057611d50612cda565b600091825260209091206002600590920201015580611d6e81612d06565b915050611d03565b505050565b607054606f5410611db857607354606d6000828254611d9a9190612d74565b9091555050606f54611db490678ac7230489e80000612905565b606f555b6069546040516331a9108f60e11b8152600481018490526001600160a01b03838116921690636352211e90602401602060405180830381865afa158015611e03573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e279190612de3565b6001600160a01b031614611e7d5760405162461bcd60e51b815260206004820152601e60248201527f596f7520617265206e6f742061206f776e6572206f6620746865206e6674000060448201526064016104e6565b60695460405163e985e9c560e01b81523360048201523060248201526001600160a01b039091169063e985e9c590604401602060405180830381865afa158015611ecb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611eef9190612d3a565b1515600114611f575760405162461bcd60e51b815260206004820152602e60248201527f596f752073686f756c6420617070726f7665206e667420746f2074686520737460448201526d185ada5b99c818dbdb9d1c9858dd60921b60648201526084016104e6565b6069546040516323b872dd60e01b81526001600160a01b03838116600483015230602483015260448201859052909116906323b872dd90606401600060405180830381600087803b158015611fab57600080fd5b505af1158015611fbf573d6000803e3d6000fd5b505050506000611fce82611594565b50905060006040518060a001604052808581526020014281526020014281526020014281526020014281525090506120126001606c5461215490919063ffffffff16565b606c556001600160a01b03831660009081526074602090815260408083208054600181810183559185529383902085516005909502019384559184015191830191909155820151600282015560608201516003820155608082015160049091015581611230576001600160a01b0383166000908152607760205260409020805460ff1916600117905550505050565b6000826120b057506000612123565b60006120bc8385612dc4565b9050826120c98583612d8b565b146121205760405162461bcd60e51b815260206004820152602160248201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6044820152607760f81b60648201526084016104e6565b90505b92915050565b60008064e8d4a510008310158015612146575064e8d4a510058311155b156121235750600192915050565b6000806121618385612d5c565b9050838110156121205760405162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f77000000000060448201526064016104e6565b600080610e7483116121c757506001612123565b50600092915050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6067546000906122328342612d74565b6121239190612d8b565b600054610100900460ff166122635760405162461bcd60e51b81526004016104e690612e00565b61226b612947565b610d2c61296e565b61227c33612470565b61228533610d2e565b610d2c5761229233611c73565b610d2c336125aa565b60005b6001600160a01b038316600090815260746020526040902054811015612428576001600160a01b03831660009081526074602052604090208054839190839081106122eb576122eb612cda565b9060005260206000209060050201600001541415612416576001600160a01b0383166000908152607460205260409020805461232990600190612d74565b8154811061233957612339612cda565b906000526020600020906005020160746000856001600160a01b03166001600160a01b03168152602001908152602001600020828154811061237d5761237d612cda565b60009182526020808320845460059093020191825560018085015490830155600280850154908301556003808501549083015560049384015493909101929092556001600160a01b03851681526074909152604090208054806123e2576123e2612e4b565b6000828152602081206005600019909301928302018181556001810182905560028101829055600381018290556004015590555b8061242081612d06565b91505061229e565b506001600160a01b0382166000908152607460205260409020546105cd576001600160a01b038216600090815260746020526040812061246791612a3f565b6105cd8261299e565b600061247b826116e1565b509091505080156105cd57606a5460405163a9059cbb60e01b81526001600160a01b038481166004830152602482018490529091169063a9059cbb906044016020604051808303816000875af11580156124d9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124fd9190612d3a565b5060005b6001600160a01b038316600090815260746020526040902054811015611d76576001600160a01b038316600090815260746020526040902080544291908390811061254e5761254e612cda565b60009182526020909120600460059092020101558061256c81612d06565b915050612501565b60006509184e72a00682101561258c57506000919050565b6509184e72b4b38211156125a257506000919050565b506001919050565b60006125b5826116e1565b5091505080156105cd57606b5460655460405163095ea7b360e01b81526001600160a01b0391821660048201526024810184905291169063095ea7b3906044016020604051808303816000875af1158015612614573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126389190612d3a565b5060408051600280825260608083018452926020830190803683375050606b5482519293506001600160a01b03169183915060009061267957612679612cda565b6001600160a01b0392831660209182029290920101526066548251911690829060019081106126aa576126aa612cda565b6001600160a01b03928316602091820292909201015260655460405163791ac94760e01b815291169063791ac947906126f0908590600090869089904290600401612e9a565b600060405180830381600087803b15801561270a57600080fd5b505af115801561271e573d6000803e3d6000fd5b5050505060005b6001600160a01b038416600090815260746020526040902054811015611230576001600160a01b038416600090815260746020526040902080544291908390811061277257612772612cda565b60009182526020909120600360059092020101558061279081612d06565b915050612725565b600061212083836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f0000000000008152506129d7565b6040805160028082526060808301845260009390929190602083019080368337019050509050848160008151811061281457612814612cda565b60200260200101906001600160a01b031690816001600160a01b031681525050838160018151811061284857612848612cda565b6001600160a01b03928316602091820292909201015260655460405163d06ca61f60e01b8152600092919091169063d06ca61f9061288c9087908690600401612ed6565b600060405180830381865afa1580156128a9573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526128d19190810190612eef565b905080600183516128e29190612d74565b815181106128f2576128f2612cda565b6020026020010151925050509392505050565b600061212083836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250612a0e565b600054610100900460ff16610d2c5760405162461bcd60e51b81526004016104e690612e00565b600054610100900460ff166129955760405162461bcd60e51b81526004016104e690612e00565b610d2c336121d0565b6000806129aa83611594565b915091508115611d76576001600160a01b03166000908152607760205260409020805460ff191690555050565b600081836129f85760405162461bcd60e51b81526004016104e69190612f75565b506000612a058486612d8b565b95945050505050565b60008184841115612a325760405162461bcd60e51b81526004016104e69190612f75565b506000612a058486612d74565b508054600082556005029060005260206000209081019061057b91905b80821115612a8b5760008082556001820181905560028201819055600382018190556004820155600501612a5c565b5090565b6001600160a01b038116811461057b57600080fd5b600060208284031215612ab657600080fd5b813561212081612a8f565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff81118282101715612b0057612b00612ac1565b604052919050565b600067ffffffffffffffff821115612b2257612b22612ac1565b5060051b60200190565b60006020808385031215612b3f57600080fd5b823567ffffffffffffffff811115612b5657600080fd5b8301601f81018513612b6757600080fd5b8035612b7a612b7582612b08565b612ad7565b81815260059190911b82018301908381019087831115612b9957600080fd5b928401925b82841015612bb757833582529284019290840190612b9e565b979650505050505050565b600060208284031215612bd457600080fd5b5035919050565b600081518084526020808501945080840160005b83811015612c0b57815187529582019590820190600101612bef565b509495945050505050565b82815260406020820152600061158c6040830184612bdb565b6020815260006121206020830184612bdb565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526017908201527f41646472657373206973206e6f7420636f6e7472616374000000000000000000604082015260600190565b6020808252601290820152712cb7ba9030b932903737ba1039ba30b5b2b960711b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b6000600019821415612d1a57612d1a612cf0565b5060010190565b600060208284031215612d3357600080fd5b5051919050565b600060208284031215612d4c57600080fd5b8151801515811461212057600080fd5b60008219821115612d6f57612d6f612cf0565b500190565b600082821015612d8657612d86612cf0565b500390565b600082612da857634e487b7160e01b600052601260045260246000fd5b500490565b600081612dbc57612dbc612cf0565b506000190190565b6000816000190483118215151615612dde57612dde612cf0565b500290565b600060208284031215612df557600080fd5b815161212081612a8f565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b634e487b7160e01b600052603160045260246000fd5b600081518084526020808501945080840160005b83811015612c0b5781516001600160a01b031687529582019590820190600101612e75565b85815284602082015260a060408201526000612eb960a0830186612e61565b6001600160a01b0394909416606083015250608001529392505050565b82815260406020820152600061158c6040830184612e61565b60006020808385031215612f0257600080fd5b825167ffffffffffffffff811115612f1957600080fd5b8301601f81018513612f2a57600080fd5b8051612f38612b7582612b08565b81815260059190911b82018301908381019087831115612f5757600080fd5b928401925b82841015612bb757835182529284019290840190612f5c565b600060208083528351808285015260005b81811015612fa257858101830151858201604001528201612f86565b81811115612fb4576000604083870101525b50601f01601f191692909201604001939250505056fe"), + ("0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545", "GovernorCharlieDelegate", "608060405234801561001057600080fd5b50613e45806100206000396000f3fe60806040526004361061031a5760003560e01c80637b3c71d3116101ab578063d50572ee116100f7578063f0843ba811610095578063fc176c041161006f578063fc176c0414610b82578063fc4eee4214610ba2578063fc66ff1414610bb8578063fe0d94c114610bd857600080fd5b8063f0843ba814610b12578063f2b0653714610b32578063f682e04c14610b6257600080fd5b8063de7bc127116100d1578063de7bc127146109ec578063deaaa7cc14610a02578063e23a9a5214610a36578063e837159c14610afc57600080fd5b8063d50572ee146109a0578063da35c664146109b6578063ddf0b009146109cc57600080fd5b8063a6d8784a11610164578063c1a287e21161013e578063c1a287e214610933578063c4d66de81461094a578063c5a8425d1461096a578063c9fb9e871461098a57600080fd5b8063a6d8784a146108e7578063abaac6a8146108fd578063b58131b01461091d57600080fd5b80637b3c71d31461083c5780637bdbe4d01461085c5780637cae57bb14610871578063806bd5811461088757806386d37e8b146108a757806399533365146108c757600080fd5b80632fedff591161026a5780633e4f49e61161022357806350442098116101fd578063504420981461074657806356781388146107665780635c60da1b1461078657806366176743146107be57600080fd5b80633e4f49e6146106d957806340e58ee5146107065780634d6733d21461072657600080fd5b80632fedff59146105ee578063328dd9821461060e57806338bd0dda1461063e5780633932abb11461066b5780633af32abf146106815780633bccf4fd146106b957600080fd5b8063158ef93e116102d757806318b62629116102b157806318b626291461056e5780631dfb1b5a1461058457806320606b70146105a457806324bc1a64146105d857600080fd5b8063158ef93e146104f757806317977c611461052157806317ba1b8b1461054e57600080fd5b8063013cf08b1461031f57806302a251a31461042857806306fdde031461044c5780630825f38f146104a25780630ea2d98c146104b7578063140499ea146104d7575b600080fd5b34801561032b57600080fd5b506103b361033a3660046132ee565b60096020819052600091825260409091208054600182015460028301546007840154600885015495850154600a860154600b870154600c880154600d890154600e9099015497996001600160a01b0390971698959794969593949293919260ff808316936101008404821693620100009004909116918d565b604080519d8e526001600160a01b03909c1660208e01529a8c019990995260608b019790975260808a019590955260a089019390935260c088019190915260e08701521515610100860152151561012085015215156101408401526101608301526101808201526101a0015b60405180910390f35b34801561043457600080fd5b5061043e60045481565b60405190815260200161041f565b34801561045857600080fd5b506104956040518060400160405280601a81526020017f496e7465726573742050726f746f636f6c20476f7665726e6f7200000000000081525081565b60405161041f9190613363565b6104b56104b0366004613450565b610beb565b005b3480156104c357600080fd5b506104b56104d23660046132ee565b610e61565b3480156104e357600080fd5b506104b56104f23660046134d6565b610ec6565b34801561050357600080fd5b506012546105119060ff1681565b604051901515815260200161041f565b34801561052d57600080fd5b5061043e61053c3660046134d6565b600a6020526000908152604090205481565b34801561055a57600080fd5b506104b56105693660046132ee565b610f07565b34801561057a57600080fd5b5061043e600f5481565b34801561059057600080fd5b506104b561059f3660046132ee565b610f64565b3480156105b057600080fd5b5061043e7f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86681565b3480156105e457600080fd5b5061043e60015481565b3480156105fa57600080fd5b506104b56106093660046132ee565b610fc1565b34801561061a57600080fd5b5061062e6106293660046132ee565b61101e565b60405161041f94939291906135ba565b34801561064a57600080fd5b5061043e6106593660046134d6565b600d6020526000908152604090205481565b34801561067757600080fd5b5061043e60035481565b34801561068d57600080fd5b5061051161069c3660046134d6565b6001600160a01b03166000908152600d6020526040902054421090565b3480156106c557600080fd5b506104b56106d4366004613623565b6112af565b3480156106e557600080fd5b506106f96106f43660046132ee565b611516565b60405161041f9190613687565b34801561071257600080fd5b506104b56107213660046132ee565b61169e565b34801561073257600080fd5b506104b56107413660046136af565b611b80565b34801561075257600080fd5b506104b56107613660046132ee565b611c45565b34801561077257600080fd5b506104b56107813660046136d9565b611ca2565b34801561079257600080fd5b506000546107a6906001600160a01b031681565b6040516001600160a01b03909116815260200161041f565b3480156107ca57600080fd5b506108146107d9366004613705565b601160209081526000928352604080842090915290825290205460ff808216916101008104909116906201000090046001600160601b031683565b60408051931515845260ff90921660208401526001600160601b03169082015260600161041f565b34801561084857600080fd5b506104b5610857366004613728565b611d09565b34801561086857600080fd5b5061043e600a81565b34801561087d57600080fd5b5061043e600c5481565b34801561089357600080fd5b506104b56108a23660046132ee565b611d58565b3480156108b357600080fd5b506104b56108c23660046132ee565b611db5565b3480156108d357600080fd5b506104b56108e23660046134d6565b611e12565b3480156108f357600080fd5b5061043e60155481565b34801561090957600080fd5b506104b56109183660046132ee565b611e8b565b34801561092957600080fd5b5061043e60055481565b34801561093f57600080fd5b5061043e6212750081565b34801561095657600080fd5b506104b56109653660046134d6565b611ee8565b34801561097657600080fd5b50600e546107a6906001600160a01b031681565b34801561099657600080fd5b5061043e60135481565b3480156109ac57600080fd5b5061043e60025481565b3480156109c257600080fd5b5061043e60075481565b3480156109d857600080fd5b506104b56109e73660046132ee565b611fd9565b3480156109f857600080fd5b5061043e60105481565b348015610a0e57600080fd5b5061043e7f150214d74d59b7d1e90c73fc22ef3d991dd0a76b046543d4d80ab92d2a50328f81565b348015610a4257600080fd5b50610acc610a51366004613705565b60408051606081018252600080825260208201819052918101919091525060009182526011602090815260408084206001600160a01b03939093168452918152918190208151606081018352905460ff8082161515835261010082041693820193909352620100009092046001600160601b03169082015290565b6040805182511515815260208084015160ff1690820152918101516001600160601b03169082015260600161041f565b348015610b0857600080fd5b5061043e60145481565b348015610b1e57600080fd5b506104b5610b2d3660046132ee565b61238f565b348015610b3e57600080fd5b50610511610b4d3660046132ee565b600b6020526000908152604090205460ff1681565b348015610b6e57600080fd5b5061043e610b7d3660046139b0565b6123ec565b348015610b8e57600080fd5b506104b5610b9d3660046132ee565b612a48565b348015610bae57600080fd5b5061043e60065481565b348015610bc457600080fd5b506008546107a6906001600160a01b031681565b6104b5610be63660046132ee565b612aa5565b60008585858585604051602001610c06959493929190613a91565b60408051601f1981840301815291815281516020928301206000818152600b90935291205490915060ff16610c7b5760405162461bcd60e51b81526020600482015260166024820152753a3c103430b9b713ba103132b2b71038bab2bab2b21760511b60448201526064015b60405180910390fd5b81421015610ccb5760405162461bcd60e51b815260206004820152601d60248201527f7478206861736e2774207375727061737365642074696d656c6f636b2e0000006044820152606401610c72565b610cd86212750083613af3565b421115610d165760405162461bcd60e51b815260206004820152600c60248201526b3a3c1034b99039ba30b6329760a11b6044820152606401610c72565b6000818152600b60205260409020805460ff191690558351606090610d3c575082610d68565b848051906020012084604051602001610d56929190613b0b565b60405160208183030381529060405290505b6000876001600160a01b03168783604051610d839190613b3c565b60006040518083038185875af1925050503d8060008114610dc0576040519150601f19603f3d011682016040523d82523d6000602084013e610dc5565b606091505b5050905080610e0f5760405162461bcd60e51b81526020600482015260166024820152753a3c1032bc32b1baba34b7b7103932bb32b93a32b21760511b6044820152606401610c72565b876001600160a01b0316837fa560e3198060a2f10670c1ec5b403077ea6ae93ca8de1c32b451dc1a943cd6e789898989604051610e4f9493929190613b58565b60405180910390a35050505050505050565b333014610e805760405162461bcd60e51b8152600401610c7290613b95565b600480549082905560408051828152602081018490527f7e3f7f0708a84de9203036abaa450dccc85ad5ff52f78c170f3edb55cf5e882891015b60405180910390a15050565b333014610ee55760405162461bcd60e51b8152600401610c7290613b95565b600880546001600160a01b0319166001600160a01b0392909216919091179055565b333014610f265760405162461bcd60e51b8152600401610c7290613b95565b600580549082905560408051828152602081018490527fccb45da8d5717e6c4544694297c4ba5cf151d455c9bb0ed4fc7a38411bc054619101610eba565b333014610f835760405162461bcd60e51b8152600401610c7290613b95565b600380549082905560408051828152602081018490527fc565b045403dc03c2eea82b81a0465edad9e2e7fc4d97e11421c209da93d7a939101610eba565b333014610fe05760405162461bcd60e51b8152600401610c7290613b95565b601480549082905560408051828152602081018490527f519a192fe8db9e38785eb494c69f530ddb21b9e34322f8d08fe29bd3849749889101610eba565b606080606080600060096000878152602001908152602001600020905080600301816004018260050183600601838054806020026020016040519081016040528092919081815260200182805480156110a057602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611082575b50505050509350828054806020026020016040519081016040528092919081815260200182805480156110f257602002820191906000526020600020905b8154815260200190600101908083116110de575b5050505050925081805480602002602001604051908101604052809291908181526020016000905b828210156111c657838290600052602060002001805461113990613bcc565b80601f016020809104026020016040519081016040528092919081815260200182805461116590613bcc565b80156111b25780601f10611187576101008083540402835291602001916111b2565b820191906000526020600020905b81548152906001019060200180831161119557829003601f168201915b50505050508152602001906001019061111a565b50505050915080805480602002602001604051908101604052809291908181526020016000905b8282101561129957838290600052602060002001805461120c90613bcc565b80601f016020809104026020016040519081016040528092919081815260200182805461123890613bcc565b80156112855780601f1061125a57610100808354040283529160200191611285565b820191906000526020600020905b81548152906001019060200180831161126857829003601f168201915b5050505050815260200190600101906111ed565b5050505090509450945094509450509193509193565b604080518082018252601a81527f496e7465726573742050726f746f636f6c20476f7665726e6f7200000000000060209182015281517f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a866818301527f75a838dcd8ee5903cc7f4a5799344d0080864f57a6e9911f8bdfb4c8ddce9b5481840152466060820152306080808301919091528351808303909101815260a0820184528051908301207f150214d74d59b7d1e90c73fc22ef3d991dd0a76b046543d4d80ab92d2a50328f60c083015260e0820189905260ff8816610100808401919091528451808403909101815261012083019094528351939092019290922061190160f01b6101408401526101428301829052610162830181905290916000906101820160408051601f198184030181528282528051602091820120600080855291840180845281905260ff8a169284019290925260608301889052608083018790529092509060019060a0016020604051602081039080840390855afa15801561143c573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811661149f5760405162461bcd60e51b815260206004820181905260248201527f63617374566f746542795369673a20696e76616c6964207369676e61747572656044820152606401610c72565b88816001600160a01b03167fb8e138887d0aa13bab447e82de9d5c1777041ecd21ca36ba824ff1e6c07ddda48a6114d7858e8e612c90565b6040805160ff90931683526001600160601b039091166020830152606090820181905260009082015260800160405180910390a3505050505050505050565b6000816007541015801561152b575060065482115b6115775760405162461bcd60e51b815260206004820152601a60248201527f73746174653a20696e76616c69642070726f706f73616c2069640000000000006044820152606401610c72565b600082815260096020908152604080832060018101546001600160a01b03168452600d90925290912054600c82015442919091109060ff16156115be575060029392505050565b816007015443116115d3575060009392505050565b816008015443116115e8575060019392505050565b8080156115fc575081600d015482600a0154115b80611618575080158015611618575081600a0154826009015411155b80611633575080158015611633575081600d01548260090154105b15611642575060039392505050565b6002820154611655575060049392505050565b600c820154610100900460ff1615611671575060079392505050565b6212750082600201546116849190613af3565b4210611694575060069392505050565b5060059392505050565b60076116a982611516565b60078111156116ba576116ba613671565b14156117085760405162461bcd60e51b815260206004820152601d60248201527f63616e742063616e63656c2065786563757465642070726f706f73616c0000006044820152606401610c72565b600081815260096020526040902060018101546001600160a01b0316336001600160a01b0316146119755760018101546001600160a01b03166000908152600d6020526040902054421015611878576005546008546001838101546001600160a01b039283169263782d6fe1929116906117829043613c07565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186803b1580156117c657600080fd5b505afa1580156117da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117fe9190613c1e565b6001600160601b03161080156118275750600e546001600160a01b0316336001600160a01b0316145b6118735760405162461bcd60e51b815260206004820152601c60248201527f63616e63656c3a2077686974656c69737465642070726f706f736572000000006044820152606401610c72565b611975565b6005546008546001838101546001600160a01b039283169263782d6fe1929116906118a39043613c07565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186803b1580156118e757600080fd5b505afa1580156118fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061191f9190613c1e565b6001600160601b0316106119755760405162461bcd60e51b815260206004820181905260248201527f63616e63656c3a2070726f706f7365722061626f7665207468726573686f6c646044820152606401610c72565b600c8101805460ff1916600117905560005b6003820154811015611b5057611b3e8260030182815481106119ab576119ab613c47565b6000918252602090912001546004840180546001600160a01b0390921691849081106119d9576119d9613c47565b90600052602060002001548460050184815481106119f9576119f9613c47565b906000526020600020018054611a0e90613bcc565b80601f0160208091040260200160405190810160405280929190818152602001828054611a3a90613bcc565b8015611a875780601f10611a5c57610100808354040283529160200191611a87565b820191906000526020600020905b815481529060010190602001808311611a6a57829003601f168201915b5050505050856006018581548110611aa157611aa1613c47565b906000526020600020018054611ab690613bcc565b80601f0160208091040260200160405190810160405280929190818152602001828054611ae290613bcc565b8015611b2f5780601f10611b0457610100808354040283529160200191611b2f565b820191906000526020600020905b815481529060010190602001808311611b1257829003601f168201915b50505050508660020154612f12565b80611b4881613c5d565b915050611987565b5060405182907f789cf55be980739dad1d0699b93b58e806b51c9d96619bfa8fe0a28abaa7b30c90600090a25050565b333014611b9f5760405162461bcd60e51b8152600401610c7290613b95565b42601554611bad9190613af3565b8110611bf45760405162461bcd60e51b81526020600482015260166024820152750caf0e0d2e4c2e8d2dedc40caf0c6cacac8e640dac2f60531b6044820152606401610c72565b6001600160a01b0382166000818152600d6020908152604091829020849055815192835282018390527f4e7b7545bc5744d0e30425959f4687475774b6c7edad77d24cb51c7d967d45159101610eba565b333014611c645760405162461bcd60e51b8152600401610c7290613b95565b601080549082905560408051828152602081018490527f2a61b867418a359864adca8bb250ea65ee8bd41dbfd0279198d8e7552d4a27c29101610eba565b81337fb8e138887d0aa13bab447e82de9d5c1777041ecd21ca36ba824ff1e6c07ddda483611cd1838583612c90565b6040805160ff90931683526001600160601b039091166020830152606090820181905260009082015260800160405180910390a35050565b83337fb8e138887d0aa13bab447e82de9d5c1777041ecd21ca36ba824ff1e6c07ddda485611d38838583612c90565b8686604051611d4a9493929190613c78565b60405180910390a350505050565b333014611d775760405162461bcd60e51b8152600401610c7290613b95565b601380549082905560408051828152602081018490527f8cb5451eee8feb516cec9cd600201bbc31a30886d70c841a085a3fa69a4294d19101610eba565b333014611dd45760405162461bcd60e51b8152600401610c7290613b95565b600180549082905560408051828152602081018490527fa74554b0f53da47d07ec571d712428b3720460f54f81375fbcf78f6b5f72e7ed9101610eba565b333014611e315760405162461bcd60e51b8152600401610c7290613b95565b600e80546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f80a07e73e552148844a9c216d9724212d609cfa54e9c1a2e97203bdd2c4ad3419101610eba565b333014611eaa5760405162461bcd60e51b8152600401610c7290613b95565b600f80549082905560408051828152602081018490527f80a384652af83fc00bfd40ef94edda7ede83e7db39931b2c889821573f314e239101610eba565b60125460ff1615611f3b5760405162461bcd60e51b815260206004820152601860248201527f616c7265616479206265656e20696e697469616c697a656400000000000000006044820152606401610c72565b600880546001600160a01b0319166001600160a01b0392909216919091179055619d8060045561335460035569d3c21bcecceda10000006005556202a300600c5560006007556a084595161401484a00000060019081556a21165458500521280000006002556119aa600f5561a8c06010556a01a784379d99db420000006013556146506014556301e133806015556012805460ff19169091179055565b6004611fe482611516565b6007811115611ff557611ff5613671565b146120425760405162461bcd60e51b815260206004820152601f60248201527f63616e206f6e6c792062652071756575656420696620737563636565646564006044820152606401610c72565b6000818152600960205260408120600e8101549091906120629042613af3565b905060005b600383015481101561234d57600b600084600301838154811061208c5761208c613c47565b6000918252602090912001546004860180546001600160a01b0390921691859081106120ba576120ba613c47565b90600052602060002001548660050185815481106120da576120da613c47565b906000526020600020018760060186815481106120f9576120f9613c47565b9060005260206000200187604051602001612118959493929190613d62565b60408051601f198184030181529181528151602092830120835290820192909252016000205460ff161561218e5760405162461bcd60e51b815260206004820152601760248201527f70726f706f73616c20616c7265616479207175657565640000000000000000006044820152606401610c72565b61233a8360030182815481106121a6576121a6613c47565b6000918252602090912001546004850180546001600160a01b0390921691849081106121d4576121d4613c47565b90600052602060002001548560050184815481106121f4576121f4613c47565b90600052602060002001805461220990613bcc565b80601f016020809104026020016040519081016040528092919081815260200182805461223590613bcc565b80156122825780601f1061225757610100808354040283529160200191612282565b820191906000526020600020905b81548152906001019060200180831161226557829003601f168201915b505050505086600601858154811061229c5761229c613c47565b9060005260206000200180546122b190613bcc565b80601f01602080910402602001604051908101604052809291908181526020018280546122dd90613bcc565b801561232a5780601f106122ff5761010080835404028352916020019161232a565b820191906000526020600020905b81548152906001019060200180831161230d57829003601f168201915b50505050508688600e0154612fac565b508061234581613c5d565b915050612067565b506002820181905560405181815283907f9a2e42fd6722813d69113e7d0079d3d940171428df7373df9c7f7617cfda28929060200160405180910390a2505050565b3330146123ae5760405162461bcd60e51b8152600401610c7290613b95565b600280549082905560408051828152602081018490527fc2adf06da6765dba7faaccde4c0ce3f91c35dd3390e7f0b6bc2844202c9fa9529101610eba565b6000600154600014156124365760405162461bcd60e51b8152602060048201526012602482015271436861726c6965206e6f742061637469766560701b6044820152606401610c72565b6005546008546001600160a01b031663782d6fe133612456600143613c07565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186803b15801561249a57600080fd5b505afa1580156124ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124d29190613c1e565b6001600160601b03161015806124ec57506124ec3361069c565b6125385760405162461bcd60e51b815260206004820152601e60248201527f766f7465732062656c6f772070726f706f73616c207468726573686f6c6400006044820152606401610c72565b8551875114801561254a575084518751145b8015612557575083518751145b6125a35760405162461bcd60e51b815260206004820152601a60248201527f696e666f726d6174696f6e206172697479206d69736d617463680000000000006044820152606401610c72565b86516125e85760405162461bcd60e51b81526020600482015260146024820152736d7573742070726f7669646520616374696f6e7360601b6044820152606401610c72565b600a8751111561262d5760405162461bcd60e51b815260206004820152601060248201526f746f6f206d616e7920616374696f6e7360801b6044820152606401610c72565b336000908152600a6020526040902054801561271657600061264e82611516565b9050600181600781111561266457612664613671565b14156126b25760405162461bcd60e51b815260206004820152601e60248201527f6f6e65206c6976652070726f706f73616c207065722070726f706f73657200006044820152606401610c72565b60008160078111156126c6576126c6613671565b14156127145760405162461bcd60e51b815260206004820152601e60248201527f6f6e65206c6976652070726f706f73616c207065722070726f706f73657200006044820152606401610c72565b505b6007805490600061272683613c5d565b9190505550600060405180610220016040528060075481526020016127483390565b6001600160a01b03168152602001600081526020018a8152602001898152602001888152602001878152602001600354436127839190613af3565b8152602001600454600354436127999190613af3565b6127a39190613af3565b815260200160008152602001600081526020016000815260200160001515815260200160001515815260200185151581526020016001548152602001600c5481525090508380156127fa57506127f83361069c565b155b1561282c574360e08201819052600f5461281391613af3565b6101008201526002546101e08201526010546102008201525b6128353361069c565b15612876576013546101e08201526014546128509043613af3565b60e08201526004546014546128659043613af3565b61286f9190613af3565b6101008201525b805160009081526009602090815260409182902083518155818401516001820180546001600160a01b0319166001600160a01b03909216919091179055918301516002830155606083015180518493926128d792600385019291019061309d565b50608082015180516128f3916004840191602090910190613102565b5060a0820151805161290f91600584019160209091019061313d565b5060c0820151805161292b916006840191602090910190613196565b5060e08281015160078301556101008084015160088401556101208401516009840155610140840151600a80850191909155610160850151600b850155610180850151600c850180546101a08801516101c089015161ffff1990921693151561ff0019169390931792151585029290921762ff0000191662010000921515929092029190911790556101e0850151600d85015561020090940151600e9093019290925583516020808601516001600160a01b0316600090815294905260409384902055830151835191840151925190923392917f7d84a6263ae0d98d3329bd7b46bb4e8d6f98cd35a7adb45c274c8b7fd5ebd5e091612a33918f918f918f918f918f90613d9b565b60405180910390a45198975050505050505050565b333014612a675760405162461bcd60e51b8152600401610c7290613b95565b600c80549082905560408051828152602081018490527fed0229422af39d4d7d33f7a27d31d6f5cb20ec628293da58dd6e8a528ed466be9101610eba565b6005612ab082611516565b6007811115612ac157612ac1613671565b14612b0e5760405162461bcd60e51b815260206004820152601c60248201527f63616e206f6e6c792062652065786563276420696620717565756564000000006044820152606401610c72565b6000818152600960205260408120600c8101805461ff001916610100179055905b6003820154811015612c6057306001600160a01b0316630825f38f836004018381548110612b5f57612b5f613c47565b9060005260206000200154846003018481548110612b7f57612b7f613c47565b6000918252602090912001546004860180546001600160a01b039092169186908110612bad57612bad613c47565b9060005260206000200154866005018681548110612bcd57612bcd613c47565b90600052602060002001876006018781548110612bec57612bec613c47565b9060005260206000200188600201546040518763ffffffff1660e01b8152600401612c1b959493929190613d62565b6000604051808303818588803b158015612c3457600080fd5b505af1158015612c48573d6000803e3d6000fd5b50505050508080612c5890613c5d565b915050612b2f565b5060405182907f712ae1383f79ac853f8d882153778e0260ef8f03b504e2866e0593e04d2b291f90600090a25050565b60006001612c9d84611516565b6007811115612cae57612cae613671565b14612cee5760405162461bcd60e51b815260206004820152601060248201526f1d9bdd1a5b99c81a5cc818db1bdcd95960821b6044820152606401610c72565b60028260ff161115612d365760405162461bcd60e51b8152602060048201526011602482015270696e76616c696420766f7465207479706560781b6044820152606401610c72565b6000838152600960209081526040808320601183528184206001600160a01b0389168552909252909120805460ff1615612da85760405162461bcd60e51b81526020600482015260136024820152721d9bdd195c88185b1c9958591e481d9bdd1959606a1b6044820152606401610c72565b600854600783015460405163782d6fe160e01b81526000926001600160a01b03169163782d6fe191612df2918b916004016001600160a01b03929092168252602082015260400190565b60206040518083038186803b158015612e0a57600080fd5b505afa158015612e1e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612e429190613c1e565b905060ff8516612e6f57806001600160601b031683600a0154612e659190613af3565b600a840155612ec9565b8460ff1660011415612e9e57806001600160601b03168360090154612e949190613af3565b6009840155612ec9565b8460ff1660021415612ec957806001600160601b031683600b0154612ec39190613af3565b600b8401555b81546001600160601b03821662010000026dffffffffffffffffffffffff00001960ff88166101000261ffff199093169290921760011791909116179091559150509392505050565b60008585858585604051602001612f2d959493929190613a91565b60408051601f1981840301815282825280516020918201206000818152600b909252919020805460ff1916905591506001600160a01b0387169082907f2fffc091a501fd91bfbff27141450d3acb40fb8e6d8382b243ec7a812a3aaf8790612f9c908990899089908990613b58565b60405180910390a3505050505050565b6000612fb88242613af3565b831015612ffd5760405162461bcd60e51b815260206004820152601360248201527236bab9ba1039b0ba34b9b33c903232b630bc9760691b6044820152606401610c72565b60008787878787604051602001613018959493929190613a91565b60408051601f1981840301815282825280516020918201206000818152600b909252919020805460ff1916600117905591506001600160a01b0389169082907f76e2796dc3a81d57b0e8504b647febcbeeb5f4af818e164f11eef8131a6a763f9061308a908b908b908b908b90613b58565b60405180910390a3979650505050505050565b8280548282559060005260206000209081019282156130f2579160200282015b828111156130f257825182546001600160a01b0319166001600160a01b039091161782556020909201916001909101906130bd565b506130fe9291506131ef565b5090565b8280548282559060005260206000209081019282156130f2579160200282015b828111156130f2578251825591602001919060010190613122565b82805482825590600052602060002090810192821561318a579160200282015b8281111561318a578251805161317a918491602090910190613204565b509160200191906001019061315d565b506130fe929150613277565b8280548282559060005260206000209081019282156131e3579160200282015b828111156131e357825180516131d3918491602090910190613204565b50916020019190600101906131b6565b506130fe929150613294565b5b808211156130fe57600081556001016131f0565b82805461321090613bcc565b90600052602060002090601f01602090048101928261323257600085556130f2565b82601f1061324b57805160ff19168380011785556130f2565b828001600101855582156130f257918201828111156130f2578251825591602001919060010190613122565b808211156130fe57600061328b82826132b1565b50600101613277565b808211156130fe5760006132a882826132b1565b50600101613294565b5080546132bd90613bcc565b6000825580601f106132cd575050565b601f0160209004906000526020600020908101906132eb91906131ef565b50565b60006020828403121561330057600080fd5b5035919050565b60005b8381101561332257818101518382015260200161330a565b83811115613331576000848401525b50505050565b6000815180845261334f816020860160208601613307565b601f01601f19169290920160200192915050565b6020815260006133766020830184613337565b9392505050565b80356001600160a01b038116811461339457600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff811182821017156133d8576133d8613399565b604052919050565b600082601f8301126133f157600080fd5b813567ffffffffffffffff81111561340b5761340b613399565b61341e601f8201601f19166020016133af565b81815284602083860101111561343357600080fd5b816020850160208301376000918101602001919091529392505050565b600080600080600060a0868803121561346857600080fd5b6134718661337d565b945060208601359350604086013567ffffffffffffffff8082111561349557600080fd5b6134a189838a016133e0565b945060608801359150808211156134b757600080fd5b506134c4888289016133e0565b95989497509295608001359392505050565b6000602082840312156134e857600080fd5b6133768261337d565b600081518084526020808501945080840160005b8381101561352a5781516001600160a01b031687529582019590820190600101613505565b509495945050505050565b600081518084526020808501945080840160005b8381101561352a57815187529582019590820190600101613549565b600081518084526020808501808196508360051b8101915082860160005b858110156135ad57828403895261359b848351613337565b98850198935090840190600101613583565b5091979650505050505050565b6080815260006135cd60808301876134f1565b82810360208401526135df8187613535565b905082810360408401526135f38186613565565b905082810360608401526136078185613565565b979650505050505050565b803560ff8116811461339457600080fd5b600080600080600060a0868803121561363b57600080fd5b8535945061364b60208701613612565b935061365960408701613612565b94979396509394606081013594506080013592915050565b634e487b7160e01b600052602160045260246000fd5b60208101600883106136a957634e487b7160e01b600052602160045260246000fd5b91905290565b600080604083850312156136c257600080fd5b6136cb8361337d565b946020939093013593505050565b600080604083850312156136ec57600080fd5b823591506136fc60208401613612565b90509250929050565b6000806040838503121561371857600080fd5b823591506136fc6020840161337d565b6000806000806060858703121561373e57600080fd5b8435935061374e60208601613612565b9250604085013567ffffffffffffffff8082111561376b57600080fd5b818701915087601f83011261377f57600080fd5b81358181111561378e57600080fd5b8860208285010111156137a057600080fd5b95989497505060200194505050565b600067ffffffffffffffff8211156137c9576137c9613399565b5060051b60200190565b600082601f8301126137e457600080fd5b813560206137f96137f4836137af565b6133af565b82815260059290921b8401810191818101908684111561381857600080fd5b8286015b8481101561383a5761382d8161337d565b835291830191830161381c565b509695505050505050565b600082601f83011261385657600080fd5b813560206138666137f4836137af565b82815260059290921b8401810191818101908684111561388557600080fd5b8286015b8481101561383a5780358352918301918301613889565b600082601f8301126138b157600080fd5b813560206138c16137f4836137af565b82815260059290921b840181019181810190868411156138e057600080fd5b8286015b8481101561383a57803567ffffffffffffffff8111156139045760008081fd5b6139128986838b01016133e0565b8452509183019183016138e4565b600082601f83011261393157600080fd5b813560206139416137f4836137af565b82815260059290921b8401810191818101908684111561396057600080fd5b8286015b8481101561383a57803567ffffffffffffffff8111156139845760008081fd5b6139928986838b01016133e0565b845250918301918301613964565b8035801515811461339457600080fd5b60008060008060008060c087890312156139c957600080fd5b863567ffffffffffffffff808211156139e157600080fd5b6139ed8a838b016137d3565b97506020890135915080821115613a0357600080fd5b613a0f8a838b01613845565b96506040890135915080821115613a2557600080fd5b613a318a838b016138a0565b95506060890135915080821115613a4757600080fd5b613a538a838b01613920565b94506080890135915080821115613a6957600080fd5b50613a7689828a016133e0565b925050613a8560a088016139a0565b90509295509295509295565b60018060a01b038616815284602082015260a060408201526000613ab860a0830186613337565b8281036060840152613aca8186613337565b9150508260808301529695505050505050565b634e487b7160e01b600052601160045260246000fd5b60008219821115613b0657613b06613add565b500190565b6001600160e01b0319831681528151600090613b2e816004850160208701613307565b919091016004019392505050565b60008251613b4e818460208701613307565b9190910192915050565b848152608060208201526000613b716080830186613337565b8281036040840152613b838186613337565b91505082606083015295945050505050565b60208082526017908201527f6d75737420636f6d652066726f6d2074686520676f762e000000000000000000604082015260600190565b600181811c90821680613be057607f821691505b60208210811415613c0157634e487b7160e01b600052602260045260246000fd5b50919050565b600082821015613c1957613c19613add565b500390565b600060208284031215613c3057600080fd5b81516001600160601b038116811461337657600080fd5b634e487b7160e01b600052603260045260246000fd5b6000600019821415613c7157613c71613add565b5060010190565b60ff851681526001600160601b038416602082015260606040820152816060820152818360808301376000818301608090810191909152601f909201601f191601019392505050565b8054600090600181811c9080831680613cdb57607f831692505b6020808410821415613cfd57634e487b7160e01b600052602260045260246000fd5b838852818015613d145760018114613d2857613d56565b60ff19861689830152604089019650613d56565b876000528160002060005b86811015613d4e5781548b8201850152908501908301613d33565b8a0183019750505b50505050505092915050565b60018060a01b038616815284602082015260a060408201526000613d8960a0830186613cc1565b8281036060840152613aca8186613cc1565b60c081526000613dae60c08301896134f1565b8281036020840152613dc08189613535565b90508281036040840152613dd48188613565565b90508281036060840152613de88187613565565b905084608084015282810360a0840152613e028185613337565b999850505050505050505056fe"), + ("0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec", "PoolExercise", "6101c06040523480156200001257600080fd5b50604051620030713803806200307183398101604081905262000035916200016a565b6001600160a01b038681166101005285811660805284811660a05283811660c052821660e052600f81900b61012052858585858585620000846000808062000101602090811b6200011917901c565b6101408181525050620000a660016000806200010160201b620001191760201c565b6101608181525050620000c860026000806200010160201b620001191760201c565b6101808181525050620000ea60036000806200010160201b620001191760201c565b6101a05250620002319a5050505050505050505050565b600081600f0b6080846001600160401b0316901b60f88660078111156200012c576200012c620001f4565b6200013992911b6200020a565b6200014591906200020a565b949350505050565b80516001600160a01b03811681146200016557600080fd5b919050565b60008060008060008060c087890312156200018457600080fd5b6200018f876200014d565b95506200019f602088016200014d565b9450620001af604088016200014d565b9350620001bf606088016200014d565b9250620001cf608088016200014d565b915060a087015180600f0b8114620001e657600080fd5b809150509295509295509295565b634e487b7160e01b600052602160045260246000fd5b600082198211156200022c57634e487b7160e01b600052601160045260246000fd5b500190565b60805160a05160c05160e05161010051610120516101405161016051610180516101a051612d6f6200030260003960008181610e1c015261137b015260008181610e420152818161135101526113f4015260008181611327015281816114b801526122c90152600081816112fe015281816113cb0152818161148f015281816114e101526122ef0152600081816103a8015281816107ed0152610cda0152600050506000818161176601526117b201526000610485015260008181611964015261212c015260005050612d6f6000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063477130981461003b578063b50e7ee314610050575b600080fd5b61004e610049366004612986565b610063565b005b61004e61005e3660046129c7565b610109565b336001600160a01b038416146100f9576001600160a01b03831660009081527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68c6020908152604080832033845290915290205460ff166100f95760405162461bcd60e51b815260206004820152600c60248201526b1b9bdd08185c1c1c9bdd995960a21b60448201526064015b60405180910390fd5b61010483838361015f565b505050565b6101156000838361015f565b5050565b600081600f0b60808467ffffffffffffffff16901b60f8866007811115610142576101426129e9565b61014d92911b612a15565b6101579190612a15565b949350505050565b608082901c8260006001600160a01b0386161560f883901c600481600781111561018b5761018b6129e9565b14806101a8575060068160078111156101a6576101a66129e9565b145b6101e35760405162461bcd60e51b815260206004820152600c60248201526b696e76616c6964207479706560a01b60448201526064016100f0565b8115806101f95750428567ffffffffffffffff16105b6102335760405162461bcd60e51b815260206004820152600b60248201526a1b9bdd08195e1c1a5c995960aa1b60448201526064016100f0565b6004816007811115610247576102476129e9565b149250506000610262600080516020612d1a83398151915290565b9050600061026f826104c0565b9050428667ffffffffffffffff16101561029a576102978267ffffffffffffffff8816610526565b90505b82806102be5750836102b45784600f0b81600f0b126102be565b84600f0b81600f0b135b6102f45760405162461bcd60e51b81526020600482015260076024820152666e6f742049544d60c81b60448201526064016100f0565b6000841561033a5785600f0b82600f0b1315610335576103328861032984610320600f82900b8b61061e565b600f0b90610659565b600f0b906106b1565b90505b610367565b85600f0b82600f0b12156103675761036461035d89610329600f8a900b8661061e565b8490610719565b90505b6000841561038c5761037b89838c89610754565b6103859082612a15565b9050610454565b6103978b8b8b6108cb565b600082156103ff576103d58c6103d07f0000000000000000000000000000000000000000000000000000000000000000600f0b866106b1565b610a5d565b90506103e18183612a15565b91506103ff8c6103f089610a8c565b6103fa8487612a2d565b610ae1565b604080518c8152602081018c9052908101849052606081018290526001600160a01b038d16907f31939b125e073bbdbf69ac6eb0cb59489894a9bea509d658589af5917b53cca19060800160405180910390a2505b610474898361046e6104678a6000610bb1565b8c8c610119565b89610be4565b61047e9082612a15565b90506104b37f00000000000000000000000000000000000000000000000000000000000000006104ad88610e13565b83610e67565b5050505050505050505050565b60004282600c015414156104de576104d88242610e82565b92915050565b6104e782610eb0565b90506104f38242610e82565b600f0b61050557610505824283610fd3565b42600c830155610516826001611051565b610521826000611051565b919050565b600080610535610e1084612a5a565b600881901c6000818152601287016020526040812054929350909160ff84169190821b821c90610568620e100042612a5a565b90505b811580156105795750808411155b156105a65760128801600061058d86612a7c565b955085815260200190815260200160002054915061056b565b600060805b80156105d25783811c156105ca576105c38183612a15565b93811c9391505b60011c6105ab565b5060118901600060018360086105e88a84612a15565b6105f392911b612a2d565b6105fd9190612a2d565b8152602081019190915260400160002054600f0b9998505050505050505050565b6000600f82810b9084900b0360016001607f1b03198112801590610649575060016001607f1b038113155b61065257600080fd5b9392505050565b600081600f0b6000141561066c57600080fd5b600082600f0b604085600f0b901b8161068757610687612a44565b05905060016001607f1b03198112801590610649575060016001607f1b0381131561065257600080fd5b6000816106c0575060006104d8565b600083600f0b12156106d157600080fd5b600f83900b6001600160801b038316810260401c90608084901c026001600160c01b0381111561070057600080fd5b60401b811981111561071157600080fd5b019392505050565b600080610737838560030160149054906101000a900460ff166110e1565b9050610157818560030160159054906101000a900460ff166110f7565b60008281527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb602052604081205b85156108c25760006107a9600161079884611112565b6107a29190612a2d565b839061111c565b905060006107b78287611128565b9050878111156107c45750865b600080881561084657896107d8848b612a97565b6107e29190612a5a565b9150610815846103d07f0000000000000000000000000000000000000000000000000000000000000000600f0b856106b1565b90506108218187612a15565b955061082d828a612a2d565b98506108468461083c89610a8c565b6103fa8486612a2d565b610850838b612a2d565b99506001600160a01b0384167f31939b125e073bbdbf69ac6eb0cb59489894a9bea509d658589af5917b53cca189856108898587612a2d565b604080519384526020840192909252908201526060810184905260800160405180910390a26108b98489856108cb565b50505050610782565b50949350505050565b6001600160a01b03831661092d5760405162461bcd60e51b815260206004820152602360248201527f455243313135353a206275726e2066726f6d20746865207a65726f206164647260448201526265737360e81b60648201526084016100f0565b61095b3384600061093d866111db565b610946866111db565b60405180602001604052806000815250611226565b60008281527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b602090815260408083206001600160a01b038716845291829052909120548211156109fc5760405162461bcd60e51b815260206004820152602560248201527f455243313135353a206275726e20616d6f756e7420657863656564732062616c604482015264616e63657360d81b60648201526084016100f0565b6001600160a01b03841660008181526020838152604080832080548790039055805187815291820186905291929133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a450505050565b600080610a6984611762565b9050612710610a788285612a97565b610a829190612a5a565b6101579084612a2d565b600081610ab157600080516020612d1a833981519152546001600160a01b03166104d8565b50507fbbd6af8edd89d04327b00c29df7f272b9b1ae01bf6d9c54a784f935706df52ec546001600160a01b031690565b80610aeb57505050565b60405163a9059cbb60e01b81526001600160a01b0384811660048301526024820183905283169063a9059cbb90604401602060405180830381600087803b158015610b3557600080fd5b505af1158015610b49573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b6d9190612ab6565b6101045760405162461bcd60e51b8152602060048201526015602482015274115490cc8c081d1c985b9cd9995c8819985a5b1959605a1b60448201526064016100f0565b60008215610bcf5781610bc5576005610bc8565b60045b90506104d8565b81610bdb576007610652565b60069392505050565b60008281527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb60205260408120835b8615610e09576000610c3a6001610c2985611112565b610c339190612a2d565b849061111c565b90506000610c488288611128565b905088811115610c555750875b600089610c62838b612a97565b610c6c9190612a5a565b9050610c78818a612a2d565b9850610c84828b612a2d565b9950600087610cc35781610cb4610c9f600f88900b866106b1565b600080516020612d1a83398151915290610719565b610cbe9190612a2d565b610ccd565b610ccd8284612a2d565b90506000610d02856103d07f0000000000000000000000000000000000000000000000000000000000000000600f0b856106b1565b9050610d0e8189612a15565b975082610d2a600080516020612d1a833981519152878c61182c565b15610d5457610d4386610d3d8486612a2d565b8c611869565b610d4d8282612a15565b9050610d7d565b610d7086610d618c610e13565b610d6b8587612a2d565b610e67565b610d7a8382612a15565b90505b610d97600080516020612d1a833981519152878c8461192c565b610da2868c876108cb565b6001600160a01b0386167f69a2ef6bf9e7ff92cbf1b71963ba1751b1abe8f99e3b3aae2ab99e416df614938c610dd88587612a2d565b60408051928352602083019190915281018890526060810185905260800160405180910390a2505050505050610c13565b5050949350505050565b600081610e40577f00000000000000000000000000000000000000000000000000000000000000006104d8565b7f000000000000000000000000000000000000000000000000000000000000000092915050565b61010483838360405180602001604052806000815250611a6c565b60006011830181610e95610e1085612a5a565b8152602081019190915260400160002054600f0b9392505050565b6000808260030160009054906101000a90046001600160a01b03166001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b158015610f0357600080fd5b505afa158015610f17573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f3b9190612ad8565b905060008360020160009054906101000a90046001600160a01b03166001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b158015610f8f57600080fd5b505afa158015610fa3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fc79190612ad8565b90506101578282611b93565b6000610fe1610e1084612a5a565b6000818152601186016020526040902080546001600160801b0319166001600160801b038516179055905061101a60ff80831690612a2d565b6001901b846012016000600884901c815260200190815260200160002060008282546110469190612a15565b909155505050505050565b80151560009081526013830160205260409020805415806110725750805442105b1561107c57505050565b60006110888484611c2e565b90506110c384826110bd6110b286600101546110ad898b611c9890919063ffffffff16565b6110e1565b600f86900b90611cc6565b86611cf9565b50501515600090815260139091016020526040812081815560010155565b6000610652836110f284600a612bd5565b611d76565b600061065261110783600a612bd5565b600f85900b906106b1565b60006104d8825490565b60006106528383611dad565b60006001600160a01b0383166111945760405162461bcd60e51b815260206004820152602b60248201527f455243313135353a2062616c616e636520717565727920666f7220746865207a60448201526a65726f206164647265737360a81b60648201526084016100f0565b7f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b6000928352602090815260408084206001600160a01b0395909516845293905250205490565b6040805160018082528183019092526060916000919060208083019080368337019050509050828160008151811061121557611215612be4565b602090810291909101015292915050565b611234868686868686611e33565b600080516020612d1a83398151915260005b845181101561175857600085828151811061126357611263612be4565b60200260200101519050600085838151811061128157611281612be4565b60200260200101519050806000141561129b575050611746565b6001600160a01b0389166112b8576112b66015850183612011565b505b6001600160a01b0388161580156112e857506000828152600080516020612cfa8339815191526020526040902054155b156112fc576112fa601585018361201d565b505b7f000000000000000000000000000000000000000000000000000000000000000082148061134957507f000000000000000000000000000000000000000000000000000000000000000082145b8061137357507f000000000000000000000000000000000000000000000000000000000000000082145b8061139d57507f000000000000000000000000000000000000000000000000000000000000000082145b1561148d576001600160a01b038916158015906113c257506001600160a01b03881615155b1561148d5760007f000000000000000000000000000000000000000000000000000000000000000083148061141657507f000000000000000000000000000000000000000000000000000000000000000083145b6001600160a01b038b166000908152600d870160209081526040808320841515845290915290205490915042906114509062015180612a15565b1061148b5760405162461bcd60e51b815260206004820152600b60248201526a1b1a5c481b1bd8dac80c5960aa1b60448201526064016100f0565b505b7f00000000000000000000000000000000000000000000000000000000000000008214806114da57507f000000000000000000000000000000000000000000000000000000000000000082145b15611682577f00000000000000000000000000000000000000000000000000000000000000008214600061150e8683612029565b90506001600160a01b038b161561163857600061152b8c86611128565b9050818111801561154557506115418285612a15565b8111155b156115dd576001600160a01b038c166000908152601488016020908152604080832086151580855260138c01845282852054855290835281842090845290915290205484906115949083612a2d565b10156115d25760405162461bcd60e51b815260206004820152600d60248201526c496e7375662062616c616e636560981b60448201526064016100f0565b6115dd878d85612043565b6001600160a01b038b161561163657611611878d858c8a8151811061160457611604612be4565b602002602001015161192c565b611636878c858c8a8151811061162957611629612be4565b60200260200101516120f4565b505b6001600160a01b038a161561167f5760006116538b86611128565b905081811115801561166d57508161166b8583612a15565b115b1561167d5761167d878c85612218565b505b50505b60f882901c826001600160a01b038b16158015906116a857506001600160a01b038a1615155b80156116e0575060058260078111156116c3576116c36129e9565b14806116e0575060078260078111156116de576116de6129e9565b145b1561174157600060058360078111156116fb576116fb6129e9565b1490506000816117225761171d611716600f85900b876106b1565b8990610719565b611724565b845b9050611732888e848461192c565b61173e888d84846120f4565b50505b505050505b8061175081612a7c565b915050611246565b5050505050505050565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031615610521576040516303793c8d60e11b81526001600160a01b0383811660048301527f000000000000000000000000000000000000000000000000000000000000000016906306f2791a9060240160206040518083038186803b1580156117f457600080fd5b505afa158015611808573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104d89190612ad8565b6001600160a01b0382166000908152600e840160209081526040808320841515845290915281205480158061186057504281115b95945050505050565b600080516020612d1a83398151915261188b84611885846122c0565b85610e67565b60006101048061189b8142612a5a565b6118a59190612a97565b6118af9190612a15565b6001600160a01b03861660009081526014840160209081526040808320848452825280832087151584529091528120805492935086929091906118f3908490612a15565b90915550508215156000908152601383016020526040812060018101805491928792611920908490612a15565b90915550505550505050565b6001600160a01b03808416600090815260178601602090815260408083208615158452825280832054601889019092529091205490917f00000000000000000000000000000000000000000000000000000000000000001663edaf7d5b863087866119978982612a2d565b6040516001600160e01b031960e088901b1681526001600160a01b03958616600482015294909316602485015290151560448401526064830152608482015260a4810184905260c401600060405180830381600087803b1580156119fa57600080fd5b505af1158015611a0e573d6000803e3d6000fd5b505050508282611a1e9190612a2d565b6001600160a01b038616600090815260178801602090815260408083208815158452909152902055611a508382612a2d565b9315156000908152601890960160205250506040909320555050565b6001600160a01b038416611acc5760405162461bcd60e51b815260206004820152602160248201527f455243313135353a206d696e7420746f20746865207a65726f206164647265736044820152607360f81b60648201526084016100f0565b611aeb33600086611adc876111db565b611ae5876111db565b86611226565b60008381527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b602090815260408083206001600160a01b0388168452918290528220805491928592611b3e908490612a15565b909155505060408051858152602081018590526001600160a01b0387169160009133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a45050505050565b600081611b9f57600080fd5b600080841215611bb457836000039350600190505b6000831215611bc65760009290920391155b6000611bd28585612314565b90508115611c00576001607f1b816001600160801b03161115611bf457600080fd5b60000391506104d89050565b60016001607f1b03816001600160801b03161115611c1d57600080fd5b91506104d89050565b505092915050565b600080611c4b83611c40576001611c43565b60005b600080610119565b831515600090815260138601602052604090206001015490915061015790600080516020612cfa83398151915260008481526020919091526040902054611c929190612a2d565b6110ad86865b600081611cb3576003830154600160a81b900460ff16610652565b505060030154600160a01b900460ff1690565b6000600f83810b9083900b0160016001607f1b03198112801590610649575060016001607f1b0381131561065257600080fd5b6000611d058583612476565b90506000611d16868387878761248f565b9050611d23868285612595565b60408051600f83810b825287810b602083015286900b818301529051841515917f4e23621c6f591f14bf9505cb8326b45af9dc6c5569fd608de2a7a2ddd6146b2e919081900360600190a2505050505050565b600081611d8257600080fd5b6000611d8e8484612314565b905060016001607f1b036001600160801b038216111561065257600080fd5b81546000908210611e0b5760405162461bcd60e51b815260206004820152602260248201527f456e756d657261626c655365743a20696e646578206f7574206f6620626f756e604482015261647360f01b60648201526084016100f0565b826000018281548110611e2057611e20612be4565b9060005260206000200154905092915050565b836001600160a01b0316856001600160a01b031614612009576001600160a01b0385811660009081527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424ec602052604080822092871682528120600080516020612cfa833981519152927fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb929091905b87518110156104b3576000878281518110611edf57611edf612be4565b602002602001015190506000811115611ff6576000898381518110611f0657611f06612be4565b6020026020010151905060006001600160a01b03168c6001600160a01b03161415611f545760008181526020889052604081208054849290611f49908490612a15565b90915550611f8a9050565b81611f5f8d83611128565b1415611f8a576000818152602087905260409020611f7d908d6125ec565b50611f88858261201d565b505b6001600160a01b038b16611fc15760008181526020889052604081208054849290611fb6908490612a2d565b90915550611ff49050565b611fcb8b82611128565b611ff4576000818152602087905260409020611fe7908c612601565b50611ff28482612011565b505b505b508061200181612a7c565b915050611ec2565b505050505050565b60006106528383612612565b60006106528383612661565b60008161203a578260040154610652565b50506005015490565b6001600160a01b03821661205657600080fd5b8015156000908152600f8401602090815260408083206010870190925290912061208184838361274c565b61208c575050505050565b6001600160a01b0393841660008181526020838152604080832080549683528184208054978a16808652838620805499909b166001600160a01b0319998a168117909b5599855295909252822080548616909717909655528054821690558254169091555050565b6001600160a01b03808416600090815260178601602090815260408083208615158452825280832054601889019092529091205490917f00000000000000000000000000000000000000000000000000000000000000001663edaf7d5b8630878661215f8982612a15565b6040516001600160e01b031960e088901b1681526001600160a01b03958616600482015294909316602485015290151560448401526064830152608482015260a4810184905260c401600060405180830381600087803b1580156121c257600080fd5b505af11580156121d6573d6000803e3d6000fd5b5050505082826121e69190612a15565b6001600160a01b038616600090815260178801602090815260408083208815158452909152902055611a508382612a15565b6001600160a01b03821661222b57600080fd5b8015156000908152600f8401602090815260408083206010870190925290912061225684838361274c565b15612262575050505050565b60008080526020828152604080832080546001600160a01b0390811680865296845282852080546001600160a01b03199081169a909216998a1790558885529490925282208054841690941790935580528154169092179091555050565b6000816122ed577f00000000000000000000000000000000000000000000000000000000000000006104d8565b7f000000000000000000000000000000000000000000000000000000000000000092915050565b60008161232057600080fd5b60006001600160c01b03841161234b5782604085901b8161234357612343612a44565b049050612462565b60c084811c6401000000008110612364576020918201911c5b620100008110612376576010918201911c5b6101008110612387576008918201911c5b60108110612397576004918201911c5b600481106123a7576002918201911c5b600281106123b6576001820191505b60bf820360018603901c6001018260ff0387901b816123d7576123d7612a44565b0492506001600160801b038311156123ee57600080fd5b608085901c83026001600160801b038616840260c088901c604089901b8281101561241a576001820391505b608084901b92900382811015612431576001820391505b829003608084901c821461244757612447612bfa565b88818161245657612456612a44565b04870196505050505050505b6001600160801b0381111561065257600080fd5b60006124828383612798565b90506106528382846127bf565b600080826124a4576019870154600f0b6124b4565b6019870154600160801b9004600f0b5b905080600f0b600014156124cc57506008860154600f0b5b60405163e101a89b60e01b8152600f87810b600483015286810b602483015285810b604483015282900b6064820152730f6e8ef18fb5bb61d545fee60f779d8aed60408f9063e101a89b9060840160206040518083038186803b15801561253257600080fd5b505af4158015612546573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061256a9190612c10565b915067b33333333333333382600f0b121561258b5767b33333333333333391505b5095945050505050565b80156125c5576009830180546001600160801b0384166001600160801b031990911617905542600b840155505050565b6008830180546001600160801b03808516600160801b02911617905542600a840155505050565b6000610652836001600160a01b038416612661565b6000610652836001600160a01b0384165b6000818152600183016020526040812054612659575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556104d8565b5060006104d8565b60008181526001830160205260408120548015612742576000612685600183612a2d565b8554909150600090869061269b90600190612a2d565b815481106126ab576126ab612be4565b90600052602060002001549050808660000183815481106126ce576126ce612be4565b6000918252602090912001556126e5826001612a15565b6000828152600188016020526040902055855486908061270757612707612c33565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506104d8565b60009150506104d8565b6001600160a01b0383811660009081526020849052604081205490911615158061015757506000808052602083905260409020546001600160a01b039081169085161490509392505050565b6000816127b3576008830154600160801b9004600f0b610652565b505060090154600f0b90565b600080826127d15784600a01546127d7565b84600b01545b6127e19042612a2d565b905061a8c0811115612800576127f961a8c082612a2d565b9050612809565b83915050610652565b600061281782613840611d76565b9050600061282a85611c40576001611c43565b851515600090815260188901602090815260408083205460138c01835281842060010154858552600080516020612cfa83398151915290935290832054939450926128889161287891612a2d565b6128829084612a2d565b83611d76565b6040805161012081018252600f87810b82528b810b602083015283900b8183015267b333333333333333606082015267e666666666666666608082018190526801000000000000000060a0830181905260c083015260e082015268056fc2a2c515da32ea6101008201529051634916d70d60e01b8152919250730f6e8ef18fb5bb61d545fee60f779d8aed60408f91634916d70d9161292991600401612c49565b60206040518083038186803b15801561294157600080fd5b505af4158015612955573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129799190612c10565b9998505050505050505050565b60008060006060848603121561299b57600080fd5b83356001600160a01b03811681146129b257600080fd5b95602085013595506040909401359392505050565b600080604083850312156129da57600080fd5b50508035926020909101359150565b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60008219821115612a2857612a286129ff565b500190565b600082821015612a3f57612a3f6129ff565b500390565b634e487b7160e01b600052601260045260246000fd5b600082612a7757634e487b7160e01b600052601260045260246000fd5b500490565b6000600019821415612a9057612a906129ff565b5060010190565b6000816000190483118215151615612ab157612ab16129ff565b500290565b600060208284031215612ac857600080fd5b8151801515811461065257600080fd5b600060208284031215612aea57600080fd5b5051919050565b600181815b80851115612b2c578160001904821115612b1257612b126129ff565b80851615612b1f57918102915b93841c9390800290612af6565b509250929050565b600082612b43575060016104d8565b81612b50575060006104d8565b8160018114612b665760028114612b7057612b8c565b60019150506104d8565b60ff841115612b8157612b816129ff565b50506001821b6104d8565b5060208310610133831016604e8410600b8410161715612baf575081810a6104d8565b612bb98383612af1565b8060001904821115612bcd57612bcd6129ff565b029392505050565b600061065260ff841683612b34565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052600160045260246000fd5b600060208284031215612c2257600080fd5b815180600f0b811461065257600080fd5b634e487b7160e01b600052603160045260246000fd5b6000610120820190508251600f0b82526020830151600f0b60208301526040830151612c7a6040840182600f0b9052565b506060830151612c8f6060840182600f0b9052565b506080830151612ca46080840182600f0b9052565b5060a0830151612cb960a0840182600f0b9052565b5060c0830151612cce60c0840182600f0b9052565b5060e0830151612ce360e0840182600f0b9052565b5061010080840151611c2682850182600f0b905256feb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eabbd6af8edd89d04327b00c29df7f272b9b1ae01bf6d9c54a784f935706df52eb"), + ("0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05", "MainchainGatewayV2", "608060405234801561001057600080fd5b506000805460ff1916905561582e806200002b6000396000f3fe60806040526004361061032d5760003560e01c80639157921c116101a5578063b2975794116100ec578063d547741f11610095578063dafae4081161006f578063dafae4081461096e578063dff525e11461098e578063e400327c146109ae578063e75235b8146109ce5761033c565b8063d547741f14610901578063d55ed10314610921578063d64af2a61461094e5761033c565b8063cdb67444116100c6578063cdb674441461089c578063cdf64a76146108b4578063d19773d2146108d45761033c565b8063b29757941461082f578063b9c362091461085c578063ca15c8731461087c5761033c565b8063a3912ec81161014e578063affed0e011610128578063affed0e0146107cc578063b1a2567e146107e2578063b1d08a03146108025761033c565b8063a3912ec81461033a578063ab7965661461077f578063ac78dfe8146107ac5761033c565b8063994390891161017f57806399439089146107155780639dcc4da314610735578063a217fddf1461076a5761033c565b80639157921c1461068f57806391d14854146106af57806393c5678f146106f55761033c565b806336568abe116102745780635c975abb1161021d5780637de5dedd116101f75780637de5dedd146106115780638456cb59146106265780638f34e3471461063b5780639010d07c1461066f5761033c565b80635c975abb146105ac5780636932be98146105c45780636c1ce670146105f15761033c565b80634d0d66731161024e5780634d0d66731461052f5780634d493f4e1461054f57806359122f6b1461057f5761033c565b806336568abe146104e75780633f4ba83a146105075780634b14557e1461051c5761033c565b80631d4a7210116102d65780632f2ff15d116102b05780632f2ff15d1461049b578063302d12db146104bb5780633644e515146104d25761033c565b80631d4a721014610428578063248a9ca3146104555780632dfdf0b5146104855761033c565b8063180ff1e911610307578063180ff1e9146103d55780631a8e55b0146103e85780631b6e7594146104085761033c565b806301ffc9a71461034457806317ce2dd41461037957806317fcb39b1461039d5761033c565b3661033c5761033a6109e6565b005b61033a6109e6565b34801561035057600080fd5b5061036461035f366004614843565b610a69565b60405190151581526020015b60405180910390f35b34801561038557600080fd5b5061038f60755481565b604051908152602001610370565b3480156103a957600080fd5b506074546103bd906001600160a01b031681565b6040516001600160a01b039091168152602001610370565b61033a6103e33660046148f4565b610aad565b3480156103f457600080fd5b5061033a6104033660046149e6565b610dbd565b34801561041457600080fd5b5061033a610423366004614a52565b610e8f565b34801561043457600080fd5b5061038f610443366004614aec565b603e6020526000908152604090205481565b34801561046157600080fd5b5061038f610470366004614b09565b60009081526072602052604090206001015490565b34801561049157600080fd5b5061038f60765481565b3480156104a757600080fd5b5061033a6104b6366004614b22565b610f64565b3480156104c757600080fd5b5061038f620f424081565b3480156104de57600080fd5b5060775461038f565b3480156104f357600080fd5b5061033a610502366004614b22565b610f8f565b34801561051357600080fd5b5061033a61101b565b61033a61052a366004614b52565b611083565b34801561053b57600080fd5b5061036461054a366004614b7d565b6110e1565b34801561055b57600080fd5b5061036461056a366004614b09565b607a6020526000908152604090205460ff1681565b34801561058b57600080fd5b5061038f61059a366004614aec565b603a6020526000908152604090205481565b3480156105b857600080fd5b5060005460ff16610364565b3480156105d057600080fd5b5061038f6105df366004614b09565b60796020526000908152604090205481565b3480156105fd57600080fd5b5061036461060c366004614c06565b61118c565b34801561061d57600080fd5b5061038f61119f565b34801561063257600080fd5b5061033a611234565b34801561064757600080fd5b5061038f7f5e5712e902fff5e704bc4d506ad976718319e019e9d2a872528a01a85db433e481565b34801561067b57600080fd5b506103bd61068a366004614c32565b61129c565b34801561069b57600080fd5b5061033a6106aa366004614c54565b6112b4565b3480156106bb57600080fd5b506103646106ca366004614b22565b60009182526072602090815260408084206001600160a01b0393909316845291905290205460ff1690565b34801561070157600080fd5b5061033a6107103660046149e6565b6115ca565b34801561072157600080fd5b506003546103bd906001600160a01b031681565b34801561074157600080fd5b50610755610750366004614c32565b611696565b60408051928352602083019190915201610370565b34801561077657600080fd5b5061038f600081565b34801561078b57600080fd5b5061038f61079a366004614aec565b603c6020526000908152604090205481565b3480156107b857600080fd5b506103646107c7366004614b09565b61172f565b3480156107d857600080fd5b5061038f60045481565b3480156107ee57600080fd5b5061033a6107fd3660046149e6565b6117ce565b34801561080e57600080fd5b5061038f61081d366004614aec565b60396020526000908152604090205481565b34801561083b57600080fd5b5061084f61084a366004614aec565b61189a565b6040516103709190614ca5565b34801561086857600080fd5b50610755610877366004614c32565b611992565b34801561088857600080fd5b5061038f610897366004614b09565b611a17565b3480156108a857600080fd5b50603754603854610755565b3480156108c057600080fd5b5061033a6108cf366004614aec565b611a2e565b3480156108e057600080fd5b5061038f6108ef366004614aec565b603b6020526000908152604090205481565b34801561090d57600080fd5b5061033a61091c366004614b22565b611a97565b34801561092d57600080fd5b5061038f61093c366004614aec565b603d6020526000908152604090205481565b34801561095a57600080fd5b5061033a610969366004614aec565b611abd565b34801561097a57600080fd5b50610364610989366004614b09565b611b26565b34801561099a57600080fd5b5061033a6109a9366004614cd2565b611bbd565b3480156109ba57600080fd5b5061033a6109c93660046149e6565b611cc7565b3480156109da57600080fd5b50600154600254610755565b60005460ff1615610a315760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b60448201526064015b60405180910390fd5b6074546001600160a01b03163314610a6757610a4b614802565b338152604080820151349101528051610a65908290611d93565b505b565b60006001600160e01b031982167f5a05180f000000000000000000000000000000000000000000000000000000001480610aa75750610aa78261210a565b92915050565b607154610100900460ff16610ac85760715460ff1615610acc565b303b155b610b3e5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610a28565b607154610100900460ff16158015610b60576071805461ffff19166101011790555b610b6b60008d612171565b6075899055610b798b61217b565b610b828a6121dd565b610c29604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f159f52c1e3a2b6a6aad3950adf713516211484e0516dad685ea662a094b7c43b918101919091527fad7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a560608201524660808201523060a082015260c00160408051601f198184030181529190528051602090910120607755565b610c338887612238565b5050610c3f87876122f8565b5050610c496123d3565b6000610c558680614da6565b90501115610d1657610c7e610c6a8680614da6565b610c776020890189614da6565b8787612467565b610ca4610c8b8680614da6565b8660005b602002810190610c9f9190614da6565b612666565b610cca610cb18680614da6565b8660015b602002810190610cc59190614da6565b612779565b610cf0610cd78680614da6565b8660025b602002810190610ceb9190614da6565b61288c565b610d16610cfd8680614da6565b8660035b602002810190610d119190614da6565b612a30565b60005b610d266040870187614da6565b9050811015610d9c57610d8a7f5e5712e902fff5e704bc4d506ad976718319e019e9d2a872528a01a85db433e4610d606040890189614da6565b84818110610d7057610d70614d90565b9050602002016020810190610d859190614aec565b612b43565b80610d9481614e06565b915050610d19565b508015610daf576071805461ff00191690555b505050505050505050505050565b6000805160206157b9833981519152546001600160a01b03163314610e1d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b82610e7d5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e8984848484612779565b50505050565b6000805160206157b9833981519152546001600160a01b03163314610eef5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b84610f4e5760405162461bcd60e51b815260206004820152602960248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220656d60448201526870747920617272617960b81b6064820152608401610a28565b610f5c868686868686612467565b505050505050565b600082815260726020526040902060010154610f808133612b65565b610f8a8383612b43565b505050565b6001600160a01b038116331461100d5760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201527f20726f6c657320666f722073656c6600000000000000000000000000000000006064820152608401610a28565b6110178282612be5565b5050565b6000805160206157b9833981519152546001600160a01b0316331461107b5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a67612c07565b60005460ff16156110c95760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610a28565b610a656110db36839003830183614ec0565b33611d93565b6000805460ff16156111285760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610a28565b611184848484808060200260200160405190810160405280939291908181526020016000905b8282101561117a5761116b60608302860136819003810190614f13565b8152602001906001019061114e565b5050505050612ca3565b949350505050565b600061119883836133bc565b9392505050565b600061122f600360009054906101000a90046001600160a01b03166001600160a01b031663926323d56040518163ffffffff1660e01b815260040160206040518083038186803b1580156111f257600080fd5b505afa158015611206573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061122a9190614f89565b613480565b905090565b6000805160206157b9833981519152546001600160a01b031633146112945760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a676134b6565b60008281526073602052604081206111989083613531565b7f5e5712e902fff5e704bc4d506ad976718319e019e9d2a872528a01a85db433e46112df8133612b65565b60006112f86112f336859003850185614ff0565b61353d565b905061130c6112f336859003850185614ff0565b8335600090815260796020526040902054146113765760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c696420726563656044820152621a5c1d60ea1b6064820152608401610a28565b82356000908152607a602052604090205460ff166113fc5760405162461bcd60e51b815260206004820152603160248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220617060448201527f70726f766564207769746864726177616c0000000000000000000000000000006064820152608401610a28565b82356000908152607a602052604090819020805460ff19169055517fd639511b37b3b002cca6cfe6bca0d833945a5af5a045578a0627fc43b79b26309061144690839086906150c4565b60405180910390a160006114606080850160608601614aec565b9050600061147661012086016101008701615151565b600181111561148757611487614c71565b141561154f5760006114a2368690038601610100870161516e565b6001600160a01b0383166000908152603b60205260409020549091506114ce90610140870135906135c6565b604082015260006114e8368790038701610100880161516e565b60408301519091506114ff9061014088013561518a565b604082015260745461151f908390339086906001600160a01b03166135e0565b6115486115326060880160408901614aec565b60745483919086906001600160a01b03166135e0565b505061158b565b61158b6115626060860160408701614aec565b60745483906001600160a01b03166115833689900389016101008a0161516e565b9291906135e0565b7f21e88e956aa3e086f6388e899965cef814688f99ad8bb29b08d396571016372d82856040516115bc9291906150c4565b60405180910390a150505050565b6000805160206157b9833981519152546001600160a01b0316331461162a5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b8261168a5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e8984848484612666565b6000806116b86000805160206157b9833981519152546001600160a01b031690565b6001600160a01b0316336001600160a01b0316146117115760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b61171b84846122f8565b90925090506117286123d3565b9250929050565b6003546040805163926323d560e01b815290516000926001600160a01b03169163926323d5916004808301926020929190829003018186803b15801561177457600080fd5b505afa158015611788573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117ac9190614f89565b6037546117b991906151a1565b6038546117c690846151a1565b101592915050565b6000805160206157b9833981519152546001600160a01b0316331461182e5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b8261188e5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e898484848461288c565b60408051808201909152600080825260208201526001600160a01b0382166000908152607860205260409081902081518083019092528054829060ff1660018111156118e8576118e8614c71565b60018111156118f9576118f9614c71565b815290546001600160a01b036101009091048116602092830152908201519192501661198d5760405162461bcd60e51b815260206004820152602560248201527f4d61696e636861696e4761746577617956323a20756e737570706f727465642060448201527f746f6b656e0000000000000000000000000000000000000000000000000000006064820152608401610a28565b919050565b6000806119b46000805160206157b9833981519152546001600160a01b031690565b6001600160a01b0316336001600160a01b031614611a0d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b61171b8484612238565b6000818152607360205260408120610aa790613a13565b6000805160206157b9833981519152546001600160a01b03163314611a8e5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a65816121dd565b600082815260726020526040902060010154611ab38133612b65565b610f8a8383612be5565b6000805160206157b9833981519152546001600160a01b03163314611b1d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a658161217b565b6003546040805163926323d560e01b815290516000926001600160a01b03169163926323d5916004808301926020929190829003018186803b158015611b6b57600080fd5b505afa158015611b7f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ba39190614f89565b600154611bb091906151a1565b6002546117c690846151a1565b6000805160206157b9833981519152546001600160a01b03163314611c1d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b85611c7c5760405162461bcd60e51b815260206004820152602960248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220656d60448201526870747920617272617960b81b6064820152608401610a28565b611c8a878787878787612467565b611c978787836000610c8f565b611ca48787836001610cb5565b611cb18787836002610cdb565b611cbe8787836003610d01565b50505050505050565b6000805160206157b9833981519152546001600160a01b03163314611d275760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b82611d875760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e8984848484612a30565b604080518082018252600080825260208201526074549184015190916001600160a01b031690611dc290613a1d565b60208401516001600160a01b0316611ee1573484604001516040015114611e375760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c69642072657175604482015262195cdd60ea1b6064820152608401610a28565b611e408161189a565b6040850151519092506001811115611e5a57611e5a614c71565b82516001811115611e6d57611e6d614c71565b14611ecd5760405162461bcd60e51b815260206004820152602a60248201527f4d61696e636861696e4761746577617956323a20696e76616c696420746f6b656044820152691b881cdd185b99185c9960b21b6064820152608401610a28565b6001600160a01b0381166020850152612087565b3415611f3b5760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c69642072657175604482015262195cdd60ea1b6064820152608401610a28565b611f48846020015161189a565b6040850151519092506001811115611f6257611f62614c71565b82516001811115611f7557611f75614c71565b14611fd55760405162461bcd60e51b815260206004820152602a60248201527f4d61696e636861696e4761746577617956323a20696e76616c696420746f6b656044820152691b881cdd185b99185c9960b21b6064820152608401610a28565b60208401516040850151611fec9185903090613ac7565b83602001516001600160a01b0316816001600160a01b031614156120875760408481015181015190517f2e1a7d4d00000000000000000000000000000000000000000000000000000000815260048101919091526001600160a01b03821690632e1a7d4d90602401600060405180830381600087803b15801561206e57600080fd5b505af1158015612082573d6000803e3d6000fd5b505050505b607680546000918261209883614e06565b91905055905060006120bf858386602001516075548a613ce190949392919063ffffffff16565b90507fd7b25068d9dc8d00765254cfb7f5070f98d263c8d68931d937c7362fa738048b6120eb8261353d565b826040516120fa9291906151c0565b60405180910390a1505050505050565b60006001600160e01b031982167f7965db0b000000000000000000000000000000000000000000000000000000001480610aa757507f01ffc9a7000000000000000000000000000000000000000000000000000000006001600160e01b0319831614610aa7565b6110178282612b43565b6074805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040519081527f9d2334c23be647e994f27a72c5eee42a43d5bdcfe15bb88e939103c2b114cbaf906020015b60405180910390a150565b6003805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040519081527fef40dc07567635f84f5edbd2f8dbc16b40d9d282dd8e7e6f4ff58236b6836169906020016121d2565b6000808284111561228b5760405162461bcd60e51b815260206004820152601c60248201527f4761746577617956323a20696e76616c6964207468726573686f6c64000000006044820152606401610a28565b505060018054600280549285905583905560048054919291849186919060006122b383614e06565b9091555060408051868152602081018690527f976f8a9c5bdf8248dec172376d6e2b80a8e3df2f0328e381c6db8e1cf138c0f891015b60405180910390a49250929050565b600080828411156123715760405162461bcd60e51b815260206004820152602760248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c696420746860448201527f726573686f6c64000000000000000000000000000000000000000000000000006064820152608401610a28565b5050603780546038805492859055839055600480549192918491869190600061239983614e06565b9091555060408051868152602081018690527f31312c97b89cc751b832d98fd459b967a2c3eef3b49757d1cf5ebaa12bb6eee191016122e9565b6002546037546123e391906151a1565b6038546001546123f391906151a1565b1115610a675760405162461bcd60e51b815260206004820152602860248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c696420746860448201527f726573686f6c64730000000000000000000000000000000000000000000000006064820152608401610a28565b848314801561247557508481145b6124e75760405162461bcd60e51b815260206004820152602860248201527f4d61696e636861696e4761746577617956323a20696e76616c6964206172726160448201527f79206c656e6774680000000000000000000000000000000000000000000000006064820152608401610a28565b60005b8581101561262c5784848281811061250457612504614d90565b90506020020160208101906125199190614aec565b6078600089898581811061252f5761252f614d90565b90506020020160208101906125449190614aec565b6001600160a01b039081168252602082019290925260400160002080547fffffffffffffffffffffff0000000000000000000000000000000000000000ff1661010093909216929092021790558282828181106125a3576125a3614d90565b90506020020160208101906125b89190615151565b607860008989858181106125ce576125ce614d90565b90506020020160208101906125e39190614aec565b6001600160a01b031681526020810191909152604001600020805460ff19166001838181111561261557612615614c71565b02179055508061262481614e06565b9150506124ea565b507fa4f03cc9c0e0aeb5b71b4ec800702753f65748c2cf3064695ba8e8b46be704448686868686866040516120fa969594939291906152c1565b8281146126c85760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b83811015612743578282828181106126e5576126e5614d90565b905060200201356039600087878581811061270257612702614d90565b90506020020160208101906127179190614aec565b6001600160a01b031681526020810191909152604001600020558061273b81614e06565b9150506126cb565b507f80bc635c452ae67f12f9b6f12ad4daa6dbbc04eeb9ebb87d354ce10c0e210dc0848484846040516115bc9493929190615339565b8281146127db5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b83811015612856578282828181106127f8576127f8614d90565b90506020020135603a600087878581811061281557612815614d90565b905060200201602081019061282a9190614aec565b6001600160a01b031681526020810191909152604001600020558061284e81614e06565b9150506127de565b507f64557254143204d91ba2d95acb9fda1e5fea55f77efd028685765bc1e94dd4b5848484846040516115bc9493929190615339565b8281146128ee5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b838110156129fa57620f424083838381811061290f5761290f614d90565b90506020020135111561298a5760405162461bcd60e51b815260206004820152602860248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c696420706560448201527f7263656e746167650000000000000000000000000000000000000000000000006064820152608401610a28565b82828281811061299c5761299c614d90565b90506020020135603b60008787858181106129b9576129b9614d90565b90506020020160208101906129ce9190614aec565b6001600160a01b03168152602081019190915260400160002055806129f281614e06565b9150506128f1565b507fb05f5de88ae0294ebb6f67c5af2fcbbd593cc6bdfe543e2869794a4c8ce3ea50848484846040516115bc9493929190615339565b828114612a925760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b83811015612b0d57828282818110612aaf57612aaf614d90565b90506020020135603c6000878785818110612acc57612acc614d90565b9050602002016020810190612ae19190614aec565b6001600160a01b0316815260208101919091526040016000205580612b0581614e06565b915050612a95565b507fb5d2963614d72181b4df1f993d45b83edf42fa19710f0204217ba1b3e183bb73848484846040516115bc9493929190615339565b612b4d8282613db6565b6000828152607360205260409020610f8a9082613e58565b60008281526072602090815260408083206001600160a01b038516845290915290205460ff1661101757612ba3816001600160a01b03166014613e6d565b612bae836020613e6d565b604051602001612bbf9291906153d0565b60408051601f198184030181529082905262461bcd60e51b8252610a2891600401615451565b612bef828261404e565b6000828152607360205260409020610f8a90826140d1565b60005460ff16612c595760405162461bcd60e51b815260206004820152601460248201527f5061757361626c653a206e6f74207061757365640000000000000000000000006044820152606401610a28565b6000805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b6000823561014084013582612cbe6080870160608801614aec565b9050612cdb612cd6368890038801610100890161516e565b613a1d565b6001612ced6040880160208901615151565b6001811115612cfe57612cfe614c71565b14612d715760405162461bcd60e51b815260206004820152602860248201527f4d61696e636861696e4761746577617956323a20696e76616c6964207265636560448201527f697074206b696e640000000000000000000000000000000000000000000000006064820152608401610a28565b60808601354614612de95760405162461bcd60e51b8152602060048201526024808201527f4d61696e636861696e4761746577617956323a20696e76616c6964206368616960448201527f6e206964000000000000000000000000000000000000000000000000000000006064820152608401610a28565b6000612dfe61084a6080890160608a01614aec565b9050612e1261012088016101008901615151565b6001811115612e2357612e23614c71565b81516001811115612e3657612e36614c71565b148015612e675750612e4e60e0880160c08901614aec565b6001600160a01b031681602001516001600160a01b0316145b612ebf5760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c696420726563656044820152621a5c1d60ea1b6064820152608401610a28565b60008481526079602052604090205415612f415760405162461bcd60e51b815260206004820152603260248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220707260448201527f6f636573736564207769746864726177616c00000000000000000000000000006064820152608401610a28565b6001612f5561012089016101008a01615151565b6001811115612f6657612f66614c71565b1480612f795750612f7782846133bc565b155b612feb5760405162461bcd60e51b815260206004820152603260248201527f4d61696e636861696e4761746577617956323a2072656163686564206461696c60448201527f79207769746864726177616c206c696d697400000000000000000000000000006064820152608401610a28565b6000612fff6112f3368a90038a018a614ff0565b9050600061300f607754836140e6565b6003549091506001600160a01b0316600061303d6130356101208d016101008e01615151565b878985614142565b60408051606081018252600080825260208201819052918101829052919b50919250819081906000805b8f5181101561323c578f818151811061308257613082614d90565b6020908102919091018101518051818301516040808401518151600081529586018083528f905260ff9093169085015260608401526080830152935060019060a0016020604051602081039080840390855afa1580156130e6573d6000803e3d6000fd5b505050602060405103519450846001600160a01b0316846001600160a01b0316106131795760405162461bcd60e51b815260206004820152602160248201527f4d61696e636861696e4761746577617956323a20696e76616c6964206f72646560448201527f72000000000000000000000000000000000000000000000000000000000000006064820152608401610a28565b6040517f953865650000000000000000000000000000000000000000000000000000000081526001600160a01b03808716600483015286955089169063953865659060240160206040518083038186803b1580156131d657600080fd5b505afa1580156131ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061320e9190614f89565b6132189083615484565b915086821061322a576001955061323c565b8061323481614e06565b915050613067565b50846132b05760405162461bcd60e51b815260206004820152603660248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220696e60448201527f73756666696369656e7420766f746520776569676874000000000000000000006064820152608401610a28565b50505060008a81526079602052604090208690555050881561332c576000888152607a602052604090819020805460ff19166001179055517f89e52969465b1f1866fc5d46fd62de953962e9cb33552443cd999eba05bd20dc906133179086908e906150c4565b60405180910390a15050505050505050610aa7565b6133368688614233565b61337561334960608d0160408e01614aec565b87607460009054906101000a90046001600160a01b03168e61010001803603810190611583919061516e565b7f21e88e956aa3e086f6388e899965cef814688f99ad8bb29b08d396571016372d848c6040516133a69291906150c4565b60405180910390a1505050505050505092915050565b6001600160a01b0382166000908152603a602052604081205482106133e357506000610aa7565b60006133f2620151804261549c565b6001600160a01b0385166000908152603e60205260409020549091508111156134385750506001600160a01b0382166000908152603c6020526040902054811015610aa7565b6001600160a01b0384166000908152603d602052604090205461345c908490615484565b6001600160a01b0385166000908152603c602052604090205411159150610aa79050565b600060025460016002548460015461349891906151a1565b6134a29190615484565b6134ac919061518a565b610aa7919061549c565b60005460ff16156134fc5760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610a28565b6000805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258612c863390565b600061119883836142c3565b60007fb9d1fe7c9deeec5dc90a2f47ff1684239519f2545b2228d3d91fb27df3189eea60001b8260000151836020015161357a85604001516142ed565b61358786606001516142ed565b6135948760800151614350565b6040516020016135a9969594939291906154be565b604051602081830303815290604052805190602001209050919050565b6000620f42406135d683856151a1565b611198919061549c565b6000816001600160a01b0316836001600160a01b031614156136905760408086015190516001600160a01b0386169180156108fc02916000818181858888f1935050505061368b57816001600160a01b031663d0e30db086604001516040518263ffffffff1660e01b81526004016000604051808303818588803b15801561366757600080fd5b505af115801561367b573d6000803e3d6000fd5b505050505061368b858585614393565b613a0c565b6000855160018111156136a5576136a5614c71565b1415613866576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000906001600160a01b038516906370a082319060240160206040518083038186803b15801561370657600080fd5b505afa15801561371a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061373e9190614f89565b9050856040015181101561385557836001600160a01b03166340c10f193083896040015161376c919061518a565b6040516001600160a01b03909216602483015260448201526064016040516020818303038152906040529060e01b6020820180516001600160e01b0383818316178352505050506040516137c091906154f8565b6000604051808303816000865af19150503d80600081146137fd576040519150601f19603f3d011682016040523d82523d6000602084013e613802565b606091505b505080925050816138555760405162461bcd60e51b815260206004820152601b60248201527f546f6b656e3a204552433230206d696e74696e67206661696c656400000000006044820152606401610a28565b613860868686614393565b50613a0c565b60018551600181111561387b5761387b614c71565b141561399e5761389083858760200151614437565b61368b57602085810151604080516001600160a01b038881166024830152604480830194909452825180830390940184526064909101825292820180516001600160e01b03167f40c10f1900000000000000000000000000000000000000000000000000000000179052519185169161390991906154f8565b6000604051808303816000865af19150503d8060008114613946576040519150601f19603f3d011682016040523d82523d6000602084013e61394b565b606091505b5050809150508061368b5760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e3a20455243373231206d696e74696e67206661696c6564000000006044820152606401610a28565b60405162461bcd60e51b815260206004820152602160248201527f546f6b656e3a20756e737570706f7274656420746f6b656e207374616e64617260448201527f64000000000000000000000000000000000000000000000000000000000000006064820152608401610a28565b5050505050565b6000610aa7825490565b600081516001811115613a3257613a32614c71565b148015613a43575060008160400151115b8015613a5157506020810151155b80613a7b5750600181516001811115613a6c57613a6c614c71565b148015613a7b57506040810151155b610a655760405162461bcd60e51b815260206004820152601360248201527f546f6b656e3a20696e76616c696420696e666f000000000000000000000000006044820152606401610a28565b600060608186516001811115613adf57613adf614c71565b1415613bbd5760408681015181516001600160a01b038881166024830152878116604483015260648083019390935283518083039093018352608490910183526020820180516001600160e01b03166323b872dd60e01b179052915191851691613b4991906154f8565b6000604051808303816000865af19150503d8060008114613b86576040519150601f19603f3d011682016040523d82523d6000602084013e613b8b565b606091505b509092509050818015613bb6575080511580613bb6575080806020019051810190613bb69190615514565b9150613c84565b600186516001811115613bd257613bd2614c71565b141561399e57602086810151604080516001600160a01b0389811660248301528881166044830152606480830194909452825180830390940184526084909101825292820180516001600160e01b03166323b872dd60e01b1790525191851691613c3c91906154f8565b6000604051808303816000865af19150503d8060008114613c79576040519150601f19603f3d011682016040523d82523d6000602084013e613c7e565b606091505b50909250505b81610f5c57613c92866144e2565b613ca6866001600160a01b03166014613e6d565b613cba866001600160a01b03166014613e6d565b613cce866001600160a01b03166014613e6d565b604051602001612bbf9493929190615536565b613d516040805160a08101825260008082526020808301829052835160608082018652838252818301849052818601849052848601919091528451808201865283815280830184905280860184905281850152845190810185528281529081018290529283015290608082015290565b83815260006020820181905250604080820180516001600160a01b039788169052602080890151825190891690820152905146908301528751606084018051918916909152805195909716940193909352935182015292909201516080820152919050565b60008281526072602090815260408083206001600160a01b038516845290915290205460ff166110175760008281526072602090815260408083206001600160a01b03851684529091529020805460ff19166001179055613e143390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000611198836001600160a01b03841661454f565b60606000613e7c8360026151a1565b613e87906002615484565b67ffffffffffffffff811115613e9f57613e9f614e21565b6040519080825280601f01601f191660200182016040528015613ec9576020820181803683370190505b5090507f300000000000000000000000000000000000000000000000000000000000000081600081518110613f0057613f00614d90565b60200101906001600160f81b031916908160001a9053507f780000000000000000000000000000000000000000000000000000000000000081600181518110613f4b57613f4b614d90565b60200101906001600160f81b031916908160001a9053506000613f6f8460026151a1565b613f7a906001615484565b90505b6001811115613fff577f303132333435363738396162636465660000000000000000000000000000000085600f1660108110613fbb57613fbb614d90565b1a60f81b828281518110613fd157613fd1614d90565b60200101906001600160f81b031916908160001a90535060049490941c93613ff881615606565b9050613f7d565b5083156111985760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610a28565b60008281526072602090815260408083206001600160a01b038516845290915290205460ff16156110175760008281526072602090815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b6000611198836001600160a01b03841661459e565b604080517f19010000000000000000000000000000000000000000000000000000000000006020808301919091526022820185905260428083018590528351808403909101815260629092019092528051910120600090611198565b6000806000836001600160a01b031663926323d56040518163ffffffff1660e01b815260040160206040518083038186803b15801561418057600080fd5b505afa158015614194573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141b89190614f89565b90506141c381613480565b925060008760018111156141d9576141d9614c71565b1415614229576001600160a01b038616600090815260396020526040902054851061420a5761420781614691565b92505b6001600160a01b0386166000908152603a602052604090205485101591505b5094509492505050565b6000614242620151804261549c565b6001600160a01b0384166000908152603e6020526040902054909150811115614291576001600160a01b03929092166000908152603e6020908152604080832094909455603d90529190912055565b6001600160a01b0383166000908152603d6020526040812080548492906142b9908490615484565b9091555050505050565b60008260000182815481106142da576142da614d90565b9060005260206000200154905092915050565b805160208083015160408085015190516000946135a9947f353bdd8d69b9e3185b3972e08b03845c0c14a21a390215302776a7a34b0e87649491939192019384526001600160a01b03928316602085015291166040830152606082015260800190565b805160208083015160408085015190516000946135a9947f1e2b74b2a792d5c0f0b6e59b037fa9d43d84fbb759337f0112fcc15ca414fc8d94919391920161561d565b600080845160018111156143a9576143a9614c71565b14156143c5576143be828486604001516146a9565b90506143ef565b6001845160018111156143da576143da614c71565b141561399e576143be82848660200151614437565b80610e89576143fd846144e2565b614411846001600160a01b03166014613e6d565b614425846001600160a01b03166014613e6d565b604051602001612bbf93929190615648565b604080513060248201526001600160a01b038481166044830152606480830185905283518084039091018152608490920183526020820180516001600160e01b03166323b872dd60e01b1790529151600092861691614495916154f8565b6000604051808303816000865af19150503d80600081146144d2576040519150601f19603f3d011682016040523d82523d6000602084013e6144d7565b606091505b509095945050505050565b606061450d826000015160018111156144fd576144fd614c71565b6001600160a01b03166001613e6d565b61451a8360200151614795565b6145278460400151614795565b604051602001614539939291906156d9565b6040516020818303038152906040529050919050565b600081815260018301602052604081205461459657508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610aa7565b506000610aa7565b600081815260018301602052604081205480156146875760006145c260018361518a565b85549091506000906145d69060019061518a565b905081811461463b5760008660000182815481106145f6576145f6614d90565b906000526020600020015490508087600001848154811061461957614619614d90565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061464c5761464c6157a2565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610aa7565b6000915050610aa7565b600060385460016038548460375461349891906151a1565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b03167fa9059cbb0000000000000000000000000000000000000000000000000000000017905291516000926060929087169161471f91906154f8565b6000604051808303816000865af19150503d806000811461475c576040519150601f19603f3d011682016040523d82523d6000602084013e614761565b606091505b50909250905081801561478c57508051158061478c57508080602001905181019061478c9190615514565b95945050505050565b6060816147d557505060408051808201909152600481527f3078303000000000000000000000000000000000000000000000000000000000602082015290565b8160005b81156147f857806147e981614e06565b915050600882901c91506147d9565b6111848482613e6d565b604080516060810182526000808252602082015290810161483e6040805160608101909152806000815260200160008152602001600081525090565b905290565b60006020828403121561485557600080fd5b81356001600160e01b03198116811461119857600080fd5b6001600160a01b0381168114610a6557600080fd5b803561198d8161486d565b8060608101831015610aa757600080fd5b8060808101831015610aa757600080fd5b60008083601f8401126148c157600080fd5b50813567ffffffffffffffff8111156148d957600080fd5b6020830191508360208260051b850101111561172857600080fd5b60008060008060008060008060008060006101408c8e03121561491657600080fd5b61491f8c614882565b9a5061492d60208d01614882565b995061493b60408d01614882565b985060608c0135975060808c0135965060a08c0135955060c08c0135945067ffffffffffffffff8060e08e0135111561497357600080fd5b6149838e60e08f01358f0161488d565b9450806101008e0135111561499757600080fd5b6149a88e6101008f01358f0161489e565b9350806101208e013511156149bc57600080fd5b506149ce8d6101208e01358e016148af565b81935080925050509295989b509295989b9093969950565b600080600080604085870312156149fc57600080fd5b843567ffffffffffffffff80821115614a1457600080fd5b614a20888389016148af565b90965094506020870135915080821115614a3957600080fd5b50614a46878288016148af565b95989497509550505050565b60008060008060008060608789031215614a6b57600080fd5b863567ffffffffffffffff80821115614a8357600080fd5b614a8f8a838b016148af565b90985096506020890135915080821115614aa857600080fd5b614ab48a838b016148af565b90965094506040890135915080821115614acd57600080fd5b50614ada89828a016148af565b979a9699509497509295939492505050565b600060208284031215614afe57600080fd5b81356111988161486d565b600060208284031215614b1b57600080fd5b5035919050565b60008060408385031215614b3557600080fd5b823591506020830135614b478161486d565b809150509250929050565b600060a08284031215614b6457600080fd5b50919050565b60006101608284031215614b6457600080fd5b60008060006101808486031215614b9357600080fd5b614b9d8585614b6a565b925061016084013567ffffffffffffffff80821115614bbb57600080fd5b818601915086601f830112614bcf57600080fd5b813581811115614bde57600080fd5b876020606083028501011115614bf357600080fd5b6020830194508093505050509250925092565b60008060408385031215614c1957600080fd5b8235614c248161486d565b946020939093013593505050565b60008060408385031215614c4557600080fd5b50508035926020909101359150565b60006101608284031215614c6757600080fd5b6111988383614b6a565b634e487b7160e01b600052602160045260246000fd5b60028110610a6557634e487b7160e01b600052602160045260246000fd5b81516040820190614cb581614c87565b808352506001600160a01b03602084015116602083015292915050565b60008060008060008060006080888a031215614ced57600080fd5b873567ffffffffffffffff80821115614d0557600080fd5b614d118b838c016148af565b909950975060208a0135915080821115614d2a57600080fd5b614d368b838c016148af565b909750955060408a0135915080821115614d4f57600080fd5b614d5b8b838c016148af565b909550935060608a0135915080821115614d7457600080fd5b50614d818a828b0161489e565b91505092959891949750929550565b634e487b7160e01b600052603260045260246000fd5b6000808335601e19843603018112614dbd57600080fd5b83018035915067ffffffffffffffff821115614dd857600080fd5b6020019150600581901b360382131561172857600080fd5b634e487b7160e01b600052601160045260246000fd5b6000600019821415614e1a57614e1a614df0565b5060010190565b634e487b7160e01b600052604160045260246000fd5b6040516060810167ffffffffffffffff81118282101715614e6857634e487b7160e01b600052604160045260246000fd5b60405290565b60028110610a6557600080fd5b600060608284031215614e8d57600080fd5b614e95614e37565b90508135614ea281614e6e565b80825250602082013560208201526040820135604082015292915050565b600060a08284031215614ed257600080fd5b614eda614e37565b8235614ee58161486d565b81526020830135614ef58161486d565b6020820152614f078460408501614e7b565b60408201529392505050565b600060608284031215614f2557600080fd5b6040516060810181811067ffffffffffffffff82111715614f5657634e487b7160e01b600052604160045260246000fd5b604052823560ff81168114614f6a57600080fd5b8152602083810135908201526040928301359281019290925250919050565b600060208284031215614f9b57600080fd5b5051919050565b600060608284031215614fb457600080fd5b614fbc614e37565b90508135614fc98161486d565b81526020820135614fd98161486d565b806020830152506040820135604082015292915050565b6000610160828403121561500357600080fd5b60405160a0810181811067ffffffffffffffff8211171561503457634e487b7160e01b600052604160045260246000fd5b60405282358152602083013561504981614e6e565b602082015261505b8460408501614fa2565b604082015261506d8460a08501614fa2565b6060820152615080846101008501614e7b565b60808201529392505050565b80356150978161486d565b6001600160a01b0390811683526020820135906150b38261486d565b166020830152604090810135910152565b6000610180820190508382528235602083015260208301356150e581614e6e565b6150ee81614c87565b80604084015250615105606083016040850161508c565b61511560c0830160a0850161508c565b61012061010084013561512781614e6e565b61513081614c87565b81840152830135610140808401919091529092013561016090910152919050565b60006020828403121561516357600080fd5b813561119881614e6e565b60006060828403121561518057600080fd5b6111988383614e7b565b60008282101561519c5761519c614df0565b500390565b60008160001904831182151516156151bb576151bb614df0565b500290565b6000610180820190508382528251602083015260208301516151e181614c87565b6040838101919091528381015180516001600160a01b03908116606086015260208201511660808501529081015160a084015250606083015180516001600160a01b0390811660c085015260208201511660e08401526040810151610100840152506080830151805161525381614c87565b6101208401526020810151610140840152604001516101609092019190915292915050565b8183526000602080850194508260005b858110156152b657813561529b8161486d565b6001600160a01b031687529582019590820190600101615288565b509495945050505050565b6060815260006152d560608301888a615278565b6020838203818501526152e982888a615278565b8481036040860152858152869250810160005b8681101561532a57833561530f81614e6e565b61531881614c87565b825292820192908201906001016152fc565b509a9950505050505050505050565b60408152600061534d604083018688615278565b82810360208401528381527f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84111561538557600080fd5b8360051b80866020840137600091016020019081529695505050505050565b60005b838110156153bf5781810151838201526020016153a7565b83811115610e895750506000910152565b7f416363657373436f6e74726f6c3a206163636f756e74200000000000000000008152600083516154088160178501602088016153a4565b7f206973206d697373696e6720726f6c652000000000000000000000000000000060179184019182015283516154458160288401602088016153a4565b01602801949350505050565b60208152600082518060208401526154708160408501602087016153a4565b601f01601f19169190910160400192915050565b6000821982111561549757615497614df0565b500190565b6000826154b957634e487b7160e01b600052601260045260246000fd5b500490565b8681526020810186905260c081016154d586614c87565b8560408301528460608301528360808301528260a0830152979650505050505050565b6000825161550a8184602087016153a4565b9190910192915050565b60006020828403121561552657600080fd5b8151801515811461119857600080fd5b7f546f6b656e3a20636f756c64206e6f74207472616e7366657220000000000000815260008551602061556f82601a8601838b016153a4565b7f2066726f6d200000000000000000000000000000000000000000000000000000601a9285019283015286516155aa81838501848b016153a4565b630103a37960e51b92018181019290925285516155cd81602485018985016153a4565b660103a37b5b2b7160cd1b6024939091019283015284516155f481602b85018489016153a4565b91909101602b01979650505050505050565b60008161561557615615614df0565b506000190190565b8481526080810161562d85614c87565b84602083015283604083015282606083015295945050505050565b7f546f6b656e3a20636f756c64206e6f74207472616e736665722000000000000081526000845161568081601a8501602089016153a4565b630103a37960e51b601a9184019182015284516156a481601e8401602089016153a4565b660103a37b5b2b7160cd1b601e929091019182015283516156cc8160258401602088016153a4565b0160250195945050505050565b7f546f6b656e496e666f280000000000000000000000000000000000000000000081526000845161571181600a8501602089016153a4565b80830190507f2c0000000000000000000000000000000000000000000000000000000000000080600a830152855161575081600b850160208a016153a4565b600b920191820152835161576b81600c8401602088016153a4565b7f2900000000000000000000000000000000000000000000000000000000000000600c9290910191820152600d0195945050505050565b634e487b7160e01b600052603160045260246000fdfeb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610348617350726f787941646d696e3a20756e617574686f72697a65642073656e64") + ]; +} diff --git a/crates/forge/bin/cmd/config.rs b/crates/forge/bin/cmd/config.rs index a8e33cdba38ca..fc325e39d99c9 100644 --- a/crates/forge/bin/cmd/config.rs +++ b/crates/forge/bin/cmd/config.rs @@ -8,25 +8,25 @@ use foundry_config::fix::fix_tomls; foundry_config::impl_figment_convert!(ConfigArgs, opts, evm_opts); /// CLI arguments for `forge config`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct ConfigArgs { /// Print only a basic set of the currently set config values. - #[clap(long)] + #[arg(long)] basic: bool, /// Print currently set config values as JSON. - #[clap(long)] + #[arg(long)] json: bool, /// Attempt to fix any configuration warnings. - #[clap(long)] + #[arg(long)] fix: bool, // support nested build arguments - #[clap(flatten)] + #[command(flatten)] opts: BuildArgs, - #[clap(flatten)] + #[command(flatten)] evm_opts: EvmArgs, } @@ -39,7 +39,10 @@ impl ConfigArgs { return Ok(()) } - let config = self.try_load_config_unsanitized_emit_warnings()?; + let config = self + .try_load_config_unsanitized_emit_warnings()? + // we explicitly normalize the version, so mimic the behavior when invoking solc + .normalized_evm_version(); let s = if self.basic { let config = config.into_basic(); diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index 635991ad49c52..2b5ce85a6f3c2 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -1,61 +1,59 @@ -use super::{install, test::FilterArgs}; +use super::{install, test::TestArgs}; use alloy_primitives::{Address, Bytes, U256}; use clap::{Parser, ValueEnum, ValueHint}; use eyre::{Context, Result}; use forge::{ coverage::{ - analysis::SourceAnalyzer, anchors::find_anchors, ContractId, CoverageReport, - CoverageReporter, DebugReporter, ItemAnchor, LcovReporter, SummaryReporter, + analysis::SourceAnalyzer, anchors::find_anchors, BytecodeReporter, ContractId, + CoverageReport, CoverageReporter, DebugReporter, LcovReporter, SummaryReporter, }, - inspectors::CheatsConfig, opts::EvmOpts, - result::SuiteResult, revm::primitives::SpecId, - utils::{build_ic_pc_map, ICPCMap}, + utils::IcPcMap, MultiContractRunnerBuilder, TestOptions, }; use foundry_cli::{ - opts::CoreBuildArgs, p_println, utils::{LoadConfig, STATIC_FUZZ_SEED}, }; -use foundry_common::{compile::ProjectCompiler, evm::EvmArgs, fs}; +use foundry_common::{compile::ProjectCompiler, fs}; use foundry_compilers::{ artifacts::{contract::CompactContractBytecode, Ast, CompactBytecode, CompactDeployedBytecode}, sourcemap::SourceMap, Artifact, Project, ProjectCompileOutput, }; use foundry_config::{Config, SolcReq}; +use rustc_hash::FxHashMap; use semver::Version; -use std::{collections::HashMap, path::PathBuf, sync::mpsc::channel}; +use std::{collections::HashMap, path::PathBuf, sync::Arc}; use yansi::Paint; /// A map, keyed by contract ID, to a tuple of the deployment source map and the runtime source map. type SourceMaps = HashMap; // Loads project's figment and merges the build cli arguments into it -foundry_config::impl_figment_convert!(CoverageArgs, opts, evm_opts); +foundry_config::impl_figment_convert!(CoverageArgs, test); /// CLI arguments for `forge coverage`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct CoverageArgs { /// The report type to use for coverage. /// /// This flag can be used multiple times. - #[clap(long, value_enum, default_value = "summary")] + #[arg(long, value_enum, default_value = "summary")] report: Vec, /// Enable viaIR with minimum optimization /// /// This can fix most of the "stack too deep" errors while resulting a /// relatively accurate source map. - #[clap(long)] + #[arg(long)] ir_minimum: bool, /// The path to output the report. /// /// If not specified, the report will be stored in the root of the project. - #[clap( + #[arg( long, short, value_hint = ValueHint::FilePath, @@ -63,14 +61,12 @@ pub struct CoverageArgs { )] report_file: Option, - #[clap(flatten)] - filter: FilterArgs, + /// Whether to include libraries in the coverage report. + #[arg(long)] + include_libs: bool, - #[clap(flatten)] - evm_opts: EvmArgs, - - #[clap(flatten)] - opts: CoreBuildArgs, + #[command(flatten)] + test: TestArgs, } impl CoverageArgs { @@ -78,7 +74,7 @@ impl CoverageArgs { let (mut config, evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; // install missing dependencies - if install::install_missing_dependencies(&mut config, self.build_args().silent) && + if install::install_missing_dependencies(&mut config, self.test.build_args().silent) && config.auto_detect_remappings { // need to re-configure here to also catch additional remappings @@ -88,58 +84,55 @@ impl CoverageArgs { // Set fuzz seed so coverage reports are deterministic config.fuzz.seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + // Coverage analysis requires the Solc AST output. + config.ast = true; + let (project, output) = self.build(&config)?; - p_println!(!self.opts.silent => "Analysing contracts..."); + p_println!(!self.test.build_args().silent => "Analysing contracts..."); let report = self.prepare(&config, output.clone())?; - p_println!(!self.opts.silent => "Running tests..."); - self.collect(project, output, report, config, evm_opts).await + p_println!(!self.test.build_args().silent => "Running tests..."); + self.collect(project, output, report, Arc::new(config), evm_opts).await } /// Builds the project. fn build(&self, config: &Config) -> Result<(Project, ProjectCompileOutput)> { // Set up the project - let project = { - let mut project = config.ephemeral_no_artifacts_project()?; - - if self.ir_minimum { - // TODO: How to detect solc version if the user does not specify a solc version in - // config case1: specify local installed solc ? - // case2: multiple solc versions used and auto_detect_solc == true - if let Some(SolcReq::Version(version)) = &config.solc { - if *version < Version::new(0, 8, 13) { - return Err(eyre::eyre!( + let mut project = config.ephemeral_no_artifacts_project()?; + if self.ir_minimum { + // TODO: How to detect solc version if the user does not specify a solc version in + // config case1: specify local installed solc ? + // case2: multiple solc versions used and auto_detect_solc == true + if let Some(SolcReq::Version(version)) = &config.solc { + if *version < Version::new(0, 8, 13) { + return Err(eyre::eyre!( "viaIR with minimum optimization is only available in Solidity 0.8.13 and above." )); - } } + } - // print warning message - p_println!(!self.opts.silent => "{}", - Paint::yellow( - concat!( - "Warning! \"--ir-minimum\" flag enables viaIR with minimum optimization, which can result in inaccurate source mappings.\n", + // print warning message + let msg = concat!( + "Warning! \"--ir-minimum\" flag enables viaIR with minimum optimization, \ + which can result in inaccurate source mappings.\n", "Only use this flag as a workaround if you are experiencing \"stack too deep\" errors.\n", "Note that \"viaIR\" is only available in Solidity 0.8.13 and above.\n", - "See more:\n", - "https://github.com/foundry-rs/foundry/issues/3357\n" - ))); - - // Enable viaIR with minimum optimization - // https://github.com/ethereum/solidity/issues/12533#issuecomment-1013073350 - // And also in new releases of solidity: - // https://github.com/ethereum/solidity/issues/13972#issuecomment-1628632202 - project.solc_config.settings = - project.solc_config.settings.with_via_ir_minimum_optimization() - } else { - project.solc_config.settings.optimizer.disable(); - project.solc_config.settings.optimizer.runs = None; - project.solc_config.settings.optimizer.details = None; - project.solc_config.settings.via_ir = None; - } - - project - }; + "See more: https://github.com/foundry-rs/foundry/issues/3357", + ).yellow(); + p_println!(!self.test.build_args().silent => "{msg}"); + + // Enable viaIR with minimum optimization + // https://github.com/ethereum/solidity/issues/12533#issuecomment-1013073350 + // And also in new releases of solidity: + // https://github.com/ethereum/solidity/issues/13972#issuecomment-1628632202 + project.solc_config.settings = + project.solc_config.settings.with_via_ir_minimum_optimization() + } else { + project.solc_config.settings.optimizer.disable(); + project.solc_config.settings.optimizer.runs = None; + project.solc_config.settings.optimizer.details = None; + project.solc_config.settings.via_ir = None; + } let output = ProjectCompiler::default() .compile(&project)? @@ -149,7 +142,7 @@ impl CoverageArgs { } /// Builds the coverage report. - #[instrument(name = "prepare coverage", skip_all)] + #[instrument(name = "prepare", skip_all)] fn prepare(&self, config: &Config, output: ProjectCompileOutput) -> Result { let project_paths = config.project_paths(); @@ -158,11 +151,14 @@ impl CoverageArgs { let mut report = CoverageReport::default(); // Collect ASTs and sources - let mut versioned_asts: HashMap> = HashMap::new(); - let mut versioned_sources: HashMap> = HashMap::new(); + let mut versioned_asts: HashMap> = HashMap::new(); + let mut versioned_sources: HashMap> = HashMap::new(); for (path, mut source_file, version) in sources.into_sources_with_version() { + report.add_source(version.clone(), source_file.id as usize, path.clone()); + // Filter out dependencies - if project_paths.has_library_ancestor(std::path::Path::new(&path)) { + if !self.include_libs && project_paths.has_library_ancestor(std::path::Path::new(&path)) + { continue } @@ -180,7 +176,6 @@ impl CoverageArgs { fs::read_to_string(&file) .wrap_err("Could not read source code for analysis")?, ); - report.add_source(version, source_file.id as usize, path); } } @@ -232,15 +227,15 @@ impl CoverageArgs { // Since our coverage inspector collects hit data using program counters, the anchors also // need to be based on program counters. // TODO: Index by contract ID - let ic_pc_maps: HashMap = bytecodes + let ic_pc_maps: HashMap = bytecodes .iter() .map(|(id, bytecodes)| { // TODO: Creation bytecode as well ( id.clone(), ( - build_ic_pc_map(SpecId::LATEST, bytecodes.0.as_ref()), - build_ic_pc_map(SpecId::LATEST, bytecodes.1.as_ref()), + IcPcMap::new(SpecId::LATEST, bytecodes.0.as_ref()), + IcPcMap::new(SpecId::LATEST, bytecodes.1.as_ref()), ), ) }) @@ -258,27 +253,43 @@ impl CoverageArgs { })?, )? .analyze()?; - let anchors: HashMap> = source_analysis - .contract_items + + // Build helper mapping used by `find_anchors` + let mut items_by_source_id: HashMap<_, Vec<_>> = + HashMap::with_capacity(source_analysis.items.len()); + + for (item_id, item) in source_analysis.items.iter().enumerate() { + items_by_source_id.entry(item.loc.source_id).or_default().push(item_id); + } + + let anchors = source_maps .iter() - .filter_map(|(contract_id, item_ids)| { + .filter(|(contract_id, _)| contract_id.version == version) + .filter_map(|(contract_id, (creation_source_map, deployed_source_map))| { + let creation_code_anchors = find_anchors( + &bytecodes.get(contract_id)?.0, + creation_source_map, + &ic_pc_maps.get(contract_id)?.0, + &source_analysis.items, + &items_by_source_id, + ); + let deployed_code_anchors = find_anchors( + &bytecodes.get(contract_id)?.1, + deployed_source_map, + &ic_pc_maps.get(contract_id)?.1, + &source_analysis.items, + &items_by_source_id, + ); // TODO: Creation source map/bytecode as well - Some(( - contract_id.clone(), - find_anchors( - &bytecodes.get(contract_id)?.1, - &source_maps.get(contract_id)?.1, - &ic_pc_maps.get(contract_id)?.1, - item_ids, - &source_analysis.items, - ), - )) + Some((contract_id.clone(), (creation_code_anchors, deployed_code_anchors))) }) .collect(); report.add_items(version, source_analysis.items); report.add_anchors(anchors); } + report.add_source_maps(source_maps); + Ok(report) } @@ -288,50 +299,60 @@ impl CoverageArgs { project: Project, output: ProjectCompileOutput, mut report: CoverageReport, - config: Config, + config: Arc, evm_opts: EvmOpts, ) -> Result<()> { let root = project.paths.root; + let verbosity = evm_opts.verbosity; // Build the contract runner let env = evm_opts.evm_env().await?; - let mut runner = MultiContractRunnerBuilder::default() + let runner = MultiContractRunnerBuilder::new(config.clone()) .initial_balance(evm_opts.initial_balance) .evm_spec(config.evm_spec_id()) .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) - .with_cheats_config(CheatsConfig::new(&config, evm_opts.clone())) - .with_test_options(TestOptions { fuzz: config.fuzz, ..Default::default() }) + .with_test_options(TestOptions { + fuzz: config.fuzz.clone(), + invariant: config.invariant, + ..Default::default() + }) .set_coverage(true) - .build(root.clone(), output, env, evm_opts)?; + .build(&root, output, env, evm_opts)?; - // Run tests - let known_contracts = runner.known_contracts.clone(); - let filter = self.filter; - let (tx, rx) = channel::<(String, SuiteResult)>(); - let handle = - tokio::task::spawn( - async move { runner.test(filter, Some(tx), Default::default()).await }, - ); + let outcome = self + .test + .run_tests(runner, config.clone(), verbosity, &self.test.filter(&config)) + .await?; // Add hit data to the coverage report - for (artifact_id, hits) in rx - .into_iter() - .flat_map(|(_, suite)| suite.test_results.into_values()) - .filter_map(|mut result| result.coverage.take()) - .flat_map(|hit_maps| { - hit_maps.0.into_values().filter_map(|map| { - Some((known_contracts.find_by_code(map.bytecode.as_ref())?.0, map)) - }) - }) - { - // TODO: Note down failing tests + let data = outcome.results.into_iter().flat_map(|(_, suite)| { + let mut hits = Vec::new(); + for (_, mut result) in suite.test_results { + let Some(hit_maps) = result.coverage.take() else { continue }; + + for map in hit_maps.0.into_values() { + if let Some((id, _)) = + suite.known_contracts.find_by_deployed_code(map.bytecode.as_ref()) + { + hits.push((id.clone(), map, true)); + } else if let Some((id, _)) = + suite.known_contracts.find_by_creation_code(map.bytecode.as_ref()) + { + hits.push((id.clone(), map, false)); + } + } + } + + hits + }); + + for (artifact_id, hits, is_deployed_code) in data { if let Some(source_id) = report.get_source_id( artifact_id.version.clone(), artifact_id.source.to_string_lossy().to_string(), ) { let source_id = *source_id; - // TODO: Distinguish between creation/runtime in a smart way report.add_hit_map( &ContractId { version: artifact_id.version.clone(), @@ -339,13 +360,11 @@ impl CoverageArgs { contract_name: artifact_id.name.clone(), }, &hits, - ); + is_deployed_code, + )?; } } - // Reattach the thread - let _ = handle.await; - // Output final report for report_kind in self.report { match report_kind { @@ -359,24 +378,26 @@ impl CoverageArgs { .report(&report) } } + CoverageReportKind::Bytecode => { + let destdir = root.join("bytecode-coverage"); + fs::create_dir_all(&destdir)?; + BytecodeReporter::new(root.clone(), destdir).report(&report)?; + Ok(()) + } CoverageReportKind::Debug => DebugReporter.report(&report), }?; } Ok(()) } - - /// Returns the flattened [`CoreBuildArgs`] - pub fn build_args(&self) -> &CoreBuildArgs { - &self.opts - } } // TODO: HTML -#[derive(Debug, Clone, ValueEnum)] +#[derive(Clone, Debug, ValueEnum)] pub enum CoverageReportKind { Summary, Lcov, Debug, + Bytecode, } /// Helper function that will link references in unlinked bytecode to the 0 address. diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index 8f1787671cb22..460dbdea70ed0 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -1,40 +1,35 @@ -use super::{retry::RetryArgs, verify}; -use alloy_dyn_abi::{DynSolValue, JsonAbiExt, ResolveSolType}; -use alloy_json_abi::{Constructor, JsonAbi as Abi}; +use alloy_chains::Chain; +use alloy_dyn_abi::{DynSolValue, JsonAbiExt, Specifier}; +use alloy_json_abi::{Constructor, JsonAbi}; +use alloy_network::{AnyNetwork, EthereumSigner, TransactionBuilder}; use alloy_primitives::{Address, Bytes}; +use alloy_provider::{Provider, ProviderBuilder}; +use alloy_rpc_types::{AnyTransactionReceipt, BlockId, TransactionRequest, WithOtherFields}; +use alloy_signer::Signer; +use alloy_transport::{Transport, TransportError}; use clap::{Parser, ValueHint}; -use ethers_contract::ContractError; -use ethers_core::{ - abi::InvalidOutputType, - types::{ - transaction::eip2718::TypedTransaction, BlockNumber, Chain, Eip1559TransactionRequest, - TransactionReceipt, TransactionRequest, - }, -}; -use ethers_middleware::MiddlewareBuilder; -use ethers_providers::Middleware; use eyre::{Context, Result}; +use forge_verify::RetryArgs; use foundry_cli::{ opts::{CoreBuildArgs, EthereumOpts, EtherscanOpts, TransactionOpts}, utils::{self, read_constructor_args_file, remove_contract, LoadConfig}, }; use foundry_common::{ - compile, estimate_eip1559_fees, + compile::{self}, fmt::parse_tokens, - types::{ToAlloy, ToEthers}, }; -use foundry_compilers::{artifacts::BytecodeObject, info::ContractInfo, utils::canonicalized}; +use foundry_compilers::{artifacts::BytecodeObject, info::ContractInfo, utils::canonicalize}; use serde_json::json; use std::{borrow::Borrow, marker::PhantomData, path::PathBuf, sync::Arc}; /// CLI arguments for `forge create`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct CreateArgs { /// The contract identifier in the form `:`. contract: ContractInfo, /// The constructor arguments. - #[clap( + #[arg( long, num_args(1..), conflicts_with = "constructor_args_path", @@ -43,7 +38,7 @@ pub struct CreateArgs { constructor_args: Vec, /// The path to a file containing the constructor arguments. - #[clap( + #[arg( long, value_hint = ValueHint::FilePath, value_name = "PATH", @@ -51,37 +46,37 @@ pub struct CreateArgs { constructor_args_path: Option, /// Print the deployment information as JSON. - #[clap(long, help_heading = "Display options")] + #[arg(long, help_heading = "Display options")] json: bool, /// Verify contract after creation. - #[clap(long)] + #[arg(long)] verify: bool, /// Send via `eth_sendTransaction` using the `--from` argument or `$ETH_FROM` as sender - #[clap(long, requires = "from")] + #[arg(long, requires = "from")] unlocked: bool, /// Prints the standard json compiler input if `--verify` is provided. /// /// The standard json compiler input can be used to manually submit contract verification in /// the browser. - #[clap(long, requires = "verify")] + #[arg(long, requires = "verify")] show_standard_json_input: bool, - #[clap(flatten)] + #[command(flatten)] opts: CoreBuildArgs, - #[clap(flatten)] + #[command(flatten)] tx: TransactionOpts, - #[clap(flatten)] + #[command(flatten)] eth: EthereumOpts, - #[clap(flatten)] - pub verifier: verify::VerifierArgs, + #[command(flatten)] + pub verifier: forge_verify::VerifierArgs, - #[clap(flatten)] + #[command(flatten)] retry: RetryArgs, } @@ -90,19 +85,17 @@ impl CreateArgs { pub async fn run(mut self) -> Result<()> { // Find Project & Compile let project = self.opts.project()?; - let mut output = if self.json || self.opts.silent { - // Suppress compile stdout messages when printing json output or when silent - compile::suppress_compile(&project) + + let target_path = if let Some(ref mut path) = self.contract.path { + canonicalize(project.root().join(path))? } else { - compile::compile(&project, false, false) - }?; + project.find_contract_path(&self.contract.name)? + }; - if let Some(ref mut path) = self.contract.path { - // paths are absolute in the project's output - *path = canonicalized(project.root().join(&path)).to_string_lossy().to_string(); - } + let mut output = + compile::compile_target(&target_path, &project, self.json || self.opts.silent)?; - let (abi, bin, _) = remove_contract(&mut output, &self.contract)?; + let (abi, bin, _) = remove_contract(&mut output, &target_path, &self.contract.name)?; let bin = match bin.object { BytecodeObject::Bytecode(_) => bin.object, @@ -139,18 +132,20 @@ impl CreateArgs { let chain_id = if let Some(chain_id) = self.chain_id() { chain_id } else { - provider.get_chainid().await?.as_u64() + provider.get_chain_id().await? }; if self.unlocked { // Deploy with unlocked account let sender = self.eth.wallet.from.expect("required"); - let provider = provider.with_sender(sender.to_ethers()); - self.deploy(abi, bin, params, provider, chain_id).await + self.deploy(abi, bin, params, provider, chain_id, sender).await } else { // Deploy with signer - let signer = self.eth.wallet.signer(chain_id).await?; - let provider = provider.with_signer(signer); - self.deploy(abi, bin, params, provider, chain_id).await + let signer = self.eth.wallet.signer().await?; + let deployer = signer.address(); + let provider = ProviderBuilder::<_, _, AnyNetwork>::default() + .signer(EthereumSigner::new(signer)) + .on_provider(provider); + self.deploy(abi, bin, params, provider, chain_id, deployer).await } } @@ -172,7 +167,7 @@ impl CreateArgs { ) -> Result<()> { // NOTE: this does not represent the same `VerifyArgs` that would be sent after deployment, // since we don't know the address yet. - let mut verify = verify::VerifyArgs { + let mut verify = forge_verify::VerifyArgs { address: Default::default(), contract: self.contract.clone(), compiler_version: None, @@ -183,15 +178,19 @@ impl CreateArgs { key: self.eth.etherscan.key.clone(), chain: Some(chain.into()), }, + rpc: Default::default(), flatten: false, force: false, - skip_is_verified_check: false, + skip_is_verified_check: true, watch: true, retry: self.retry, - libraries: vec![], + libraries: self.opts.libraries.clone(), root: None, verifier: self.verifier.clone(), + via_ir: self.opts.via_ir, + evm_version: self.opts.compiler.evm_version, show_standard_json_input: self.show_standard_json_input, + guess_constructor_args: false, }; // Check config for Etherscan API Keys to avoid preflight check failing if no @@ -205,16 +204,15 @@ impl CreateArgs { } /// Deploys the contract - async fn deploy( + async fn deploy, T: Transport + Clone>( self, - abi: Abi, + abi: JsonAbi, bin: BytecodeObject, args: Vec, - provider: M, + provider: P, chain: u64, + deployer_address: Address, ) -> Result<()> { - let deployer_address = - provider.default_sender().expect("no sender address set for provider"); let bin = bin.into_bytes().unwrap_or_else(|| { panic!("no bytecode found in bin object for {}", self.contract.name) }); @@ -222,7 +220,7 @@ impl CreateArgs { let factory = ContractFactory::new(abi.clone(), bin.clone(), provider.clone()); let is_args_empty = args.is_empty(); - let deployer = + let mut deployer = factory.deploy_tokens(args.clone()).context("failed to deploy contract").map_err(|e| { if is_args_empty { e.wrap_err("no arguments provided for contract constructor; consider --constructor-args or --constructor-args-path") @@ -230,57 +228,53 @@ impl CreateArgs { e } })?; - let is_legacy = self.tx.legacy || - Chain::try_from(chain).map(|x| Chain::is_legacy(&x)).unwrap_or_default(); - let mut deployer = if is_legacy { deployer.legacy() } else { deployer }; - - // set tx value if specified - if let Some(value) = self.tx.value { - deployer.tx.set_value(value.to_ethers()); - } - - // fill tx first because if you target a lower gas than current base, eth_estimateGas - // will fail and create will fail - provider.fill_transaction(&mut deployer.tx, None).await?; - - // the max - let mut priority_fee = self.tx.priority_gas_price; - - // set gas price if specified - if let Some(gas_price) = self.tx.gas_price { - deployer.tx.set_gas_price(gas_price.to_ethers()); - } else if !is_legacy { - // estimate EIP1559 fees - let (max_fee, max_priority_fee) = estimate_eip1559_fees(&provider, Some(chain)) - .await - .wrap_err("Failed to estimate EIP1559 fees. This chain might not support EIP1559, try adding --legacy to your command.")?; - deployer.tx.set_gas_price(max_fee); - if priority_fee.is_none() { - priority_fee = Some(max_priority_fee.to_alloy()); - } - } + let is_legacy = self.tx.legacy || Chain::from(chain).is_legacy(); - // set gas limit if specified - if let Some(gas_limit) = self.tx.gas_limit { - deployer.tx.set_gas(gas_limit.to_ethers()); + deployer.tx.set_from(deployer_address); + deployer.tx.set_chain_id(chain); + // `to` field must be set explicitly, cannot be None. + if deployer.tx.to.is_none() { + deployer.tx.set_create(); } + deployer.tx.set_nonce(if let Some(nonce) = self.tx.nonce { + Ok(nonce.to()) + } else { + provider.get_transaction_count(deployer_address, BlockId::latest()).await + }?); - // set nonce if specified - if let Some(nonce) = self.tx.nonce { - deployer.tx.set_nonce(nonce.to_ethers()); + // set tx value if specified + if let Some(value) = self.tx.value { + deployer.tx.set_value(value); } - // set priority fee if specified - if let Some(priority_fee) = priority_fee { - if is_legacy { - eyre::bail!("there is no priority fee for legacy txs"); - } - deployer.tx = match deployer.tx { - TypedTransaction::Eip1559(eip1559_tx_request) => TypedTransaction::Eip1559( - eip1559_tx_request.max_priority_fee_per_gas(priority_fee.to_ethers()), - ), - _ => deployer.tx, + deployer.tx.set_gas_limit(if let Some(gas_limit) = self.tx.gas_limit { + Ok(gas_limit.to()) + } else { + provider.estimate_gas(&deployer.tx, BlockId::latest()).await + }?); + + if is_legacy { + let gas_price = if let Some(gas_price) = self.tx.gas_price { + gas_price.to() + } else { + provider.get_gas_price().await? + }; + deployer.tx.set_gas_price(gas_price); + } else { + let estimate = provider.estimate_eip1559_fees(None).await.wrap_err("Failed to estimate EIP1559 fees. This chain might not support EIP1559, try adding --legacy to your command.")?; + let priority_fee = if let Some(priority_fee) = self.tx.priority_gas_price { + priority_fee.to() + } else { + estimate.max_priority_fee_per_gas + }; + let max_fee = if let Some(max_fee) = self.tx.gas_price { + max_fee.to() + } else { + estimate.max_fee_per_gas }; + + deployer.tx.set_max_fee_per_gas(max_fee); + deployer.tx.set_max_priority_fee_per_gas(priority_fee); } // Before we actually deploy the contract we try check if the verify settings are valid @@ -303,42 +297,46 @@ impl CreateArgs { let address = deployed_contract; if self.json { let output = json!({ - "deployer": deployer_address.to_alloy().to_string(), + "deployer": deployer_address.to_string(), "deployedTo": address.to_string(), "transactionHash": receipt.transaction_hash }); println!("{output}"); } else { - println!("Deployer: {}", deployer_address.to_alloy()); + println!("Deployer: {deployer_address}"); println!("Deployed to: {address}"); println!("Transaction hash: {:?}", receipt.transaction_hash); }; if !self.verify { - return Ok(()) + return Ok(()); } println!("Starting contract verification..."); let num_of_optimizations = if self.opts.compiler.optimize { self.opts.compiler.optimizer_runs } else { None }; - let verify = verify::VerifyArgs { + let verify = forge_verify::VerifyArgs { address, contract: self.contract, compiler_version: None, constructor_args, constructor_args_path: None, num_of_optimizations, - etherscan: EtherscanOpts { key: self.eth.etherscan.key, chain: Some(chain.into()) }, + etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain.into()) }, + rpc: Default::default(), flatten: false, force: false, skip_is_verified_check: false, watch: true, retry: self.retry, - libraries: vec![], + libraries: self.opts.libraries.clone(), root: None, verifier: self.verifier, + via_ir: self.opts.via_ir, + evm_version: self.opts.compiler.evm_version, show_standard_json_input: self.show_standard_json_input, + guess_constructor_args: false, }; println!("Waiting for {} to detect contract deployment...", verify.verifier.verifier); verify.run().await @@ -371,7 +369,7 @@ impl CreateArgs { /// compatibility with less-abstract Contracts. /// /// For full usage docs, see [`DeploymentTxFactory`]. -pub type ContractFactory = DeploymentTxFactory, M>; +pub type ContractFactory = DeploymentTxFactory, P, T>; /// Helper which manages the deployment transaction of a smart contract. It /// wraps a deployment transaction, and retrieves the contract address output @@ -380,16 +378,16 @@ pub type ContractFactory = DeploymentTxFactory, M>; /// Currently, we recommend using the [`ContractDeployer`] type alias. #[derive(Debug)] #[must_use = "ContractDeploymentTx does nothing unless you `send` it"] -pub struct ContractDeploymentTx { +pub struct ContractDeploymentTx { /// the actual deployer, exposed for overriding the defaults - pub deployer: Deployer, + pub deployer: Deployer, /// marker for the `Contract` type to create afterwards /// /// this type will be used to construct it via `From::from(Contract)` _contract: PhantomData, } -impl Clone for ContractDeploymentTx +impl Clone for ContractDeploymentTx where B: Clone, { @@ -398,8 +396,8 @@ where } } -impl From> for ContractDeploymentTx { - fn from(deployer: Deployer) -> Self { +impl From> for ContractDeploymentTx { + fn from(deployer: Deployer) -> Self { Self { deployer, _contract: PhantomData } } } @@ -407,17 +405,17 @@ impl From> for ContractDeploymentTx { /// Helper which manages the deployment transaction of a smart contract #[derive(Debug)] #[must_use = "Deployer does nothing unless you `send` it"] -pub struct Deployer { +pub struct Deployer { /// The deployer's transaction, exposed for overriding the defaults - pub tx: TypedTransaction, - abi: Abi, + pub tx: WithOtherFields, + abi: JsonAbi, client: B, confs: usize, - block: BlockNumber, - _m: PhantomData, + _p: PhantomData

, + _t: PhantomData, } -impl Clone for Deployer +impl Clone for Deployer where B: Clone, { @@ -427,53 +425,38 @@ where abi: self.abi.clone(), client: self.client.clone(), confs: self.confs, - block: self.block, - _m: PhantomData, + _p: PhantomData, + _t: PhantomData, } } } -impl Deployer +impl Deployer where - B: Borrow + Clone, - M: Middleware, + B: Borrow

+ Clone, + P: Provider, + T: Transport + Clone, { - /// Uses a Legacy transaction instead of an EIP-1559 one to do the deployment - pub fn legacy(mut self) -> Self { - self.tx = match self.tx { - TypedTransaction::Eip1559(inner) => { - let tx: TransactionRequest = inner.into(); - TypedTransaction::Legacy(tx) - } - other => other, - }; - self - } - /// Broadcasts the contract deployment transaction and after waiting for it to /// be sufficiently confirmed (default: 1), it returns a tuple with /// the [`Contract`](crate::Contract) struct at the deployed contract's address - /// and the corresponding [`TransactionReceipt`]. + /// and the corresponding [`AnyReceipt`]. pub async fn send_with_receipt( self, - ) -> Result<(Address, TransactionReceipt), ContractError> { - let pending_tx = self + ) -> Result<(Address, AnyTransactionReceipt), ContractDeploymentError> { + let receipt = self .client .borrow() - .send_transaction(self.tx, Some(self.block.into())) - .await - .map_err(ContractError::from_middleware_error)?; - - // TODO: Should this be calculated "optimistically" by address/nonce? - let receipt = pending_tx - .confirmations(self.confs) - .await - .ok() - .flatten() - .ok_or(ContractError::ContractNotDeployed)?; - let address = receipt.contract_address.ok_or(ContractError::ContractNotDeployed)?; - - Ok((address.to_alloy(), receipt)) + .send_transaction(self.tx) + .await? + .with_required_confirmations(self.confs as u64) + .get_receipt() + .await?; + + let address = + receipt.contract_address.ok_or(ContractDeploymentError::ContractNotDeployed)?; + + Ok((address, receipt)) } } @@ -514,14 +497,15 @@ where /// # Ok(()) /// # } #[derive(Debug)] -pub struct DeploymentTxFactory { +pub struct DeploymentTxFactory { client: B, - abi: Abi, + abi: JsonAbi, bytecode: Bytes, - _m: PhantomData, + _p: PhantomData

, + _t: PhantomData, } -impl Clone for DeploymentTxFactory +impl Clone for DeploymentTxFactory where B: Clone, { @@ -530,39 +514,42 @@ where client: self.client.clone(), abi: self.abi.clone(), bytecode: self.bytecode.clone(), - _m: PhantomData, + _p: PhantomData, + _t: PhantomData, } } } -impl DeploymentTxFactory +impl DeploymentTxFactory where - B: Borrow + Clone, - M: Middleware, + B: Borrow

+ Clone, + P: Provider, + T: Transport + Clone, { /// Creates a factory for deployment of the Contract with bytecode, and the /// constructor defined in the abi. The client will be used to send any deployment /// transaction. - pub fn new(abi: Abi, bytecode: Bytes, client: B) -> Self { - Self { client, abi, bytecode, _m: PhantomData } + pub fn new(abi: JsonAbi, bytecode: Bytes, client: B) -> Self { + Self { client, abi, bytecode, _p: PhantomData, _t: PhantomData } } /// Create a deployment tx using the provided tokens as constructor /// arguments - pub fn deploy_tokens(self, params: Vec) -> Result, ContractError> + pub fn deploy_tokens( + self, + params: Vec, + ) -> Result, ContractDeploymentError> where B: Clone, { // Encode the constructor args & concatenate with the bytecode if necessary let data: Bytes = match (self.abi.constructor(), params.is_empty()) { - (None, false) => return Err(ContractError::ConstructorError), + (None, false) => return Err(ContractDeploymentError::ConstructorError), (None, true) => self.bytecode.clone(), (Some(constructor), _) => { let input: Bytes = constructor .abi_encode_input(¶ms) - .map_err(|f| { - ContractError::DetokenizationError(InvalidOutputType(f.to_string())) - })? + .map_err(ContractDeploymentError::DetokenizationError)? .into(); // Concatenate the bytecode and abi-encoded constructor call. self.bytecode.iter().copied().chain(input).collect() @@ -570,27 +557,32 @@ where }; // create the tx object. Since we're deploying a contract, `to` is `None` - // We default to EIP1559 transactions, but the sender can convert it back - // to a legacy one. - let tx = Eip1559TransactionRequest { - to: None, - data: Some(data.to_ethers()), - ..Default::default() - }; - - let tx = tx.into(); + let tx = WithOtherFields::new(TransactionRequest::default().input(data.into())); Ok(Deployer { client: self.client.clone(), abi: self.abi, tx, confs: 1, - block: BlockNumber::Latest, - _m: PhantomData, + _p: PhantomData, + _t: PhantomData, }) } } +#[derive(thiserror::Error, Debug)] +/// An Error which is thrown when interacting with a smart contract +pub enum ContractDeploymentError { + #[error("constructor is not defined in the ABI")] + ConstructorError, + #[error(transparent)] + DetokenizationError(#[from] alloy_dyn_abi::Error), + #[error("contract was not deployed")] + ContractNotDeployed, + #[error(transparent)] + RpcError(#[from] TransportError), +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/forge/bin/cmd/debug.rs b/crates/forge/bin/cmd/debug.rs index bdcea1c0db60c..8fe1d2e32a258 100644 --- a/crates/forge/bin/cmd/debug.rs +++ b/crates/forge/bin/cmd/debug.rs @@ -1,5 +1,6 @@ -use super::{build::BuildArgs, retry::RETRY_VERIFY_ON_CREATE, script::ScriptArgs}; use clap::{Parser, ValueHint}; +use forge_script::ScriptArgs; +use forge_verify::retry::RETRY_VERIFY_ON_CREATE; use foundry_cli::opts::CoreBuildArgs; use foundry_common::evm::EvmArgs; use std::path::PathBuf; @@ -8,34 +9,34 @@ use std::path::PathBuf; foundry_config::impl_figment_convert!(DebugArgs, opts, evm_opts); /// CLI arguments for `forge debug`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct DebugArgs { /// The contract you want to run. Either the file path or contract name. /// /// If multiple contracts exist in the same file you must specify the target contract with /// --target-contract. - #[clap(value_hint = ValueHint::FilePath)] + #[arg(value_hint = ValueHint::FilePath)] pub path: PathBuf, /// Arguments to pass to the script function. pub args: Vec, /// The name of the contract you want to run. - #[clap(long, visible_alias = "tc", value_name = "CONTRACT_NAME")] + #[arg(long, visible_alias = "tc", value_name = "CONTRACT_NAME")] pub target_contract: Option, /// The signature of the function you want to call in the contract, or raw calldata. - #[clap(long, short, default_value = "run()", value_name = "SIGNATURE")] + #[arg(long, short, default_value = "run()", value_name = "SIGNATURE")] pub sig: String, /// Open the script in the debugger. - #[clap(long)] + #[arg(long)] pub debug: bool, - #[clap(flatten)] + #[command(flatten)] pub opts: CoreBuildArgs, - #[clap(flatten)] + #[command(flatten)] pub evm_opts: EvmArgs, } @@ -47,7 +48,7 @@ impl DebugArgs { target_contract: self.target_contract, sig: self.sig, gas_estimate_multiplier: 130, - opts: BuildArgs { args: self.opts, ..Default::default() }, + opts: self.opts, evm_opts: self.evm_opts, debug: true, retry: RETRY_VERIFY_ON_CREATE, diff --git a/crates/forge/bin/cmd/doc/mod.rs b/crates/forge/bin/cmd/doc/mod.rs index dbef459ca73cf..30de31e5a2031 100644 --- a/crates/forge/bin/cmd/doc/mod.rs +++ b/crates/forge/bin/cmd/doc/mod.rs @@ -1,26 +1,29 @@ use clap::{Parser, ValueHint}; use eyre::Result; -use forge_doc::{ContractInheritance, Deployments, DocBuilder, GitSource, Inheritdoc}; +use forge_doc::{ + ContractInheritance, Deployments, DocBuilder, GitSource, InferInlineHyperlinks, Inheritdoc, +}; use foundry_cli::opts::GH_REPO_PREFIX_REGEX; +use foundry_common::compile::ProjectCompiler; use foundry_config::{find_project_root_path, load_config_with_root}; use std::{path::PathBuf, process::Command}; mod server; use server::Server; -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct DocArgs { /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] pub root: Option, /// The doc's output path. /// /// By default, it is the `docs/` in project root. - #[clap( + #[arg( long, short, value_hint = ValueHint::DirPath, @@ -29,32 +32,32 @@ pub struct DocArgs { out: Option, /// Build the `mdbook` from generated files. - #[clap(long, short)] + #[arg(long, short)] build: bool, /// Serve the documentation. - #[clap(long, short)] + #[arg(long, short)] serve: bool, /// Open the documentation in a browser after serving. - #[clap(long, requires = "serve")] + #[arg(long, requires = "serve")] open: bool, /// Hostname for serving documentation. - #[clap(long, requires = "serve")] + #[arg(long, requires = "serve")] hostname: Option, /// Port for serving documentation. - #[clap(long, short, requires = "serve")] + #[arg(long, short, requires = "serve")] port: Option, /// The relative path to the `hardhat-deploy` or `forge-deploy` artifact directory. Leave blank /// for default. - #[clap(long)] + #[arg(long)] deployments: Option>, /// Whether to create docs for external libraries. - #[clap(long, short)] + #[arg(long, short)] include_libraries: bool, } @@ -62,6 +65,9 @@ impl DocArgs { pub fn run(self) -> Result<()> { let root = self.root.clone().unwrap_or(find_project_root_path(None)?); let config = load_config_with_root(Some(root.clone())); + let project = config.project()?; + let compiler = ProjectCompiler::new().quiet(true); + let _output = compiler.compile(&project)?; let mut doc_config = config.doc.clone(); if let Some(out) = self.out { @@ -98,6 +104,7 @@ impl DocArgs { .with_fmt(config.fmt) .with_preprocessor(ContractInheritance { include_libraries: self.include_libraries }) .with_preprocessor(Inheritdoc::default()) + .with_preprocessor(InferInlineHyperlinks::default()) .with_preprocessor(GitSource { root: root.clone(), commit, diff --git a/crates/forge/bin/cmd/doc/server.rs b/crates/forge/bin/cmd/doc/server.rs index 7c092713615d4..f5991ba438377 100644 --- a/crates/forge/bin/cmd/doc/server.rs +++ b/crates/forge/bin/cmd/doc/server.rs @@ -1,6 +1,7 @@ use axum::{routing::get_service, Router}; use forge_doc::mdbook::{utils::fs::get_404_output_file, MDBook}; use std::{ + io, net::{SocketAddr, ToSocketAddrs}, path::PathBuf, }; @@ -82,18 +83,20 @@ impl Server { open(serving_url); } - let _ = thread_handle.join(); - - Ok(()) + match thread_handle.join() { + Ok(r) => r.map_err(Into::into), + Err(e) => std::panic::resume_unwind(e), + } } } #[tokio::main] -async fn serve(build_dir: PathBuf, address: SocketAddr, file_404: &str) { +async fn serve(build_dir: PathBuf, address: SocketAddr, file_404: &str) -> io::Result<()> { let file_404 = build_dir.join(file_404); let svc = ServeDir::new(build_dir).not_found_service(ServeFile::new(file_404)); let app = Router::new().nest_service("/", get_service(svc)); - hyper::Server::bind(&address).serve(app.into_make_service()).await.unwrap(); + let tcp_listener = tokio::net::TcpListener::bind(address).await?; + axum::serve(tcp_listener, app.into_make_service()).await } fn open>(path: P) { diff --git a/crates/forge/bin/cmd/flatten.rs b/crates/forge/bin/cmd/flatten.rs index 64d62b4d9d349..c1351d06d95a9 100644 --- a/crates/forge/bin/cmd/flatten.rs +++ b/crates/forge/bin/cmd/flatten.rs @@ -4,20 +4,21 @@ use foundry_cli::{ opts::{CoreBuildArgs, ProjectPathsArgs}, utils::LoadConfig, }; -use foundry_common::fs; +use foundry_common::{compile::compile_target, fs}; +use foundry_compilers::{error::SolcError, flatten::Flattener}; use std::path::PathBuf; /// CLI arguments for `forge flatten`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct FlattenArgs { /// The path to the contract to flatten. - #[clap(value_hint = ValueHint::FilePath, value_name = "PATH")] + #[arg(value_hint = ValueHint::FilePath, value_name = "PATH")] pub target_path: PathBuf, /// The path to output the flattened contract. /// /// If not specified, the flattened contract will be output to stdout. - #[clap( + #[arg( long, short, value_hint = ValueHint::FilePath, @@ -25,7 +26,7 @@ pub struct FlattenArgs { )] pub output: Option, - #[clap(flatten)] + #[command(flatten)] project_paths: ProjectPathsArgs, } @@ -35,14 +36,26 @@ impl FlattenArgs { // flatten is a subset of `BuildArgs` so we can reuse that to get the config let build_args = CoreBuildArgs { project_paths, ..Default::default() }; + let mut config = build_args.try_load_config_emit_warnings()?; + // `Flattener` uses the typed AST for better flattening results. + config.ast = true; + let project = config.ephemeral_no_artifacts_project()?; - let config = build_args.try_load_config_emit_warnings()?; - - let paths = config.project_paths(); let target_path = dunce::canonicalize(target_path)?; - let flattened = paths - .flatten(&target_path) - .map_err(|err| eyre::Error::msg(format!("Failed to flatten the file: {err}")))?; + let compiler_output = compile_target(&target_path, &project, false); + + let flattened = match compiler_output { + Ok(compiler_output) => { + Flattener::new(&project, &compiler_output, &target_path).map(|f| f.flatten()) + } + Err(_) => { + // Fallback to the old flattening implementation if we couldn't compile the target + // successfully. This would be the case if the target has invalid + // syntax. (e.g. Solang) + project.paths.flatten(&target_path) + } + } + .map_err(|err: SolcError| eyre::eyre!("Failed to flatten: {err}"))?; match output { Some(output) => { diff --git a/crates/forge/bin/cmd/fmt.rs b/crates/forge/bin/cmd/fmt.rs index 77d0e6aed4314..06e05a5680dc6 100644 --- a/crates/forge/bin/cmd/fmt.rs +++ b/crates/forge/bin/cmd/fmt.rs @@ -12,31 +12,31 @@ use std::{ io::{Read, Write as _}, path::{Path, PathBuf}, }; -use yansi::Color; +use yansi::{Color, Paint, Style}; /// CLI arguments for `forge fmt`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct FmtArgs { /// Path to the file, directory or '-' to read from stdin. - #[clap(value_hint = ValueHint::FilePath, value_name = "PATH", num_args(1..))] + #[arg(value_hint = ValueHint::FilePath, value_name = "PATH", num_args(1..))] paths: Vec, /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, /// Run in 'check' mode. /// /// Exits with 0 if input is formatted correctly. /// Exits with 1 if formatting is required. - #[clap(long)] + #[arg(long)] check: bool, /// In 'check' and stdin modes, outputs raw formatted code instead of the diff. - #[clap(long, short)] + #[arg(long, short)] raw: bool, } @@ -217,24 +217,24 @@ where } for op in group { for change in diff.iter_inline_changes(&op) { - let dimmed = Color::Default.style().dimmed(); + let dimmed = Style::new().dim(); let (sign, s) = match change.tag() { - ChangeTag::Delete => ("-", Color::Red.style()), - ChangeTag::Insert => ("+", Color::Green.style()), + ChangeTag::Delete => ("-", Color::Red.foreground()), + ChangeTag::Insert => ("+", Color::Green.foreground()), ChangeTag::Equal => (" ", dimmed), }; let _ = write!( diff_summary, "{}{} |{}", - dimmed.paint(Line(change.old_index())), - dimmed.paint(Line(change.new_index())), - s.bold().paint(sign), + Line(change.old_index()).paint(dimmed), + Line(change.new_index()).paint(dimmed), + sign.paint(s.bold()), ); for (emphasized, value) in change.iter_strings_lossy() { let s = if emphasized { s.underline().bg(Color::Black) } else { s }; - let _ = write!(diff_summary, "{}", s.paint(value)); + let _ = write!(diff_summary, "{}", value.paint(s)); } if change.missing_newline() { diff --git a/crates/forge/bin/cmd/geiger/find.rs b/crates/forge/bin/cmd/geiger/find.rs index d86dcd0fc2957..3ea9c02341098 100644 --- a/crates/forge/bin/cmd/geiger/find.rs +++ b/crates/forge/bin/cmd/geiger/find.rs @@ -26,7 +26,7 @@ pub fn find_cheatcodes_in_string(src: &str) -> Result fmt::Display for SolFileMetricsPrinter<'a, 'b> { ($($name:literal => $field:ident),*) => {$( let $field = &metrics.cheatcodes.$field[..]; if !$field.is_empty() { - writeln!(f, " {} {}", Paint::red(metrics.cheatcodes.$field.len()), Paint::red($name))?; + writeln!(f, " {} {}", metrics.cheatcodes.$field.len().red(), $name.red())?; for &loc in $field { let content = &metrics.contents[loc.range()]; let (line, col) = offset_to_line_column(&metrics.contents, loc.start()); let pos = format!(" --> {}:{}:{}", file.display(), line, col); - writeln!(f,"{}", Paint::red(pos))?; + writeln!(f,"{}", pos.red())?; for line in content.lines() { - writeln!(f, " {}", Paint::red(line))?; + writeln!(f, " {}", line.red())?; } } } @@ -71,12 +71,7 @@ impl<'a, 'b> fmt::Display for SolFileMetricsPrinter<'a, 'b> { } if !metrics.cheatcodes.is_empty() { - writeln!( - f, - "{} {}", - Paint::red(metrics.cheatcodes.len()), - Paint::red(file.display()) - )?; + writeln!(f, "{} {}", metrics.cheatcodes.len().red(), file.display().red())?; print_unsafe_fn!( "ffi" => ffi, "readFile" => read_file, @@ -97,7 +92,7 @@ impl<'a, 'b> fmt::Display for SolFileMetricsPrinter<'a, 'b> { } /// Unsafe usage metrics collection. -#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct UnsafeCheatcodes { pub ffi: Vec, pub read_file: Vec, diff --git a/crates/forge/bin/cmd/geiger/mod.rs b/crates/forge/bin/cmd/geiger/mod.rs index 8e5b67d6f24d2..27555ed1a7944 100644 --- a/crates/forge/bin/cmd/geiger/mod.rs +++ b/crates/forge/bin/cmd/geiger/mod.rs @@ -16,10 +16,10 @@ use find::{find_cheatcodes_in_file, SolFileMetricsPrinter}; mod visitor; /// CLI arguments for `forge geiger`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct GeigerArgs { /// Paths to files or directories to detect. - #[clap( + #[arg( conflicts_with = "root", value_hint = ValueHint::FilePath, value_name = "PATH", @@ -31,17 +31,17 @@ pub struct GeigerArgs { /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, /// Run in "check" mode. /// /// The exit code of the program will be the number of unsafe cheatcodes found. - #[clap(long)] + #[arg(long)] pub check: bool, /// Globs to ignore. - #[clap( + #[arg( long, value_hint = ValueHint::FilePath, value_name = "PATH", @@ -50,7 +50,7 @@ pub struct GeigerArgs { ignore: Vec, /// Print a report of all files, even if no unsafe functions are found. - #[clap(long)] + #[arg(long)] full: bool, } @@ -91,7 +91,7 @@ impl GeigerArgs { let sources = self.sources(&config).wrap_err("Failed to resolve files")?; if config.ffi { - eprintln!("{}\n", Paint::red("ffi enabled")); + eprintln!("{}\n", "ffi enabled".red()); } let root = config.__root.0; diff --git a/crates/forge/bin/cmd/generate/mod.rs b/crates/forge/bin/cmd/generate/mod.rs index 9e25d6532a808..fd40a78ae6ca8 100644 --- a/crates/forge/bin/cmd/generate/mod.rs +++ b/crates/forge/bin/cmd/generate/mod.rs @@ -7,7 +7,7 @@ use yansi::Paint; /// CLI arguments for `forge generate`. #[derive(Debug, Parser)] pub struct GenerateArgs { - #[clap(subcommand)] + #[command(subcommand)] pub sub: GenerateSubcommands, } @@ -20,7 +20,7 @@ pub enum GenerateSubcommands { #[derive(Debug, Parser)] pub struct GenerateTestArgs { /// Contract name for test generation. - #[clap(long, short, value_name = "CONTRACT_NAME")] + #[arg(long, short, value_name = "CONTRACT_NAME")] pub contract_name: String, } @@ -44,7 +44,7 @@ impl GenerateTestArgs { // Write the test content to the test file. fs::write(&test_file_path, test_content)?; - println!("{} test file: {}", Paint::green("Generated"), test_file_path.to_str().unwrap()); + println!("{} test file: {}", "Generated".green(), test_file_path.to_str().unwrap()); Ok(()) } } diff --git a/crates/forge/bin/cmd/init.rs b/crates/forge/bin/cmd/init.rs index 864babf557102..87010244ccfe6 100644 --- a/crates/forge/bin/cmd/init.rs +++ b/crates/forge/bin/cmd/init.rs @@ -9,36 +9,36 @@ use std::path::{Path, PathBuf}; use yansi::Paint; /// CLI arguments for `forge init`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Default, Parser)] pub struct InitArgs { /// The root directory of the new project. - #[clap(value_hint = ValueHint::DirPath, default_value = ".", value_name = "PATH")] - root: PathBuf, + #[arg(value_hint = ValueHint::DirPath, default_value = ".", value_name = "PATH")] + pub root: PathBuf, /// The template to start from. - #[clap(long, short)] - template: Option, + #[arg(long, short)] + pub template: Option, /// Branch argument that can only be used with template option. /// If not specified, the default branch is used. - #[clap(long, short, requires = "template")] - branch: Option, + #[arg(long, short, requires = "template")] + pub branch: Option, /// Do not install dependencies from the network. - #[clap(long, conflicts_with = "template", visible_alias = "no-deps")] - offline: bool, + #[arg(long, conflicts_with = "template", visible_alias = "no-deps")] + pub offline: bool, /// Create the project even if the specified root directory is not empty. - #[clap(long, conflicts_with = "template")] - force: bool, + #[arg(long, conflicts_with = "template")] + pub force: bool, /// Create a .vscode/settings.json file with Solidity settings, and generate a remappings.txt /// file. - #[clap(long, conflicts_with = "template")] - vscode: bool, + #[arg(long, conflicts_with = "template")] + pub vscode: bool, - #[clap(flatten)] - opts: DependencyInstallOpts, + #[command(flatten)] + pub opts: DependencyInstallOpts, } impl InitArgs { @@ -84,7 +84,7 @@ impl InitArgs { git.submodule_init()?; } else { // if not shallow, initialize and clone submodules (without fetching latest) - git.submodule_update(false, false, true, true, None::)?; + git.submodule_update(false, false, true, true, std::iter::empty::())?; } } else { // if target is not empty @@ -159,16 +159,11 @@ impl InitArgs { } } - p_println!(!quiet => " {} forge project", Paint::green("Initialized")); + p_println!(!quiet => " {} forge project", "Initialized".green()); Ok(()) } } -/// Returns the commit hash of the project if it exists -pub fn get_commit_hash(root: &Path) -> Option { - Git::new(root).commit_hash(true, "HEAD").ok() -} - /// Initialises `root` as a git repository, if it isn't one already. /// /// Creates `.gitignore` and `.github/workflows/test.yml`, if they don't exist already. diff --git a/crates/forge/bin/cmd/inspect.rs b/crates/forge/bin/cmd/inspect.rs index f1c4e09035053..b76ca2878da60 100644 --- a/crates/forge/bin/cmd/inspect.rs +++ b/crates/forge/bin/cmd/inspect.rs @@ -1,9 +1,8 @@ -use alloy_json_abi::JsonAbi; use clap::Parser; use comfy_table::{presets::ASCII_MARKDOWN, Table}; use eyre::Result; use foundry_cli::opts::{CompilerArgs, CoreBuildArgs}; -use foundry_common::compile; +use foundry_common::compile::ProjectCompiler; use foundry_compilers::{ artifacts::{ output_selection::{ @@ -15,25 +14,24 @@ use foundry_compilers::{ info::ContractInfo, utils::canonicalize, }; -use serde_json::{to_value, Value}; use std::fmt; /// CLI arguments for `forge inspect`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct InspectArgs { /// The identifier of the contract to inspect in the form `(:)?`. pub contract: ContractInfo, /// The contract artifact field to inspect. - #[clap(value_enum)] + #[arg(value_enum)] pub field: ContractArtifactField, /// Pretty print the selected field, if supported. - #[clap(long)] + #[arg(long)] pub pretty: bool, /// All build arguments are supported - #[clap(flatten)] + #[command(flatten)] build: CoreBuildArgs, } @@ -64,107 +62,73 @@ impl InspectArgs { // Build the project let project = modified_build_args.project()?; - let outcome = if let Some(ref mut contract_path) = contract.path { + let mut compiler = ProjectCompiler::new().quiet(true); + if let Some(contract_path) = &mut contract.path { let target_path = canonicalize(&*contract_path)?; *contract_path = target_path.to_string_lossy().to_string(); - compile::compile_files(&project, vec![target_path], true) - } else { - compile::suppress_compile(&project) - }?; + compiler = compiler.files([target_path]); + } + let output = compiler.compile(&project)?; // Find the artifact - let found_artifact = outcome.find_contract(&contract); - - trace!(target: "forge", artifact=?found_artifact, input=?contract, "Found contract"); - - // Unwrap the inner artifact - let artifact = found_artifact.ok_or_else(|| { + let artifact = output.find_contract(&contract).ok_or_else(|| { eyre::eyre!("Could not find artifact `{contract}` in the compiled artifacts") })?; - // Match on ContractArtifactFields and Pretty Print + // Match on ContractArtifactFields and pretty-print match field { ContractArtifactField::Abi => { let abi = artifact .abi .as_ref() .ok_or_else(|| eyre::eyre!("Failed to fetch lossless ABI"))?; - print_abi(abi, pretty)?; + if pretty { + let source = foundry_cli::utils::abi_to_solidity(abi, &contract.name)?; + println!("{source}"); + } else { + print_json(abi)?; + } } ContractArtifactField::Bytecode => { - let tval: Value = to_value(&artifact.bytecode)?; - println!( - "{}", - tval.get("object").unwrap_or(&tval).as_str().ok_or_else(|| eyre::eyre!( - "Failed to extract artifact bytecode as a string" - ))? - ); + print_json_str(&artifact.bytecode, Some("object"))?; } ContractArtifactField::DeployedBytecode => { - let tval: Value = to_value(&artifact.deployed_bytecode)?; - println!( - "{}", - tval.get("object").unwrap_or(&tval).as_str().ok_or_else(|| eyre::eyre!( - "Failed to extract artifact deployed bytecode as a string" - ))? - ); + print_json_str(&artifact.deployed_bytecode, Some("object"))?; } ContractArtifactField::Assembly | ContractArtifactField::AssemblyOptimized => { - println!( - "{}", - to_value(&artifact.assembly)?.as_str().ok_or_else(|| eyre::eyre!( - "Failed to extract artifact assembly as a string" - ))? - ); + print_json_str(&artifact.assembly, None)?; } ContractArtifactField::MethodIdentifiers => { - println!( - "{}", - serde_json::to_string_pretty(&to_value(&artifact.method_identifiers)?)? - ); + print_json(&artifact.method_identifiers)?; } ContractArtifactField::GasEstimates => { - println!("{}", serde_json::to_string_pretty(&to_value(&artifact.gas_estimates)?)?); + print_json(&artifact.gas_estimates)?; } ContractArtifactField::StorageLayout => { print_storage_layout(artifact.storage_layout.as_ref(), pretty)?; } ContractArtifactField::DevDoc => { - println!("{}", serde_json::to_string_pretty(&to_value(&artifact.devdoc)?)?); + print_json(&artifact.devdoc)?; } ContractArtifactField::Ir => { - println!( - "{}", - to_value(&artifact.ir)? - .as_str() - .ok_or_else(|| eyre::eyre!("Failed to extract artifact ir as a string"))? - ); + print_json_str(&artifact.ir, None)?; } ContractArtifactField::IrOptimized => { - println!( - "{}", - to_value(&artifact.ir_optimized)?.as_str().ok_or_else(|| eyre::eyre!( - "Failed to extract artifact optimized ir as a string" - ))? - ); + print_json_str(&artifact.ir_optimized, None)?; } ContractArtifactField::Metadata => { - println!("{}", serde_json::to_string_pretty(&to_value(&artifact.metadata)?)?); + print_json(&artifact.metadata)?; } ContractArtifactField::UserDoc => { - println!("{}", serde_json::to_string_pretty(&to_value(&artifact.userdoc)?)?); + print_json(&artifact.userdoc)?; } ContractArtifactField::Ewasm => { - println!( - "{}", - to_value(&artifact.ewasm)?.as_str().ok_or_else(|| eyre::eyre!( - "Failed to extract artifact ewasm as a string" - ))? - ); + print_json_str(&artifact.ewasm, None)?; } ContractArtifactField::Errors => { let mut out = serde_json::Map::new(); if let Some(abi) = &artifact.abi { + let abi = &abi; // Print the signature of all errors for er in abi.errors.iter().flat_map(|(_, errors)| errors) { let types = er.inputs.iter().map(|p| p.ty.clone()).collect::>(); @@ -176,11 +140,13 @@ impl InspectArgs { ); } } - println!("{}", serde_json::to_string_pretty(&out)?); + print_json(&out)?; } ContractArtifactField::Events => { let mut out = serde_json::Map::new(); if let Some(abi) = &artifact.abi { + let abi = &abi; + // print the signature of all events including anonymous for ev in abi.events.iter().flat_map(|(_, events)| events) { let types = ev.inputs.iter().map(|p| p.ty.clone()).collect::>(); @@ -190,7 +156,7 @@ impl InspectArgs { ); } } - println!("{}", serde_json::to_string_pretty(&out)?); + print_json(&out)?; } }; @@ -198,24 +164,13 @@ impl InspectArgs { } } -pub fn print_abi(abi: &JsonAbi, pretty: bool) -> Result<()> { - let s = if pretty { - foundry_cli::utils::abi_to_solidity(abi, "")? - } else { - serde_json::to_string_pretty(&abi)? - }; - println!("{s}"); - Ok(()) -} - pub fn print_storage_layout(storage_layout: Option<&StorageLayout>, pretty: bool) -> Result<()> { let Some(storage_layout) = storage_layout else { eyre::bail!("Could not get storage layout"); }; if !pretty { - println!("{}", serde_json::to_string_pretty(&to_value(storage_layout)?)?); - return Ok(()) + return print_json(&storage_layout) } let mut table = Table::new(); @@ -229,13 +184,12 @@ pub fn print_storage_layout(storage_layout: Option<&StorageLayout>, pretty: bool storage_type.map_or("?", |t| &t.label), &slot.slot, &slot.offset.to_string(), - &storage_type.map_or("?", |t| &t.number_of_bytes), + storage_type.map_or("?", |t| &t.number_of_bytes), &slot.contract, ]); } println!("{table}"); - Ok(()) } @@ -409,6 +363,26 @@ impl ContractArtifactField { } } +fn print_json(obj: &impl serde::Serialize) -> Result<()> { + println!("{}", serde_json::to_string_pretty(obj)?); + Ok(()) +} + +fn print_json_str(obj: &impl serde::Serialize, key: Option<&str>) -> Result<()> { + let value = serde_json::to_value(obj)?; + let mut value_ref = &value; + if let Some(key) = key { + if let Some(value2) = value.get(key) { + value_ref = value2; + } + } + match value_ref.as_str() { + Some(s) => println!("{s}"), + None => println!("{value_ref:#}"), + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/forge/bin/cmd/install.rs b/crates/forge/bin/cmd/install.rs index d01fe97940621..ccec7d5de4f12 100644 --- a/crates/forge/bin/cmd/install.rs +++ b/crates/forge/bin/cmd/install.rs @@ -21,8 +21,8 @@ static DEPENDENCY_VERSION_TAG_REGEX: Lazy = Lazy::new(|| Regex::new(r"^v?\d+(\.\d+)*$").unwrap()); /// CLI arguments for `forge install`. -#[derive(Debug, Clone, Parser)] -#[clap(override_usage = "forge install [OPTIONS] [DEPENDENCIES]... +#[derive(Clone, Debug, Parser)] +#[command(override_usage = "forge install [OPTIONS] [DEPENDENCIES]... forge install [OPTIONS] /@... forge install [OPTIONS] =/@... forge install [OPTIONS] ...")] @@ -46,10 +46,10 @@ pub struct InstallArgs { /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] pub root: Option, - #[clap(flatten)] + #[command(flatten)] opts: DependencyInstallOpts, } @@ -62,24 +62,24 @@ impl InstallArgs { } } -#[derive(Debug, Clone, Default, Copy, Parser)] +#[derive(Clone, Copy, Debug, Default, Parser)] pub struct DependencyInstallOpts { /// Perform shallow clones instead of deep ones. /// /// Improves performance and reduces disk usage, but prevents switching branches or tags. - #[clap(long)] + #[arg(long)] pub shallow: bool, /// Install without adding the dependency as a submodule. - #[clap(long)] + #[arg(long)] pub no_git: bool, /// Do not create a commit. - #[clap(long)] + #[arg(long)] pub no_commit: bool, /// Do not print any messages. - #[clap(short, long)] + #[arg(short, long)] pub quiet: bool, } @@ -103,9 +103,7 @@ impl DependencyInstallOpts { if self.install(config, Vec::new()).is_err() && !quiet { eprintln!( "{}", - Paint::yellow( - "Your project has missing dependencies that could not be installed." - ) + "Your project has missing dependencies that could not be installed.".yellow() ) } true @@ -124,10 +122,24 @@ impl DependencyInstallOpts { let libs = git.root.join(install_lib_dir); if dependencies.is_empty() && !self.no_git { - p_println!(!self.quiet => "Updating dependencies in {}", libs.display()); - // recursively fetch all submodules (without fetching latest) - git.submodule_update(false, false, false, true, Some(&libs))?; + // Use the root of the git repository to look for submodules. + let root = Git::root_of(git.root)?; + match git.has_submodules(Some(&root)) { + Ok(true) => { + p_println!(!quiet => "Updating dependencies in {}", libs.display()); + // recursively fetch all submodules (without fetching latest) + git.submodule_update(false, false, false, true, Some(&libs))?; + } + + Err(err) => { + warn!(?err, "Failed to check for submodules"); + } + _ => { + // no submodules, nothing to do + } + } } + fs::create_dir_all(&libs)?; let installer = Installer { git, no_commit }; @@ -179,7 +191,7 @@ impl DependencyInstallOpts { } if !quiet { - let mut msg = format!(" {} {}", Paint::green("Installed"), dep.name); + let mut msg = format!(" {} {}", "Installed".green(), dep.name); if let Some(tag) = dep.tag.or(installed_tag) { msg.push(' '); msg.push_str(tag.as_str()); @@ -222,6 +234,15 @@ impl Installer<'_> { // checkout the tag if necessary self.git_checkout(&dep, path, false)?; + trace!("updating dependency submodules recursively"); + self.git.root(path).submodule_update( + false, + false, + false, + true, + std::iter::empty::(), + )?; + // remove git artifacts fs::remove_dir_all(path.join(".git"))?; @@ -245,6 +266,15 @@ impl Installer<'_> { // checkout the tag if necessary self.git_checkout(&dep, path, true)?; + trace!("updating dependency submodules recursively"); + self.git.root(path).submodule_update( + false, + false, + false, + true, + std::iter::empty::(), + )?; + if !self.no_commit { self.git.add(Some(path))?; } @@ -301,10 +331,7 @@ impl Installer<'_> { let path = path.strip_prefix(self.git.root).unwrap(); trace!(?dep, url, ?path, "installing git submodule"); - self.git.submodule_add(true, url, path)?; - - trace!("initializing submodule recursively"); - self.git.submodule_update(false, false, false, true, Some(path)) + self.git.submodule_add(true, url, path) } fn git_checkout(self, dep: &Dependency, path: &Path, recurse: bool) -> Result { @@ -491,6 +518,7 @@ mod tests { use tempfile::tempdir; #[test] + #[ignore = "slow"] fn get_oz_tags() { let tmp = tempdir().unwrap(); let git = Git::new(tmp.path()); diff --git a/crates/forge/bin/cmd/mod.rs b/crates/forge/bin/cmd/mod.rs index 7b0c979efe6eb..c8d1dbb0e0064 100644 --- a/crates/forge/bin/cmd/mod.rs +++ b/crates/forge/bin/cmd/mod.rs @@ -21,11 +21,11 @@ //! use foundry_config::{figment::Figment, *}; //! //! // A new clap subcommand that accepts both `EvmArgs` and `BuildArgs` -//! #[derive(Debug, Clone, Parser)] +//! #[derive(Clone, Debug, Parser)] //! pub struct MyArgs { -//! #[clap(flatten)] +//! #[command(flatten)] //! evm_opts: EvmArgs, -//! #[clap(flatten)] +//! #[command(flatten)] //! opts: BuildArgs, //! } //! @@ -42,6 +42,7 @@ pub mod bind; pub mod build; pub mod cache; +pub mod clone; pub mod config; pub mod coverage; pub mod create; @@ -56,12 +57,9 @@ pub mod inspect; pub mod install; pub mod remappings; pub mod remove; -pub mod retry; -pub mod script; pub mod selectors; pub mod snapshot; pub mod test; pub mod tree; pub mod update; -pub mod verify; pub mod watch; diff --git a/crates/forge/bin/cmd/remappings.rs b/crates/forge/bin/cmd/remappings.rs index a1a5f903f3ff2..b33f3442cf9be 100644 --- a/crates/forge/bin/cmd/remappings.rs +++ b/crates/forge/bin/cmd/remappings.rs @@ -1,54 +1,48 @@ use clap::{Parser, ValueHint}; use eyre::Result; use foundry_cli::utils::LoadConfig; -use foundry_compilers::remappings::RelativeRemapping; use foundry_config::impl_figment_convert_basic; -use foundry_evm::hashbrown::HashMap; -use std::path::PathBuf; +use std::{collections::BTreeMap, path::PathBuf}; /// CLI arguments for `forge remappings`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct RemappingArgs { /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, /// Pretty-print the remappings, grouping each of them by context. - #[clap(long)] + #[arg(long)] pretty: bool, } impl_figment_convert_basic!(RemappingArgs); impl RemappingArgs { - // TODO: Do people use `forge remappings >> file`? pub fn run(self) -> Result<()> { let config = self.try_load_config_emit_warnings()?; if self.pretty { - let groups = config.remappings.into_iter().fold( - HashMap::new(), - |mut groups: HashMap, Vec>, remapping| { - groups.entry(remapping.context.clone()).or_default().push(remapping); - groups - }, - ); - for (group, remappings) in groups.into_iter() { + let mut groups = BTreeMap::<_, Vec<_>>::new(); + for remapping in config.remappings { + groups.entry(remapping.context.clone()).or_default().push(remapping); + } + for (group, remappings) in groups { if let Some(group) = group { println!("Context: {group}"); } else { println!("Global:"); } - for mut remapping in remappings.into_iter() { + for mut remapping in remappings { remapping.context = None; // avoid writing context twice println!("- {remapping}"); } println!(); } } else { - for remapping in config.remappings.into_iter() { + for remapping in config.remappings { println!("{remapping}"); } } diff --git a/crates/forge/bin/cmd/remove.rs b/crates/forge/bin/cmd/remove.rs index f5deb00b2e1fa..a4d62b9f77d88 100644 --- a/crates/forge/bin/cmd/remove.rs +++ b/crates/forge/bin/cmd/remove.rs @@ -8,20 +8,21 @@ use foundry_config::impl_figment_convert_basic; use std::path::PathBuf; /// CLI arguments for `forge remove`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct RemoveArgs { /// The dependencies you want to remove. + #[arg(required = true)] dependencies: Vec, /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, /// Override the up-to-date check. - #[clap(short, long)] + #[arg(short, long)] force: bool, } impl_figment_convert_basic!(RemoveArgs); diff --git a/crates/forge/bin/cmd/script/artifacts.rs b/crates/forge/bin/cmd/script/artifacts.rs deleted file mode 100644 index 0a2bd77dd90da..0000000000000 --- a/crates/forge/bin/cmd/script/artifacts.rs +++ /dev/null @@ -1,9 +0,0 @@ -use alloy_json_abi::JsonAbi; - -/// Bundles info of an artifact -pub struct ArtifactInfo<'a> { - pub contract_name: String, - pub contract_id: String, - pub abi: &'a JsonAbi, - pub code: &'a Vec, -} diff --git a/crates/forge/bin/cmd/script/broadcast.rs b/crates/forge/bin/cmd/script/broadcast.rs deleted file mode 100644 index 270552ec78d7f..0000000000000 --- a/crates/forge/bin/cmd/script/broadcast.rs +++ /dev/null @@ -1,668 +0,0 @@ -use super::{ - multi::MultiChainSequence, providers::ProvidersManager, receipts::clear_pendings, - sequence::ScriptSequence, transaction::TransactionWithMetadata, verify::VerifyBundle, *, -}; -use ethers_core::{types::TxHash, utils::format_units}; -use ethers_providers::{JsonRpcClient, Middleware, Provider}; -use ethers_signers::Signer; -use eyre::{bail, ContextCompat, Result, WrapErr}; -use foundry_cli::{ - init_progress, - opts::WalletSigner, - update_progress, - utils::{has_batch_support, has_different_gas_calc}, -}; -use foundry_common::{estimate_eip1559_fees, shell, try_get_http_provider, RetryProvider}; -use futures::StreamExt; -use std::{cmp::min, collections::HashSet, ops::Mul, sync::Arc}; - -impl ScriptArgs { - /// Sends the transactions which haven't been broadcasted yet. - pub async fn send_transactions( - &self, - deployment_sequence: &mut ScriptSequence, - fork_url: &str, - script_wallets: &[LocalWallet], - ) -> Result<()> { - let provider = Arc::new(try_get_http_provider(fork_url)?); - let already_broadcasted = deployment_sequence.receipts.len(); - - if already_broadcasted < deployment_sequence.transactions.len() { - let required_addresses: HashSet

= deployment_sequence - .typed_transactions() - .into_iter() - .skip(already_broadcasted) - .map(|(_, tx)| (*tx.from().expect("No sender for onchain transaction!")).to_alloy()) - .collect(); - - let (send_kind, chain) = if self.unlocked { - let chain = provider.get_chainid().await?; - let mut senders = HashSet::from([self - .evm_opts - .sender - .wrap_err("--sender must be set with --unlocked")?]); - // also take all additional senders that where set manually via broadcast - senders.extend( - deployment_sequence - .typed_transactions() - .iter() - .filter_map(|(_, tx)| tx.from().copied().map(|addr| addr.to_alloy())), - ); - (SendTransactionsKind::Unlocked(senders), chain.as_u64()) - } else { - let local_wallets = self - .wallets - .find_all(provider.clone(), required_addresses, script_wallets) - .await?; - let chain = local_wallets.values().last().wrap_err("Error accessing local wallet when trying to send onchain transaction, did you set a private key, mnemonic or keystore?")?.chain_id(); - (SendTransactionsKind::Raw(local_wallets), chain) - }; - - // We only wait for a transaction receipt before sending the next transaction, if there - // is more than one signer. There would be no way of assuring their order - // otherwise. Or if the chain does not support batched transactions (eg. Arbitrum). - let sequential_broadcast = - send_kind.signers_count() != 1 || self.slow || !has_batch_support(chain); - - // Make a one-time gas price estimation - let (gas_price, eip1559_fees) = { - match deployment_sequence.transactions.front().unwrap().typed_tx() { - TypedTransaction::Eip1559(_) => { - let fees = estimate_eip1559_fees(&provider, Some(chain)) - .await - .wrap_err("Failed to estimate EIP1559 fees. This chain might not support EIP1559, try adding --legacy to your command.")?; - - (None, Some(fees)) - } - _ => (provider.get_gas_price().await.ok(), None), - } - }; - - // Iterate through transactions, matching the `from` field with the associated - // wallet. Then send the transaction. Panics if we find a unknown `from` - let sequence = deployment_sequence - .transactions - .iter() - .skip(already_broadcasted) - .map(|tx_with_metadata| { - let tx = tx_with_metadata.typed_tx(); - let from = (*tx.from().expect("No sender for onchain transaction!")).to_alloy(); - - let kind = send_kind.for_sender(&from)?; - let is_fixed_gas_limit = tx_with_metadata.is_fixed_gas_limit; - - let mut tx = tx.clone(); - - tx.set_chain_id(chain); - - if let Some(gas_price) = self.with_gas_price { - tx.set_gas_price(gas_price.to_ethers()); - } else { - // fill gas price - match tx { - TypedTransaction::Eip1559(ref mut inner) => { - let eip1559_fees = - eip1559_fees.expect("Could not get eip1559 fee estimation."); - if let Some(priority_gas_price) = self.priority_gas_price { - inner.max_priority_fee_per_gas = - Some(priority_gas_price.to_ethers()); - } else { - inner.max_priority_fee_per_gas = Some(eip1559_fees.1); - } - inner.max_fee_per_gas = Some(eip1559_fees.0); - } - _ => { - tx.set_gas_price(gas_price.expect("Could not get gas_price.")); - } - } - } - - Ok((tx, kind, is_fixed_gas_limit)) - }) - .collect::>>()?; - - let pb = init_progress!(deployment_sequence.transactions, "txes"); - - // We send transactions and wait for receipts in batches of 100, since some networks - // cannot handle more than that. - let batch_size = 100; - let mut index = 0; - - for (batch_number, batch) in sequence.chunks(batch_size).map(|f| f.to_vec()).enumerate() - { - let mut pending_transactions = vec![]; - - shell::println(format!( - "##\nSending transactions [{} - {}].", - batch_number * batch_size, - batch_number * batch_size + min(batch_size, batch.len()) - 1 - ))?; - for (tx, kind, is_fixed_gas_limit) in batch.into_iter() { - let tx_hash = self.send_transaction( - provider.clone(), - tx, - kind, - sequential_broadcast, - fork_url, - is_fixed_gas_limit, - ); - - if sequential_broadcast { - let tx_hash = tx_hash.await?; - deployment_sequence.add_pending(index, tx_hash.to_alloy()); - - update_progress!(pb, (index + already_broadcasted)); - index += 1; - - clear_pendings( - provider.clone(), - deployment_sequence, - Some(vec![tx_hash.to_alloy()]), - ) - .await?; - } else { - pending_transactions.push(tx_hash); - } - } - - if !pending_transactions.is_empty() { - let mut buffer = futures::stream::iter(pending_transactions).buffered(7); - - while let Some(tx_hash) = buffer.next().await { - let tx_hash = tx_hash?; - deployment_sequence.add_pending(index, tx_hash.to_alloy()); - - update_progress!(pb, (index + already_broadcasted)); - index += 1; - } - - // Checkpoint save - deployment_sequence.save()?; - - if !sequential_broadcast { - shell::println("##\nWaiting for receipts.")?; - clear_pendings(provider.clone(), deployment_sequence, None).await?; - } - } - - // Checkpoint save - deployment_sequence.save()?; - } - } - - shell::println("\n\n==========================")?; - shell::println("\nONCHAIN EXECUTION COMPLETE & SUCCESSFUL.")?; - - let (total_gas, total_gas_price, total_paid) = deployment_sequence.receipts.iter().fold( - (U256::ZERO, U256::ZERO, U256::ZERO), - |acc, receipt| { - let gas_used = receipt.gas_used.unwrap_or_default().to_alloy(); - let gas_price = receipt.effective_gas_price.unwrap_or_default().to_alloy(); - (acc.0 + gas_used, acc.1 + gas_price, acc.2 + gas_used.mul(gas_price)) - }, - ); - let paid = format_units(total_paid.to_ethers(), 18).unwrap_or_else(|_| "N/A".to_string()); - let avg_gas_price = - format_units(total_gas_price.to_ethers() / deployment_sequence.receipts.len(), 9) - .unwrap_or_else(|_| "N/A".to_string()); - shell::println(format!( - "Total Paid: {} ETH ({} gas * avg {} gwei)", - paid.trim_end_matches('0'), - total_gas, - avg_gas_price.trim_end_matches('0').trim_end_matches('.') - ))?; - - Ok(()) - } - - async fn send_transaction( - &self, - provider: Arc, - mut tx: TypedTransaction, - kind: SendTransactionKind<'_>, - sequential_broadcast: bool, - fork_url: &str, - is_fixed_gas_limit: bool, - ) -> Result { - let from = tx.from().expect("no sender"); - - if sequential_broadcast { - let nonce = forge::next_nonce((*from).to_alloy(), fork_url, None) - .await - .map_err(|_| eyre::eyre!("Not able to query the EOA nonce."))?; - - let tx_nonce = tx.nonce().expect("no nonce"); - if let Ok(tx_nonce) = u64::try_from(tx_nonce.to_alloy()) { - if nonce != tx_nonce { - bail!("EOA nonce changed unexpectedly while sending transactions. Expected {tx_nonce} got {nonce} from provider.") - } - } - } - - match kind { - SendTransactionKind::Unlocked(addr) => { - debug!("sending transaction from unlocked account {:?}: {:?}", addr, tx); - - // Chains which use `eth_estimateGas` are being sent sequentially and require their - // gas to be re-estimated right before broadcasting. - if !is_fixed_gas_limit && - (has_different_gas_calc(provider.get_chainid().await?.as_u64()) || - self.skip_simulation) - { - self.estimate_gas(&mut tx, &provider).await?; - } - - // Submit the transaction - let pending = provider.send_transaction(tx, None).await?; - - Ok(pending.tx_hash()) - } - SendTransactionKind::Raw(signer) => self.broadcast(provider, signer, tx).await, - } - } - - /// Executes the created transactions, and if no error has occurred, broadcasts - /// them. - pub async fn handle_broadcastable_transactions( - &self, - mut result: ScriptResult, - libraries: Libraries, - decoder: &CallTraceDecoder, - mut script_config: ScriptConfig, - verify: VerifyBundle, - ) -> Result<()> { - if let Some(txs) = result.transactions.take() { - script_config.collect_rpcs(&txs); - script_config.check_multi_chain_constraints(&libraries)?; - script_config.check_shanghai_support().await?; - - if !script_config.missing_rpc { - trace!(target: "script", "creating deployments"); - - let mut deployments = self - .create_script_sequences( - txs, - &result, - &mut script_config, - decoder, - &verify.known_contracts, - ) - .await?; - - if script_config.has_multiple_rpcs() { - trace!(target: "script", "broadcasting multi chain deployment"); - - let multi = MultiChainSequence::new( - deployments.clone(), - &self.sig, - script_config.target_contract(), - &script_config.config.broadcast, - self.broadcast, - )?; - - if self.broadcast { - self.multi_chain_deployment( - multi, - libraries, - &script_config.config, - result.script_wallets, - verify, - ) - .await?; - } - } else if self.broadcast { - self.single_deployment( - deployments.first_mut().expect("to be set."), - script_config, - libraries, - result, - verify, - ) - .await?; - } - - if !self.broadcast { - shell::println("\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.")?; - } - } else { - shell::println("\nIf you wish to simulate on-chain transactions pass a RPC URL.")?; - } - } - Ok(()) - } - - /// Broadcasts a single chain script. - async fn single_deployment( - &self, - deployment_sequence: &mut ScriptSequence, - script_config: ScriptConfig, - libraries: Libraries, - result: ScriptResult, - verify: VerifyBundle, - ) -> Result<()> { - trace!(target: "script", "broadcasting single chain deployment"); - - if self.verify { - deployment_sequence.verify_preflight_check(&script_config.config, &verify)?; - } - - let rpc = script_config.total_rpcs.into_iter().next().expect("exists; qed"); - - deployment_sequence.add_libraries(libraries); - - self.send_transactions(deployment_sequence, &rpc, &result.script_wallets).await?; - - if self.verify { - return deployment_sequence.verify_contracts(&script_config.config, verify).await - } - Ok(()) - } - - /// Given the collected transactions it creates a list of [`ScriptSequence`]. List length will - /// be higher than 1, if we're dealing with a multi chain deployment. - /// - /// If `--skip-simulation` is not passed, it will make an onchain simulation of the transactions - /// before adding them to [`ScriptSequence`]. - async fn create_script_sequences( - &self, - txs: BroadcastableTransactions, - script_result: &ScriptResult, - script_config: &mut ScriptConfig, - decoder: &CallTraceDecoder, - known_contracts: &ContractsByArtifact, - ) -> Result> { - if !txs.is_empty() { - let gas_filled_txs = self - .fills_transactions_with_gas(txs, script_config, decoder, known_contracts) - .await?; - - let returns = self.get_returns(&*script_config, &script_result.returned)?; - - return self - .bundle_transactions( - gas_filled_txs, - &script_config.target_contract().clone(), - &mut script_config.config, - returns, - ) - .await - } else if self.broadcast { - eyre::bail!("No onchain transactions generated in script"); - } - - Ok(vec![]) - } - - /// Takes the collected transactions and executes them locally before converting them to - /// [`TransactionWithMetadata`] with the appropriate gas execution estimation. If - /// `--skip-simulation` is passed, then it will skip the execution. - async fn fills_transactions_with_gas( - &self, - txs: BroadcastableTransactions, - script_config: &ScriptConfig, - decoder: &CallTraceDecoder, - known_contracts: &ContractsByArtifact, - ) -> Result> { - let gas_filled_txs = if self.skip_simulation { - shell::println("\nSKIPPING ON CHAIN SIMULATION.")?; - txs.into_iter() - .map(|btx| { - let mut tx = TransactionWithMetadata::from_typed_transaction(btx.transaction); - tx.rpc = btx.rpc; - tx - }) - .collect() - } else { - self.onchain_simulation( - txs, - script_config, - decoder, - known_contracts, - ) - .await - .wrap_err("\nTransaction failed when running the on-chain simulation. Check the trace above for more information.")? - }; - Ok(gas_filled_txs) - } - - /// Returns all transactions of the [`TransactionWithMetadata`] type in a list of - /// [`ScriptSequence`]. List length will be higher than 1, if we're dealing with a multi - /// chain deployment. - /// - /// Each transaction will be added with the correct transaction type and gas estimation. - async fn bundle_transactions( - &self, - transactions: VecDeque, - target: &ArtifactId, - config: &mut Config, - returns: HashMap, - ) -> Result> { - // User might be using both "in-code" forks and `--fork-url`. - let last_rpc = &transactions.back().expect("exists; qed").rpc; - let is_multi_deployment = transactions.iter().any(|tx| &tx.rpc != last_rpc); - - let mut total_gas_per_rpc: HashMap = HashMap::new(); - - // Batches sequence of transactions from different rpcs. - let mut new_sequence = VecDeque::new(); - let mut manager = ProvidersManager::default(); - let mut deployments = vec![]; - - // Config is used to initialize the sequence chain, so we need to change when handling a new - // sequence. This makes sure we don't lose the original value. - let original_config_chain = config.chain; - - // Peeking is used to check if the next rpc url is different. If so, it creates a - // [`ScriptSequence`] from all the collected transactions up to this point. - let mut txes_iter = transactions.into_iter().peekable(); - - while let Some(mut tx) = txes_iter.next() { - let tx_rpc = match tx.rpc.clone() { - Some(rpc) => rpc, - None => { - let rpc = self.evm_opts.ensure_fork_url()?.clone(); - // Fills the RPC inside the transaction, if missing one. - tx.rpc = Some(rpc.clone()); - rpc - } - }; - - let provider_info = manager.get_or_init_provider(&tx_rpc, self.legacy).await?; - - // Handles chain specific requirements. - tx.change_type(provider_info.is_legacy); - tx.transaction.set_chain_id(provider_info.chain); - - if !self.skip_simulation { - let typed_tx = tx.typed_tx_mut(); - - if has_different_gas_calc(provider_info.chain) { - trace!("estimating with different gas calculation"); - let gas = *typed_tx.gas().expect("gas is set by simulation."); - - // We are trying to show the user an estimation of the total gas usage. - // - // However, some transactions might depend on previous ones. For - // example, tx1 might deploy a contract that tx2 uses. That - // will result in the following `estimate_gas` call to fail, - // since tx1 hasn't been broadcasted yet. - // - // Not exiting here will not be a problem when actually broadcasting, because - // for chains where `has_different_gas_calc` returns true, - // we await each transaction before broadcasting the next - // one. - if let Err(err) = self.estimate_gas(typed_tx, &provider_info.provider).await { - trace!("gas estimation failed: {err}"); - - // Restore gas value, since `estimate_gas` will remove it. - typed_tx.set_gas(gas); - } - } - - let total_gas = total_gas_per_rpc.entry(tx_rpc.clone()).or_insert(U256::ZERO); - *total_gas += (*typed_tx.gas().expect("gas is set")).to_alloy(); - } - - new_sequence.push_back(tx); - // We only create a [`ScriptSequence`] object when we collect all the rpc related - // transactions. - if let Some(next_tx) = txes_iter.peek() { - if next_tx.rpc == Some(tx_rpc) { - continue - } - } - - config.chain = Some(provider_info.chain.into()); - let sequence = ScriptSequence::new( - new_sequence, - returns.clone(), - &self.sig, - target, - config, - self.broadcast, - is_multi_deployment, - )?; - - deployments.push(sequence); - - new_sequence = VecDeque::new(); - } - - // Restore previous config chain. - config.chain = original_config_chain; - - if !self.skip_simulation { - // Present gas information on a per RPC basis. - for (rpc, total_gas) in total_gas_per_rpc { - let provider_info = manager.get(&rpc).expect("provider is set."); - - // We don't store it in the transactions, since we want the most updated value. - // Right before broadcasting. - let per_gas = if let Some(gas_price) = self.with_gas_price { - gas_price - } else { - provider_info.gas_price()? - }; - - shell::println("\n==========================")?; - shell::println(format!("\nChain {}", provider_info.chain))?; - - shell::println(format!( - "\nEstimated gas price: {} gwei", - format_units(per_gas.to_ethers(), 9) - .unwrap_or_else(|_| "[Could not calculate]".to_string()) - .trim_end_matches('0') - .trim_end_matches('.') - ))?; - shell::println(format!("\nEstimated total gas used for script: {total_gas}"))?; - shell::println(format!( - "\nEstimated amount required: {} ETH", - format_units(total_gas.saturating_mul(per_gas).to_ethers(), 18) - .unwrap_or_else(|_| "[Could not calculate]".to_string()) - .trim_end_matches('0') - ))?; - shell::println("\n==========================")?; - } - } - Ok(deployments) - } - - /// Uses the signer to submit a transaction to the network. If it fails, it tries to retrieve - /// the transaction hash that can be used on a later run with `--resume`. - async fn broadcast( - &self, - provider: Arc, - signer: &WalletSigner, - mut legacy_or_1559: TypedTransaction, - ) -> Result { - debug!("sending transaction: {:?}", legacy_or_1559); - - // Chains which use `eth_estimateGas` are being sent sequentially and require their gas - // to be re-estimated right before broadcasting. - if has_different_gas_calc(signer.chain_id()) || self.skip_simulation { - // if already set, some RPC endpoints might simply return the gas value that is - // already set in the request and omit the estimate altogether, so - // we remove it here - let _ = legacy_or_1559.gas_mut().take(); - - self.estimate_gas(&mut legacy_or_1559, &provider).await?; - } - - // Signing manually so we skip `fill_transaction` and its `eth_createAccessList` - // request. - let signature = signer - .sign_transaction(&legacy_or_1559) - .await - .wrap_err("Failed to sign transaction")?; - - // Submit the raw transaction - let pending = provider.send_raw_transaction(legacy_or_1559.rlp_signed(&signature)).await?; - - Ok(pending.tx_hash()) - } - - async fn estimate_gas(&self, tx: &mut TypedTransaction, provider: &Provider) -> Result<()> - where - T: JsonRpcClient, - { - // if already set, some RPC endpoints might simply return the gas value that is already - // set in the request and omit the estimate altogether, so we remove it here - let _ = tx.gas_mut().take(); - - tx.set_gas( - provider - .estimate_gas(tx, None) - .await - .wrap_err_with(|| format!("Failed to estimate gas for tx: {:?}", tx.sighash()))? * - self.gas_estimate_multiplier / - 100, - ); - Ok(()) - } -} - -/// How to send a single transaction -#[derive(Clone)] -enum SendTransactionKind<'a> { - Unlocked(Address), - Raw(&'a WalletSigner), -} - -/// Represents how to send _all_ transactions -enum SendTransactionsKind { - /// Send via `eth_sendTransaction` and rely on the `from` address being unlocked. - Unlocked(HashSet
), - /// Send a signed transaction via `eth_sendRawTransaction` - Raw(HashMap), -} - -impl SendTransactionsKind { - /// Returns the [`SendTransactionKind`] for the given address - /// - /// Returns an error if no matching signer is found or the address is not unlocked - fn for_sender(&self, addr: &Address) -> Result> { - match self { - SendTransactionsKind::Unlocked(unlocked) => { - if !unlocked.contains(addr) { - bail!("Sender address {:?} is not unlocked", addr) - } - Ok(SendTransactionKind::Unlocked(*addr)) - } - SendTransactionsKind::Raw(wallets) => { - if let Some(wallet) = wallets.get(addr) { - Ok(SendTransactionKind::Raw(wallet)) - } else { - bail!("No matching signer for {:?} found", addr) - } - } - } - } - - /// How many signers are set - fn signers_count(&self) -> usize { - match self { - SendTransactionsKind::Unlocked(addr) => addr.len(), - SendTransactionsKind::Raw(signers) => signers.len(), - } - } -} diff --git a/crates/forge/bin/cmd/script/build.rs b/crates/forge/bin/cmd/script/build.rs deleted file mode 100644 index 1ac4b63e3d37f..0000000000000 --- a/crates/forge/bin/cmd/script/build.rs +++ /dev/null @@ -1,291 +0,0 @@ -use super::*; -use alloy_primitives::{Address, Bytes}; -use eyre::{Context, ContextCompat, Result}; -use forge::link::{link_with_nonce_or_address, PostLinkInput, ResolvedDependency}; -use foundry_cli::utils::get_cached_entry_by_name; -use foundry_common::{ - compact_to_contract, - compile::{self, ContractSources}, - fs, -}; -use foundry_compilers::{ - artifacts::{CompactContractBytecode, ContractBytecode, ContractBytecodeSome, Libraries}, - cache::SolFilesCache, - contracts::ArtifactContracts, - info::ContractInfo, - ArtifactId, Project, ProjectCompileOutput, -}; -use std::{collections::BTreeMap, str::FromStr}; - -impl ScriptArgs { - /// Compiles the file or project and the verify metadata. - pub fn compile(&mut self, script_config: &mut ScriptConfig) -> Result { - trace!(target: "script", "compiling script"); - - self.build(script_config) - } - - /// Compiles the file with auto-detection and compiler params. - pub fn build(&mut self, script_config: &mut ScriptConfig) -> Result { - let (project, output) = self.get_project_and_output(script_config)?; - let output = output.with_stripped_file_prefixes(project.root()); - - let mut sources: ContractSources = Default::default(); - - let contracts = output - .into_artifacts() - .map(|(id, artifact)| -> Result<_> { - // Sources are only required for the debugger, but it *might* mean that there's - // something wrong with the build and/or artifacts. - if let Some(source) = artifact.source_file() { - let abs_path = source - .ast - .ok_or_else(|| eyre::eyre!("Source from artifact has no AST."))? - .absolute_path; - let source_code = fs::read_to_string(abs_path).wrap_err_with(|| { - format!("Failed to read artifact source file for `{}`", id.identifier()) - })?; - let contract = artifact.clone().into_contract_bytecode(); - let source_contract = compact_to_contract(contract)?; - sources - .0 - .entry(id.clone().name) - .or_default() - .insert(source.id, (source_code, source_contract)); - } else { - warn!("source not found for artifact={:?}", id); - } - Ok((id, artifact)) - }) - .collect::>()?; - - let mut output = self.link( - project, - contracts, - script_config.config.parsed_libraries()?, - script_config.evm_opts.sender, - script_config.sender_nonce, - )?; - - output.sources = sources; - script_config.target_contract = Some(output.target.clone()); - - Ok(output) - } - - pub fn link( - &self, - project: Project, - contracts: ArtifactContracts, - libraries_addresses: Libraries, - sender: Address, - nonce: u64, - ) -> Result { - let mut run_dependencies = vec![]; - let mut contract = CompactContractBytecode::default(); - let mut highlevel_known_contracts = BTreeMap::new(); - - let mut target_fname = dunce::canonicalize(&self.path) - .wrap_err("Couldn't convert contract path to absolute path.")? - .strip_prefix(project.root()) - .wrap_err("Couldn't strip project root from contract path.")? - .to_str() - .wrap_err("Bad path to string.")? - .to_string(); - - let no_target_name = if let Some(target_name) = &self.target_contract { - target_fname = target_fname + ":" + target_name; - false - } else { - true - }; - - let mut extra_info = ExtraLinkingInfo { - no_target_name, - target_fname: target_fname.clone(), - contract: &mut contract, - dependencies: &mut run_dependencies, - matched: false, - target_id: None, - }; - - // link_with_nonce_or_address expects absolute paths - let mut libs = libraries_addresses.clone(); - for (file, libraries) in libraries_addresses.libs.iter() { - if file.is_relative() { - let mut absolute_path = project.root().clone(); - absolute_path.push(file); - libs.libs.insert(absolute_path, libraries.clone()); - } - } - - link_with_nonce_or_address( - contracts.clone(), - &mut highlevel_known_contracts, - libs, - sender, - nonce, - &mut extra_info, - |post_link_input| { - let PostLinkInput { - contract, - known_contracts: highlevel_known_contracts, - id, - extra, - dependencies, - } = post_link_input; - - fn unique_deps(deps: Vec) -> Vec<(String, Bytes)> { - let mut filtered = Vec::new(); - let mut seen = HashSet::new(); - for dep in deps { - if !seen.insert(dep.id.clone()) { - continue - } - filtered.push((dep.id, dep.bytecode)); - } - - filtered - } - - // if it's the target contract, grab the info - if extra.no_target_name { - // Match artifact source, and ignore interfaces - if id.source == std::path::Path::new(&extra.target_fname) && - contract.bytecode.as_ref().map_or(false, |b| b.object.bytes_len() > 0) - { - if extra.matched { - eyre::bail!("Multiple contracts in the target path. Please specify the contract name with `--tc ContractName`") - } - *extra.dependencies = unique_deps(dependencies); - *extra.contract = contract.clone(); - extra.matched = true; - extra.target_id = Some(id.clone()); - } - } else { - let (path, name) = extra - .target_fname - .rsplit_once(':') - .expect("The target specifier is malformed."); - let path = std::path::Path::new(path); - if path == id.source && name == id.name { - *extra.dependencies = unique_deps(dependencies); - *extra.contract = contract.clone(); - extra.matched = true; - extra.target_id = Some(id.clone()); - } - } - - if let Ok(tc) = ContractBytecode::from(contract).try_into() { - highlevel_known_contracts.insert(id, tc); - } - - Ok(()) - }, - project.root(), - )?; - - let target = extra_info - .target_id - .ok_or_else(|| eyre::eyre!("Could not find target contract: {}", target_fname))?; - - let (new_libraries, predeploy_libraries): (Vec<_>, Vec<_>) = - run_dependencies.into_iter().unzip(); - - // Merge with user provided libraries - let mut new_libraries = Libraries::parse(&new_libraries)?; - for (file, libraries) in libraries_addresses.libs.into_iter() { - new_libraries.libs.entry(file).or_default().extend(libraries) - } - - Ok(BuildOutput { - target, - contract, - known_contracts: contracts, - highlevel_known_contracts: ArtifactContracts(highlevel_known_contracts), - predeploy_libraries, - sources: Default::default(), - project, - libraries: new_libraries, - }) - } - - pub fn get_project_and_output( - &mut self, - script_config: &ScriptConfig, - ) -> Result<(Project, ProjectCompileOutput)> { - let project = script_config.config.project()?; - - let filters = self.opts.skip.clone().unwrap_or_default(); - // We received a valid file path. - // If this file does not exist, `dunce::canonicalize` will - // result in an error and it will be handled below. - if let Ok(target_contract) = dunce::canonicalize(&self.path) { - let output = compile::compile_target_with_filter( - &target_contract, - &project, - self.opts.args.silent, - self.verify, - filters, - )?; - return Ok((project, output)) - } - - if !project.paths.has_input_files() { - eyre::bail!("The project doesn't have any input files. Make sure the `script` directory is configured properly in foundry.toml. Otherwise, provide the path to the file.") - } - - let contract = ContractInfo::from_str(&self.path)?; - self.target_contract = Some(contract.name.clone()); - - // We received `contract_path:contract_name` - if let Some(path) = contract.path { - let path = - dunce::canonicalize(path).wrap_err("Could not canonicalize the target path")?; - let output = compile::compile_target_with_filter( - &path, - &project, - self.opts.args.silent, - self.verify, - filters, - )?; - self.path = path.to_string_lossy().to_string(); - return Ok((project, output)) - } - - // We received `contract_name`, and need to find its file path. - let output = if self.opts.args.silent { - compile::suppress_compile(&project) - } else { - compile::compile(&project, false, false) - }?; - let cache = - SolFilesCache::read_joined(&project.paths).wrap_err("Could not open compiler cache")?; - - let (path, _) = get_cached_entry_by_name(&cache, &contract.name) - .wrap_err("Could not find target contract in cache")?; - self.path = path.to_string_lossy().to_string(); - - Ok((project, output)) - } -} - -struct ExtraLinkingInfo<'a> { - no_target_name: bool, - target_fname: String, - contract: &'a mut CompactContractBytecode, - dependencies: &'a mut Vec<(String, Bytes)>, - matched: bool, - target_id: Option, -} - -pub struct BuildOutput { - pub project: Project, - pub target: ArtifactId, - pub contract: CompactContractBytecode, - pub known_contracts: ArtifactContracts, - pub highlevel_known_contracts: ArtifactContracts, - pub libraries: Libraries, - pub predeploy_libraries: Vec, - pub sources: ContractSources, -} diff --git a/crates/forge/bin/cmd/script/cmd.rs b/crates/forge/bin/cmd/script/cmd.rs deleted file mode 100644 index f2a8d21f14472..0000000000000 --- a/crates/forge/bin/cmd/script/cmd.rs +++ /dev/null @@ -1,361 +0,0 @@ -use super::{multi::MultiChainSequence, sequence::ScriptSequence, verify::VerifyBundle, *}; -use alloy_primitives::Bytes; -use ethers_core::types::transaction::eip2718::TypedTransaction; -use ethers_providers::Middleware; -use ethers_signers::Signer; -use eyre::Result; -use foundry_cli::utils::LoadConfig; -use foundry_common::{contracts::flatten_contracts, try_get_http_provider, types::ToAlloy}; -use foundry_debugger::DebuggerBuilder; -use std::sync::Arc; - -/// Helper alias type for the collection of data changed due to the new sender. -type NewSenderChanges = (CallTraceDecoder, Libraries, ArtifactContracts); - -impl ScriptArgs { - /// Executes the script - pub async fn run_script(mut self) -> Result<()> { - trace!(target: "script", "executing script command"); - - let (config, evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; - let mut script_config = ScriptConfig { - // dapptools compatibility - sender_nonce: 1, - config, - evm_opts, - debug: self.debug, - ..Default::default() - }; - - self.maybe_load_private_key(&mut script_config)?; - - if let Some(ref fork_url) = script_config.evm_opts.fork_url { - // when forking, override the sender's nonce to the onchain value - script_config.sender_nonce = - forge::next_nonce(script_config.evm_opts.sender, fork_url, None).await? - } else { - // if not forking, then ignore any pre-deployed library addresses - script_config.config.libraries = Default::default(); - } - - let build_output = self.compile(&mut script_config)?; - - let mut verify = VerifyBundle::new( - &build_output.project, - &script_config.config, - flatten_contracts(&build_output.highlevel_known_contracts, false), - self.retry, - self.verifier.clone(), - ); - - let BuildOutput { - project, - contract, - mut highlevel_known_contracts, - predeploy_libraries, - known_contracts: default_known_contracts, - sources, - mut libraries, - .. - } = build_output; - - // Execute once with default sender. - let sender = script_config.evm_opts.sender; - - // We need to execute the script even if just resuming, in case we need to collect private - // keys from the execution. - let mut result = - self.execute(&mut script_config, contract, sender, &predeploy_libraries).await?; - - if self.resume || (self.verify && !self.broadcast) { - return self - .resume_deployment( - script_config, - project, - default_known_contracts, - libraries, - result, - verify, - ) - .await - } - - let known_contracts = flatten_contracts(&highlevel_known_contracts, true); - let mut decoder = self.decode_traces(&script_config, &mut result, &known_contracts)?; - - if self.debug { - let mut debugger = DebuggerBuilder::new() - .debug_arenas(result.debug.as_deref().unwrap_or_default()) - .decoder(&decoder) - .sources(sources) - .breakpoints(result.breakpoints.clone()) - .build()?; - debugger.try_run()?; - } - - if let Some((new_traces, updated_libraries, updated_contracts)) = self - .maybe_prepare_libraries( - &mut script_config, - project, - default_known_contracts, - predeploy_libraries, - &mut result, - ) - .await? - { - decoder = new_traces; - highlevel_known_contracts = updated_contracts; - libraries = updated_libraries; - } - - if self.json { - self.show_json(&script_config, &result)?; - } else { - self.show_traces(&script_config, &decoder, &mut result).await?; - } - - verify.known_contracts = flatten_contracts(&highlevel_known_contracts, false); - self.check_contract_sizes(&result, &highlevel_known_contracts)?; - - self.handle_broadcastable_transactions(result, libraries, &decoder, script_config, verify) - .await - } - - // In case there are libraries to be deployed, it makes sure that these are added to the list of - // broadcastable transactions with the appropriate sender. - async fn maybe_prepare_libraries( - &mut self, - script_config: &mut ScriptConfig, - project: Project, - default_known_contracts: ArtifactContracts, - predeploy_libraries: Vec, - result: &mut ScriptResult, - ) -> Result> { - if let Some(new_sender) = self.maybe_new_sender( - &script_config.evm_opts, - result.transactions.as_ref(), - &predeploy_libraries, - )? { - // We have a new sender, so we need to relink all the predeployed libraries. - let (libraries, highlevel_known_contracts) = self - .rerun_with_new_deployer( - project, - script_config, - new_sender, - result, - default_known_contracts, - ) - .await?; - - // redo traces for the new addresses - let new_traces = self.decode_traces( - &*script_config, - result, - &flatten_contracts(&highlevel_known_contracts, true), - )?; - - return Ok(Some((new_traces, libraries, highlevel_known_contracts))) - } - - // Add predeploy libraries to the list of broadcastable transactions. - let mut lib_deploy = self.create_deploy_transactions( - script_config.evm_opts.sender, - script_config.sender_nonce, - &predeploy_libraries, - &script_config.evm_opts.fork_url, - ); - - if let Some(txs) = &mut result.transactions { - for tx in txs.iter() { - lib_deploy.push_back(BroadcastableTransaction { - rpc: tx.rpc.clone(), - transaction: TypedTransaction::Legacy(tx.transaction.clone().into()), - }); - } - *txs = lib_deploy; - } - - Ok(None) - } - - /// Resumes the deployment and/or verification of the script. - async fn resume_deployment( - &mut self, - script_config: ScriptConfig, - project: Project, - default_known_contracts: ArtifactContracts, - libraries: Libraries, - result: ScriptResult, - verify: VerifyBundle, - ) -> Result<()> { - if self.multi { - return self - .multi_chain_deployment( - MultiChainSequence::load( - &script_config.config.broadcast, - &self.sig, - script_config.target_contract(), - )?, - libraries, - &script_config.config, - result.script_wallets, - verify, - ) - .await - } - self.resume_single_deployment( - script_config, - project, - default_known_contracts, - result, - verify, - ) - .await - .map_err(|err| { - eyre::eyre!("{err}\n\nIf you were trying to resume or verify a multi chain deployment, add `--multi` to your command invocation.") - }) - } - - /// Resumes the deployment and/or verification of a single RPC script. - async fn resume_single_deployment( - &mut self, - script_config: ScriptConfig, - project: Project, - default_known_contracts: ArtifactContracts, - result: ScriptResult, - mut verify: VerifyBundle, - ) -> Result<()> { - trace!(target: "script", "resuming single deployment"); - - let fork_url = script_config - .evm_opts - .fork_url - .as_deref() - .ok_or_else(|| eyre::eyre!("Missing `--fork-url` field."))?; - let provider = Arc::new(try_get_http_provider(fork_url)?); - - let chain = provider.get_chainid().await?.as_u64(); - verify.set_chain(&script_config.config, chain.into()); - - let broadcasted = self.broadcast || self.resume; - let mut deployment_sequence = match ScriptSequence::load( - &script_config.config, - &self.sig, - script_config.target_contract(), - chain, - broadcasted, - ) { - Ok(seq) => seq, - // If the script was simulated, but there was no attempt to broadcast yet, - // try to read the script sequence from the `dry-run/` folder - Err(_) if broadcasted => ScriptSequence::load( - &script_config.config, - &self.sig, - script_config.target_contract(), - chain, - false, - )?, - Err(err) => eyre::bail!(err), - }; - - if self.verify { - deployment_sequence.verify_preflight_check(&script_config.config, &verify)?; - } - - receipts::wait_for_pending(provider, &mut deployment_sequence).await?; - - if self.resume { - self.send_transactions(&mut deployment_sequence, fork_url, &result.script_wallets) - .await?; - } - - if self.verify { - // We might have predeployed libraries from the broadcasting, so we need to - // relink the contracts with them, since their mapping is - // not included in the solc cache files. - let BuildOutput { highlevel_known_contracts, .. } = self.link( - project, - default_known_contracts, - Libraries::parse(&deployment_sequence.libraries)?, - script_config.config.sender, // irrelevant, since we're not creating any - 0, // irrelevant, since we're not creating any - )?; - - verify.known_contracts = flatten_contracts(&highlevel_known_contracts, false); - - deployment_sequence.verify_contracts(&script_config.config, verify).await?; - } - - Ok(()) - } - - /// Reruns the execution with a new sender and relinks the libraries accordingly - async fn rerun_with_new_deployer( - &mut self, - project: Project, - script_config: &mut ScriptConfig, - new_sender: Address, - first_run_result: &mut ScriptResult, - default_known_contracts: ArtifactContracts, - ) -> Result<(Libraries, ArtifactContracts)> { - // if we had a new sender that requires relinking, we need to - // get the nonce mainnet for accurate addresses for predeploy libs - let nonce = forge::next_nonce( - new_sender, - script_config.evm_opts.fork_url.as_ref().ok_or_else(|| { - eyre::eyre!("You must provide an RPC URL (see --fork-url) when broadcasting.") - })?, - None, - ) - .await?; - script_config.sender_nonce = nonce; - - let BuildOutput { - libraries, contract, highlevel_known_contracts, predeploy_libraries, .. - } = self.link( - project, - default_known_contracts, - script_config.config.parsed_libraries()?, - new_sender, - nonce, - )?; - - let mut txs = self.create_deploy_transactions( - new_sender, - nonce, - &predeploy_libraries, - &script_config.evm_opts.fork_url, - ); - - let result = - self.execute(script_config, contract, new_sender, &predeploy_libraries).await?; - - if let Some(new_txs) = &result.transactions { - for new_tx in new_txs.iter() { - txs.push_back(BroadcastableTransaction { - rpc: new_tx.rpc.clone(), - transaction: TypedTransaction::Legacy(new_tx.transaction.clone().into()), - }); - } - } - - *first_run_result = result; - first_run_result.transactions = Some(txs); - - Ok((libraries, highlevel_known_contracts)) - } - - /// In case the user has loaded *only* one private-key, we can assume that he's using it as the - /// `--sender` - fn maybe_load_private_key(&mut self, script_config: &mut ScriptConfig) -> Result<()> { - if let Some(ref private_key) = self.wallets.private_key { - self.wallets.private_keys = Some(vec![private_key.clone()]); - } - if let Some(wallets) = self.wallets.private_keys()? { - if wallets.len() == 1 { - script_config.evm_opts.sender = wallets.first().unwrap().address().to_alloy() - } - } - Ok(()) - } -} diff --git a/crates/forge/bin/cmd/script/executor.rs b/crates/forge/bin/cmd/script/executor.rs deleted file mode 100644 index fec89e62a3a0f..0000000000000 --- a/crates/forge/bin/cmd/script/executor.rs +++ /dev/null @@ -1,328 +0,0 @@ -use super::{ - artifacts::ArtifactInfo, - runner::SimulationStage, - transaction::{AdditionalContract, TransactionWithMetadata}, - *, -}; -use alloy_primitives::{Address, Bytes, U256}; -use ethers_core::types::transaction::eip2718::TypedTransaction; -use eyre::Result; -use forge::{ - backend::Backend, - executors::ExecutorBuilder, - inspectors::{cheatcodes::BroadcastableTransactions, CheatsConfig}, - traces::{CallTraceDecoder, Traces}, - utils::CallKind, -}; -use foundry_cli::utils::{ensure_clean_constructor, needs_setup}; -use foundry_common::{shell, types::ToEthers, RpcUrl}; -use foundry_compilers::artifacts::CompactContractBytecode; -use futures::future::join_all; -use parking_lot::RwLock; -use std::{collections::VecDeque, sync::Arc}; - -/// Helper alias type for the processed result of a runner onchain simulation. -type RunnerResult = (Option, Traces); - -impl ScriptArgs { - /// Locally deploys and executes the contract method that will collect all broadcastable - /// transactions. - pub async fn execute( - &self, - script_config: &mut ScriptConfig, - contract: CompactContractBytecode, - sender: Address, - predeploy_libraries: &[Bytes], - ) -> Result { - trace!(target: "script", "start executing script"); - - let CompactContractBytecode { abi, bytecode, .. } = contract; - - let abi = abi.ok_or_else(|| eyre::eyre!("no ABI found for contract"))?; - let bytecode = bytecode - .ok_or_else(|| eyre::eyre!("no bytecode found for contract"))? - .object - .into_bytes() - .ok_or_else(|| { - eyre::eyre!("expected fully linked bytecode, found unlinked bytecode") - })?; - - ensure_clean_constructor(&abi)?; - - let mut runner = self.prepare_runner(script_config, sender, SimulationStage::Local).await; - let (address, mut result) = runner.setup( - predeploy_libraries, - bytecode, - needs_setup(&abi), - script_config.sender_nonce, - self.broadcast, - script_config.evm_opts.fork_url.is_none(), - )?; - - let (func, calldata) = self.get_method_and_calldata(&abi)?; - script_config.called_function = Some(func); - - // Only call the method if `setUp()` succeeded. - if result.success { - let script_result = runner.script(address, calldata)?; - - result.success &= script_result.success; - result.gas_used = script_result.gas_used; - result.logs.extend(script_result.logs); - result.traces.extend(script_result.traces); - result.debug = script_result.debug; - result.labeled_addresses.extend(script_result.labeled_addresses); - result.returned = script_result.returned; - result.script_wallets.extend(script_result.script_wallets); - result.breakpoints = script_result.breakpoints; - - match (&mut result.transactions, script_result.transactions) { - (Some(txs), Some(new_txs)) => { - txs.extend(new_txs); - } - (None, Some(new_txs)) => { - result.transactions = Some(new_txs); - } - _ => {} - } - } - - Ok(result) - } - - /// Simulates onchain state by executing a list of transactions locally and persisting their - /// state. Returns the transactions and any CREATE2 contract address created. - pub async fn onchain_simulation( - &self, - transactions: BroadcastableTransactions, - script_config: &ScriptConfig, - decoder: &CallTraceDecoder, - contracts: &ContractsByArtifact, - ) -> Result> { - trace!(target: "script", "executing onchain simulation"); - - let runners = Arc::new( - self.build_runners(script_config) - .await - .into_iter() - .map(|(rpc, runner)| (rpc, Arc::new(RwLock::new(runner)))) - .collect::>(), - ); - - if script_config.evm_opts.verbosity > 3 { - println!("=========================="); - println!("Simulated On-chain Traces:\n"); - } - - let address_to_abi: BTreeMap = decoder - .contracts - .iter() - .filter_map(|(addr, contract_id)| { - let contract_name = get_contract_name(contract_id); - if let Ok(Some((_, (abi, code)))) = - contracts.find_by_name_or_identifier(contract_name) - { - let info = ArtifactInfo { - contract_name: contract_name.to_string(), - contract_id: contract_id.to_string(), - abi, - code, - }; - return Some((*addr, info)) - } - None - }) - .collect(); - - let mut final_txs = VecDeque::new(); - - // Executes all transactions from the different forks concurrently. - let futs = - transactions - .into_iter() - .map(|transaction| async { - let mut runner = runners - .get(transaction.rpc.as_ref().expect("to have been filled already.")) - .expect("to have been built.") - .write(); - - if let TypedTransaction::Legacy(mut tx) = transaction.transaction { - let result = runner - .simulate( - tx.from.expect( - "Transaction doesn't have a `from` address at execution time", - ).to_alloy(), - tx.to.clone(), - tx.data.clone().map(|b| b.to_alloy()), - tx.value.map(|v| v.to_alloy()), - ) - .expect("Internal EVM error"); - - if !result.success || result.traces.is_empty() { - return Ok((None, result.traces)) - } - - let created_contracts = result - .traces - .iter() - .flat_map(|(_, traces)| { - traces.arena.iter().filter_map(|node| { - if matches!(node.kind(), CallKind::Create | CallKind::Create2) { - return Some(AdditionalContract { - opcode: node.kind(), - address: node.trace.address, - init_code: node.trace.data.as_bytes().to_vec().into(), - }) - } - None - }) - }) - .collect(); - - // Simulate mining the transaction if the user passes `--slow`. - if self.slow { - runner.executor.env.block.number += U256::from(1); - } - - let is_fixed_gas_limit = tx.gas.is_some(); - // If tx.gas is already set that means it was specified in script - if !is_fixed_gas_limit { - // We inflate the gas used by the user specified percentage - tx.gas = Some( - U256::from(result.gas_used * self.gas_estimate_multiplier / 100) - .to_ethers(), - ); - } else { - println!("Gas limit was set in script to {:}", tx.gas.unwrap()); - } - - let tx = TransactionWithMetadata::new( - tx.into(), - transaction.rpc, - &result, - &address_to_abi, - decoder, - created_contracts, - is_fixed_gas_limit, - )?; - - Ok((Some(tx), result.traces)) - } else { - unreachable!() - } - }) - .collect::>(); - - let mut abort = false; - for res in join_all(futs).await { - // type hint - let res: Result = res; - - let (tx, mut traces) = res?; - - // Transaction will be `None`, if execution didn't pass. - if tx.is_none() || script_config.evm_opts.verbosity > 3 { - // Identify all contracts created during the call. - if traces.is_empty() { - eyre::bail!( - "forge script requires tracing enabled to collect created contracts" - ); - } - - for (_kind, trace) in &mut traces { - decoder.decode(trace).await; - println!("{trace}"); - } - } - - if let Some(tx) = tx { - final_txs.push_back(tx); - } else { - abort = true; - } - } - - if abort { - eyre::bail!("Simulated execution failed.") - } - - Ok(final_txs) - } - - /// Build the multiple runners from different forks. - async fn build_runners(&self, script_config: &ScriptConfig) -> HashMap { - let sender = script_config.evm_opts.sender; - - if !shell::verbosity().is_silent() { - let n = script_config.total_rpcs.len(); - let s = if n != 1 { "s" } else { "" }; - println!("\n## Setting up {n} EVM{s}."); - } - - let futs = script_config - .total_rpcs - .iter() - .map(|rpc| async { - let mut script_config = script_config.clone(); - script_config.evm_opts.fork_url = Some(rpc.clone()); - - ( - rpc.clone(), - self.prepare_runner(&mut script_config, sender, SimulationStage::OnChain).await, - ) - }) - .collect::>(); - - join_all(futs).await.into_iter().collect() - } - - /// Creates the Runner that drives script execution - async fn prepare_runner( - &self, - script_config: &mut ScriptConfig, - sender: Address, - stage: SimulationStage, - ) -> ScriptRunner { - trace!("preparing script runner"); - let env = - script_config.evm_opts.evm_env().await.expect("Could not instantiate fork environment"); - - // The db backend that serves all the data. - let db = match &script_config.evm_opts.fork_url { - Some(url) => match script_config.backends.get(url) { - Some(db) => db.clone(), - None => { - let backend = Backend::spawn( - script_config.evm_opts.get_fork(&script_config.config, env.clone()), - ) - .await; - script_config.backends.insert(url.clone(), backend); - script_config.backends.get(url).unwrap().clone() - } - }, - None => { - // It's only really `None`, when we don't pass any `--fork-url`. And if so, there is - // no need to cache it, since there won't be any onchain simulation that we'd need - // to cache the backend for. - Backend::spawn(script_config.evm_opts.get_fork(&script_config.config, env.clone())) - .await - } - }; - - // We need to enable tracing to decode contract names: local or external. - let mut builder = ExecutorBuilder::new() - .inspectors(|stack| stack.trace(true)) - .spec(script_config.config.evm_spec_id()) - .gas_limit(script_config.evm_opts.gas_limit()); - - if let SimulationStage::Local = stage { - builder = builder.inspectors(|stack| { - stack.debug(self.debug).cheatcodes( - CheatsConfig::new(&script_config.config, script_config.evm_opts.clone()).into(), - ) - }); - } - - ScriptRunner::new(builder.build(env, db), script_config.evm_opts.initial_balance, sender) - } -} diff --git a/crates/forge/bin/cmd/script/mod.rs b/crates/forge/bin/cmd/script/mod.rs deleted file mode 100644 index 472b38c752fdf..0000000000000 --- a/crates/forge/bin/cmd/script/mod.rs +++ /dev/null @@ -1,971 +0,0 @@ -use self::{build::BuildOutput, runner::ScriptRunner}; -use super::{build::BuildArgs, retry::RetryArgs}; -use alloy_dyn_abi::FunctionExt; -use alloy_json_abi::{Function, InternalType, JsonAbi as Abi}; -use alloy_primitives::{Address, Bytes, U256}; -use clap::{Parser, ValueHint}; -use dialoguer::Confirm; -use ethers_core::types::{ - transaction::eip2718::TypedTransaction, Log, NameOrAddress, TransactionRequest, -}; -use ethers_providers::{Http, Middleware}; -use ethers_signers::LocalWallet; -use eyre::{ContextCompat, Result, WrapErr}; -use forge::{ - backend::Backend, - debug::DebugArena, - decode::decode_console_logs, - opts::EvmOpts, - traces::{ - identifier::{EtherscanIdentifier, LocalTraceIdentifier, SignaturesIdentifier}, - CallTraceDecoder, CallTraceDecoderBuilder, TraceCallData, TraceKind, TraceRetData, Traces, - }, - utils::CallKind, -}; -use foundry_cli::opts::MultiWallet; -use foundry_common::{ - abi::encode_function_args, - contracts::get_contract_name, - errors::UnlinkedByteCode, - evm::{Breakpoints, EvmArgs}, - fmt::{format_token, format_token_raw}, - shell, - types::{ToAlloy, ToEthers}, - ContractsByArtifact, RpcUrl, CONTRACT_MAX_SIZE, SELECTOR_LEN, -}; -use foundry_compilers::{ - artifacts::{ContractBytecodeSome, Libraries}, - contracts::ArtifactContracts, - ArtifactId, Project, -}; -use foundry_config::{ - figment, - figment::{ - value::{Dict, Map}, - Metadata, Profile, Provider, - }, - Config, NamedChain, -}; -use foundry_evm::{ - constants::DEFAULT_CREATE2_DEPLOYER, - decode, - inspectors::cheatcodes::{BroadcastableTransaction, BroadcastableTransactions}, -}; -use futures::future; -use itertools::Itertools; -use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; -use yansi::Paint; - -mod artifacts; -mod broadcast; -mod build; -mod cmd; -mod executor; -mod multi; -mod providers; -mod receipts; -mod runner; -mod sequence; -pub mod transaction; -mod verify; - -// Loads project's figment and merges the build cli arguments into it -foundry_config::merge_impl_figment_convert!(ScriptArgs, opts, evm_opts); - -/// CLI arguments for `forge script`. -#[derive(Debug, Clone, Parser, Default)] -pub struct ScriptArgs { - /// The contract you want to run. Either the file path or contract name. - /// - /// If multiple contracts exist in the same file you must specify the target contract with - /// --target-contract. - #[clap(value_hint = ValueHint::FilePath)] - pub path: String, - - /// Arguments to pass to the script function. - pub args: Vec, - - /// The name of the contract you want to run. - #[clap(long, visible_alias = "tc", value_name = "CONTRACT_NAME")] - pub target_contract: Option, - - /// The signature of the function you want to call in the contract, or raw calldata. - #[clap( - long, - short, - default_value = "run()", - value_parser = foundry_common::clap_helpers::strip_0x_prefix - )] - pub sig: String, - - /// Max priority fee per gas for EIP1559 transactions. - #[clap( - long, - env = "ETH_PRIORITY_GAS_PRICE", - value_parser = foundry_cli::utils::parse_ether_value, - value_name = "PRICE" - )] - pub priority_gas_price: Option, - - /// Use legacy transactions instead of EIP1559 ones. - /// - /// This is auto-enabled for common networks without EIP1559. - #[clap(long)] - pub legacy: bool, - - /// Broadcasts the transactions. - #[clap(long)] - pub broadcast: bool, - - /// Skips on-chain simulation. - #[clap(long)] - pub skip_simulation: bool, - - /// Relative percentage to multiply gas estimates by. - #[clap(long, short, default_value = "130")] - pub gas_estimate_multiplier: u64, - - /// Send via `eth_sendTransaction` using the `--from` argument or `$ETH_FROM` as sender - #[clap( - long, - requires = "sender", - conflicts_with_all = &["private_key", "private_keys", "froms", "ledger", "trezor", "aws"], - )] - pub unlocked: bool, - - /// Resumes submitting transactions that failed or timed-out previously. - /// - /// It DOES NOT simulate the script again and it expects nonces to have remained the same. - /// - /// Example: If transaction N has a nonce of 22, then the account should have a nonce of 22, - /// otherwise it fails. - #[clap(long)] - pub resume: bool, - - /// If present, --resume or --verify will be assumed to be a multi chain deployment. - #[clap(long)] - pub multi: bool, - - /// Open the script in the debugger. - /// - /// Takes precedence over broadcast. - #[clap(long)] - pub debug: bool, - - /// Makes sure a transaction is sent, - /// only after its previous one has been confirmed and succeeded. - #[clap(long)] - pub slow: bool, - - /// Disables interactive prompts that might appear when deploying big contracts. - /// - /// For more info on the contract size limit, see EIP-170: - #[clap(long)] - pub non_interactive: bool, - - /// The Etherscan (or equivalent) API key - #[clap(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")] - pub etherscan_api_key: Option, - - /// Verifies all the contracts found in the receipts of a script, if any. - #[clap(long)] - pub verify: bool, - - /// Output results in JSON format. - #[clap(long)] - pub json: bool, - - /// Gas price for legacy transactions, or max fee per gas for EIP1559 transactions. - #[clap( - long, - env = "ETH_GAS_PRICE", - value_parser = foundry_cli::utils::parse_ether_value, - value_name = "PRICE", - )] - pub with_gas_price: Option, - - #[clap(flatten)] - pub opts: BuildArgs, - - #[clap(flatten)] - pub wallets: MultiWallet, - - #[clap(flatten)] - pub evm_opts: EvmArgs, - - #[clap(flatten)] - pub verifier: super::verify::VerifierArgs, - - #[clap(flatten)] - pub retry: RetryArgs, -} - -// === impl ScriptArgs === - -impl ScriptArgs { - fn decode_traces( - &self, - script_config: &ScriptConfig, - result: &mut ScriptResult, - known_contracts: &ContractsByArtifact, - ) -> Result { - let verbosity = script_config.evm_opts.verbosity; - let mut etherscan_identifier = EtherscanIdentifier::new( - &script_config.config, - script_config.evm_opts.get_remote_chain_id(), - )?; - - let mut local_identifier = LocalTraceIdentifier::new(known_contracts); - let mut decoder = CallTraceDecoderBuilder::new() - .with_labels(result.labeled_addresses.clone()) - .with_verbosity(verbosity) - .with_signature_identifier(SignaturesIdentifier::new( - Config::foundry_cache_dir(), - script_config.config.offline, - )?) - .build(); - - // Decoding traces using etherscan is costly as we run into rate limits, - // causing scripts to run for a very long time unnecessarily. - // Therefore, we only try and use etherscan if the user has provided an API key. - let should_use_etherscan_traces = script_config.config.etherscan_api_key.is_some(); - - for (_, trace) in &mut result.traces { - decoder.identify(trace, &mut local_identifier); - if should_use_etherscan_traces { - decoder.identify(trace, &mut etherscan_identifier); - } - } - Ok(decoder) - } - - fn get_returns( - &self, - script_config: &ScriptConfig, - returned: &Bytes, - ) -> Result> { - let func = script_config.called_function.as_ref().expect("There should be a function."); - let mut returns = HashMap::new(); - - match func.abi_decode_output(returned, false) { - Ok(decoded) => { - for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() { - let internal_type = - output.internal_type.clone().unwrap_or(InternalType::Other { - contract: None, - ty: "unknown".to_string(), - }); - - let label = if !output.name.is_empty() { - output.name.to_string() - } else { - index.to_string() - }; - - returns.insert( - label, - NestedValue { - internal_type: internal_type.to_string(), - value: format_token_raw(token), - }, - ); - } - } - Err(_) => { - shell::println(format!("{returned:?}"))?; - } - } - - Ok(returns) - } - - async fn show_traces( - &self, - script_config: &ScriptConfig, - decoder: &CallTraceDecoder, - result: &mut ScriptResult, - ) -> Result<()> { - let verbosity = script_config.evm_opts.verbosity; - let func = script_config.called_function.as_ref().expect("There should be a function."); - - if !result.success || verbosity > 3 { - if result.traces.is_empty() { - warn!(verbosity, "no traces"); - } - - shell::println("Traces:")?; - for (kind, trace) in &mut result.traces { - let should_include = match kind { - TraceKind::Setup => verbosity >= 5, - TraceKind::Execution => verbosity > 3, - _ => false, - } || !result.success; - - if should_include { - decoder.decode(trace).await; - shell::println(format!("{trace}"))?; - } - } - shell::println(String::new())?; - } - - if result.success { - shell::println(format!("{}", Paint::green("Script ran successfully.")))?; - } - - if script_config.evm_opts.fork_url.is_none() { - shell::println(format!("Gas used: {}", result.gas_used))?; - } - - if result.success && !result.returned.is_empty() { - shell::println("\n== Return ==")?; - match func.abi_decode_output(&result.returned, false) { - Ok(decoded) => { - for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() { - let internal_type = - output.internal_type.clone().unwrap_or(InternalType::Other { - contract: None, - ty: "unknown".to_string(), - }); - - let label = if !output.name.is_empty() { - output.name.to_string() - } else { - index.to_string() - }; - shell::println(format!( - "{}: {internal_type} {}", - label.trim_end(), - format_token(token) - ))?; - } - } - Err(_) => { - shell::println(format!("{:x?}", (&result.returned)))?; - } - } - } - - let console_logs = decode_console_logs(&result.logs); - if !console_logs.is_empty() { - shell::println("\n== Logs ==")?; - for log in console_logs { - shell::println(format!(" {log}"))?; - } - } - - if !result.success { - return Err(eyre::eyre!( - "script failed: {}", - decode::decode_revert(&result.returned[..], None, None) - )) - } - - Ok(()) - } - - fn show_json(&self, script_config: &ScriptConfig, result: &ScriptResult) -> Result<()> { - let returns = self.get_returns(script_config, &result.returned)?; - - let console_logs = decode_console_logs(&result.logs); - let output = JsonResult { logs: console_logs, gas_used: result.gas_used, returns }; - let j = serde_json::to_string(&output)?; - shell::println(j)?; - - Ok(()) - } - - /// It finds the deployer from the running script and uses it to predeploy libraries. - /// - /// If there are multiple candidate addresses, it skips everything and lets `--sender` deploy - /// them instead. - fn maybe_new_sender( - &self, - evm_opts: &EvmOpts, - transactions: Option<&BroadcastableTransactions>, - predeploy_libraries: &[Bytes], - ) -> Result> { - let mut new_sender = None; - - if let Some(txs) = transactions { - // If the user passed a `--sender` don't check anything. - if !predeploy_libraries.is_empty() && self.evm_opts.sender.is_none() { - for tx in txs.iter() { - match &tx.transaction { - TypedTransaction::Legacy(tx) => { - if tx.to.is_none() { - let sender = tx.from.expect("no sender").to_alloy(); - if let Some(ns) = new_sender { - if sender != ns { - shell::println("You have more than one deployer who could predeploy libraries. Using `--sender` instead.")?; - return Ok(None) - } - } else if sender != evm_opts.sender { - new_sender = Some(sender); - } - } - } - _ => unreachable!(), - } - } - } - } - Ok(new_sender) - } - - /// Helper for building the transactions for any libraries that need to be deployed ahead of - /// linking - fn create_deploy_transactions( - &self, - from: Address, - nonce: u64, - data: &[Bytes], - fork_url: &Option, - ) -> BroadcastableTransactions { - data.iter() - .enumerate() - .map(|(i, bytes)| BroadcastableTransaction { - rpc: fork_url.clone(), - transaction: TypedTransaction::Legacy(TransactionRequest { - from: Some(from.to_ethers()), - data: Some(bytes.clone().to_ethers()), - nonce: Some((nonce + i as u64).into()), - ..Default::default() - }), - }) - .collect() - } - - /// Returns the Function and calldata based on the signature - /// - /// If the `sig` is a valid human-readable function we find the corresponding function in the - /// `abi` If the `sig` is valid hex, we assume it's calldata and try to find the - /// corresponding function by matching the selector, first 4 bytes in the calldata. - /// - /// Note: We assume that the `sig` is already stripped of its prefix, See [`ScriptArgs`] - fn get_method_and_calldata(&self, abi: &Abi) -> Result<(Function, Bytes)> { - let (func, data) = if let Ok(func) = Function::parse(&self.sig) { - ( - abi.functions().find(|&abi_func| abi_func.selector() == func.selector()).wrap_err( - format!("Function `{}` is not implemented in your script.", self.sig), - )?, - encode_function_args(&func, &self.args)?.into(), - ) - } else { - let decoded = hex::decode(&self.sig).wrap_err("Invalid hex calldata")?; - let selector = &decoded[..SELECTOR_LEN]; - ( - abi.functions().find(|&func| selector == &func.selector()[..]).ok_or_else( - || { - eyre::eyre!( - "Function selector `{}` not found in the ABI", - hex::encode(selector) - ) - }, - )?, - decoded.into(), - ) - }; - - Ok((func.clone(), data)) - } - - /// Checks if the transaction is a deployment with either a size above the `CONTRACT_MAX_SIZE` - /// or specified `code_size_limit`. - /// - /// If `self.broadcast` is enabled, it asks confirmation of the user. Otherwise, it just warns - /// the user. - fn check_contract_sizes( - &self, - result: &ScriptResult, - known_contracts: &BTreeMap, - ) -> Result<()> { - // (name, &init, &deployed)[] - let mut bytecodes: Vec<(String, &[u8], &[u8])> = vec![]; - - // From artifacts - for (artifact, bytecode) in known_contracts.iter() { - if bytecode.bytecode.object.is_unlinked() { - return Err(UnlinkedByteCode::Bytecode(artifact.identifier()).into()) - } - let init_code = bytecode.bytecode.object.as_bytes().unwrap(); - // Ignore abstract contracts - if let Some(ref deployed_code) = bytecode.deployed_bytecode.bytecode { - if deployed_code.object.is_unlinked() { - return Err(UnlinkedByteCode::DeployedBytecode(artifact.identifier()).into()) - } - let deployed_code = deployed_code.object.as_bytes().unwrap(); - bytecodes.push((artifact.name.clone(), init_code, deployed_code)); - } - } - - // From traces - let create_nodes = result.traces.iter().flat_map(|(_, traces)| { - traces - .arena - .iter() - .filter(|node| matches!(node.kind(), CallKind::Create | CallKind::Create2)) - }); - let mut unknown_c = 0usize; - for node in create_nodes { - // Calldata == init code - if let TraceCallData::Raw(ref init_code) = node.trace.data { - // Output is the runtime code - if let TraceRetData::Raw(ref deployed_code) = node.trace.output { - // Only push if it was not present already - if !bytecodes.iter().any(|(_, b, _)| *b == init_code.as_ref()) { - bytecodes.push((format!("Unknown{unknown_c}"), init_code, deployed_code)); - unknown_c += 1; - } - continue - } - } - // Both should be raw and not decoded since it's just bytecode - eyre::bail!("Create node returned decoded data: {:?}", node); - } - - let mut prompt_user = false; - let max_size = match self.evm_opts.env.code_size_limit { - Some(size) => size, - None => CONTRACT_MAX_SIZE, - }; - - for (data, to) in result.transactions.iter().flat_map(|txes| { - txes.iter().filter_map(|tx| { - tx.transaction - .data() - .filter(|data| data.len() > max_size) - .map(|data| (data, tx.transaction.to())) - }) - }) { - let mut offset = 0; - - // Find if it's a CREATE or CREATE2. Otherwise, skip transaction. - if let Some(NameOrAddress::Address(to)) = to { - if to.to_alloy() == DEFAULT_CREATE2_DEPLOYER { - // Size of the salt prefix. - offset = 32; - } - } else if to.is_some() { - continue - } - - // Find artifact with a deployment code same as the data. - if let Some((name, _, deployed_code)) = - bytecodes.iter().find(|(_, init_code, _)| *init_code == &data[offset..]) - { - let deployment_size = deployed_code.len(); - - if deployment_size > max_size { - prompt_user = self.broadcast; - shell::println(format!( - "{}", - Paint::red(format!( - "`{name}` is above the contract size limit ({deployment_size} > {max_size})." - )) - ))?; - } - } - } - - // Only prompt if we're broadcasting and we've not disabled interactivity. - if prompt_user && - !self.non_interactive && - !Confirm::new().with_prompt("Do you wish to continue?".to_string()).interact()? - { - eyre::bail!("User canceled the script."); - } - - Ok(()) - } -} - -impl Provider for ScriptArgs { - fn metadata(&self) -> Metadata { - Metadata::named("Script Args Provider") - } - - fn data(&self) -> Result, figment::Error> { - let mut dict = Dict::default(); - if let Some(ref etherscan_api_key) = self.etherscan_api_key { - dict.insert( - "etherscan_api_key".to_string(), - figment::value::Value::from(etherscan_api_key.to_string()), - ); - } - Ok(Map::from([(Config::selected_profile(), dict)])) - } -} - -#[derive(Default)] -pub struct ScriptResult { - pub success: bool, - pub logs: Vec, - pub traces: Traces, - pub debug: Option>, - pub gas_used: u64, - pub labeled_addresses: BTreeMap, - pub transactions: Option, - pub returned: Bytes, - pub address: Option
, - pub script_wallets: Vec, - pub breakpoints: Breakpoints, -} - -#[derive(Serialize, Deserialize)] -struct JsonResult { - logs: Vec, - gas_used: u64, - returns: HashMap, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct NestedValue { - pub internal_type: String, - pub value: String, -} - -#[derive(Clone, Debug, Default)] -pub struct ScriptConfig { - pub config: Config, - pub evm_opts: EvmOpts, - pub sender_nonce: u64, - /// Maps a rpc url to a backend - pub backends: HashMap, - /// Script target contract - pub target_contract: Option, - /// Function called by the script - pub called_function: Option, - /// Unique list of rpc urls present - pub total_rpcs: HashSet, - /// If true, one of the transactions did not have a rpc - pub missing_rpc: bool, - /// Should return some debug information - pub debug: bool, -} - -impl ScriptConfig { - fn collect_rpcs(&mut self, txs: &BroadcastableTransactions) { - self.missing_rpc = txs.iter().any(|tx| tx.rpc.is_none()); - - self.total_rpcs - .extend(txs.iter().filter_map(|tx| tx.rpc.as_ref().cloned()).collect::>()); - - if let Some(rpc) = &self.evm_opts.fork_url { - self.total_rpcs.insert(rpc.clone()); - } - } - - fn has_multiple_rpcs(&self) -> bool { - self.total_rpcs.len() > 1 - } - - /// Certain features are disabled for multi chain deployments, and if tried, will return - /// error. [library support] - fn check_multi_chain_constraints(&self, libraries: &Libraries) -> Result<()> { - if self.has_multiple_rpcs() || (self.missing_rpc && !self.total_rpcs.is_empty()) { - shell::eprintln(format!( - "{}", - Paint::yellow( - "Multi chain deployment is still under development. Use with caution." - ) - ))?; - if !libraries.libs.is_empty() { - eyre::bail!( - "Multi chain deployment does not support library linking at the moment." - ) - } - } - Ok(()) - } - - /// Returns the script target contract - fn target_contract(&self) -> &ArtifactId { - self.target_contract.as_ref().expect("should exist after building") - } - - /// Checks if the RPCs used point to chains that support EIP-3855. - /// If not, warns the user. - async fn check_shanghai_support(&self) -> Result<()> { - let chain_ids = self.total_rpcs.iter().map(|rpc| async move { - let provider = ethers_providers::Provider::::try_from(rpc).ok()?; - let id = provider.get_chainid().await.ok()?; - let id_u64: u64 = id.try_into().ok()?; - NamedChain::try_from(id_u64).ok() - }); - - let chains = future::join_all(chain_ids).await; - let iter = chains.iter().flatten().map(|c| (c.supports_shanghai(), c)); - if iter.clone().any(|(s, _)| !s) { - let msg = format!( - "\ -EIP-3855 is not supported in one or more of the RPCs used. -Unsupported Chain IDs: {}. -Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly. -For more information, please see https://eips.ethereum.org/EIPS/eip-3855", - iter.filter(|(supported, _)| !supported) - .map(|(_, chain)| *chain as u64) - .format(", ") - ); - shell::println(Paint::yellow(msg))?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use foundry_cli::utils::LoadConfig; - use foundry_config::{NamedChain, UnresolvedEnvVarError}; - use std::fs; - use tempfile::tempdir; - - #[test] - fn can_parse_sig() { - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "Contract.sol", - "--sig", - "0x522bb704000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfFFb92266", - ]); - assert_eq!( - args.sig, - "522bb704000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfFFb92266" - ); - } - - #[test] - fn can_parse_unlocked() { - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "Contract.sol", - "--sender", - "0x4e59b44847b379578588920ca78fbf26c0b4956c", - "--unlocked", - ]); - assert!(args.unlocked); - - let key = U256::ZERO; - let args = ScriptArgs::try_parse_from([ - "foundry-cli", - "Contract.sol", - "--sender", - "0x4e59b44847b379578588920ca78fbf26c0b4956c", - "--unlocked", - "--private-key", - key.to_string().as_str(), - ]); - assert!(args.is_err()); - } - - #[test] - fn can_merge_script_config() { - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "Contract.sol", - "--etherscan-api-key", - "goerli", - ]); - let config = args.load_config(); - assert_eq!(config.etherscan_api_key, Some("goerli".to_string())); - } - - #[test] - fn can_parse_verifier_url() { - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "script", - "script/Test.s.sol:TestScript", - "--fork-url", - "http://localhost:8545", - "--verifier-url", - "http://localhost:3000/api/verify", - "--etherscan-api-key", - "blacksmith", - "--broadcast", - "--verify", - "-vvvvv", - ]); - assert_eq!( - args.verifier.verifier_url, - Some("http://localhost:3000/api/verify".to_string()) - ); - } - - #[test] - fn can_extract_code_size_limit() { - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "script", - "script/Test.s.sol:TestScript", - "--fork-url", - "http://localhost:8545", - "--broadcast", - "--code-size-limit", - "50000", - ]); - assert_eq!(args.evm_opts.env.code_size_limit, Some(50000)); - } - - #[test] - fn can_extract_script_etherscan_key() { - let temp = tempdir().unwrap(); - let root = temp.path(); - - let config = r#" - [profile.default] - etherscan_api_key = "mumbai" - - [etherscan] - mumbai = { key = "https://etherscan-mumbai.com/" } - "#; - - let toml_file = root.join(Config::FILE_NAME); - fs::write(toml_file, config).unwrap(); - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "Contract.sol", - "--etherscan-api-key", - "mumbai", - "--root", - root.as_os_str().to_str().unwrap(), - ]); - - let config = args.load_config(); - let mumbai = config.get_etherscan_api_key(Some(NamedChain::PolygonMumbai.into())); - assert_eq!(mumbai, Some("https://etherscan-mumbai.com/".to_string())); - } - - #[test] - fn can_extract_script_rpc_alias() { - let temp = tempdir().unwrap(); - let root = temp.path(); - - let config = r#" - [profile.default] - - [rpc_endpoints] - polygonMumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_CAN_EXTRACT_RPC_ALIAS}" - "#; - - let toml_file = root.join(Config::FILE_NAME); - fs::write(toml_file, config).unwrap(); - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "DeployV1", - "--rpc-url", - "polygonMumbai", - "--root", - root.as_os_str().to_str().unwrap(), - ]); - - let err = args.load_config_and_evm_opts().unwrap_err(); - - assert!(err.downcast::().is_ok()); - - std::env::set_var("_CAN_EXTRACT_RPC_ALIAS", "123456"); - let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); - assert_eq!(config.eth_rpc_url, Some("polygonMumbai".to_string())); - assert_eq!( - evm_opts.fork_url, - Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string()) - ); - } - - #[test] - fn can_extract_script_rpc_and_etherscan_alias() { - let temp = tempdir().unwrap(); - let root = temp.path(); - - let config = r#" - [profile.default] - - [rpc_endpoints] - mumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_EXTRACT_RPC_ALIAS}" - - [etherscan] - mumbai = { key = "${_POLYSCAN_API_KEY}", chain = 80001, url = "https://api-testnet.polygonscan.com/" } - "#; - - let toml_file = root.join(Config::FILE_NAME); - fs::write(toml_file, config).unwrap(); - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "DeployV1", - "--rpc-url", - "mumbai", - "--etherscan-api-key", - "mumbai", - "--root", - root.as_os_str().to_str().unwrap(), - ]); - let err = args.load_config_and_evm_opts().unwrap_err(); - - assert!(err.downcast::().is_ok()); - - std::env::set_var("_EXTRACT_RPC_ALIAS", "123456"); - std::env::set_var("_POLYSCAN_API_KEY", "polygonkey"); - let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); - assert_eq!(config.eth_rpc_url, Some("mumbai".to_string())); - assert_eq!( - evm_opts.fork_url, - Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string()) - ); - let etherscan = config.get_etherscan_api_key(Some(80001u64.into())); - assert_eq!(etherscan, Some("polygonkey".to_string())); - let etherscan = config.get_etherscan_api_key(None); - assert_eq!(etherscan, Some("polygonkey".to_string())); - } - - #[test] - fn can_extract_script_rpc_and_sole_etherscan_alias() { - let temp = tempdir().unwrap(); - let root = temp.path(); - - let config = r#" - [profile.default] - - [rpc_endpoints] - mumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_SOLE_EXTRACT_RPC_ALIAS}" - - [etherscan] - mumbai = { key = "${_SOLE_POLYSCAN_API_KEY}" } - "#; - - let toml_file = root.join(Config::FILE_NAME); - fs::write(toml_file, config).unwrap(); - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "DeployV1", - "--rpc-url", - "mumbai", - "--root", - root.as_os_str().to_str().unwrap(), - ]); - let err = args.load_config_and_evm_opts().unwrap_err(); - - assert!(err.downcast::().is_ok()); - - std::env::set_var("_SOLE_EXTRACT_RPC_ALIAS", "123456"); - std::env::set_var("_SOLE_POLYSCAN_API_KEY", "polygonkey"); - let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); - assert_eq!( - evm_opts.fork_url, - Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string()) - ); - let etherscan = config.get_etherscan_api_key(Some(80001u64.into())); - assert_eq!(etherscan, Some("polygonkey".to_string())); - let etherscan = config.get_etherscan_api_key(None); - assert_eq!(etherscan, Some("polygonkey".to_string())); - } - - // - #[test] - fn test_5923() { - let args: ScriptArgs = - ScriptArgs::parse_from(["foundry-cli", "DeployV1", "--priority-gas-price", "100"]); - assert!(args.priority_gas_price.is_some()); - } -} diff --git a/crates/forge/bin/cmd/script/multi.rs b/crates/forge/bin/cmd/script/multi.rs deleted file mode 100644 index 2eb5e2b4ef7cd..0000000000000 --- a/crates/forge/bin/cmd/script/multi.rs +++ /dev/null @@ -1,179 +0,0 @@ -use super::{ - receipts, - sequence::{ScriptSequence, DRY_RUN_DIR}, - verify::VerifyBundle, - ScriptArgs, -}; -use ethers_signers::LocalWallet; -use eyre::{ContextCompat, Report, Result, WrapErr}; -use foundry_cli::utils::now; -use foundry_common::{fs, get_http_provider}; -use foundry_compilers::{artifacts::Libraries, ArtifactId}; -use foundry_config::Config; -use futures::future::join_all; -use serde::{Deserialize, Serialize}; -use std::{ - io::{BufWriter, Write}, - path::{Path, PathBuf}, - sync::Arc, -}; - -/// Holds the sequences of multiple chain deployments. -#[derive(Deserialize, Serialize, Clone, Default)] -pub struct MultiChainSequence { - pub deployments: Vec, - pub path: PathBuf, - pub timestamp: u64, -} - -impl Drop for MultiChainSequence { - fn drop(&mut self) { - self.deployments.iter_mut().for_each(|sequence| sequence.sort_receipts()); - self.save().expect("could not save multi deployment sequence"); - } -} - -impl MultiChainSequence { - pub fn new( - deployments: Vec, - sig: &str, - target: &ArtifactId, - log_folder: &Path, - broadcasted: bool, - ) -> Result { - let path = - MultiChainSequence::get_path(&log_folder.join("multi"), sig, target, broadcasted)?; - - Ok(MultiChainSequence { deployments, path, timestamp: now().as_secs() }) - } - - /// Saves to ./broadcast/multi/contract_filename[-timestamp]/sig.json - pub fn get_path( - out: &Path, - sig: &str, - target: &ArtifactId, - broadcasted: bool, - ) -> Result { - let mut out = out.to_path_buf(); - - if !broadcasted { - out.push(DRY_RUN_DIR); - } - - let target_fname = target - .source - .file_name() - .wrap_err_with(|| format!("No filename for {:?}", target.source))? - .to_string_lossy(); - out.push(format!("{target_fname}-latest")); - - fs::create_dir_all(&out)?; - - let filename = sig - .split_once('(') - .wrap_err_with(|| format!("Failed to compute file name: Signature {sig} is invalid."))? - .0; - out.push(format!("{filename}.json")); - - Ok(out) - } - - /// Loads the sequences for the multi chain deployment. - pub fn load(log_folder: &Path, sig: &str, target: &ArtifactId) -> Result { - let path = MultiChainSequence::get_path(&log_folder.join("multi"), sig, target, true)?; - foundry_compilers::utils::read_json_file(path).wrap_err("Multi-chain deployment not found.") - } - - /// Saves the transactions as file if it's a standalone deployment. - pub fn save(&mut self) -> Result<()> { - self.timestamp = now().as_secs(); - - //../Contract-latest/run.json - let mut writer = BufWriter::new(fs::create_file(&self.path)?); - serde_json::to_writer_pretty(&mut writer, &self)?; - writer.flush()?; - - //../Contract-[timestamp]/run.json - let path = self.path.to_string_lossy(); - let file = PathBuf::from(&path.replace("-latest", &format!("-{}", self.timestamp))); - fs::create_dir_all(file.parent().unwrap())?; - fs::copy(&self.path, &file)?; - - println!("\nTransactions saved to: {}\n", self.path.display()); - - Ok(()) - } -} - -impl ScriptArgs { - /// Given a [`MultiChainSequence`] with multiple sequences of different chains, it executes them - /// all in parallel. Supports `--resume` and `--verify`. - pub async fn multi_chain_deployment( - &self, - mut deployments: MultiChainSequence, - libraries: Libraries, - config: &Config, - script_wallets: Vec, - verify: VerifyBundle, - ) -> Result<()> { - if !libraries.is_empty() { - eyre::bail!("Libraries are currently not supported on multi deployment setups."); - } - - if self.verify { - for sequence in &deployments.deployments { - sequence.verify_preflight_check(config, &verify)?; - } - } - - if self.resume { - trace!(target: "script", "resuming multi chain deployment"); - - let futs = deployments - .deployments - .iter_mut() - .map(|sequence| async move { - let provider = Arc::new(get_http_provider( - sequence.typed_transactions().first().unwrap().0.clone(), - )); - receipts::wait_for_pending(provider, sequence).await - }) - .collect::>(); - - let errors = - join_all(futs).await.into_iter().filter(|res| res.is_err()).collect::>(); - - if !errors.is_empty() { - return Err(eyre::eyre!("{errors:?}")) - } - } - - trace!(target: "script", "broadcasting multi chain deployments"); - - let mut results: Vec> = Vec::new(); - - for sequence in deployments.deployments.iter_mut() { - let result = match self - .send_transactions( - sequence, - &sequence.typed_transactions().first().unwrap().0.clone(), - &script_wallets, - ) - .await - { - Ok(_) if self.verify => sequence.verify_contracts(config, verify.clone()).await, - Ok(_) => Ok(()), - Err(err) => Err(err), - }; - results.push(result); - } - - let errors = results.into_iter().filter(|res| res.is_err()).collect::>(); - - if !errors.is_empty() { - return Err(eyre::eyre!("{errors:?}")) - } - - Ok(()) - } -} diff --git a/crates/forge/bin/cmd/script/transaction.rs b/crates/forge/bin/cmd/script/transaction.rs deleted file mode 100644 index f010f1b90b410..0000000000000 --- a/crates/forge/bin/cmd/script/transaction.rs +++ /dev/null @@ -1,462 +0,0 @@ -use super::{artifacts::ArtifactInfo, ScriptResult}; -use alloy_dyn_abi::JsonAbiExt; -use alloy_json_abi::Function; -use alloy_primitives::{Address, Bytes, B256}; -use ethers_core::types::{transaction::eip2718::TypedTransaction, NameOrAddress}; -use eyre::{ContextCompat, Result, WrapErr}; -use foundry_common::{ - fmt::format_token_raw, - types::{ToAlloy, ToEthers}, - RpcUrl, SELECTOR_LEN, -}; -use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, traces::CallTraceDecoder, utils::CallKind}; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; - -#[derive(Debug, Deserialize, Serialize, Clone, Default)] -#[serde(rename_all = "camelCase")] -pub struct AdditionalContract { - #[serde(rename = "transactionType")] - pub opcode: CallKind, - #[serde(serialize_with = "wrapper::serialize_addr")] - pub address: Address, - pub init_code: Bytes, -} - -#[derive(Debug, Serialize, Deserialize, Clone, Default)] -#[serde(rename_all = "camelCase")] -pub struct TransactionWithMetadata { - pub hash: Option, - #[serde(rename = "transactionType")] - pub opcode: CallKind, - #[serde(default = "default_string")] - pub contract_name: Option, - #[serde(default = "default_address", serialize_with = "wrapper::serialize_opt_addr")] - pub contract_address: Option
, - #[serde(default = "default_string")] - pub function: Option, - #[serde(default = "default_vec_of_strings")] - pub arguments: Option>, - #[serde(skip)] - pub rpc: Option, - pub transaction: TypedTransaction, - pub additional_contracts: Vec, - pub is_fixed_gas_limit: bool, -} - -fn default_string() -> Option { - Some("".to_string()) -} - -fn default_address() -> Option
{ - Some(Address::ZERO) -} - -fn default_vec_of_strings() -> Option> { - Some(vec![]) -} - -impl TransactionWithMetadata { - pub fn from_typed_transaction(transaction: TypedTransaction) -> Self { - Self { transaction, ..Default::default() } - } - - pub fn new( - transaction: TypedTransaction, - rpc: Option, - result: &ScriptResult, - local_contracts: &BTreeMap, - decoder: &CallTraceDecoder, - additional_contracts: Vec, - is_fixed_gas_limit: bool, - ) -> Result { - let mut metadata = Self { transaction, rpc, is_fixed_gas_limit, ..Default::default() }; - - // Specify if any contract was directly created with this transaction - if let Some(NameOrAddress::Address(to)) = metadata.transaction.to().cloned() { - if to.to_alloy() == DEFAULT_CREATE2_DEPLOYER { - metadata.set_create( - true, - Address::from_slice(&result.returned), - local_contracts, - )?; - } else { - metadata - .set_call(to.to_alloy(), local_contracts, decoder) - .wrap_err("Could not decode transaction type.")?; - } - } else if metadata.transaction.to().is_none() { - metadata.set_create( - false, - result.address.wrap_err("There should be a contract address from CREATE.")?, - local_contracts, - )?; - } - - // Add the additional contracts created in this transaction, so we can verify them later. - if let Some(tx_address) = metadata.contract_address { - metadata.additional_contracts = additional_contracts - .into_iter() - .filter_map(|contract| { - // Filter out the transaction contract repeated init_code. - if contract.address != tx_address { - Some(contract) - } else { - None - } - }) - .collect(); - } - - Ok(metadata) - } - - /// Populate the transaction as CREATE tx - /// - /// If this is a CREATE2 transaction this attempt to decode the arguments from the CREATE2 - /// deployer's function - fn set_create( - &mut self, - is_create2: bool, - address: Address, - contracts: &BTreeMap, - ) -> Result<()> { - if is_create2 { - self.opcode = CallKind::Create2; - } else { - self.opcode = CallKind::Create; - } - - self.contract_name = contracts.get(&address).map(|info| info.contract_name.clone()); - self.contract_address = Some(address); - - if let Some(data) = self.transaction.data() { - if let Some(info) = contracts.get(&address) { - // constructor args are postfixed to creation code - // and create2 transactions are prefixed by 32 byte salt - let contains_constructor_args = if is_create2 { - data.len() - 32 > info.code.len() - } else { - data.len() > info.code.len() - }; - - if contains_constructor_args { - if let Some(constructor) = info.abi.constructor() { - let creation_code = if is_create2 { &data[32..] } else { data }; - - let on_err = || { - let inputs = constructor - .inputs - .iter() - .map(|p| p.ty.clone()) - .collect::>() - .join(","); - let signature = format!("constructor({inputs})"); - let bytecode = hex::encode(creation_code); - (signature, bytecode) - }; - - // the constructor args start after bytecode - let constructor_args = &creation_code[info.code.len()..]; - - let constructor_fn = Function { - name: "constructor".to_string(), - inputs: constructor.inputs.clone(), - outputs: vec![], - state_mutability: constructor.state_mutability, - }; - - if let Ok(arguments) = - constructor_fn.abi_decode_input(constructor_args, false) - { - self.arguments = Some(arguments.iter().map(format_token_raw).collect()); - } else { - let (signature, bytecode) = on_err(); - error!(constructor=?signature, contract=?self.contract_name, bytecode, "Failed to decode constructor arguments") - }; - } - } - } - } - Ok(()) - } - - /// Populate the transaction as CALL tx - fn set_call( - &mut self, - target: Address, - local_contracts: &BTreeMap, - decoder: &CallTraceDecoder, - ) -> Result<()> { - self.opcode = CallKind::Call; - - if let Some(data) = self.transaction.data() { - if data.0.len() >= SELECTOR_LEN { - if let Some(info) = local_contracts.get(&target) { - // This CALL is made to a local contract. - - self.contract_name = Some(info.contract_name.clone()); - if let Some(function) = info - .abi - .functions() - .find(|function| function.selector() == data.0[..SELECTOR_LEN]) - { - self.function = Some(function.signature()); - self.arguments = Some( - function - .abi_decode_input(&data.0[SELECTOR_LEN..], false) - .map(|tokens| tokens.iter().map(format_token_raw).collect())?, - ); - } - } else { - // This CALL is made to an external contract. We can only decode it, if it has - // been verified and identified by etherscan. - - if let Some(function) = - decoder.functions.get(&data.0[..SELECTOR_LEN]).and_then(|v| v.first()) - { - self.contract_name = decoder.contracts.get(&target).cloned(); - - self.function = Some(function.signature()); - self.arguments = Some( - function - .abi_decode_input(&data.0[SELECTOR_LEN..], false) - .map(|tokens| tokens.iter().map(format_token_raw).collect())?, - ); - } - } - self.contract_address = Some(target); - } - } - Ok(()) - } - - pub fn set_tx(&mut self, tx: TypedTransaction) { - self.transaction = tx; - } - - pub fn change_type(&mut self, is_legacy: bool) { - self.transaction = if is_legacy { - TypedTransaction::Legacy(self.transaction.clone().into()) - } else { - TypedTransaction::Eip1559(self.transaction.clone().into()) - }; - } - - pub fn typed_tx(&self) -> &TypedTransaction { - &self.transaction - } - - pub fn typed_tx_mut(&mut self) -> &mut TypedTransaction { - &mut self.transaction - } - - pub fn is_create2(&self) -> bool { - self.opcode == CallKind::Create2 - } -} - -// wrapper for modifying ethers-rs type serialization -pub mod wrapper { - pub use super::*; - use ethers_core::{ - types::{Bloom, Bytes, Log, TransactionReceipt, H256, U256, U64}, - utils::to_checksum, - }; - - pub fn serialize_addr(addr: &Address, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&to_checksum(&addr.to_ethers(), None)) - } - - pub fn serialize_opt_addr(opt: &Option
, serializer: S) -> Result - where - S: serde::Serializer, - { - match opt { - Some(addr) => serialize_addr(addr, serializer), - None => serializer.serialize_none(), - } - } - - pub fn serialize_vec_with_wrapped( - vec: &[T], - serializer: S, - ) -> Result - where - S: serde::Serializer, - T: Clone, - WrappedType: serde::Serialize + From, - { - serializer.collect_seq(vec.iter().cloned().map(WrappedType::from)) - } - - // copied from https://github.com/gakonst/ethers-rs - #[derive(Serialize, Deserialize)] - struct WrappedLog { - /// The contract address that emitted the log. - #[serde(serialize_with = "serialize_addr")] - pub address: Address, - - /// Array of 0 to 4 32 Bytes of indexed log arguments. - /// - /// (In solidity: The first topic is the hash of the signature of the event - /// (e.g. `Deposit(address,bytes32,uint256)`), except you declared the event - /// with the anonymous specifier.) - pub topics: Vec, - - /// Data - pub data: Bytes, - - /// Block Hash - #[serde(rename = "blockHash")] - #[serde(skip_serializing_if = "Option::is_none")] - pub block_hash: Option, - - /// Block Number - #[serde(rename = "blockNumber")] - #[serde(skip_serializing_if = "Option::is_none")] - pub block_number: Option, - - /// Transaction Hash - #[serde(rename = "transactionHash")] - #[serde(skip_serializing_if = "Option::is_none")] - pub transaction_hash: Option, - - /// Transaction Index - #[serde(rename = "transactionIndex")] - #[serde(skip_serializing_if = "Option::is_none")] - pub transaction_index: Option, - - /// Integer of the log index position in the block. None if it's a pending log. - #[serde(rename = "logIndex")] - #[serde(skip_serializing_if = "Option::is_none")] - pub log_index: Option, - - /// Integer of the transactions index position log was created from. - /// None when it's a pending log. - #[serde(rename = "transactionLogIndex")] - #[serde(skip_serializing_if = "Option::is_none")] - pub transaction_log_index: Option, - - /// Log Type - #[serde(rename = "logType")] - #[serde(skip_serializing_if = "Option::is_none")] - pub log_type: Option, - - /// True when the log was removed, due to a chain reorganization. - /// false if it's a valid log. - #[serde(skip_serializing_if = "Option::is_none")] - pub removed: Option, - } - impl From for WrappedLog { - fn from(log: Log) -> Self { - Self { - address: log.address.to_alloy(), - topics: log.topics, - data: log.data, - block_hash: log.block_hash, - block_number: log.block_number, - transaction_hash: log.transaction_hash, - transaction_index: log.transaction_index, - log_index: log.log_index, - transaction_log_index: log.transaction_log_index, - log_type: log.log_type, - removed: log.removed, - } - } - } - - fn serialize_logs( - logs: &[Log], - serializer: S, - ) -> Result { - serialize_vec_with_wrapped::(logs, serializer) - } - - // "Receipt" of an executed transaction: details of its execution. - // copied from https://github.com/gakonst/ethers-rs - #[derive(Default, Clone, Serialize, Deserialize)] - pub struct WrappedTransactionReceipt { - /// Transaction hash. - #[serde(rename = "transactionHash")] - pub transaction_hash: H256, - /// Index within the block. - #[serde(rename = "transactionIndex")] - pub transaction_index: U64, - /// Hash of the block this transaction was included within. - #[serde(rename = "blockHash")] - pub block_hash: Option, - /// Number of the block this transaction was included within. - #[serde(rename = "blockNumber")] - pub block_number: Option, - /// The address of the sender. - #[serde(serialize_with = "serialize_addr")] - pub from: Address, - // The address of the receiver. `None` when its a contract creation transaction. - #[serde(serialize_with = "serialize_opt_addr")] - pub to: Option
, - /// Cumulative gas used within the block after this was executed. - #[serde(rename = "cumulativeGasUsed")] - pub cumulative_gas_used: U256, - /// Gas used by this transaction alone. - /// - /// Gas used is `None` if the the client is running in light client mode. - #[serde(rename = "gasUsed")] - pub gas_used: Option, - /// Contract address created, or `None` if not a deployment. - #[serde(rename = "contractAddress", serialize_with = "serialize_opt_addr")] - pub contract_address: Option
, - /// Logs generated within this transaction. - #[serde(serialize_with = "serialize_logs")] - pub logs: Vec, - /// Status: either 1 (success) or 0 (failure). Only present after activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658) - pub status: Option, - /// State root. Only present before activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub root: Option, - /// Logs bloom - #[serde(rename = "logsBloom")] - pub logs_bloom: Bloom, - /// Transaction type, Some(1) for AccessList transaction, None for Legacy - #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] - pub transaction_type: Option, - /// The price paid post-execution by the transaction (i.e. base fee + priority fee). - /// Both fields in 1559-style transactions are *maximums* (max fee + max priority fee), the - /// amount that's actually paid by users can only be determined post-execution - #[serde(rename = "effectiveGasPrice", default, skip_serializing_if = "Option::is_none")] - pub effective_gas_price: Option, - } - impl From for WrappedTransactionReceipt { - fn from(receipt: TransactionReceipt) -> Self { - Self { - transaction_hash: receipt.transaction_hash, - transaction_index: receipt.transaction_index, - block_hash: receipt.block_hash, - block_number: receipt.block_number, - from: receipt.from.to_alloy(), - to: receipt.to.map(|addr| addr.to_alloy()), - cumulative_gas_used: receipt.cumulative_gas_used, - gas_used: receipt.gas_used, - contract_address: receipt.contract_address.map(|addr| addr.to_alloy()), - logs: receipt.logs, - status: receipt.status, - root: receipt.root, - logs_bloom: receipt.logs_bloom, - transaction_type: receipt.transaction_type, - effective_gas_price: receipt.effective_gas_price, - } - } - } - - pub fn serialize_receipts( - receipts: &[TransactionReceipt], - serializer: S, - ) -> Result { - serialize_vec_with_wrapped::( - receipts, serializer, - ) - } -} diff --git a/crates/forge/bin/cmd/selectors.rs b/crates/forge/bin/cmd/selectors.rs index 4b2d045c9ddfa..8e07fb1f61caf 100644 --- a/crates/forge/bin/cmd/selectors.rs +++ b/crates/forge/bin/cmd/selectors.rs @@ -6,60 +6,53 @@ use foundry_cli::{ utils::FoundryPathExt, }; use foundry_common::{ - compile, + compile::{compile_target, ProjectCompiler}, selectors::{import_selectors, SelectorImportData}, }; use foundry_compilers::{artifacts::output_selection::ContractOutputSelection, info::ContractInfo}; use std::fs::canonicalize; /// CLI arguments for `forge selectors`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub enum SelectorsSubcommands { /// Check for selector collisions between contracts - #[clap(visible_alias = "co")] + #[command(visible_alias = "co")] Collision { - /// First contract - #[clap( - help = "The first of the two contracts for which to look selector collisions for, in the form `(:)?`", - value_name = "FIRST_CONTRACT" - )] + /// The first of the two contracts for which to look selector collisions for, in the form + /// `(:)?`. first_contract: ContractInfo, - /// Second contract - #[clap( - help = "The second of the two contracts for which to look selector collisions for, in the form `(:)?`", - value_name = "SECOND_CONTRACT" - )] + /// The second of the two contracts for which to look selector collisions for, in the form + /// `(:)?`. second_contract: ContractInfo, - /// Support build args - #[clap(flatten)] + #[command(flatten)] build: Box, }, /// Upload selectors to registry - #[clap(visible_alias = "up")] + #[command(visible_alias = "up")] Upload { /// The name of the contract to upload selectors for. - #[clap(required_unless_present = "all")] + #[arg(required_unless_present = "all")] contract: Option, /// Upload selectors for all contracts in the project. - #[clap(long, required_unless_present = "contract")] + #[arg(long, required_unless_present = "contract")] all: bool, - #[clap(flatten)] + #[command(flatten)] project_paths: ProjectPathsArgs, }, /// List selectors from current workspace - #[clap(visible_alias = "ls")] + #[command(visible_alias = "ls")] List { /// The name of the contract to list selectors for. - #[clap(help = "The name of the contract to list selectors for.")] + #[arg(help = "The name of the contract to list selectors for.")] contract: Option, - #[clap(flatten)] + #[command(flatten)] project_paths: ProjectPathsArgs, }, } @@ -78,9 +71,14 @@ impl SelectorsSubcommands { }; let project = build_args.project()?; - let outcome = compile::suppress_compile(&project)?; + let output = if let Some(name) = &contract { + let target_path = project.find_contract_path(name)?; + compile_target(&target_path, &project, false)? + } else { + ProjectCompiler::new().compile(&project)? + }; let artifacts = if all { - outcome + output .into_artifacts_with_files() .filter(|(file, _, _)| { let is_sources_path = file @@ -93,7 +91,7 @@ impl SelectorsSubcommands { .collect() } else { let contract = contract.unwrap(); - let found_artifact = outcome.find_first(&contract); + let found_artifact = output.find_first(&contract); let artifact = found_artifact .ok_or_else(|| { eyre::eyre!( @@ -122,41 +120,34 @@ impl SelectorsSubcommands { } } SelectorsSubcommands::Collision { mut first_contract, mut second_contract, build } => { - // Build first project - let first_project = build.project()?; - let first_outcome = if let Some(ref mut contract_path) = first_contract.path { + // Compile the project with the two contracts included + let project = build.project()?; + let mut compiler = ProjectCompiler::new().quiet(true); + + if let Some(contract_path) = &mut first_contract.path { let target_path = canonicalize(&*contract_path)?; *contract_path = target_path.to_string_lossy().to_string(); - compile::compile_files(&first_project, vec![target_path], true) - } else { - compile::suppress_compile(&first_project) - }?; - - // Build second project - let second_project = build.project()?; - let second_outcome = if let Some(ref mut contract_path) = second_contract.path { + compiler = compiler.files([target_path]); + } + if let Some(contract_path) = &mut second_contract.path { let target_path = canonicalize(&*contract_path)?; *contract_path = target_path.to_string_lossy().to_string(); - compile::compile_files(&second_project, vec![target_path], true) - } else { - compile::suppress_compile(&second_project) - }?; - - // Find the artifacts - let first_found_artifact = first_outcome.find_contract(&first_contract); - let second_found_artifact = second_outcome.find_contract(&second_contract); + compiler = compiler.files([target_path]); + } - // Unwrap inner artifacts - let first_artifact = first_found_artifact.ok_or_else(|| { - eyre::eyre!("Failed to extract first artifact bytecode as a string") - })?; - let second_artifact = second_found_artifact.ok_or_else(|| { - eyre::eyre!("Failed to extract second artifact bytecode as a string") - })?; + let output = compiler.compile(&project)?; // Check method selectors for collisions - let first_method_map = first_artifact.method_identifiers.as_ref().unwrap(); - let second_method_map = second_artifact.method_identifiers.as_ref().unwrap(); + let methods = |contract: &ContractInfo| -> eyre::Result<_> { + let artifact = output + .find_contract(contract) + .ok_or_else(|| eyre::eyre!("Could not find artifact for {contract}"))?; + artifact.method_identifiers.as_ref().ok_or_else(|| { + eyre::eyre!("Could not find method identifiers for {contract}") + }) + }; + let first_method_map = methods(&first_contract)?; + let second_method_map = methods(&second_contract)?; let colliding_methods: Vec<(&String, &String, &String)> = first_method_map .iter() @@ -197,7 +188,7 @@ impl SelectorsSubcommands { // compile the project to get the artifacts/abis let project = build_args.project()?; - let outcome = compile::suppress_compile(&project)?; + let outcome = ProjectCompiler::new().quiet(true).compile(&project)?; let artifacts = if let Some(contract) = contract { let found_artifact = outcome.find_first(&contract); let artifact = found_artifact diff --git a/crates/forge/bin/cmd/snapshot.rs b/crates/forge/bin/cmd/snapshot.rs index cee3a66e98328..5f91951d834ca 100644 --- a/crates/forge/bin/cmd/snapshot.rs +++ b/crates/forge/bin/cmd/snapshot.rs @@ -1,11 +1,8 @@ -use super::{ - test, - test::{Test, TestOutcome}, -}; +use super::test; use alloy_primitives::U256; use clap::{builder::RangedU64ValueParser, Parser, ValueHint}; use eyre::{Context, Result}; -use forge::result::TestKindReport; +use forge::result::{SuiteTestResult, TestKindReport, TestOutcome}; use foundry_cli::utils::STATIC_FUZZ_SEED; use once_cell::sync::Lazy; use regex::Regex; @@ -27,12 +24,12 @@ pub static RE_BASIC_SNAPSHOT_ENTRY: Lazy = Lazy::new(|| { }); /// CLI arguments for `forge snapshot`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct SnapshotArgs { /// Output a diff against a pre-existing snapshot. /// /// By default, the comparison is done with .gas-snapshot. - #[clap( + #[arg( conflicts_with = "snap", long, value_hint = ValueHint::FilePath, @@ -45,7 +42,7 @@ pub struct SnapshotArgs { /// Outputs a diff if the snapshots do not match. /// /// By default, the comparison is done with .gas-snapshot. - #[clap( + #[arg( conflicts_with = "diff", long, value_hint = ValueHint::FilePath, @@ -55,11 +52,11 @@ pub struct SnapshotArgs { // Hidden because there is only one option /// How to format the output. - #[clap(long, hide(true))] + #[arg(long, hide(true))] format: Option, /// Output file for the snapshot. - #[clap( + #[arg( long, default_value = ".gas-snapshot", value_hint = ValueHint::FilePath, @@ -68,7 +65,7 @@ pub struct SnapshotArgs { snap: PathBuf, /// Tolerates gas deviations up to the specified percentage. - #[clap( + #[arg( long, value_parser = RangedU64ValueParser::::new().range(0..100), value_name = "SNAPSHOT_THRESHOLD" @@ -76,11 +73,11 @@ pub struct SnapshotArgs { tolerance: Option, /// All test arguments are supported - #[clap(flatten)] + #[command(flatten)] pub(crate) test: test::TestArgs, /// Additional configs for test results - #[clap(flatten)] + #[command(flatten)] config: SnapshotConfig, } @@ -124,7 +121,7 @@ impl SnapshotArgs { } // TODO implement pretty tables -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub enum Format { Table, } @@ -141,22 +138,22 @@ impl FromStr for Format { } /// Additional filters that can be applied on the test results -#[derive(Debug, Clone, Parser, Default)] +#[derive(Clone, Debug, Default, Parser)] struct SnapshotConfig { /// Sort results by gas used (ascending). - #[clap(long)] + #[arg(long)] asc: bool, /// Sort results by gas used (descending). - #[clap(conflicts_with = "asc", long)] + #[arg(conflicts_with = "asc", long)] desc: bool, /// Only include tests that used more gas that the given amount. - #[clap(long, value_name = "MIN_GAS")] + #[arg(long, value_name = "MIN_GAS")] min: Option, /// Only include tests that used less gas that the given amount. - #[clap(long, value_name = "MAX_GAS")] + #[arg(long, value_name = "MAX_GAS")] max: Option, } @@ -175,7 +172,7 @@ impl SnapshotConfig { true } - fn apply(&self, outcome: TestOutcome) -> Vec { + fn apply(&self, outcome: TestOutcome) -> Vec { let mut tests = outcome .into_tests() .filter(|test| self.is_in_gas_range(test.gas_used())) @@ -197,7 +194,7 @@ impl SnapshotConfig { /// `(gas:? 40181)` for normal tests /// `(runs: 256, μ: 40181, ~: 40181)` for fuzz tests /// `(runs: 256, calls: 40181, reverts: 40181)` for invariant tests -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct SnapshotEntry { pub contract_name: String, pub signature: String, @@ -274,7 +271,7 @@ fn read_snapshot(path: impl AsRef) -> Result> { /// Writes a series of tests to a snapshot file after sorting them fn write_to_snapshot_file( - tests: &[Test], + tests: &[SuiteTestResult], path: impl AsRef, _format: Option, ) -> Result<()> { @@ -293,7 +290,7 @@ fn write_to_snapshot_file( } /// A Snapshot entry diff -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct SnapshotDiff { pub signature: String, pub source_gas_used: TestKindReport, @@ -318,7 +315,7 @@ impl SnapshotDiff { /// Compares the set of tests with an existing snapshot /// /// Returns true all tests match -fn check(tests: Vec, snaps: Vec, tolerance: Option) -> bool { +fn check(tests: Vec, snaps: Vec, tolerance: Option) -> bool { let snaps = snaps .into_iter() .map(|s| ((s.contract_name, s.signature), s.gas_used)) @@ -352,7 +349,7 @@ fn check(tests: Vec, snaps: Vec, tolerance: Option) -> } /// Compare the set of tests with an existing snapshot -fn diff(tests: Vec, snaps: Vec) -> Result<()> { +fn diff(tests: Vec, snaps: Vec) -> Result<()> { let snaps = snaps .into_iter() .map(|s| ((s.contract_name, s.signature), s.gas_used)) @@ -401,21 +398,21 @@ fn diff(tests: Vec, snaps: Vec) -> Result<()> { fn fmt_pct_change(change: f64) -> String { let change_pct = change * 100.0; match change.partial_cmp(&0.0).unwrap_or(Ordering::Equal) { - Ordering::Less => Paint::green(format!("{change_pct:.3}%")).to_string(), + Ordering::Less => format!("{change_pct:.3}%").green().to_string(), Ordering::Equal => { format!("{change_pct:.3}%") } - Ordering::Greater => Paint::red(format!("{change_pct:.3}%")).to_string(), + Ordering::Greater => format!("{change_pct:.3}%").red().to_string(), } } fn fmt_change(change: i128) -> String { match change.cmp(&0) { - Ordering::Less => Paint::green(format!("{change}")).to_string(), + Ordering::Less => format!("{change}").green().to_string(), Ordering::Equal => { format!("{change}") } - Ordering::Greater => Paint::red(format!("{change}")).to_string(), + Ordering::Greater => format!("{change}").red().to_string(), } } diff --git a/crates/forge/bin/cmd/test/filter.rs b/crates/forge/bin/cmd/test/filter.rs index 7af0e4042a8f7..eb8baea4620f4 100644 --- a/crates/forge/bin/cmd/test/filter.rs +++ b/crates/forge/bin/cmd/test/filter.rs @@ -10,31 +10,31 @@ use std::{fmt, path::Path}; /// /// See also `FileFilter`. #[derive(Clone, Parser)] -#[clap(next_help_heading = "Test filtering")] +#[command(next_help_heading = "Test filtering")] pub struct FilterArgs { /// Only run test functions matching the specified regex pattern. - #[clap(long = "match-test", visible_alias = "mt", value_name = "REGEX")] + #[arg(long = "match-test", visible_alias = "mt", value_name = "REGEX")] pub test_pattern: Option, /// Only run test functions that do not match the specified regex pattern. - #[clap(long = "no-match-test", visible_alias = "nmt", value_name = "REGEX")] + #[arg(long = "no-match-test", visible_alias = "nmt", value_name = "REGEX")] pub test_pattern_inverse: Option, /// Only run tests in contracts matching the specified regex pattern. - #[clap(long = "match-contract", visible_alias = "mc", value_name = "REGEX")] + #[arg(long = "match-contract", visible_alias = "mc", value_name = "REGEX")] pub contract_pattern: Option, /// Only run tests in contracts that do not match the specified regex pattern. - #[clap(long = "no-match-contract", visible_alias = "nmc", value_name = "REGEX")] + #[arg(long = "no-match-contract", visible_alias = "nmc", value_name = "REGEX")] pub contract_pattern_inverse: Option, /// Only run tests in source files matching the specified glob pattern. - #[clap(long = "match-path", visible_alias = "mp", value_name = "GLOB")] + #[arg(long = "match-path", visible_alias = "mp", value_name = "GLOB")] pub path_pattern: Option, /// Only run tests in source files that do not match the specified glob pattern. - #[clap( - name = "no-match-path", + #[arg( + id = "no-match-path", long = "no-match-path", visible_alias = "nmp", value_name = "GLOB" @@ -43,29 +43,37 @@ pub struct FilterArgs { } impl FilterArgs { + /// Returns true if the filter is empty. + pub fn is_empty(&self) -> bool { + self.test_pattern.is_none() && + self.test_pattern_inverse.is_none() && + self.contract_pattern.is_none() && + self.contract_pattern_inverse.is_none() && + self.path_pattern.is_none() && + self.path_pattern_inverse.is_none() + } + /// Merges the set filter globs with the config's values - pub fn merge_with_config(&self, config: &Config) -> ProjectPathsAwareFilter { - let mut filter = self.clone(); - if filter.test_pattern.is_none() { - filter.test_pattern = config.test_pattern.clone().map(|p| p.into()); + pub fn merge_with_config(mut self, config: &Config) -> ProjectPathsAwareFilter { + if self.test_pattern.is_none() { + self.test_pattern = config.test_pattern.clone().map(Into::into); } - if filter.test_pattern_inverse.is_none() { - filter.test_pattern_inverse = config.test_pattern_inverse.clone().map(|p| p.into()); + if self.test_pattern_inverse.is_none() { + self.test_pattern_inverse = config.test_pattern_inverse.clone().map(Into::into); } - if filter.contract_pattern.is_none() { - filter.contract_pattern = config.contract_pattern.clone().map(|p| p.into()); + if self.contract_pattern.is_none() { + self.contract_pattern = config.contract_pattern.clone().map(Into::into); } - if filter.contract_pattern_inverse.is_none() { - filter.contract_pattern_inverse = - config.contract_pattern_inverse.clone().map(|p| p.into()); + if self.contract_pattern_inverse.is_none() { + self.contract_pattern_inverse = config.contract_pattern_inverse.clone().map(Into::into); } - if filter.path_pattern.is_none() { - filter.path_pattern = config.path_pattern.clone().map(Into::into); + if self.path_pattern.is_none() { + self.path_pattern = config.path_pattern.clone().map(Into::into); } - if filter.path_pattern_inverse.is_none() { - filter.path_pattern_inverse = config.path_pattern_inverse.clone().map(Into::into); + if self.path_pattern_inverse.is_none() { + self.path_pattern_inverse = config.path_pattern_inverse.clone().map(Into::into); } - ProjectPathsAwareFilter { args_filter: filter, paths: config.project_paths() } + ProjectPathsAwareFilter { args_filter: self, paths: config.project_paths() } } } @@ -86,53 +94,48 @@ impl FileFilter for FilterArgs { /// Returns true if the file regex pattern match the `file` /// /// If no file regex is set this returns true if the file ends with `.t.sol`, see - /// [FoundryPathExr::is_sol_test()] + /// [`FoundryPathExt::is_sol_test()`]. fn is_match(&self, file: &Path) -> bool { - if let Some(file) = file.as_os_str().to_str() { - if let Some(ref glob) = self.path_pattern { - return glob.is_match(file) - } - if let Some(ref glob) = self.path_pattern_inverse { - return !glob.is_match(file) - } + if let Some(glob) = &self.path_pattern { + return glob.is_match(file) + } + if let Some(glob) = &self.path_pattern_inverse { + return !glob.is_match(file) } file.is_sol_test() } } impl TestFilter for FilterArgs { - fn matches_test(&self, test_name: impl AsRef) -> bool { + fn matches_test(&self, test_name: &str) -> bool { let mut ok = true; - let test_name = test_name.as_ref(); if let Some(re) = &self.test_pattern { - ok &= re.is_match(test_name); + ok = ok && re.is_match(test_name); } if let Some(re) = &self.test_pattern_inverse { - ok &= !re.is_match(test_name); + ok = ok && !re.is_match(test_name); } ok } - fn matches_contract(&self, contract_name: impl AsRef) -> bool { + fn matches_contract(&self, contract_name: &str) -> bool { let mut ok = true; - let contract_name = contract_name.as_ref(); if let Some(re) = &self.contract_pattern { - ok &= re.is_match(contract_name); + ok = ok && re.is_match(contract_name); } if let Some(re) = &self.contract_pattern_inverse { - ok &= !re.is_match(contract_name); + ok = ok && !re.is_match(contract_name); } ok } - fn matches_path(&self, path: impl AsRef) -> bool { + fn matches_path(&self, path: &Path) -> bool { let mut ok = true; - let path = path.as_ref(); - if let Some(ref glob) = self.path_pattern { - ok &= glob.is_match(path); + if let Some(re) = &self.path_pattern { + ok = ok && re.is_match(path); } - if let Some(ref glob) = self.path_pattern_inverse { - ok &= !glob.is_match(path); + if let Some(re) = &self.path_pattern_inverse { + ok = ok && !re.is_match(path); } ok } @@ -140,31 +143,30 @@ impl TestFilter for FilterArgs { impl fmt::Display for FilterArgs { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut patterns = Vec::new(); - if let Some(ref p) = self.test_pattern { - patterns.push(format!("\tmatch-test: `{}`", p.as_str())); + if let Some(p) = &self.test_pattern { + writeln!(f, "\tmatch-test: `{}`", p.as_str())?; } - if let Some(ref p) = self.test_pattern_inverse { - patterns.push(format!("\tno-match-test: `{}`", p.as_str())); + if let Some(p) = &self.test_pattern_inverse { + writeln!(f, "\tno-match-test: `{}`", p.as_str())?; } - if let Some(ref p) = self.contract_pattern { - patterns.push(format!("\tmatch-contract: `{}`", p.as_str())); + if let Some(p) = &self.contract_pattern { + writeln!(f, "\tmatch-contract: `{}`", p.as_str())?; } - if let Some(ref p) = self.contract_pattern_inverse { - patterns.push(format!("\tno-match-contract: `{}`", p.as_str())); + if let Some(p) = &self.contract_pattern_inverse { + writeln!(f, "\tno-match-contract: `{}`", p.as_str())?; } - if let Some(ref p) = self.path_pattern { - patterns.push(format!("\tmatch-path: `{}`", p.as_str())); + if let Some(p) = &self.path_pattern { + writeln!(f, "\tmatch-path: `{}`", p.as_str())?; } - if let Some(ref p) = self.path_pattern_inverse { - patterns.push(format!("\tno-match-path: `{}`", p.as_str())); + if let Some(p) = &self.path_pattern_inverse { + writeln!(f, "\tno-match-path: `{}`", p.as_str())?; } - write!(f, "{}", patterns.join("\n")) + Ok(()) } } /// A filter that combines all command line arguments and the paths of the current projects -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ProjectPathsAwareFilter { args_filter: FilterArgs, paths: ProjectPathsConfig, @@ -173,12 +175,17 @@ pub struct ProjectPathsAwareFilter { // === impl ProjectPathsAwareFilter === impl ProjectPathsAwareFilter { - /// Returns the CLI arguments + /// Returns true if the filter is empty. + pub fn is_empty(&self) -> bool { + self.args_filter.is_empty() + } + + /// Returns the CLI arguments. pub fn args(&self) -> &FilterArgs { &self.args_filter } - /// Returns the CLI arguments mutably + /// Returns the CLI arguments mutably. pub fn args_mut(&mut self) -> &mut FilterArgs { &mut self.args_filter } @@ -189,24 +196,25 @@ impl FileFilter for ProjectPathsAwareFilter { /// /// If no file regex is set this returns true if the file ends with `.t.sol`, see /// [FoundryPathExr::is_sol_test()] - fn is_match(&self, file: &Path) -> bool { + fn is_match(&self, mut file: &Path) -> bool { + file = file.strip_prefix(&self.paths.root).unwrap_or(file); self.args_filter.is_match(file) } } impl TestFilter for ProjectPathsAwareFilter { - fn matches_test(&self, test_name: impl AsRef) -> bool { + fn matches_test(&self, test_name: &str) -> bool { self.args_filter.matches_test(test_name) } - fn matches_contract(&self, contract_name: impl AsRef) -> bool { + fn matches_contract(&self, contract_name: &str) -> bool { self.args_filter.matches_contract(contract_name) } - fn matches_path(&self, path: impl AsRef) -> bool { - let path = path.as_ref(); + fn matches_path(&self, mut path: &Path) -> bool { // we don't want to test files that belong to a library - self.args_filter.matches_path(path) && !self.paths.has_library_ancestor(Path::new(path)) + path = path.strip_prefix(&self.paths.root).unwrap_or(path); + self.args_filter.matches_path(path) && !self.paths.has_library_ancestor(path) } } diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index e9e822214974a..ed0c1305cb7bc 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -5,24 +5,21 @@ use eyre::Result; use forge::{ decode::decode_console_logs, gas_report::GasReport, - inspectors::CheatsConfig, - result::{SuiteResult, TestResult, TestStatus}, - traces::{ - identifier::{EtherscanIdentifier, LocalTraceIdentifier, SignaturesIdentifier}, - CallTraceDecoderBuilder, TraceKind, - }, - MultiContractRunner, MultiContractRunnerBuilder, TestOptions, TestOptionsBuilder, + multi_runner::matches_contract, + result::{SuiteResult, TestOutcome, TestStatus}, + traces::{identifier::SignaturesIdentifier, CallTraceDecoderBuilder, TraceKind}, + MultiContractRunner, MultiContractRunnerBuilder, TestFilter, TestOptions, TestOptionsBuilder, }; use foundry_cli::{ opts::CoreBuildArgs, utils::{self, LoadConfig}, }; use foundry_common::{ - compact_to_contract, - compile::{self, ContractSources, ProjectCompiler}, + compile::{ContractSources, ProjectCompiler}, evm::EvmArgs, - get_contract_name, get_file_name, shell, + shell, }; +use foundry_compilers::{artifacts::output_selection::OutputSelection, utils::source_files_iter}; use foundry_config::{ figment, figment::{ @@ -31,9 +28,15 @@ use foundry_config::{ }, get_available_profiles, Config, }; -use foundry_debugger::DebuggerBuilder; +use foundry_debugger::Debugger; +use foundry_evm::traces::identifier::TraceIdentifiers; use regex::Regex; -use std::{collections::BTreeMap, fs, sync::mpsc::channel, time::Duration}; +use std::{ + collections::{BTreeMap, BTreeSet}, + path::PathBuf, + sync::{mpsc::channel, Arc}, + time::Instant, +}; use watchexec::config::{InitConfig, RuntimeConfig}; use yansi::Paint; @@ -42,13 +45,14 @@ mod summary; use summary::TestSummaryReporter; pub use filter::FilterArgs; +use forge::traces::render_trace_arena; // Loads project's figment and merges the build cli arguments into it foundry_config::merge_impl_figment_convert!(TestArgs, opts, evm_opts); /// CLI arguments for `forge test`. -#[derive(Debug, Clone, Parser)] -#[clap(next_help_heading = "Test options")] +#[derive(Clone, Debug, Parser)] +#[command(next_help_heading = "Test options")] pub struct TestArgs { /// Run a test in the debugger. /// @@ -65,58 +69,62 @@ pub struct TestArgs { /// If the fuzz test does not fail, it will open the debugger on the last fuzz case. /// /// For more fine-grained control of which fuzz case is run, see forge run. - #[clap(long, value_name = "TEST_FUNCTION")] + #[arg(long, value_name = "TEST_FUNCTION")] debug: Option, /// Print a gas report. - #[clap(long, env = "FORGE_GAS_REPORT")] + #[arg(long, env = "FORGE_GAS_REPORT")] gas_report: bool, /// Exit with code 0 even if a test fails. - #[clap(long, env = "FORGE_ALLOW_FAILURE")] + #[arg(long, env = "FORGE_ALLOW_FAILURE")] allow_failure: bool, /// Output test results in JSON format. - #[clap(long, short, help_heading = "Display options")] + #[arg(long, short, help_heading = "Display options")] json: bool, - /// Stop running tests after the first failure - #[clap(long)] + /// Stop running tests after the first failure. + #[arg(long)] pub fail_fast: bool, - /// The Etherscan (or equivalent) API key - #[clap(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")] + /// The Etherscan (or equivalent) API key. + #[arg(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")] etherscan_api_key: Option, - /// List tests instead of running them - #[clap(long, short, help_heading = "Display options")] + /// List tests instead of running them. + #[arg(long, short, help_heading = "Display options")] list: bool, /// Set seed used to generate randomness during your fuzz runs. - #[clap(long)] + #[arg(long)] pub fuzz_seed: Option, - #[clap(long, env = "FOUNDRY_FUZZ_RUNS", value_name = "RUNS")] + #[arg(long, env = "FOUNDRY_FUZZ_RUNS", value_name = "RUNS")] pub fuzz_runs: Option, - #[clap(flatten)] + /// File to rerun fuzz failures from. + #[arg(long)] + pub fuzz_input_file: Option, + + #[command(flatten)] filter: FilterArgs, - #[clap(flatten)] + #[command(flatten)] evm_opts: EvmArgs, - #[clap(flatten)] + #[command(flatten)] opts: CoreBuildArgs, - #[clap(flatten)] + #[command(flatten)] pub watch: WatchArgs, - /// Print test summary table - #[clap(long, help_heading = "Display options")] + /// Print test summary table. + #[arg(long, help_heading = "Display options")] pub summary: bool, - /// Print detailed test summary table - #[clap(long, help_heading = "Display options")] + /// Print detailed test summary table. + #[arg(long, help_heading = "Display options", requires = "summary")] pub detailed: bool, } @@ -132,6 +140,72 @@ impl TestArgs { self.execute_tests().await } + /// Returns sources which include any tests to be executed. + /// If no filters are provided, sources are filtered by existence of test/invariant methods in + /// them, If filters are provided, sources are additionaly filtered by them. + pub fn get_sources_to_compile( + &self, + config: &Config, + filter: &ProjectPathsAwareFilter, + ) -> Result> { + let mut project = config.create_project(true, true)?; + project.solc_config.settings.output_selection = + OutputSelection::common_output_selection(["abi".to_string()]); + let output = project.compile()?; + + if output.has_compiler_errors() { + println!("{}", output); + eyre::bail!("Compilation failed"); + } + + // ABIs of all sources + let abis = output + .into_artifacts() + .filter_map(|(id, artifact)| artifact.abi.map(|abi| (id, abi))) + .collect::>(); + + // Filter sources by their abis and contract names. + let mut test_sources = abis + .iter() + .filter(|(id, abi)| matches_contract(id, abi, filter)) + .map(|(id, _)| id.source.clone()) + .collect::>(); + + if test_sources.is_empty() { + if filter.is_empty() { + println!( + "No tests found in project! \ + Forge looks for functions that starts with `test`." + ); + } else { + println!("No tests match the provided pattern:"); + print!("{filter}"); + + // Try to suggest a test when there's no match + if let Some(test_pattern) = &filter.args().test_pattern { + let test_name = test_pattern.as_str(); + let candidates = abis + .into_iter() + .filter(|(id, _)| { + filter.matches_path(&id.source) && filter.matches_contract(&id.name) + }) + .flat_map(|(_, abi)| abi.functions.into_keys()) + .collect::>(); + if let Some(suggestion) = utils::did_you_mean(test_name, candidates).pop() { + println!("\nDid you mean `{suggestion}`?"); + } + } + } + + eyre::bail!("No tests to run"); + } + + // Always recompile all sources to ensure that `getCode` cheatcode can use any artifact. + test_sources.extend(source_files_iter(project.paths.sources)); + + Ok(test_sources) + } + /// Executes all the tests in the project. /// /// This will trigger the build process first. On success all test contracts that match the @@ -142,14 +216,19 @@ impl TestArgs { // Merge all configs let (mut config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; - let mut filter = self.filter(&config); - - trace!(target: "forge::test", ?filter, "using filter"); + // Explicitly enable isolation for gas reports for more correct gas accounting + if self.gas_report { + evm_opts.isolate = true; + } else { + // Do not collect gas report traces if gas report is not enabled. + config.fuzz.gas_report_samples = 0; + config.invariant.gas_report_samples = 0; + } - // Set up the project + // Set up the project. let mut project = config.project()?; - // install missing dependencies + // Install missing dependencies. if install::install_missing_dependencies(&mut config, self.build_args().silent) && config.auto_detect_remappings { @@ -158,21 +237,24 @@ impl TestArgs { project = config.project()?; } - let compiler = ProjectCompiler::default(); - let output = match (config.sparse_mode, self.opts.silent | self.json) { - (false, false) => compiler.compile(&project), - (true, false) => compiler.compile_sparse(&project, filter.clone()), - (false, true) => compile::suppress_compile(&project), - (true, true) => compile::suppress_compile_sparse(&project, filter.clone()), - }?; - // Create test options from general project settings - // and compiler output + let mut filter = self.filter(&config); + trace!(target: "forge::test", ?filter, "using filter"); + + let sources_to_compile = self.get_sources_to_compile(&config, &filter)?; + + let compiler = ProjectCompiler::new() + .quiet_if(self.json || self.opts.silent) + .files(sources_to_compile); + + let output = compiler.compile(&project)?; + + // Create test options from general project settings and compiler output. let project_root = &project.paths.root; let toml = config.get_config_path(); let profiles = get_available_profiles(toml)?; let test_options: TestOptions = TestOptionsBuilder::default() - .fuzz(config.fuzz) + .fuzz(config.fuzz.clone()) .invariant(config.invariant) .profiles(profiles) .build(&output, project_root)?; @@ -188,189 +270,287 @@ impl TestArgs { // Prepare the test builder let should_debug = self.debug.is_some(); - let mut runner_builder = MultiContractRunnerBuilder::default() + // Clone the output only if we actually need it later for the debugger. + let output_clone = should_debug.then(|| output.clone()); + + let config = Arc::new(config); + + let runner = MultiContractRunnerBuilder::new(config.clone()) .set_debug(should_debug) .initial_balance(evm_opts.initial_balance) .evm_spec(config.evm_spec_id()) .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) - .with_cheats_config(CheatsConfig::new(&config, evm_opts.clone())) - .with_test_options(test_options.clone()); - - let mut runner = runner_builder.clone().build( - project_root, - output.clone(), - env.clone(), - evm_opts.clone(), - )?; + .with_test_options(test_options) + .enable_isolation(evm_opts.isolate) + .build(project_root, output, env, evm_opts)?; - if should_debug { - filter.args_mut().test_pattern = self.debug.clone(); - let num_filtered = runner.matching_test_function_count(&filter); - if num_filtered != 1 { + if let Some(debug_test_pattern) = &self.debug { + let test_pattern = &mut filter.args_mut().test_pattern; + if test_pattern.is_some() { eyre::bail!( - "{num_filtered} tests matched your criteria, but exactly 1 test must match in order to run the debugger.\n\n\ - Use --match-contract and --match-path to further limit the search." + "Cannot specify both --debug and --match-test. \ + Use --match-contract and --match-path to further limit the search instead." ); } - let test_funcs = runner.get_matching_test_functions(&filter); - // if we debug a fuzz test, we should not collect data on the first run - if !test_funcs.first().expect("matching function exists").inputs.is_empty() { - runner_builder = runner_builder.set_debug(false); - runner = runner_builder.clone().build( - project_root, - output.clone(), - env.clone(), - evm_opts.clone(), - )?; + *test_pattern = Some(debug_test_pattern.clone()); + } + + let outcome = self.run_tests(runner, config, verbosity, &filter).await?; + + if should_debug { + // Get first non-empty suite result. We will have only one such entry + let Some((suite_result, test_result)) = outcome + .results + .iter() + .find(|(_, r)| !r.test_results.is_empty()) + .map(|(_, r)| (r, r.test_results.values().next().unwrap())) + else { + return Err(eyre::eyre!("no tests were executed")); + }; + + let sources = ContractSources::from_project_output( + output_clone.as_ref().unwrap(), + project.root(), + &suite_result.libraries, + )?; + + // Run the debugger. + let mut builder = Debugger::builder() + .debug_arenas(test_result.debug.as_slice()) + .sources(sources) + .breakpoints(test_result.breakpoints.clone()); + if let Some(decoder) = &outcome.last_run_decoder { + builder = builder.decoder(decoder); } + let mut debugger = builder.build(); + debugger.try_run()?; + } + + Ok(outcome) + } + + /// Run all tests that matches the filter predicate from a test runner + pub async fn run_tests( + &self, + mut runner: MultiContractRunner, + config: Arc, + verbosity: u8, + filter: &ProjectPathsAwareFilter, + ) -> eyre::Result { + if self.list { + return list(runner, filter, self.json); + } + + trace!(target: "forge::test", "running all tests"); + + let num_filtered = runner.matching_test_functions(filter).count(); + if self.debug.is_some() && num_filtered != 1 { + eyre::bail!( + "{num_filtered} tests matched your criteria, but exactly 1 test must match in order to run the debugger.\n\n\ + Use --match-contract and --match-path to further limit the search.\n\ + Filter used:\n{filter}" + ); + } + + if self.json { + let results = runner.test_collect(filter); + println!("{}", serde_json::to_string(&results)?); + return Ok(TestOutcome::new(results, self.allow_failure)); } - let known_contracts = runner.known_contracts.clone(); - let mut local_identifier = LocalTraceIdentifier::new(&known_contracts); let remote_chain_id = runner.evm_opts.get_remote_chain_id(); - let outcome = self - .run_tests(runner, config.clone(), verbosity, filter.clone(), test_options.clone()) - .await?; + // Run tests. + let (tx, rx) = channel::<(String, SuiteResult)>(); + let timer = Instant::now(); + let handle = tokio::task::spawn_blocking({ + let filter = filter.clone(); + move || runner.test(&filter, tx) + }); - if should_debug { - let tests = outcome.clone().into_tests(); - let mut decoders = Vec::new(); - for test in tests { - let mut result = test.result; - // Identify addresses in each trace - let mut builder = CallTraceDecoderBuilder::new() - .with_labels(result.labeled_addresses.clone()) - .with_events(local_identifier.events().cloned()) - .with_verbosity(verbosity); - - // Signatures are of no value for gas reports - if !self.gas_report { - let sig_identifier = - SignaturesIdentifier::new(Config::foundry_cache_dir(), config.offline)?; - builder = builder.with_signature_identifier(sig_identifier.clone()); + let mut gas_report = self + .gas_report + .then(|| GasReport::new(config.gas_reports.clone(), config.gas_reports_ignore.clone())); + + let mut outcome = TestOutcome::empty(self.allow_failure); + + let mut any_test_failed = false; + for (contract_name, suite_result) in rx { + let tests = &suite_result.test_results; + + // Set up trace identifiers. + let known_contracts = suite_result.known_contracts.clone(); + let mut identifier = TraceIdentifiers::new().with_local(&known_contracts); + + // Avoid using etherscan for gas report as we decode more traces and this will be + // expensive. + if !self.gas_report { + identifier = identifier.with_etherscan(&config, remote_chain_id)?; + } + + // Build the trace decoder. + let mut builder = CallTraceDecoderBuilder::new() + .with_known_contracts(&known_contracts) + .with_verbosity(verbosity); + // Signatures are of no value for gas reports. + if !self.gas_report { + builder = builder.with_signature_identifier(SignaturesIdentifier::new( + Config::foundry_cache_dir(), + config.offline, + )?); + } + let mut decoder = builder.build(); + + // We identify addresses if we're going to print *any* trace or gas report. + let identify_addresses = verbosity >= 3 || self.gas_report || self.debug.is_some(); + + // Print suite header. + println!(); + for warning in suite_result.warnings.iter() { + eprintln!("{} {warning}", "Warning:".yellow().bold()); + } + if !tests.is_empty() { + let len = tests.len(); + let tests = if len > 1 { "tests" } else { "test" }; + println!("Ran {len} {tests} for {contract_name}"); + } + + // Process individual test results, printing logs and traces when necessary. + for (name, result) in tests { + shell::println(result.short_result(name))?; + + // We only display logs at level 2 and above + if verbosity >= 2 { + // We only decode logs from Hardhat and DS-style console events + let console_logs = decode_console_logs(&result.logs); + if !console_logs.is_empty() { + println!("Logs:"); + for log in console_logs { + println!(" {log}"); + } + println!(); + } } - let mut decoder = builder.build(); - - if !result.traces.is_empty() { - // Set up identifiers - // Do not re-query etherscan for contracts that you've already queried today. - let mut etherscan_identifier = - EtherscanIdentifier::new(&config, remote_chain_id)?; - - // Decode the traces - for (kind, trace) in &mut result.traces { - decoder.identify(trace, &mut local_identifier); - decoder.identify(trace, &mut etherscan_identifier); - - let should_include = match kind { - // At verbosity level 3, we only display traces for failed tests - // At verbosity level 4, we also display the setup trace for failed - // tests At verbosity level 5, we display - // all traces for all tests - TraceKind::Setup => { - (verbosity >= 5) || - (verbosity == 4 && result.status == TestStatus::Failure) - } - TraceKind::Execution => { - verbosity > 3 || - (verbosity == 3 && result.status == TestStatus::Failure) - } - _ => false, - }; + // We shouldn't break out of the outer loop directly here so that we finish + // processing the remaining tests and print the suite summary. + any_test_failed |= result.status == TestStatus::Failure; + + if result.traces.is_empty() { + continue; + } - // We decode the trace if we either need to build a gas report or we need - // to print it - if should_include || self.gas_report { - decoder.decode(trace).await; + // Clear the addresses and labels from previous runs. + decoder.clear_addresses(); + decoder + .labels + .extend(result.labeled_addresses.iter().map(|(k, v)| (*k, v.clone()))); + + // Identify addresses and decode traces. + let mut decoded_traces = Vec::with_capacity(result.traces.len()); + for (kind, arena) in &result.traces { + if identify_addresses { + decoder.identify(arena, &mut identifier); + } + + // verbosity: + // - 0..3: nothing + // - 3: only display traces for failed tests + // - 4: also display the setup trace for failed tests + // - 5..: display all traces for all tests + let should_include = match kind { + TraceKind::Execution => { + (verbosity == 3 && result.status.is_failure()) || verbosity >= 4 + } + TraceKind::Setup => { + (verbosity == 4 && result.status.is_failure()) || verbosity >= 5 } + TraceKind::Deployment => false, + }; + + if should_include { + decoded_traces.push(render_trace_arena(arena, &decoder).await?); } } - decoders.push(decoder); - } + if !decoded_traces.is_empty() { + shell::println("Traces:")?; + for trace in &decoded_traces { + shell::println(trace)?; + } + } + + if let Some(gas_report) = &mut gas_report { + gas_report + .analyze(result.traces.iter().map(|(_, arena)| arena), &decoder) + .await; + + for trace in result.gas_report_traces.iter() { + decoder.clear_addresses(); - let mut sources: ContractSources = Default::default(); - for (id, artifact) in output.into_artifacts() { - // Sources are only required for the debugger, but it *might* mean that there's - // something wrong with the build and/or artifacts. - if let Some(source) = artifact.source_file() { - let abs_path = source - .ast - .ok_or_else(|| eyre::eyre!("Source from artifact has no AST."))? - .absolute_path; - let source_code = fs::read_to_string(abs_path)?; - let contract = artifact.clone().into_contract_bytecode(); - let source_contract = compact_to_contract(contract)?; - sources - .0 - .entry(id.clone().name) - .or_default() - .insert(source.id, (source_code, source_contract)); + // Re-execute setup and deployment traces to collect identities created in + // setUp and constructor. + for (kind, arena) in &result.traces { + if !matches!(kind, TraceKind::Execution) { + decoder.identify(arena, &mut identifier); + } + } + + for arena in trace { + decoder.identify(arena, &mut identifier); + gas_report.analyze([arena], &decoder).await; + } + } } } - let test = outcome.clone().into_tests().next().unwrap(); - let result = test.result; - // Run the debugger - let mut debugger = DebuggerBuilder::new() - // TODO: `Option::as_slice` in 1.75 - .debug_arenas(result.debug.as_ref().map(core::slice::from_ref).unwrap_or_default()) - .decoders(&decoders) - .sources(sources) - .breakpoints(result.breakpoints) - .build()?; - debugger.try_run()?; + // Print suite summary. + shell::println(suite_result.summary())?; + + // Add the suite result to the outcome. + outcome.results.insert(contract_name, suite_result); + outcome.last_run_decoder = Some(decoder); + + // Stop processing the remaining suites if any test failed and `fail_fast` is set. + if self.fail_fast && any_test_failed { + break; + } } + let duration = timer.elapsed(); - Ok(outcome) - } + trace!(target: "forge::test", len=outcome.results.len(), %any_test_failed, "done with results"); - /// Run all tests that matches the filter predicate from a test runner - pub async fn run_tests( - &self, - mut runner: MultiContractRunner, - config: Config, - verbosity: u8, - mut filter: ProjectPathsAwareFilter, - test_options: TestOptions, - ) -> eyre::Result { - if self.debug.is_some() { - filter.args_mut().test_pattern = self.debug.clone(); - // Run the test - let results = runner.test(&filter, None, test_options).await; - - Ok(TestOutcome::new(results, self.allow_failure)) - } else if self.list { - list(runner, filter, self.json) - } else { - if self.detailed && !self.summary { - return Err(eyre::eyre!( - "Missing `--summary` option in your command. You must pass it along with the `--detailed` option to view detailed test summary." - )); + if let Some(gas_report) = gas_report { + let finalized = gas_report.finalize(); + shell::println(&finalized)?; + outcome.gas_report = Some(finalized); + } + + if !outcome.results.is_empty() { + shell::println(outcome.summary(duration))?; + + if self.summary { + let mut summary_table = TestSummaryReporter::new(self.detailed); + shell::println("\n\nTest Summary:")?; + summary_table.print_summary(&outcome); } - test( - config, - runner, - verbosity, - filter, - self.json, - self.allow_failure, - test_options, - self.gas_report, - self.fail_fast, - self.summary, - self.detailed, - ) - .await } + + // Reattach the task. + if let Err(e) = handle.await { + match e.try_into_panic() { + Ok(payload) => std::panic::resume_unwind(payload), + Err(e) => return Err(e.into()), + } + } + + Ok(outcome) } /// Returns the flattened [`FilterArgs`] arguments merged with [`Config`]. pub fn filter(&self, config: &Config) -> ProjectPathsAwareFilter { - self.filter.merge_with_config(config) + self.filter.clone().merge_with_config(config) } /// Returns whether `BuildArgs` was configured with `--watch` @@ -403,9 +583,14 @@ impl Provider for TestArgs { if let Some(fuzz_runs) = self.fuzz_runs { fuzz_dict.insert("runs".to_string(), fuzz_runs.into()); } + if let Some(fuzz_input_file) = self.fuzz_input_file.clone() { + fuzz_dict.insert("failure_persist_file".to_string(), fuzz_input_file.into()); + } dict.insert("fuzz".to_string(), fuzz_dict.into()); - if let Some(ref etherscan_api_key) = self.etherscan_api_key { + if let Some(etherscan_api_key) = + self.etherscan_api_key.as_ref().filter(|s| !s.trim().is_empty()) + { dict.insert("etherscan_api_key".to_string(), etherscan_api_key.to_string().into()); } @@ -413,163 +598,13 @@ impl Provider for TestArgs { } } -/// The result of a single test -#[derive(Debug, Clone)] -pub struct Test { - /// The identifier of the artifact/contract in the form of `:` - pub artifact_id: String, - /// The signature of the solidity test - pub signature: String, - /// Result of the executed solidity test - pub result: TestResult, -} - -impl Test { - pub fn gas_used(&self) -> u64 { - self.result.kind.report().gas() - } - - /// Returns the contract name of the artifact id - pub fn contract_name(&self) -> &str { - get_contract_name(&self.artifact_id) - } - - /// Returns the file name of the artifact id - pub fn file_name(&self) -> &str { - get_file_name(&self.artifact_id) - } -} - -/// Represents the bundled results of all tests -#[derive(Clone, Debug)] -pub struct TestOutcome { - /// Whether failures are allowed - pub allow_failure: bool, - /// Results for each suite of tests `contract -> SuiteResult` - pub results: BTreeMap, -} - -impl TestOutcome { - fn new(results: BTreeMap, allow_failure: bool) -> Self { - Self { results, allow_failure } - } - - /// Iterator over all succeeding tests and their names - pub fn successes(&self) -> impl Iterator { - self.tests().filter(|(_, t)| t.status == TestStatus::Success) - } - - /// Iterator over all failing tests and their names - pub fn failures(&self) -> impl Iterator { - self.tests().filter(|(_, t)| t.status == TestStatus::Failure) - } - - pub fn skips(&self) -> impl Iterator { - self.tests().filter(|(_, t)| t.status == TestStatus::Skipped) - } - - /// Iterator over all tests and their names - pub fn tests(&self) -> impl Iterator { - self.results.values().flat_map(|suite| suite.tests()) - } - - /// Returns an iterator over all `Test` - pub fn into_tests(self) -> impl Iterator { - self.results - .into_iter() - .flat_map(|(file, SuiteResult { test_results, .. })| { - test_results.into_iter().map(move |t| (file.clone(), t)) - }) - .map(|(artifact_id, (signature, result))| Test { artifact_id, signature, result }) - } - - /// Checks if there are any failures and failures are disallowed - pub fn ensure_ok(&self) -> Result<()> { - let failures = self.failures().count(); - if self.allow_failure || failures == 0 { - return Ok(()) - } - - if !shell::verbosity().is_normal() { - // skip printing and exit early - std::process::exit(1); - } - - println!(); - println!("Failing tests:"); - for (suite_name, suite) in self.results.iter() { - let failures = suite.failures().count(); - if failures == 0 { - continue - } - - let term = if failures > 1 { "tests" } else { "test" }; - println!("Encountered {failures} failing {term} in {suite_name}"); - for (name, result) in suite.failures() { - short_test_result(name, result); - } - println!(); - } - let successes = self.successes().count(); - println!( - "Encountered a total of {} failing tests, {} tests succeeded", - Paint::red(failures.to_string()), - Paint::green(successes.to_string()) - ); - - std::process::exit(1); - } - - pub fn duration(&self) -> Duration { - self.results - .values() - .fold(Duration::ZERO, |acc, SuiteResult { duration, .. }| acc + *duration) - } - - pub fn summary(&self) -> String { - let failed = self.failures().count(); - let result = if failed == 0 { Paint::green("ok") } else { Paint::red("FAILED") }; - format!( - "Test result: {}. {} passed; {} failed; {} skipped; finished in {:.2?}", - result, - Paint::green(self.successes().count()), - Paint::red(failed), - Paint::yellow(self.skips().count()), - self.duration() - ) - } -} - -fn short_test_result(name: &str, result: &TestResult) { - println!("{result} {name} {}", result.kind.report()); -} - -/// Formats the aggregated summary of all test suites into a string (for printing). -fn format_aggregated_summary( - num_test_suites: usize, - total_passed: usize, - total_failed: usize, - total_skipped: usize, -) -> String { - let total_tests = total_passed + total_failed + total_skipped; - format!( - " \nRan {} test suites: {} tests passed, {} failed, {} skipped ({} total tests)", - num_test_suites, - Paint::green(total_passed), - Paint::red(total_failed), - Paint::yellow(total_skipped), - total_tests - ) -} - /// Lists all matching tests fn list( runner: MultiContractRunner, - filter: ProjectPathsAwareFilter, + filter: &ProjectPathsAwareFilter, json: bool, ) -> Result { - let results = runner.list(&filter); + let results = runner.list(filter); if json { println!("{}", serde_json::to_string(&results)?); @@ -582,210 +617,14 @@ fn list( } } } - Ok(TestOutcome::new(BTreeMap::new(), false)) -} - -/// Runs all the tests -#[allow(clippy::too_many_arguments)] -async fn test( - config: Config, - mut runner: MultiContractRunner, - verbosity: u8, - filter: ProjectPathsAwareFilter, - json: bool, - allow_failure: bool, - test_options: TestOptions, - gas_reporting: bool, - fail_fast: bool, - summary: bool, - detailed: bool, -) -> Result { - trace!(target: "forge::test", "running all tests"); - - if runner.matching_test_function_count(&filter) == 0 { - let filter_str = filter.to_string(); - if filter_str.is_empty() { - println!( - "\nNo tests found in project! Forge looks for functions that starts with `test`." - ); - } else { - println!("\nNo tests match the provided pattern:"); - println!("{filter_str}"); - // Try to suggest a test when there's no match - if let Some(ref test_pattern) = filter.args().test_pattern { - let test_name = test_pattern.as_str(); - let candidates = runner.get_tests(&filter); - if let Some(suggestion) = utils::did_you_mean(test_name, candidates).pop() { - println!("\nDid you mean `{suggestion}`?"); - } - } - } - } - - if json { - let results = runner.test(filter, None, test_options).await; - println!("{}", serde_json::to_string(&results)?); - return Ok(TestOutcome::new(results, allow_failure)) - } - - // Set up identifiers - let known_contracts = runner.known_contracts.clone(); - let mut local_identifier = LocalTraceIdentifier::new(&known_contracts); - let remote_chain_id = runner.evm_opts.get_remote_chain_id(); - // Do not re-query etherscan for contracts that you've already queried today. - let mut etherscan_identifier = EtherscanIdentifier::new(&config, remote_chain_id)?; - - // Set up test reporter channel - let (tx, rx) = channel::<(String, SuiteResult)>(); - - // Run tests - let handle = - tokio::task::spawn(async move { runner.test(filter, Some(tx), test_options).await }); - - let mut results = BTreeMap::new(); - let mut gas_report = GasReport::new(config.gas_reports, config.gas_reports_ignore); - let sig_identifier = SignaturesIdentifier::new(Config::foundry_cache_dir(), config.offline)?; - - let mut total_passed = 0; - let mut total_failed = 0; - let mut total_skipped = 0; - let mut suite_results: Vec = Vec::new(); - - 'outer: for (contract_name, suite_result) in rx { - results.insert(contract_name.clone(), suite_result.clone()); - - let mut tests = suite_result.test_results.clone(); - println!(); - for warning in suite_result.warnings.iter() { - eprintln!("{} {warning}", Paint::yellow("Warning:").bold()); - } - if !tests.is_empty() { - let term = if tests.len() > 1 { "tests" } else { "test" }; - println!("Running {} {term} for {contract_name}", tests.len()); - } - for (name, result) in &mut tests { - short_test_result(name, result); - - // If the test failed, we want to stop processing the rest of the tests - if fail_fast && result.status == TestStatus::Failure { - break 'outer - } - - // We only display logs at level 2 and above - if verbosity >= 2 { - // We only decode logs from Hardhat and DS-style console events - let console_logs = decode_console_logs(&result.logs); - if !console_logs.is_empty() { - println!("Logs:"); - for log in console_logs { - println!(" {log}"); - } - println!(); - } - } - - if result.traces.is_empty() { - continue - } - - // Identify addresses in each trace - let mut builder = CallTraceDecoderBuilder::new() - .with_labels(result.labeled_addresses.iter().map(|(a, s)| (*a, s.clone()))) - .with_events(local_identifier.events().cloned()) - .with_verbosity(verbosity); - - // Signatures are of no value for gas reports - if !gas_reporting { - builder = builder.with_signature_identifier(sig_identifier.clone()); - } - - let mut decoder = builder.build(); - - // Decode the traces - let mut decoded_traces = Vec::with_capacity(result.traces.len()); - for (kind, trace) in &mut result.traces { - decoder.identify(trace, &mut local_identifier); - decoder.identify(trace, &mut etherscan_identifier); - - // verbosity: - // - 0..3: nothing - // - 3: only display traces for failed tests - // - 4: also display the setup trace for failed tests - // - 5..: display all traces for all tests - let should_include = match kind { - TraceKind::Execution => { - (verbosity == 3 && result.status.is_failure()) || verbosity >= 4 - } - TraceKind::Setup => { - (verbosity == 4 && result.status.is_failure()) || verbosity >= 5 - } - TraceKind::Deployment => false, - }; - - // Decode the trace if we either need to build a gas report or we need to print it - if should_include || gas_reporting { - decoder.decode(trace).await; - } - - if should_include { - decoded_traces.push(trace.to_string()); - } - } - - if !decoded_traces.is_empty() { - println!("Traces:"); - decoded_traces.into_iter().for_each(|trace| println!("{trace}")); - } - - if gas_reporting { - gas_report.analyze(&result.traces); - } - } - let block_outcome = - TestOutcome::new([(contract_name.clone(), suite_result)].into(), allow_failure); - - total_passed += block_outcome.successes().count(); - total_failed += block_outcome.failures().count(); - total_skipped += block_outcome.skips().count(); - - println!("{}", block_outcome.summary()); - - if summary { - suite_results.push(block_outcome.clone()); - } - } - - if gas_reporting { - println!("{}", gas_report.finalize()); - } - - let num_test_suites = results.len(); - - if num_test_suites > 0 { - println!( - "{}", - format_aggregated_summary(num_test_suites, total_passed, total_failed, total_skipped) - ); - - if summary { - let mut summary_table = TestSummaryReporter::new(detailed); - println!("\n\nTest Summary:"); - summary_table.print_summary(suite_results); - } - } - - // reattach the task - let _results = handle.await?; - - trace!(target: "forge::test", "received {} results", results.len()); - - Ok(TestOutcome::new(results, allow_failure)) + Ok(TestOutcome::empty(false)) } #[cfg(test)] mod tests { use super::*; use foundry_config::Chain; + use foundry_test_utils::forgetest_async; #[test] fn watch_parse() { @@ -801,7 +640,7 @@ mod tests { // #[test] - fn issue_5913() { + fn fuzz_seed_exists() { let args: TestArgs = TestArgs::parse_from(["foundry-cli", "-vvv", "--gas-report", "--fuzz-seed", "0x10"]); assert!(args.fuzz_seed.is_some()); @@ -819,4 +658,62 @@ mod tests { test("--chain-id=1", Chain::mainnet()); test("--chain-id=42", Chain::from_id(42)); } + + forgetest_async!(gas_report_fuzz_invariant, |prj, _cmd| { + prj.insert_ds_test(); + prj.add_source( + "Contracts.sol", + r#" +//SPDX-license-identifier: MIT + +import "./test.sol"; + +contract Foo { + function foo() public {} +} + +contract Bar { + function bar() public {} +} + + +contract FooBarTest is DSTest { + Foo public targetContract; + + function setUp() public { + targetContract = new Foo(); + } + + function invariant_dummy() public { + assertTrue(true); + } + + function testFuzz_bar(uint256 _val) public { + (new Bar()).bar(); + } +} + "#, + ) + .unwrap(); + + let args = TestArgs::parse_from([ + "foundry-cli", + "--gas-report", + "--root", + &prj.root().to_string_lossy(), + "--silent", + ]); + + let outcome = args.run().await.unwrap(); + let gas_report = outcome.gas_report.unwrap(); + + assert_eq!(gas_report.contracts.len(), 3); + let call_cnts = gas_report + .contracts + .values() + .flat_map(|c| c.functions.values().flat_map(|f| f.values().map(|v| v.calls.len()))) + .collect::>(); + // assert that all functions were called at least 100 times + assert!(call_cnts.iter().all(|c| *c > 100)); + }); } diff --git a/crates/forge/bin/cmd/test/summary.rs b/crates/forge/bin/cmd/test/summary.rs index 5f8bd9650bd8b..561ea6b6824c6 100644 --- a/crates/forge/bin/cmd/test/summary.rs +++ b/crates/forge/bin/cmd/test/summary.rs @@ -48,64 +48,46 @@ impl TestSummaryReporter { Self { table, is_detailed } } - pub(crate) fn print_summary(&mut self, mut test_results: Vec) { - // Sort by suite name first - - // Using `sort_by_cached_key` so that the key extraction logic runs only once - test_results.sort_by_cached_key(|test_outcome| { - test_outcome - .results - .keys() - .next() - .and_then(|suite| suite.split(':').nth(1)) - .unwrap() - .to_string() - }); - + pub(crate) fn print_summary(&mut self, outcome: &TestOutcome) { // Traverse the test_results vector and build the table - for suite in &test_results { - for contract in suite.results.keys() { - let mut row = Row::new(); - let suite_name = contract.split(':').nth(1).unwrap(); - let suite_path = contract.split(':').nth(0).unwrap(); - - let passed = suite.successes().count(); - let mut passed_cell = Cell::new(passed).set_alignment(CellAlignment::Center); + for (contract, suite) in &outcome.results { + let mut row = Row::new(); + let (suite_path, suite_name) = contract.split_once(':').unwrap(); - let failed = suite.failures().count(); - let mut failed_cell = Cell::new(failed).set_alignment(CellAlignment::Center); + let passed = suite.successes().count(); + let mut passed_cell = Cell::new(passed).set_alignment(CellAlignment::Center); - let skipped = suite.skips().count(); - let mut skipped_cell = Cell::new(skipped).set_alignment(CellAlignment::Center); + let failed = suite.failures().count(); + let mut failed_cell = Cell::new(failed).set_alignment(CellAlignment::Center); - let duration = suite.duration(); + let skipped = suite.skips().count(); + let mut skipped_cell = Cell::new(skipped).set_alignment(CellAlignment::Center); - row.add_cell(Cell::new(suite_name)); + row.add_cell(Cell::new(suite_name)); - if passed > 0 { - passed_cell = passed_cell.fg(Color::Green); - } - row.add_cell(passed_cell); - - if failed > 0 { - failed_cell = failed_cell.fg(Color::Red); - } - row.add_cell(failed_cell); + if passed > 0 { + passed_cell = passed_cell.fg(Color::Green); + } + row.add_cell(passed_cell); - if skipped > 0 { - skipped_cell = skipped_cell.fg(Color::Yellow); - } - row.add_cell(skipped_cell); + if failed > 0 { + failed_cell = failed_cell.fg(Color::Red); + } + row.add_cell(failed_cell); - if self.is_detailed { - row.add_cell(Cell::new(suite_path)); - row.add_cell(Cell::new(format!("{:.2?}", duration).to_string())); - } + if skipped > 0 { + skipped_cell = skipped_cell.fg(Color::Yellow); + } + row.add_cell(skipped_cell); - self.table.add_row(row); + if self.is_detailed { + row.add_cell(Cell::new(suite_path)); + row.add_cell(Cell::new(format!("{:.2?}", suite.duration).to_string())); } + + self.table.add_row(row); } - // Print the summary table + println!("\n{}", self.table); } } diff --git a/crates/forge/bin/cmd/tree.rs b/crates/forge/bin/cmd/tree.rs index ccd260fd072d0..8133010253894 100644 --- a/crates/forge/bin/cmd/tree.rs +++ b/crates/forge/bin/cmd/tree.rs @@ -7,19 +7,19 @@ use foundry_compilers::{ }; /// CLI arguments for `forge tree`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct TreeArgs { /// Do not de-duplicate (repeats all shared dependencies) - #[clap(long)] + #[arg(long)] no_dedupe: bool, /// Character set to use in output. /// /// [possible values: utf8, ascii] - #[clap(long, default_value = "utf8")] + #[arg(long, default_value = "utf8")] charset: Charset, - #[clap(flatten)] + #[command(flatten)] opts: ProjectPathsArgs, } diff --git a/crates/forge/bin/cmd/update.rs b/crates/forge/bin/cmd/update.rs index 28b6429967473..05f8e0eb20d37 100644 --- a/crates/forge/bin/cmd/update.rs +++ b/crates/forge/bin/cmd/update.rs @@ -8,7 +8,7 @@ use foundry_config::{impl_figment_convert_basic, Config}; use std::path::PathBuf; /// CLI arguments for `forge update`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct UpdateArgs { /// The dependencies you want to update. dependencies: Vec, @@ -17,15 +17,15 @@ pub struct UpdateArgs { /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, /// Override the up-to-date check. - #[clap(short, long)] + #[arg(short, long)] force: bool, /// Recursively update submodules. - #[clap(short, long)] + #[arg(short, long)] recursive: bool, } impl_figment_convert_basic!(UpdateArgs); @@ -44,7 +44,7 @@ impl UpdateArgs { git.submodule_update(self.force, true, false, false, paths)?; // initialize submodules of each submodule recursively (otherwise direct submodule // dependencies will revert to last commit) - git.submodule_foreach(false, "git submodule update --init --progress --recursive ") + git.submodule_foreach(false, "git submodule update --init --progress --recursive") } } } diff --git a/crates/forge/bin/cmd/watch.rs b/crates/forge/bin/cmd/watch.rs index 8a9c9c983457f..1412cb15e4f66 100644 --- a/crates/forge/bin/cmd/watch.rs +++ b/crates/forge/bin/cmd/watch.rs @@ -15,13 +15,13 @@ use watchexec::{ Watchexec, }; -#[derive(Debug, Clone, Parser, Default)] -#[clap(next_help_heading = "Watch options")] +#[derive(Clone, Debug, Default, Parser)] +#[command(next_help_heading = "Watch options")] pub struct WatchArgs { /// Watch the given files or directories for changes. /// /// If no paths are provided, the source and test directories of the project are watched. - #[clap( + #[arg( long, short, num_args(0..), @@ -30,13 +30,13 @@ pub struct WatchArgs { pub watch: Option>, /// Do not restart the command while it's still running. - #[clap(long)] + #[arg(long)] pub no_restart: bool, /// Explicitly re-run all tests when a change is made. /// /// By default, only the tests of the last modified test file are executed. - #[clap(long)] + #[arg(long)] pub run_all: bool, /// File update debounce delay. @@ -52,7 +52,7 @@ pub struct WatchArgs { /// /// When using --poll mode, you'll want a larger duration, or risk /// overloading disk I/O. - #[clap(long, value_name = "DELAY")] + #[arg(long, value_name = "DELAY")] pub watch_delay: Option, } @@ -150,7 +150,7 @@ pub async fn watch_test(args: TestArgs) -> Result<()> { Ok(()) } -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] struct WatchTestState { /// the root directory of the project project_root: PathBuf, @@ -264,6 +264,8 @@ fn watch_command(mut args: Vec) -> Command { fn cmd_args(num: usize) -> Vec { clean_cmd_args(num, std::env::args().collect()) } + +#[instrument(level = "debug", ret)] fn clean_cmd_args(num: usize, mut cmd_args: Vec) -> Vec { if let Some(pos) = cmd_args.iter().position(|arg| arg == "--watch" || arg == "-w") { cmd_args.drain(pos..=(pos + num)); @@ -274,11 +276,12 @@ fn clean_cmd_args(num: usize, mut cmd_args: Vec) -> Vec { // this removes any `w` from concatenated short flags if let Some(pos) = cmd_args.iter().position(|arg| { fn contains_w_in_short(arg: &str) -> Option { - let mut iter = arg.chars(); - if iter.next()? != '-' { + let mut iter = arg.chars().peekable(); + if *iter.peek()? != '-' { return None } - if iter.next()? == '-' { + iter.next(); + if *iter.peek()? == '-' { return None } Some(iter.any(|c| c == 'w')) diff --git a/crates/forge/bin/main.rs b/crates/forge/bin/main.rs index f2d8e99f8fdcf..f598c1212e617 100644 --- a/crates/forge/bin/main.rs +++ b/crates/forge/bin/main.rs @@ -5,22 +5,29 @@ use clap::{CommandFactory, Parser}; use clap_complete::generate; use eyre::Result; use foundry_cli::{handler, utils}; +use foundry_evm::inspectors::cheatcodes::{set_execution_context, ForgeContext}; mod cmd; +use cmd::{cache::CacheSubcommands, generate::GenerateSubcommands, watch}; + mod opts; +use opts::{Forge, ForgeSubcommand}; -use cmd::{cache::CacheSubcommands, generate::GenerateSubcommands, watch}; -use opts::{Opts, Subcommands}; +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; fn main() -> Result<()> { - handler::install()?; + handler::install(); utils::load_dotenv(); utils::subscriber(); utils::enable_paint(); - let opts = Opts::parse(); - match opts.sub { - Subcommands::Test(cmd) => { + let opts = Forge::parse(); + init_execution_context(&opts.cmd); + + match opts.cmd { + ForgeSubcommand::Test(cmd) => { if cmd.is_watch() { utils::block_on(watch::watch_test(cmd)) } else { @@ -28,67 +35,69 @@ fn main() -> Result<()> { outcome.ensure_ok() } } - Subcommands::Script(cmd) => { + ForgeSubcommand::Script(cmd) => { // install the shell before executing the command foundry_common::shell::set_shell(foundry_common::shell::Shell::from_args( - cmd.opts.args.silent, + cmd.opts.silent, cmd.json, ))?; utils::block_on(cmd.run_script()) } - Subcommands::Coverage(cmd) => utils::block_on(cmd.run()), - Subcommands::Bind(cmd) => cmd.run(), - Subcommands::Build(cmd) => { + ForgeSubcommand::Coverage(cmd) => utils::block_on(cmd.run()), + ForgeSubcommand::Bind(cmd) => cmd.run(), + ForgeSubcommand::Build(cmd) => { if cmd.is_watch() { utils::block_on(watch::watch_build(cmd)) } else { cmd.run().map(|_| ()) } } - Subcommands::Debug(cmd) => utils::block_on(cmd.run()), - Subcommands::VerifyContract(args) => utils::block_on(args.run()), - Subcommands::VerifyCheck(args) => utils::block_on(args.run()), - Subcommands::Cache(cmd) => match cmd.sub { + ForgeSubcommand::Debug(cmd) => utils::block_on(cmd.run()), + ForgeSubcommand::VerifyContract(args) => utils::block_on(args.run()), + ForgeSubcommand::VerifyCheck(args) => utils::block_on(args.run()), + ForgeSubcommand::Clone(cmd) => utils::block_on(cmd.run()), + ForgeSubcommand::Cache(cmd) => match cmd.sub { CacheSubcommands::Clean(cmd) => cmd.run(), CacheSubcommands::Ls(cmd) => cmd.run(), }, - Subcommands::Create(cmd) => utils::block_on(cmd.run()), - Subcommands::Update(cmd) => cmd.run(), - Subcommands::Install(cmd) => cmd.run(), - Subcommands::Remove(cmd) => cmd.run(), - Subcommands::Remappings(cmd) => cmd.run(), - Subcommands::Init(cmd) => cmd.run(), - Subcommands::Completions { shell } => { - generate(shell, &mut Opts::command(), "forge", &mut std::io::stdout()); + ForgeSubcommand::Create(cmd) => utils::block_on(cmd.run()), + ForgeSubcommand::Update(cmd) => cmd.run(), + ForgeSubcommand::Install(cmd) => cmd.run(), + ForgeSubcommand::Remove(cmd) => cmd.run(), + ForgeSubcommand::Remappings(cmd) => cmd.run(), + ForgeSubcommand::Init(cmd) => cmd.run(), + ForgeSubcommand::Completions { shell } => { + generate(shell, &mut Forge::command(), "forge", &mut std::io::stdout()); Ok(()) } - Subcommands::GenerateFigSpec => { + ForgeSubcommand::GenerateFigSpec => { clap_complete::generate( clap_complete_fig::Fig, - &mut Opts::command(), + &mut Forge::command(), "forge", &mut std::io::stdout(), ); Ok(()) } - Subcommands::Clean { root } => { + ForgeSubcommand::Clean { root } => { let config = utils::load_config_with_root(root); - config.project()?.cleanup()?; + let project = config.project()?; + config.cleanup(&project)?; Ok(()) } - Subcommands::Snapshot(cmd) => { + ForgeSubcommand::Snapshot(cmd) => { if cmd.is_watch() { utils::block_on(watch::watch_snapshot(cmd)) } else { utils::block_on(cmd.run()) } } - Subcommands::Fmt(cmd) => cmd.run(), - Subcommands::Config(cmd) => cmd.run(), - Subcommands::Flatten(cmd) => cmd.run(), - Subcommands::Inspect(cmd) => cmd.run(), - Subcommands::Tree(cmd) => cmd.run(), - Subcommands::Geiger(cmd) => { + ForgeSubcommand::Fmt(cmd) => cmd.run(), + ForgeSubcommand::Config(cmd) => cmd.run(), + ForgeSubcommand::Flatten(cmd) => cmd.run(), + ForgeSubcommand::Inspect(cmd) => cmd.run(), + ForgeSubcommand::Tree(cmd) => cmd.run(), + ForgeSubcommand::Geiger(cmd) => { let check = cmd.check; let n = cmd.run()?; if check && n > 0 { @@ -96,10 +105,33 @@ fn main() -> Result<()> { } Ok(()) } - Subcommands::Doc(cmd) => cmd.run(), - Subcommands::Selectors { command } => utils::block_on(command.run()), - Subcommands::Generate(cmd) => match cmd.sub { + ForgeSubcommand::Doc(cmd) => cmd.run(), + ForgeSubcommand::Selectors { command } => utils::block_on(command.run()), + ForgeSubcommand::Generate(cmd) => match cmd.sub { GenerateSubcommands::Test(cmd) => cmd.run(), }, + ForgeSubcommand::VerifyBytecode(cmd) => utils::block_on(cmd.run()), } } + +/// Set the program execution context based on `forge` subcommand used. +/// The execution context can be set only once per program, and it can be checked by using +/// cheatcodes. +fn init_execution_context(subcommand: &ForgeSubcommand) { + let context = match subcommand { + ForgeSubcommand::Test(_) => ForgeContext::Test, + ForgeSubcommand::Coverage(_) => ForgeContext::Coverage, + ForgeSubcommand::Snapshot(_) => ForgeContext::Snapshot, + ForgeSubcommand::Script(cmd) => { + if cmd.broadcast { + ForgeContext::ScriptBroadcast + } else if cmd.resume { + ForgeContext::ScriptResume + } else { + ForgeContext::ScriptDryRun + } + } + _ => ForgeContext::Unknown, + }; + set_execution_context(context); +} diff --git a/crates/forge/bin/opts.rs b/crates/forge/bin/opts.rs index 28b5039811ce7..6ca78da0f44e1 100644 --- a/crates/forge/bin/opts.rs +++ b/crates/forge/bin/opts.rs @@ -1,25 +1,12 @@ use crate::cmd::{ - bind::BindArgs, - build::BuildArgs, - cache::CacheArgs, - config, coverage, - create::CreateArgs, - debug::DebugArgs, - doc::DocArgs, - flatten, - fmt::FmtArgs, - geiger, generate, - init::InitArgs, - inspect, - install::InstallArgs, - remappings::RemappingArgs, - remove::RemoveArgs, - script::ScriptArgs, - selectors::SelectorsSubcommands, - snapshot, test, tree, update, - verify::{VerifyArgs, VerifyCheckArgs}, + bind::BindArgs, build::BuildArgs, cache::CacheArgs, clone::CloneArgs, config, coverage, + create::CreateArgs, debug::DebugArgs, doc::DocArgs, flatten, fmt::FmtArgs, geiger, generate, + init::InitArgs, inspect, install::InstallArgs, remappings::RemappingArgs, remove::RemoveArgs, + selectors::SelectorsSubcommands, snapshot, test, tree, update, }; use clap::{Parser, Subcommand, ValueHint}; +use forge_script::ScriptArgs; +use forge_verify::{bytecode::VerifyBytecodeArgs, VerifyArgs, VerifyCheckArgs}; use std::path::PathBuf; const VERSION_MESSAGE: &str = concat!( @@ -31,23 +18,24 @@ const VERSION_MESSAGE: &str = concat!( ")" ); -#[derive(Debug, Parser)] -#[clap(name = "forge", version = VERSION_MESSAGE)] -pub struct Opts { - #[clap(subcommand)] - pub sub: Subcommands, -} - -#[derive(Debug, Subcommand)] -#[clap( - about = "Build, test, fuzz, debug and deploy Solidity contracts.", +/// Build, test, fuzz, debug and deploy Solidity contracts. +#[derive(Parser)] +#[command( + name = "forge", + version = VERSION_MESSAGE, after_help = "Find more information in the book: http://book.getfoundry.sh/reference/forge/forge.html", - next_display_order = None + next_display_order = None, )] +pub struct Forge { + #[command(subcommand)] + pub cmd: ForgeSubcommand, +} + +#[derive(Subcommand)] #[allow(clippy::large_enum_variant)] -pub enum Subcommands { +pub enum ForgeSubcommand { /// Run the project's tests. - #[clap(visible_alias = "t")] + #[command(visible_alias = "t")] Test(test::TestArgs), /// Run a smart contract as a script, building transactions that can be sent onchain. @@ -57,71 +45,74 @@ pub enum Subcommands { Coverage(coverage::CoverageArgs), /// Generate Rust bindings for smart contracts. - #[clap(alias = "bi")] + #[command(alias = "bi")] Bind(BindArgs), /// Build the project's smart contracts. - #[clap(visible_aliases = ["b", "compile"])] + #[command(visible_aliases = ["b", "compile"])] Build(BuildArgs), + /// Clone a contract from Etherscan. + Clone(CloneArgs), + /// Debugs a single smart contract as a script. - #[clap(visible_alias = "d")] + #[command(visible_alias = "d")] Debug(DebugArgs), /// Update one or multiple dependencies. /// /// If no arguments are provided, then all dependencies are updated. - #[clap(visible_alias = "u")] + #[command(visible_alias = "u")] Update(update::UpdateArgs), /// Install one or multiple dependencies. /// /// If no arguments are provided, then existing dependencies will be installed. - #[clap(visible_alias = "i")] + #[command(visible_alias = "i")] Install(InstallArgs), /// Remove one or multiple dependencies. - #[clap(visible_alias = "rm")] + #[command(visible_alias = "rm")] Remove(RemoveArgs), /// Get the automatically inferred remappings for the project. - #[clap(visible_alias = "re")] + #[command(visible_alias = "re")] Remappings(RemappingArgs), /// Verify smart contracts on Etherscan. - #[clap(visible_alias = "v")] + #[command(visible_alias = "v")] VerifyContract(VerifyArgs), /// Check verification status on Etherscan. - #[clap(visible_alias = "vc")] + #[command(visible_alias = "vc")] VerifyCheck(VerifyCheckArgs), /// Deploy a smart contract. - #[clap(visible_alias = "c")] + #[command(visible_alias = "c")] Create(CreateArgs), /// Create a new Forge project. Init(InitArgs), /// Generate shell completions script. - #[clap(visible_alias = "com")] + #[command(visible_alias = "com")] Completions { - #[clap(value_enum)] + #[arg(value_enum)] shell: clap_complete::Shell, }, /// Generate Fig autocompletion spec. - #[clap(visible_alias = "fig")] + #[command(visible_alias = "fig")] GenerateFigSpec, /// Remove the build artifacts and cache directories. - #[clap(visible_alias = "cl")] + #[command(visible_alias = "cl")] Clean { /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, }, @@ -129,26 +120,26 @@ pub enum Subcommands { Cache(CacheArgs), /// Create a snapshot of each test's gas usage. - #[clap(visible_alias = "s")] + #[command(visible_alias = "s")] Snapshot(snapshot::SnapshotArgs), /// Display the current config. - #[clap(visible_alias = "co")] + #[command(visible_alias = "co")] Config(config::ConfigArgs), /// Flatten a source file and all of its imports into one file. - #[clap(visible_alias = "f")] + #[command(visible_alias = "f")] Flatten(flatten::FlattenArgs), /// Format Solidity source files. Fmt(FmtArgs), /// Get specialized information about a smart contract. - #[clap(visible_alias = "in")] + #[command(visible_alias = "in")] Inspect(inspect::InspectArgs), /// Display a tree visualization of the project's dependency graph. - #[clap(visible_alias = "tr")] + #[command(visible_alias = "tr")] Tree(tree::TreeArgs), /// Detects usage of unsafe cheat codes in a project and its dependencies. @@ -158,12 +149,27 @@ pub enum Subcommands { Doc(DocArgs), /// Function selector utilities - #[clap(visible_alias = "se")] + #[command(visible_alias = "se")] Selectors { - #[clap(subcommand)] + #[command(subcommand)] command: SelectorsSubcommands, }, /// Generate scaffold files. Generate(generate::GenerateArgs), + + /// Verify the deployed bytecode against its source. + #[clap(visible_alias = "vb")] + VerifyBytecode(VerifyBytecodeArgs), +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::CommandFactory; + + #[test] + fn verify_cli() { + Forge::command().debug_assert(); + } } diff --git a/crates/forge/src/coverage.rs b/crates/forge/src/coverage.rs index 2561de9aee086..19724bc2fa9a4 100644 --- a/crates/forge/src/coverage.rs +++ b/crates/forge/src/coverage.rs @@ -1,8 +1,14 @@ //! Coverage reports. use comfy_table::{presets::ASCII_MARKDOWN, Attribute, Cell, Color, Row, Table}; +use evm_disassembler::disassemble_bytes; +use foundry_common::fs; pub use foundry_evm::coverage::*; -use std::io::Write; +use std::{ + collections::{hash_map, HashMap}, + io::Write, + path::PathBuf, +}; /// A coverage reporter. pub trait CoverageReporter { @@ -111,7 +117,9 @@ impl<'a> CoverageReporter for LcovReporter<'a> { )?; } // Statements are not in the LCOV format - CoverageItemKind::Statement => (), + CoverageItemKind::Statement => { + writeln!(self.destination, "DA:{line},{hits}")?; + } } } @@ -153,20 +161,131 @@ impl CoverageReporter for DebugReporter { for (contract_id, anchors) in &report.anchors { println!("Anchors for {contract_id}:"); - anchors.iter().for_each(|anchor| { - println!("- {anchor}"); - println!( - " - Refers to item: {}", - report - .items - .get(&contract_id.version) - .and_then(|items| items.get(anchor.item_id)) - .map_or("None".to_owned(), |item| item.to_string()) - ); - }); + anchors + .0 + .iter() + .map(|anchor| (false, anchor)) + .chain(anchors.1.iter().map(|anchor| (true, anchor))) + .for_each(|(is_deployed, anchor)| { + println!("- {anchor}"); + if is_deployed { + println!("- Creation code"); + } else { + println!("- Runtime code"); + } + println!( + " - Refers to item: {}", + report + .items + .get(&contract_id.version) + .and_then(|items| items.get(anchor.item_id)) + .map_or("None".to_owned(), |item| item.to_string()) + ); + }); println!(); } Ok(()) } } + +pub struct BytecodeReporter { + root: PathBuf, + destdir: PathBuf, +} + +impl BytecodeReporter { + pub fn new(root: PathBuf, destdir: PathBuf) -> BytecodeReporter { + Self { root, destdir } + } +} + +impl CoverageReporter for BytecodeReporter { + fn report(self, report: &CoverageReport) -> eyre::Result<()> { + use std::fmt::Write; + + let no_source_elements = Vec::new(); + let mut line_number_cache = LineNumberCache::new(self.root.clone()); + + for (contract_id, hits) in &report.bytecode_hits { + let ops = disassemble_bytes(hits.bytecode.to_vec())?; + let mut formatted = String::new(); + + let source_elements = + report.source_maps.get(contract_id).map(|sm| &sm.1).unwrap_or(&no_source_elements); + + for (code, source_element) in std::iter::zip(ops.iter(), source_elements) { + let hits = hits + .hits + .get(&(code.offset as usize)) + .map(|h| format!("[{:03}]", h)) + .unwrap_or(" ".to_owned()); + let source_id = source_element.index; + let source_path = source_id.and_then(|i| { + report.source_paths.get(&(contract_id.version.clone(), i as usize)) + }); + + let code = format!("{:?}", code); + let start = source_element.offset; + let end = source_element.offset + source_element.length; + + if let Some(source_path) = source_path { + let (sline, spos) = line_number_cache.get_position(source_path, start)?; + let (eline, epos) = line_number_cache.get_position(source_path, end)?; + writeln!( + formatted, + "{} {:40} // {}: {}:{}-{}:{} ({}-{})", + hits, code, source_path, sline, spos, eline, epos, start, end + )?; + } else if let Some(source_id) = source_id { + writeln!( + formatted, + "{} {:40} // SRCID{}: ({}-{})", + hits, code, source_id, start, end + )?; + } else { + writeln!(formatted, "{} {:40}", hits, code)?; + } + } + fs::write( + &self.destdir.join(contract_id.contract_name.clone()).with_extension("asm"), + formatted, + )?; + } + + Ok(()) + } +} + +/// Cache line number offsets for source files +struct LineNumberCache { + root: PathBuf, + line_offsets: HashMap>, +} + +impl LineNumberCache { + pub fn new(root: PathBuf) -> Self { + LineNumberCache { root, line_offsets: HashMap::new() } + } + + pub fn get_position(&mut self, path: &str, offset: usize) -> eyre::Result<(usize, usize)> { + let line_offsets = match self.line_offsets.entry(path.to_owned()) { + hash_map::Entry::Occupied(o) => o.into_mut(), + hash_map::Entry::Vacant(v) => { + let text = fs::read_to_string(self.root.join(path))?; + let mut line_offsets = vec![0]; + for line in text.lines() { + let line_offset = line.as_ptr() as usize - text.as_ptr() as usize; + line_offsets.push(line_offset); + } + v.insert(line_offsets) + } + }; + let lo = match line_offsets.binary_search(&offset) { + Ok(lo) => lo, + Err(lo) => lo - 1, + }; + let pos = offset - line_offsets.get(lo).unwrap() + 1; + Ok((lo, pos)) + } +} diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index 2fb0ff46a34eb..058af0587dab9 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -2,17 +2,20 @@ use crate::{ constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS}, - hashbrown::HashSet, - traces::{CallTraceArena, TraceCallData, TraceKind}, + traces::{CallTraceArena, CallTraceDecoder, CallTraceNode, DecodedCallData}, }; -use alloy_primitives::U256; use comfy_table::{presets::ASCII_MARKDOWN, *}; use foundry_common::{calc, TestFunctionExt}; +use foundry_evm::traces::CallKind; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Display}; +use std::{ + collections::{BTreeMap, HashSet}, + fmt::Display, +}; +use yansi::Paint; /// Represents the gas report for a set of contracts. -#[derive(Default, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct GasReport { /// Whether to report any contracts. report_any: bool, @@ -22,7 +25,7 @@ pub struct GasReport { ignore: HashSet, /// All contracts that were analyzed grouped by their identifier /// ``test/Counter.t.sol:CounterTest - contracts: BTreeMap, + pub contracts: BTreeMap, } impl GasReport { @@ -37,100 +40,110 @@ impl GasReport { } /// Whether the given contract should be reported. + #[instrument(level = "trace", skip(self), ret)] fn should_report(&self, contract_name: &str) -> bool { if self.ignore.contains(contract_name) { - // could be specified in both ignore and report_for - return self.report_for.contains(contract_name) + let contains_anyway = self.report_for.contains(contract_name); + if contains_anyway { + // If the user listed the contract in 'gas_reports' (the foundry.toml field) a + // report for the contract is generated even if it's listed in the ignore + // list. This is addressed this way because getting a report you don't expect is + // preferable than not getting one you expect. A warning is printed to stderr + // indicating the "double listing". + eprintln!( + "{}: {} is listed in both 'gas_reports' and 'gas_reports_ignore'.", + "warning".yellow().bold(), + contract_name + ); + } + return contains_anyway; } self.report_any || self.report_for.contains(contract_name) } /// Analyzes the given traces and generates a gas report. - pub fn analyze(&mut self, traces: &[(TraceKind, CallTraceArena)]) { - traces.iter().for_each(|(_, trace)| { - self.analyze_node(0, trace); - }); + pub async fn analyze( + &mut self, + arenas: impl IntoIterator, + decoder: &CallTraceDecoder, + ) { + for node in arenas.into_iter().flat_map(|arena| arena.nodes()) { + self.analyze_node(node, decoder).await; + } } - fn analyze_node(&mut self, node_index: usize, arena: &CallTraceArena) { - let node = &arena.arena[node_index]; + async fn analyze_node(&mut self, node: &CallTraceNode, decoder: &CallTraceDecoder) { let trace = &node.trace; if trace.address == CHEATCODE_ADDRESS || trace.address == HARDHAT_CONSOLE_ADDRESS { - return + return; } - if let Some(name) = &trace.contract { - let contract_name = name.rsplit(':').next().unwrap_or(name.as_str()); - // If the user listed the contract in 'gas_reports' (the foundry.toml field) a - // report for the contract is generated even if it's listed in the ignore - // list. This is addressed this way because getting a report you don't expect is - // preferable than not getting one you expect. A warning is printed to stderr - // indicating the "double listing". - if self.report_for.contains(contract_name) && self.ignore.contains(contract_name) { - eprintln!( - "{}: {} is listed in both 'gas_reports' and 'gas_reports_ignore'.", - yansi::Paint::yellow("warning").bold(), - contract_name - ); - } + // Only include top-level calls which accout for calldata and base (21.000) cost. + // Only include Calls and Creates as only these calls are isolated in inspector. + if trace.depth > 1 && + (trace.kind == CallKind::Call || + trace.kind == CallKind::Create || + trace.kind == CallKind::Create2) + { + return; + } - if self.should_report(contract_name) { - let contract_info = self.contracts.entry(name.to_string()).or_default(); - - match &trace.data { - TraceCallData::Raw(bytes) => { - if trace.created() { - contract_info.gas = U256::from(trace.gas_cost); - contract_info.size = U256::from(bytes.len()); - } - } - TraceCallData::Decoded { signature, .. } => { - let name = signature.split('(').next().unwrap(); - // ignore any test/setup functions - let should_include = - !(name.is_test() || name.is_invariant_test() || name.is_setup()); - if should_include { - let gas_info = contract_info - .functions - .entry(name.into()) - .or_default() - .entry(signature.clone()) - .or_default(); - gas_info.calls.push(U256::from(trace.gas_cost)); - } - } - } - } + let decoded = decoder.decode_function(&node.trace).await; + + let Some(name) = &decoded.contract else { return }; + let contract_name = name.rsplit(':').next().unwrap_or(name); + + if !self.should_report(contract_name) { + return; } - node.children.iter().for_each(|index| { - self.analyze_node(*index, arena); - }); + let contract_info = self.contracts.entry(name.to_string()).or_default(); + if trace.kind.is_any_create() { + trace!(contract_name, "adding create gas info"); + contract_info.gas = trace.gas_used; + contract_info.size = trace.data.len(); + } else if let Some(DecodedCallData { signature, .. }) = decoded.func { + let name = signature.split('(').next().unwrap(); + // ignore any test/setup functions + let should_include = !(name.is_test() || name.is_invariant_test() || name.is_setup()); + if should_include { + trace!(contract_name, signature, "adding gas info"); + let gas_info = contract_info + .functions + .entry(name.to_string()) + .or_default() + .entry(signature.clone()) + .or_default(); + gas_info.calls.push(trace.gas_used); + } + } } /// Finalizes the gas report by calculating the min, max, mean, and median for each function. #[must_use] pub fn finalize(mut self) -> Self { - self.contracts.iter_mut().for_each(|(_, contract)| { - contract.functions.iter_mut().for_each(|(_, sigs)| { - sigs.iter_mut().for_each(|(_, func)| { + trace!("finalizing gas report"); + for contract in self.contracts.values_mut() { + for sigs in contract.functions.values_mut() { + for func in sigs.values_mut() { func.calls.sort_unstable(); func.min = func.calls.first().copied().unwrap_or_default(); func.max = func.calls.last().copied().unwrap_or_default(); func.mean = calc::mean(&func.calls); - func.median = U256::from(calc::median_sorted(func.calls.as_slice())); - }); - }); - }); + func.median = calc::median_sorted(&func.calls); + } + } + } self } } impl Display for GasReport { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - for (name, contract) in self.contracts.iter() { + for (name, contract) in &self.contracts { if contract.functions.is_empty() { + trace!(name, "gas report contract without functions"); continue } @@ -176,18 +189,19 @@ impl Display for GasReport { } } -#[derive(Debug, Serialize, Deserialize, Default)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct ContractInfo { - pub gas: U256, - pub size: U256, + pub gas: u64, + pub size: usize, + /// Function name -> Function signature -> GasInfo pub functions: BTreeMap>, } -#[derive(Debug, Serialize, Deserialize, Default)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct GasInfo { - pub calls: Vec, - pub min: U256, - pub mean: U256, - pub median: U256, - pub max: U256, + pub calls: Vec, + pub min: u64, + pub mean: u64, + pub median: u64, + pub max: u64, } diff --git a/crates/forge/src/lib.rs b/crates/forge/src/lib.rs index a8e5b97604761..98dc0aa4783ef 100644 --- a/crates/forge/src/lib.rs +++ b/crates/forge/src/lib.rs @@ -1,23 +1,21 @@ #[macro_use] extern crate tracing; -use alloy_primitives::B256; use foundry_compilers::ProjectCompileOutput; use foundry_config::{ validate_profiles, Config, FuzzConfig, InlineConfig, InlineConfigError, InlineConfigParser, InvariantConfig, NatSpec, }; - -use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner}; +use proptest::test_runner::{ + FailurePersistence, FileFailurePersistence, RngAlgorithm, TestRng, TestRunner, +}; use std::path::Path; pub mod coverage; pub mod gas_report; -pub mod link; - -mod multi_runner; +pub mod multi_runner; pub use multi_runner::{MultiContractRunner, MultiContractRunnerBuilder}; mod runner; @@ -30,7 +28,7 @@ pub use foundry_common::traits::TestFilter; pub use foundry_evm::*; /// Metadata on how to run fuzz/invariant tests -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct TestOptions { /// The base "fuzz" test configuration. To be used as a fallback in case /// no more specific configs are found for a given run. @@ -93,12 +91,19 @@ impl TestOptions { /// - `contract_id` is the id of the test contract, expressed as a relative path from the /// project root. /// - `test_fn` is the name of the test function declared inside the test contract. - pub fn fuzz_runner(&self, contract_id: S, test_fn: S) -> TestRunner - where - S: Into, - { - let fuzz = self.fuzz_config(contract_id, test_fn); - self.fuzzer_with_cases(fuzz.runs) + pub fn fuzz_runner(&self, contract_id: &str, test_fn: &str) -> TestRunner { + let fuzz_config = self.fuzz_config(contract_id, test_fn).clone(); + let failure_persist_path = fuzz_config + .failure_persist_dir + .unwrap() + .join(fuzz_config.failure_persist_file.unwrap()) + .into_os_string() + .into_string() + .unwrap(); + self.fuzzer_with_cases( + fuzz_config.runs, + Some(Box::new(FileFailurePersistence::Direct(failure_persist_path.leak()))), + ) } /// Returns an "invariant" test runner instance. Parameters are used to select tight scoped fuzz @@ -108,12 +113,9 @@ impl TestOptions { /// - `contract_id` is the id of the test contract, expressed as a relative path from the /// project root. /// - `test_fn` is the name of the test function declared inside the test contract. - pub fn invariant_runner(&self, contract_id: S, test_fn: S) -> TestRunner - where - S: Into, - { + pub fn invariant_runner(&self, contract_id: &str, test_fn: &str) -> TestRunner { let invariant = self.invariant_config(contract_id, test_fn); - self.fuzzer_with_cases(invariant.runs) + self.fuzzer_with_cases(invariant.runs, None) } /// Returns a "fuzz" configuration setup. Parameters are used to select tight scoped fuzz @@ -123,10 +125,7 @@ impl TestOptions { /// - `contract_id` is the id of the test contract, expressed as a relative path from the /// project root. /// - `test_fn` is the name of the test function declared inside the test contract. - pub fn fuzz_config(&self, contract_id: S, test_fn: S) -> &FuzzConfig - where - S: Into, - { + pub fn fuzz_config(&self, contract_id: &str, test_fn: &str) -> &FuzzConfig { self.inline_fuzz.get(contract_id, test_fn).unwrap_or(&self.fuzz) } @@ -137,29 +136,29 @@ impl TestOptions { /// - `contract_id` is the id of the test contract, expressed as a relative path from the /// project root. /// - `test_fn` is the name of the test function declared inside the test contract. - pub fn invariant_config(&self, contract_id: S, test_fn: S) -> &InvariantConfig - where - S: Into, - { + pub fn invariant_config(&self, contract_id: &str, test_fn: &str) -> &InvariantConfig { self.inline_invariant.get(contract_id, test_fn).unwrap_or(&self.invariant) } - pub fn fuzzer_with_cases(&self, cases: u32) -> TestRunner { - // TODO: Add Options to modify the persistence - let cfg = proptest::test_runner::Config { - failure_persistence: None, + pub fn fuzzer_with_cases( + &self, + cases: u32, + file_failure_persistence: Option>, + ) -> TestRunner { + let config = proptest::test_runner::Config { + failure_persistence: file_failure_persistence, cases, max_global_rejects: self.fuzz.max_test_rejects, ..Default::default() }; - if let Some(ref fuzz_seed) = self.fuzz.seed { - trace!(target: "forge::test", "building deterministic fuzzer with seed {}", fuzz_seed); - let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &B256::from(*fuzz_seed).0); - TestRunner::new_with_rng(cfg, rng) + if let Some(seed) = &self.fuzz.seed { + trace!(target: "forge::test", %seed, "building deterministic fuzzer"); + let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()); + TestRunner::new_with_rng(config, rng) } else { trace!(target: "forge::test", "building stochastic fuzzer"); - TestRunner::new(cfg) + TestRunner::new(config) } } } @@ -211,23 +210,3 @@ impl TestOptionsBuilder { TestOptions::new(output, root, profiles, base_fuzz, base_invariant) } } - -mod utils2 { - use alloy_primitives::Address; - use ethers_core::types::BlockId; - use ethers_providers::{Middleware, Provider}; - use eyre::Context; - use foundry_common::types::{ToAlloy, ToEthers}; - - pub async fn next_nonce( - caller: Address, - provider_url: &str, - block: Option, - ) -> eyre::Result { - let provider = Provider::try_from(provider_url) - .wrap_err_with(|| format!("bad fork_url provider: {provider_url}"))?; - let res = provider.get_transaction_count(caller.to_ethers(), block).await?.to_alloy(); - res.try_into().map_err(Into::into) - } -} -pub use utils2::*; diff --git a/crates/forge/src/link.rs b/crates/forge/src/link.rs deleted file mode 100644 index 5eff7ee9ea4a8..0000000000000 --- a/crates/forge/src/link.rs +++ /dev/null @@ -1,722 +0,0 @@ -use alloy_primitives::{Address, Bytes}; -use eyre::Result; -use foundry_compilers::{ - artifacts::{BytecodeObject, CompactBytecode, CompactContractBytecode, Libraries}, - contracts::ArtifactContracts, - ArtifactId, -}; -use std::{ - collections::{BTreeMap, HashMap}, - fmt, - path::{Path, PathBuf}, - str::FromStr, -}; - -/// Data passed to the post link handler of the linker for each linked artifact. -#[derive(Debug)] -pub struct PostLinkInput<'a, T, U> { - /// The fully linked bytecode of the artifact - pub contract: CompactContractBytecode, - /// All artifacts passed to the linker - pub known_contracts: &'a mut BTreeMap, - /// The ID of the artifact - pub id: ArtifactId, - /// Extra data passed to the handler, which can be used as a scratch space. - pub extra: &'a mut U, - /// Each dependency of the contract in their resolved form. - pub dependencies: Vec, -} - -/// Dependencies for an artifact. -#[derive(Debug)] -struct ArtifactDependencies { - /// All references to dependencies in the artifact's unlinked bytecode. - dependencies: Vec, - /// The ID of the artifact - artifact_id: ArtifactId, -} - -/// A dependency of an artifact. -#[derive(Debug)] -struct ArtifactDependency { - file: String, - key: String, - version: String, -} - -struct ArtifactCode { - code: CompactContractBytecode, - artifact_id: ArtifactId, -} - -impl std::fmt::Debug for ArtifactCode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.artifact_id.fmt(f) - } -} - -#[derive(Debug)] -struct AllArtifactsBySlug { - /// all artifacts grouped by identifier - inner: BTreeMap, -} - -impl AllArtifactsBySlug { - /// Finds the code for the target of the artifact and the matching key. - fn find_code(&self, identifier: &String, version: &String) -> Option { - trace!(target: "forge::link", identifier, "fetching artifact by identifier"); - let code = self - .inner - .get(identifier) - .or(self.inner.get(&format!("{}.{}", identifier, version)))?; - - Some(code.code.clone()) - } -} - -#[derive(Debug)] -pub struct ResolvedDependency { - /// The address the linker resolved - pub address: Address, - /// The nonce used to resolve the dependency - pub nonce: u64, - pub id: String, - pub bytecode: Bytes, -} - -impl std::fmt::Display for ResolvedDependency { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{} @ {} (resolved with nonce {})", self.id, self.address, self.nonce) - } -} - -/// Links the given artifacts with a link key constructor function, passing the result of each -/// linkage to the given callback. -/// -/// This function will recursively link all artifacts until none are unlinked. It does this by: -/// -/// 1. Using the specified predeployed library addresses (`deployed_library_addresses`) for known -/// libraries (specified by the user) 2. Otherwise, computing the address the library would live at -/// if deployed by `sender`, given a starting nonce of `nonce`. -/// -/// If the library was already deployed previously in step 2, the linker will re-use the previously -/// computed address instead of re-computing it. -/// -/// The linker will call `post_link` for each linked artifact, providing: -/// -/// 1. User-specified data (`extra`) -/// 2. The linked artifact's bytecode -/// 3. The ID of the artifact -/// 4. The dependencies necessary to deploy the contract -/// -/// # Note -/// -/// If you want to collect all dependencies of a set of contracts, you cannot just collect the -/// `dependencies` passed to the callback in a `Vec`, since the same library contract (with the -/// exact same address) might show up as a dependency for multiple contracts. -/// -/// Instead, you must deduplicate *and* preserve the deployment order by pushing the dependencies to -/// a `Vec` iff it has not been seen before. -/// -/// For an example of this, see [here](https://github.com/foundry-rs/foundry/blob/2308972dbc3a89c03488a05aceb3c428bb3e08c0/cli/src/cmd/forge/script/build.rs#L130-L151C9). -#[allow(clippy::too_many_arguments)] -pub fn link_with_nonce_or_address( - contracts: ArtifactContracts, - known_contracts: &mut BTreeMap, - deployed_library_addresses: Libraries, - sender: Address, - nonce: u64, - extra: &mut U, - post_link: impl Fn(PostLinkInput) -> eyre::Result<()>, - root: impl AsRef, -) -> Result<()> { - // create a mapping of fname => Vec<(fname, file, key)>, - let link_tree: BTreeMap = contracts - .iter() - .map(|(id, contract)| { - let key = id.identifier(); - let version = id.version.to_string(); - // Check if the version has metadata appended to it, which will be after the semver - // version with a `+` separator. If so, strip it off. - let version = match version.find('+') { - Some(idx) => (version[..idx]).to_string(), - None => version, - }; - let references = contract - .all_link_references() - .iter() - .flat_map(|(file, link)| link.keys().map(|key| (file.to_string(), key.to_string()))) - .map(|(file, key)| ArtifactDependency { - file, - key, - version: version.clone().to_owned(), - }) - .collect(); - - let references = - ArtifactDependencies { dependencies: references, artifact_id: id.clone() }; - (key, references) - }) - .collect(); - - let artifacts_by_slug = AllArtifactsBySlug { - inner: contracts - .iter() - .map(|(artifact_id, c)| { - ( - artifact_id.identifier(), - ArtifactCode { code: c.clone(), artifact_id: artifact_id.clone() }, - ) - }) - .collect(), - }; - - for (id, contract) in contracts.into_iter() { - let (abi, maybe_deployment_bytes, maybe_runtime) = ( - contract.abi.as_ref(), - contract.bytecode.as_ref(), - contract.deployed_bytecode.as_ref(), - ); - let mut internally_deployed_libraries = HashMap::new(); - - if let (Some(abi), Some(bytecode), Some(runtime)) = - (abi, maybe_deployment_bytes, maybe_runtime) - { - // we are going to mutate, but library contract addresses may change based on - // the test so we clone - let mut target_bytecode = bytecode.clone(); - let mut rt = runtime.clone(); - let mut target_bytecode_runtime = rt.bytecode.expect("No target runtime").clone(); - - // instantiate a vector that gets filled with library deployment bytecode - let mut dependencies = vec![]; - - match bytecode.object { - BytecodeObject::Unlinked(_) => { - trace!(target: "forge::link", target=id.identifier(), version=?id.version, "unlinked contract"); - - // link needed - recurse_link( - id.identifier(), - (&mut target_bytecode, &mut target_bytecode_runtime), - &artifacts_by_slug, - &link_tree, - &mut dependencies, - &mut internally_deployed_libraries, - &deployed_library_addresses, - &mut nonce.clone(), - sender, - root.as_ref(), - ); - } - BytecodeObject::Bytecode(ref bytes) => { - if bytes.as_ref().is_empty() { - // Handle case where bytecode bytes are empty - let tc = CompactContractBytecode { - abi: Some(abi.clone()), - bytecode: None, - deployed_bytecode: None, - }; - - let post_link_input = PostLinkInput { - contract: tc, - known_contracts, - id, - extra, - dependencies, - }; - - post_link(post_link_input)?; - continue - } - } - } - - rt.bytecode = Some(target_bytecode_runtime); - let tc = CompactContractBytecode { - abi: Some(abi.clone()), - bytecode: Some(target_bytecode), - deployed_bytecode: Some(rt), - }; - - let post_link_input = - PostLinkInput { contract: tc, known_contracts, id, extra, dependencies }; - - post_link(post_link_input)?; - } - } - Ok(()) -} - -/// Recursively links bytecode given a target contract artifact name, the bytecode(s) to be linked, -/// a mapping of contract artifact name to bytecode, a dependency mapping, a mutable list that -/// will be filled with the predeploy libraries, initial nonce, and the sender. -#[allow(clippy::too_many_arguments)] -fn recurse_link<'a>( - // target name - target: String, - // to-be-modified/linked bytecode - target_bytecode: (&'a mut CompactBytecode, &'a mut CompactBytecode), - // All contract artifacts - artifacts: &'a AllArtifactsBySlug, - // fname => Vec<(fname, file, key)> - dependency_tree: &'a BTreeMap, - // library deployment vector (file:contract:address, bytecode) - deployment: &'a mut Vec, - // libraries we have already deployed during the linking process. - // the key is `file:contract` and the value is the address we computed - internally_deployed_libraries: &'a mut HashMap, - // deployed library addresses fname => address - deployed_library_addresses: &'a Libraries, - // nonce to start at - nonce: &mut u64, - // sender - sender: Address, - // project root path - root: impl AsRef, -) { - // check if we have dependencies - if let Some(dependencies) = dependency_tree.get(&target) { - trace!(target: "forge::link", ?target, "linking contract"); - - // for each dependency, try to link - dependencies.dependencies.iter().for_each(|dep| { - let ArtifactDependency { file, key, version } = dep; - let next_target = format!("{file}:{key}"); - let root = PathBuf::from(root.as_ref().to_str().unwrap()); - // get the dependency - trace!(target: "forge::link", dependency=next_target, file, key, version=?dependencies.artifact_id.version, "get dependency"); - let artifact = match artifacts - .find_code(&next_target, version) { - Some(artifact) => artifact, - None => { - // In some project setups, like JS-style workspaces, you might not have node_modules available at the root of the foundry project. - // In this case, imported dependencies from outside the root might not have their paths tripped correctly. - // Therefore, we fall back to a manual path join to locate the file. - let fallback_path = dunce::canonicalize(root.join(file)).unwrap_or_else(|e| panic!("No artifact for contract \"{next_target}\". Attempted to compose fallback path but got got error {e}")); - let fallback_path = fallback_path.to_str().unwrap_or("No artifact for contract \"{next_target}\". Attempted to compose fallback path but could not create valid string"); - let fallback_target = format!("{fallback_path}:{key}"); - - trace!(target: "forge::link", fallback_dependency=fallback_target, file, key, version=?dependencies.artifact_id.version, "get dependency with fallback path"); - - match artifacts.find_code(&fallback_target, version) { - Some(artifact) => artifact, - None => panic!("No artifact for contract {next_target}"), - }}, - }; - let mut next_target_bytecode = artifact - .bytecode - .unwrap_or_else(|| panic!("No bytecode for contract {next_target}")); - let mut next_target_runtime_bytecode = artifact - .deployed_bytecode - .expect("No target runtime bytecode") - .bytecode - .expect("No target runtime"); - - // make sure dependency is fully linked - if let Some(deps) = dependency_tree.get(&format!("{file}:{key}")) { - if !deps.dependencies.is_empty() { - trace!(target: "forge::link", dependency=next_target, file, key, version=?dependencies.artifact_id.version, "dependency has dependencies"); - - // actually link the nested dependencies to this dependency - recurse_link( - format!("{file}:{key}"), - (&mut next_target_bytecode, &mut next_target_runtime_bytecode), - artifacts, - dependency_tree, - deployment, - internally_deployed_libraries, - deployed_library_addresses, - nonce, - sender, - root, - ); - } - } - - let mut deployed_address = None; - - if let Some(library_file) = deployed_library_addresses - .libs - .get(&PathBuf::from_str(file).expect("Invalid library path.")) - { - if let Some(address) = library_file.get(key) { - deployed_address = - Some(Address::from_str(address).expect("Invalid library address passed.")); - } - } - - let address = if let Some(deployed_address) = deployed_address { - trace!(target: "forge::link", dependency=next_target, file, key, "dependency has pre-defined address"); - - // the user specified the library address - deployed_address - } else if let Some((cached_nonce, deployed_address)) = internally_deployed_libraries.get(&format!("{file}:{key}")) { - trace!(target: "forge::link", dependency=next_target, file, key, "dependency was previously deployed"); - - // we previously deployed the library - let library = format!("{file}:{key}:0x{deployed_address:x}"); - - // push the dependency into the library deployment vector - deployment.push(ResolvedDependency { - id: library, - address: *deployed_address, - nonce: *cached_nonce, - bytecode: next_target_bytecode.object.into_bytes().unwrap_or_else(|| panic!("Bytecode should be linked for {next_target}")), - }); - *deployed_address - } else { - trace!(target: "forge::link", dependency=next_target, file, key, "dependency has to be deployed"); - - // we need to deploy the library - let used_nonce = *nonce; - let computed_address = sender.create(used_nonce); - *nonce += 1; - let library = format!("{file}:{key}:0x{computed_address:x}"); - - // push the dependency into the library deployment vector - deployment.push(ResolvedDependency { - id: library, - address: computed_address, - nonce: used_nonce, - bytecode: next_target_bytecode.object.into_bytes().unwrap_or_else(|| panic!("Bytecode should be linked for {next_target}")), - }); - - // remember this library for later - internally_deployed_libraries.insert(format!("{file}:{key}"), (used_nonce, computed_address)); - - computed_address - }; - - // link the dependency to the target - target_bytecode.0.link(file.clone(), key.clone(), address); - target_bytecode.1.link(file.clone(), key.clone(), address); - trace!(target: "forge::link", ?target, dependency=next_target, file, key, "linking dependency done"); - }); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use foundry_common::ContractsByArtifact; - use foundry_compilers::{Project, ProjectPathsConfig}; - - struct LinkerTest { - contracts: ArtifactContracts, - dependency_assertions: HashMap>, - project: Project, - } - - impl LinkerTest { - fn new(path: impl Into) -> Self { - let path = path.into(); - let paths = ProjectPathsConfig::builder() - .root("../../testdata/linking") - .lib("../../testdata/lib") - .sources(path.clone()) - .tests(path) - .build() - .unwrap(); - - let project = - Project::builder().paths(paths).ephemeral().no_artifacts().build().unwrap(); - let contracts = project - .compile() - .unwrap() - .with_stripped_file_prefixes(project.root()) - .into_artifacts() - .map(|(id, c)| (id, c.into_contract_bytecode())) - .collect::(); - - Self { contracts, dependency_assertions: HashMap::new(), project } - } - - fn assert_dependencies( - mut self, - artifact_id: String, - deps: Vec<(String, u64, Address)>, - ) -> Self { - self.dependency_assertions.insert(artifact_id, deps); - self - } - - fn test_with_sender_and_nonce(self, sender: Address, initial_nonce: u64) { - let mut called_once = false; - link_with_nonce_or_address( - self.contracts, - &mut ContractsByArtifact::default(), - Default::default(), - sender, - initial_nonce, - &mut called_once, - |post_link_input| { - *post_link_input.extra = true; - let identifier = post_link_input.id.identifier(); - - // Skip ds-test as it always has no dependencies etc. (and the path is outside root so is not sanitized) - if identifier.contains("DSTest") { - return Ok(()) - } - - let assertions = self - .dependency_assertions - .get(&identifier) - .unwrap_or_else(|| panic!("Unexpected artifact: {identifier}")); - - assert_eq!( - post_link_input.dependencies.len(), - assertions.len(), - "artifact {identifier} has more/less dependencies than expected ({} vs {}): {:#?}", - post_link_input.dependencies.len(), - assertions.len(), - post_link_input.dependencies - ); - - for (expected, actual) in assertions.iter().zip(post_link_input.dependencies.iter()) { - let expected_lib_id = format!("{}:{:?}", expected.0, expected.2); - assert_eq!(expected_lib_id, actual.id, "unexpected dependency, expected: {}, got: {}", expected_lib_id, actual.id); - assert_eq!(actual.nonce, expected.1, "nonce wrong for dependency, expected: {}, got: {}", expected.1, actual.nonce); - assert_eq!(actual.address, expected.2, "address wrong for dependency, expected: {}, got: {}", expected.2, actual.address); - } - - Ok(()) - }, - self.project.root(), - ) - .expect("Linking failed"); - - assert!(called_once, "linker did nothing"); - } - } - - #[test] - fn link_simple() { - LinkerTest::new("../../testdata/linking/simple") - .assert_dependencies("simple/Simple.t.sol:Lib".to_string(), vec![]) - .assert_dependencies( - "simple/Simple.t.sol:LibraryConsumer".to_string(), - vec![( - "simple/Simple.t.sol:Lib".to_string(), - 1, - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - )], - ) - .assert_dependencies( - "simple/Simple.t.sol:SimpleLibraryLinkingTest".to_string(), - vec![( - "simple/Simple.t.sol:Lib".to_string(), - 1, - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - )], - ) - .test_with_sender_and_nonce(Address::default(), 1); - } - - #[test] - fn link_nested() { - LinkerTest::new("../../testdata/linking/nested") - .assert_dependencies("nested/Nested.t.sol:Lib".to_string(), vec![]) - .assert_dependencies( - "nested/Nested.t.sol:NestedLib".to_string(), - vec![( - "nested/Nested.t.sol:Lib".to_string(), - 1, - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - )], - ) - .assert_dependencies( - "nested/Nested.t.sol:LibraryConsumer".to_string(), - vec![ - // Lib shows up here twice, because the linker sees it twice, but it should - // have the same address and nonce. - ( - "nested/Nested.t.sol:Lib".to_string(), - 1, - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "nested/Nested.t.sol:Lib".to_string(), - 1, - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "nested/Nested.t.sol:NestedLib".to_string(), - 2, - Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d").unwrap(), - ), - ], - ) - .assert_dependencies( - "nested/Nested.t.sol:NestedLibraryLinkingTest".to_string(), - vec![ - ( - "nested/Nested.t.sol:Lib".to_string(), - 1, - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "nested/Nested.t.sol:Lib".to_string(), - 1, - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "nested/Nested.t.sol:NestedLib".to_string(), - 2, - Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d").unwrap(), - ), - ], - ) - .test_with_sender_and_nonce(Address::default(), 1); - } - - /// This test ensures that complicated setups with many libraries, some of which are referenced - /// in more than one place, result in correct linking. - /// - /// Each `assert_dependencies` should be considered in isolation, i.e. read it as "if I wanted - /// to deploy this contract, I would have to deploy the dependencies in this order with this - /// nonce". - /// - /// A library may show up more than once, but it should *always* have the same nonce and address - /// with respect to the single `assert_dependencies` call. There should be no gaps in the nonce - /// otherwise, i.e. whenever a new dependency is encountered, the nonce should be a single - /// increment larger than the previous largest nonce. - #[test] - fn link_duplicate() { - LinkerTest::new("../../testdata/linking/duplicate") - .assert_dependencies("duplicate/Duplicate.t.sol:A".to_string(), vec![]) - .assert_dependencies("duplicate/Duplicate.t.sol:B".to_string(), vec![]) - .assert_dependencies( - "duplicate/Duplicate.t.sol:C".to_string(), - vec![( - "duplicate/Duplicate.t.sol:A".to_string(), - 1, - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - )], - ) - .assert_dependencies( - "duplicate/Duplicate.t.sol:D".to_string(), - vec![( - "duplicate/Duplicate.t.sol:B".to_string(), - 1, - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - )], - ) - .assert_dependencies( - "duplicate/Duplicate.t.sol:E".to_string(), - vec![ - ( - "duplicate/Duplicate.t.sol:A".to_string(), - 1, - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:C".to_string(), - 2, - Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d").unwrap(), - ), - ], - ) - .assert_dependencies( - "duplicate/Duplicate.t.sol:LibraryConsumer".to_string(), - vec![ - ( - "duplicate/Duplicate.t.sol:A".to_string(), - 1, - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:B".to_string(), - 2, - Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:A".to_string(), - 1, - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:C".to_string(), - 3, - Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:B".to_string(), - 2, - Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:D".to_string(), - 4, - Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:A".to_string(), - 1, - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:C".to_string(), - 3, - Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:E".to_string(), - 5, - Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f").unwrap(), - ), - ], - ) - .assert_dependencies( - "duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest".to_string(), - vec![ - ( - "duplicate/Duplicate.t.sol:A".to_string(), - 1, - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:B".to_string(), - 2, - Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:A".to_string(), - 1, - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:C".to_string(), - 3, - Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:B".to_string(), - 2, - Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:D".to_string(), - 4, - Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:A".to_string(), - 1, - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:C".to_string(), - 3, - Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:E".to_string(), - 5, - Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f").unwrap(), - ), - ], - ) - .test_with_sender_and_nonce(Address::default(), 1); - } -} diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 5d39d48582ed4..9fed56982a8b8 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -1,144 +1,114 @@ //! Forge test runner for multiple contracts. -use crate::{ - link::{link_with_nonce_or_address, PostLinkInput, ResolvedDependency}, - result::SuiteResult, - ContractRunner, TestFilter, TestOptions, -}; -use alloy_json_abi::{Function, JsonAbi as Abi}; +use crate::{result::SuiteResult, ContractRunner, TestFilter, TestOptions}; +use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; -use foundry_common::{ContractsByArtifact, TestFunctionExt}; -use foundry_compilers::{ - artifacts::CompactContractBytecode, contracts::ArtifactContracts, Artifact, ArtifactId, - ArtifactOutput, ProjectCompileOutput, -}; +use foundry_common::{get_contract_name, ContractsByArtifact, TestFunctionExt}; +use foundry_compilers::{artifacts::Libraries, Artifact, ArtifactId, ProjectCompileOutput}; +use foundry_config::Config; use foundry_evm::{ - backend::Backend, - executors::{Executor, ExecutorBuilder}, - fork::CreateFork, - inspectors::CheatsConfig, - opts::EvmOpts, - revm, + backend::Backend, decode::RevertDecoder, executors::ExecutorBuilder, fork::CreateFork, + inspectors::CheatsConfig, opts::EvmOpts, revm, }; +use foundry_linking::{LinkOutput, Linker}; use rayon::prelude::*; use revm::primitives::SpecId; use std::{ - collections::{BTreeMap, HashSet}, - iter::Iterator, + borrow::Borrow, + collections::BTreeMap, + fmt::Debug, path::Path, - sync::{mpsc::Sender, Arc}, + sync::{mpsc, Arc}, + time::Instant, }; -pub type DeployableContracts = BTreeMap)>; +#[derive(Debug, Clone)] +pub struct TestContract { + pub abi: JsonAbi, + pub bytecode: Bytes, + pub libs_to_deploy: Vec, + pub libraries: Libraries, +} + +pub type DeployableContracts = BTreeMap; /// A multi contract runner receives a set of contracts deployed in an EVM instance and proceeds /// to run all test functions in these contracts. pub struct MultiContractRunner { - /// Mapping of contract name to Abi, creation bytecode and library bytecode which + /// Mapping of contract name to JsonAbi, creation bytecode and library bytecode which /// needs to be deployed & linked against pub contracts: DeployableContracts, - /// Compiled contracts by name that have an Abi and runtime bytecode - pub known_contracts: ContractsByArtifact, /// The EVM instance used in the test runner pub evm_opts: EvmOpts, /// The configured evm pub env: revm::primitives::Env, /// The EVM spec pub evm_spec: SpecId, - /// All known errors, used for decoding reverts - pub errors: Option, + /// Revert decoder. Contains all known errors and their selectors. + pub revert_decoder: RevertDecoder, /// The address which will be used as the `from` field in all EVM calls pub sender: Option
, - /// A map of contract names to absolute source file paths - pub source_paths: BTreeMap, /// The fork to use at launch pub fork: Option, - /// Additional cheatcode inspector related settings derived from the `Config` - pub cheats_config: Arc, + /// Project config. + pub config: Arc, /// Whether to collect coverage info pub coverage: bool, /// Whether to collect debug info pub debug: bool, /// Settings related to fuzz and/or invariant tests pub test_options: TestOptions, + /// Whether to enable call isolation + pub isolation: bool, + /// Output of the project compilation + pub output: ProjectCompileOutput, } impl MultiContractRunner { - /// Returns the number of matching tests - pub fn matching_test_function_count(&self, filter: &impl TestFilter) -> usize { - self.matching_test_functions(filter).count() - } - - /// Returns all test functions matching the filter - pub fn get_matching_test_functions<'a>( + /// Returns an iterator over all contracts that match the filter. + pub fn matching_contracts<'a>( &'a self, - filter: &'a impl TestFilter, - ) -> Vec<&Function> { - self.matching_test_functions(filter).collect() + filter: &'a dyn TestFilter, + ) -> impl Iterator { + self.contracts + .iter() + .filter(|&(id, TestContract { abi, .. })| matches_contract(id, abi, filter)) } - /// Returns all test functions matching the filter + /// Returns an iterator over all test functions that match the filter. pub fn matching_test_functions<'a>( &'a self, - filter: &'a impl TestFilter, + filter: &'a dyn TestFilter, ) -> impl Iterator { - self.contracts - .iter() - .filter(|(id, _)| { - filter.matches_path(id.source.to_string_lossy()) && - filter.matches_contract(&id.name) - }) - .flat_map(|(_, (abi, _, _))| { - abi.functions().filter(|func| filter.matches_test(func.signature())) - }) + self.matching_contracts(filter) + .flat_map(|(_, TestContract { abi, .. })| abi.functions()) + .filter(|func| is_matching_test(func, filter)) } - /// Get an iterator over all test contract functions that matches the filter path and contract - /// name - fn filtered_tests<'a>( + /// Returns an iterator over all test functions in contracts that match the filter. + pub fn all_test_functions<'a>( &'a self, - filter: &'a impl TestFilter, + filter: &'a dyn TestFilter, ) -> impl Iterator { self.contracts .iter() - .filter(|(id, _)| { - filter.matches_path(id.source.to_string_lossy()) && - filter.matches_contract(&id.name) - }) - .flat_map(|(_, (abi, _, _))| abi.functions()) - } - - /// Get all test names matching the filter - pub fn get_tests(&self, filter: &impl TestFilter) -> Vec { - self.filtered_tests(filter) - .map(|func| func.name.clone()) - .filter(|name| name.is_test()) - .collect() + .filter(|(id, _)| filter.matches_path(&id.source) && filter.matches_contract(&id.name)) + .flat_map(|(_, TestContract { abi, .. })| abi.functions()) + .filter(|func| func.is_test() || func.is_invariant_test()) } /// Returns all matching tests grouped by contract grouped by file (file -> (contract -> tests)) - pub fn list( - &self, - filter: &impl TestFilter, - ) -> BTreeMap>> { - self.contracts - .iter() - .filter(|(id, _)| { - filter.matches_path(id.source.to_string_lossy()) && - filter.matches_contract(&id.name) - }) - .filter(|(_, (abi, _, _))| abi.functions().any(|func| filter.matches_test(&func.name))) - .map(|(id, (abi, _, _))| { + pub fn list(&self, filter: &dyn TestFilter) -> BTreeMap>> { + self.matching_contracts(filter) + .map(|(id, TestContract { abi, .. })| { let source = id.source.as_path().display().to_string(); let name = id.name.clone(); let tests = abi .functions() - .filter(|func| func.name.is_test()) - .filter(|func| filter.matches_test(func.signature())) + .filter(|func| is_matching_test(func, filter)) .map(|func| func.name.clone()) .collect::>(); - (source, name, tests) }) .fold(BTreeMap::new(), |mut acc, (source, name, tests)| { @@ -147,95 +117,120 @@ impl MultiContractRunner { }) } - /// Executes _all_ tests that match the given `filter` + /// Executes _all_ tests that match the given `filter`. + /// + /// The same as [`test`](Self::test), but returns the results instead of streaming them. + /// + /// Note that this method returns only when all tests have been executed. + pub fn test_collect(&mut self, filter: &dyn TestFilter) -> BTreeMap { + self.test_iter(filter).collect() + } + + /// Executes _all_ tests that match the given `filter`. + /// + /// The same as [`test`](Self::test), but returns the results instead of streaming them. + /// + /// Note that this method returns only when all tests have been executed. + pub fn test_iter( + &mut self, + filter: &dyn TestFilter, + ) -> impl Iterator { + let (tx, rx) = mpsc::channel(); + self.test(filter, tx); + rx.into_iter() + } + + /// Executes _all_ tests that match the given `filter`. /// /// This will create the runtime based on the configured `evm` ops and create the `Backend` /// before executing all contracts and their tests in _parallel_. /// /// Each Executor gets its own instance of the `Backend`. - pub async fn test( - &mut self, - filter: impl TestFilter, - stream_result: Option>, - test_options: TestOptions, - ) -> BTreeMap { + pub fn test(&mut self, filter: &dyn TestFilter, tx: mpsc::Sender<(String, SuiteResult)>) { trace!("running all tests"); - // the db backend that serves all the data, each contract gets its own instance - let db = Backend::spawn(self.fork.take()).await; - let filter = &filter; - - self.contracts - .par_iter() - .filter(|(id, _)| { - filter.matches_path(id.source.to_string_lossy()) && - filter.matches_contract(&id.name) - }) - .filter(|(_, (abi, _, _))| abi.functions().any(|func| filter.matches_test(&func.name))) - .map_with(stream_result, |stream_result, (id, (abi, deploy_code, libs))| { - let executor = ExecutorBuilder::new() - .inspectors(|stack| { - stack - .cheatcodes(self.cheats_config.clone()) - .trace(self.evm_opts.verbosity >= 3 || self.debug) - .debug(self.debug) - .coverage(self.coverage) - }) - .spec(self.evm_spec) - .gas_limit(self.evm_opts.gas_limit()) - .build(self.env.clone(), db.clone()); - let identifier = id.identifier(); - trace!(contract=%identifier, "start executing all tests in contract"); - - let result = self.run_tests( - &identifier, - abi, - executor, - deploy_code.clone(), - libs, - filter, - test_options.clone(), - ); - trace!(contract=?identifier, "executed all tests in contract"); - - if let Some(stream_result) = stream_result { - let _ = stream_result.send((identifier.clone(), result.clone())); - } + // The DB backend that serves all the data. + let db = Backend::spawn(self.fork.take()); + + let find_timer = Instant::now(); + let contracts = self.matching_contracts(filter).collect::>(); + let find_time = find_timer.elapsed(); + debug!( + "Found {} test contracts out of {} in {:?}", + contracts.len(), + self.contracts.len(), + find_time, + ); - (identifier, result) - }) - .collect() + contracts.par_iter().for_each_with(tx, |tx, &(id, contract)| { + let result = self.run_tests(id, contract, db.clone(), filter); + let _ = tx.send((id.identifier(), result)); + }) } - #[instrument(skip_all, fields(name = %name))] - #[allow(clippy::too_many_arguments)] fn run_tests( &self, - name: &str, - contract: &Abi, - executor: Executor, - deploy_code: Bytes, - libs: &[Bytes], - filter: impl TestFilter, - test_options: TestOptions, + artifact_id: &ArtifactId, + contract: &TestContract, + db: Backend, + filter: &dyn TestFilter, ) -> SuiteResult { + let identifier = artifact_id.identifier(); + let mut span_name = identifier.as_str(); + + let linker = + Linker::new(self.config.project_paths().root, self.output.artifact_ids().collect()); + let linked_contracts = linker.get_linked_artifacts(&contract.libraries).unwrap_or_default(); + let known_contracts = Arc::new(ContractsByArtifact::new(linked_contracts)); + + let cheats_config = CheatsConfig::new( + &self.config, + self.evm_opts.clone(), + Some(known_contracts.clone()), + None, + Some(artifact_id.version.clone()), + ); + + let executor = ExecutorBuilder::new() + .inspectors(|stack| { + stack + .cheatcodes(Arc::new(cheats_config)) + .trace(self.evm_opts.verbosity >= 3 || self.debug) + .debug(self.debug) + .coverage(self.coverage) + .enable_isolation(self.isolation) + }) + .spec(self.evm_spec) + .gas_limit(self.evm_opts.gas_limit()) + .build(self.env.clone(), db); + + if !enabled!(tracing::Level::TRACE) { + span_name = get_contract_name(&identifier); + } + let _guard = info_span!("run_tests", name = span_name).entered(); + + debug!("start executing all tests in contract"); + let runner = ContractRunner::new( - name, + &identifier, executor, contract, - deploy_code, self.evm_opts.initial_balance, self.sender, - self.errors.as_ref(), - libs, + &self.revert_decoder, self.debug, ); - runner.run_tests(filter, test_options, Some(&self.known_contracts)) + let r = runner.run_tests(filter, &self.test_options, known_contracts); + + debug!(duration=?r.duration, "executed all tests in contract"); + + r } } /// Builder used for instantiating the multi-contract runner -#[derive(Debug, Default, Clone)] +#[derive(Clone, Debug)] +#[must_use = "builders do nothing unless you call `build` on them"] pub struct MultiContractRunnerBuilder { /// The address which will be used to deploy the initial contracts and send all /// transactions @@ -246,174 +241,157 @@ pub struct MultiContractRunnerBuilder { pub evm_spec: Option, /// The fork to use at launch pub fork: Option, - /// Additional cheatcode inspector related settings derived from the `Config` - pub cheats_config: Option, + /// Project config. + pub config: Arc, /// Whether or not to collect coverage info pub coverage: bool, /// Whether or not to collect debug info pub debug: bool, + /// Whether to enable call isolation + pub isolation: bool, /// Settings related to fuzz and/or invariant tests pub test_options: Option, } impl MultiContractRunnerBuilder { - /// Given an EVM, proceeds to return a runner which is able to execute all tests - /// against that evm - pub fn build( - self, - root: impl AsRef, - output: ProjectCompileOutput, - env: revm::primitives::Env, - evm_opts: EvmOpts, - ) -> Result - where - A: ArtifactOutput, - { - // This is just the contracts compiled, but we need to merge this with the read cached - // artifacts - let contracts = output - .with_stripped_file_prefixes(&root) - .into_artifacts() - .map(|(i, c)| (i, c.into_contract_bytecode())) - .collect::>(); - - let mut known_contracts = ContractsByArtifact::default(); - let source_paths = contracts - .iter() - .map(|(i, _)| (i.identifier(), root.as_ref().join(&i.source).to_string_lossy().into())) - .collect::>(); - // create a mapping of name => (abi, deployment code, Vec) - let mut deployable_contracts = DeployableContracts::default(); - - fn unique_deps(deps: Vec) -> Vec { - let mut filtered = Vec::new(); - let mut seen = HashSet::new(); - for dep in deps { - if !seen.insert(dep.id.clone()) { - continue - } - filtered.push(dep); - } - - filtered + pub fn new(config: Arc) -> Self { + Self { + config, + sender: Default::default(), + initial_balance: Default::default(), + evm_spec: Default::default(), + fork: Default::default(), + coverage: Default::default(), + debug: Default::default(), + isolation: Default::default(), + test_options: Default::default(), } - - link_with_nonce_or_address( - ArtifactContracts::from_iter(contracts), - &mut known_contracts, - Default::default(), - evm_opts.sender, - 1, - &mut deployable_contracts, - |post_link_input| { - let PostLinkInput { - contract, - known_contracts, - id, - extra: deployable_contracts, - dependencies, - } = post_link_input; - let dependencies = unique_deps(dependencies); - - let abi = contract.abi.expect("We should have an abi by now"); - - // get bytes if deployable, else add to known contracts and return. - // interfaces and abstract contracts should be known to enable fuzzing of their ABI - // but they should not be deployable and their source code should be skipped by the - // debugger and linker. - let Some(bytecode) = contract.bytecode.and_then(|b| b.object.into_bytes()) else { - known_contracts.insert(id.clone(), (abi.clone(), vec![])); - return Ok(()) - }; - - // if it's a test, add it to deployable contracts - if abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) && - abi.functions() - .any(|func| func.name.is_test() || func.name.is_invariant_test()) - { - deployable_contracts.insert( - id.clone(), - ( - abi.clone(), - bytecode, - dependencies.into_iter().map(|dep| dep.bytecode).collect::>(), - ), - ); - } - - contract - .deployed_bytecode - .and_then(|d_bcode| d_bcode.bytecode) - .and_then(|bcode| bcode.object.into_bytes()) - .and_then(|bytes| known_contracts.insert(id.clone(), (abi, bytes.to_vec()))); - Ok(()) - }, - root, - )?; - - let execution_info = known_contracts.flatten(); - Ok(MultiContractRunner { - contracts: deployable_contracts, - known_contracts, - evm_opts, - env, - evm_spec: self.evm_spec.unwrap_or(SpecId::MERGE), - sender: self.sender, - errors: Some(execution_info.2), - source_paths, - fork: self.fork, - cheats_config: self.cheats_config.unwrap_or_default().into(), - coverage: self.coverage, - debug: self.debug, - test_options: self.test_options.unwrap_or_default(), - }) } - #[must_use] pub fn sender(mut self, sender: Address) -> Self { self.sender = Some(sender); self } - #[must_use] pub fn initial_balance(mut self, initial_balance: U256) -> Self { self.initial_balance = initial_balance; self } - #[must_use] pub fn evm_spec(mut self, spec: SpecId) -> Self { self.evm_spec = Some(spec); self } - #[must_use] pub fn with_fork(mut self, fork: Option) -> Self { self.fork = fork; self } - #[must_use] - pub fn with_cheats_config(mut self, cheats_config: CheatsConfig) -> Self { - self.cheats_config = Some(cheats_config); - self - } - - #[must_use] pub fn with_test_options(mut self, test_options: TestOptions) -> Self { self.test_options = Some(test_options); self } - #[must_use] pub fn set_coverage(mut self, enable: bool) -> Self { self.coverage = enable; self } - #[must_use] pub fn set_debug(mut self, enable: bool) -> Self { self.debug = enable; self } + + pub fn enable_isolation(mut self, enable: bool) -> Self { + self.isolation = enable; + self + } + + /// Given an EVM, proceeds to return a runner which is able to execute all tests + /// against that evm + pub fn build( + self, + root: &Path, + output: ProjectCompileOutput, + env: revm::primitives::Env, + evm_opts: EvmOpts, + ) -> Result { + let output = output.with_stripped_file_prefixes(root); + let linker = Linker::new(root, output.artifact_ids().collect()); + + // Build revert decoder from ABIs of all artifacts. + let abis = linker + .contracts + .iter() + .filter_map(|(_, contract)| contract.abi.as_ref().map(|abi| abi.borrow())); + let revert_decoder = RevertDecoder::new().with_abis(abis); + + // Create a mapping of name => (abi, deployment code, Vec) + let mut deployable_contracts = DeployableContracts::default(); + + for (id, contract) in linker.contracts.iter() { + let Some(abi) = contract.abi.as_ref() else { + continue; + }; + + // if it's a test, link it and add to deployable contracts + if abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) && + abi.functions().any(|func| func.name.is_test() || func.name.is_invariant_test()) + { + let LinkOutput { libs_to_deploy, libraries } = linker.link_with_nonce_or_address( + Default::default(), + evm_opts.sender, + 1, + id, + )?; + + let linked_contract = linker.link(id, &libraries)?; + + let Some(bytecode) = linked_contract + .get_bytecode_bytes() + .map(|b| b.into_owned()) + .filter(|b| !b.is_empty()) + else { + continue; + }; + + deployable_contracts.insert( + id.clone(), + TestContract { + abi: abi.clone().into_owned(), + bytecode, + libs_to_deploy, + libraries, + }, + ); + } + } + + Ok(MultiContractRunner { + contracts: deployable_contracts, + evm_opts, + env, + evm_spec: self.evm_spec.unwrap_or(SpecId::MERGE), + sender: self.sender, + revert_decoder, + fork: self.fork, + config: self.config, + coverage: self.coverage, + debug: self.debug, + test_options: self.test_options.unwrap_or_default(), + isolation: self.isolation, + output, + }) + } +} + +pub fn matches_contract(id: &ArtifactId, abi: &JsonAbi, filter: &dyn TestFilter) -> bool { + (filter.matches_path(&id.source) && filter.matches_contract(&id.name)) && + abi.functions().any(|func| is_matching_test(func, filter)) +} + +/// Returns `true` if the function is a test function that matches the given filter. +pub(crate) fn is_matching_test(func: &Function, filter: &dyn TestFilter) -> bool { + (func.is_test() || func.is_invariant_test()) && filter.matches_test(&func.signature()) } diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index 604fbb6983026..2dd5508e85d69 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -1,32 +1,208 @@ //! Test outcomes. -use alloy_primitives::Address; -use ethers_core::types::Log; -use foundry_common::evm::Breakpoints; +use alloy_primitives::{Address, Log}; +use foundry_common::{ + evm::Breakpoints, get_contract_name, get_file_name, shell, ContractsByArtifact, +}; +use foundry_compilers::artifacts::Libraries; use foundry_evm::{ coverage::HitMaps, debug::DebugArena, executors::EvmError, - fuzz::{CounterExample, FuzzCase}, - traces::{TraceKind, Traces}, + fuzz::{CounterExample, FuzzCase, FuzzFixtures}, + traces::{CallTraceArena, CallTraceDecoder, TraceKind, Traces}, }; use serde::{Deserialize, Serialize}; use std::{ - collections::BTreeMap, + collections::{BTreeMap, HashMap}, fmt::{self, Write}, + sync::Arc, time::Duration, }; use yansi::Paint; -/// Results and duration for a set of tests included in the same test contract -#[derive(Debug, Clone, Serialize)] +use crate::gas_report::GasReport; + +/// The aggregated result of a test run. +#[derive(Clone, Debug)] +pub struct TestOutcome { + /// The results of all test suites by their identifier (`path:contract_name`). + /// + /// Essentially `identifier => signature => result`. + pub results: BTreeMap, + /// Whether to allow test failures without failing the entire test run. + pub allow_failure: bool, + /// The decoder used to decode traces and logs. + /// + /// This is `None` if traces and logs were not decoded. + /// + /// Note that `Address` fields only contain the last executed test case's data. + pub last_run_decoder: Option, + /// The gas report, if requested. + pub gas_report: Option, +} + +impl TestOutcome { + /// Creates a new test outcome with the given results. + pub fn new(results: BTreeMap, allow_failure: bool) -> Self { + Self { results, allow_failure, last_run_decoder: None, gas_report: None } + } + + /// Creates a new empty test outcome. + pub fn empty(allow_failure: bool) -> Self { + Self::new(BTreeMap::new(), allow_failure) + } + + /// Returns an iterator over all individual succeeding tests and their names. + pub fn successes(&self) -> impl Iterator { + self.tests().filter(|(_, t)| t.status == TestStatus::Success) + } + + /// Returns an iterator over all individual skipped tests and their names. + pub fn skips(&self) -> impl Iterator { + self.tests().filter(|(_, t)| t.status == TestStatus::Skipped) + } + + /// Returns an iterator over all individual failing tests and their names. + pub fn failures(&self) -> impl Iterator { + self.tests().filter(|(_, t)| t.status == TestStatus::Failure) + } + + /// Returns an iterator over all individual tests and their names. + pub fn tests(&self) -> impl Iterator { + self.results.values().flat_map(|suite| suite.tests()) + } + + /// Flattens the test outcome into a list of individual tests. + // TODO: Replace this with `tests` and make it return `TestRef<'_>` + pub fn into_tests_cloned(&self) -> impl Iterator + '_ { + self.results + .iter() + .flat_map(|(file, suite)| { + suite + .test_results + .iter() + .map(move |(sig, result)| (file.clone(), sig.clone(), result.clone())) + }) + .map(|(artifact_id, signature, result)| SuiteTestResult { + artifact_id, + signature, + result, + }) + } + + /// Flattens the test outcome into a list of individual tests. + pub fn into_tests(self) -> impl Iterator { + self.results + .into_iter() + .flat_map(|(file, suite)| { + suite.test_results.into_iter().map(move |t| (file.clone(), t)) + }) + .map(|(artifact_id, (signature, result))| SuiteTestResult { + artifact_id, + signature, + result, + }) + } + + /// Returns the number of tests that passed. + pub fn passed(&self) -> usize { + self.successes().count() + } + + /// Returns the number of tests that were skipped. + pub fn skipped(&self) -> usize { + self.skips().count() + } + + /// Returns the number of tests that failed. + pub fn failed(&self) -> usize { + self.failures().count() + } + + /// Sums up all the durations of all individual test suites. + /// + /// Note that this is not necessarily the wall clock time of the entire test run. + pub fn total_time(&self) -> Duration { + self.results.values().map(|suite| suite.duration).sum() + } + + /// Formats the aggregated summary of all test suites into a string (for printing). + pub fn summary(&self, wall_clock_time: Duration) -> String { + let num_test_suites = self.results.len(); + let suites = if num_test_suites == 1 { "suite" } else { "suites" }; + let total_passed = self.passed(); + let total_failed = self.failed(); + let total_skipped = self.skipped(); + let total_tests = total_passed + total_failed + total_skipped; + format!( + "\nRan {} test {} in {:.2?} ({:.2?} CPU time): {} tests passed, {} failed, {} skipped ({} total tests)", + num_test_suites, + suites, + wall_clock_time, + self.total_time(), + total_passed.green(), + total_failed.red(), + total_skipped.yellow(), + total_tests + ) + } + + /// Checks if there are any failures and failures are disallowed. + pub fn ensure_ok(&self) -> eyre::Result<()> { + let outcome = self; + let failures = outcome.failures().count(); + if outcome.allow_failure || failures == 0 { + return Ok(()); + } + + if !shell::verbosity().is_normal() { + // TODO: Avoid process::exit + std::process::exit(1); + } + + shell::println("")?; + shell::println("Failing tests:")?; + for (suite_name, suite) in outcome.results.iter() { + let failed = suite.failed(); + if failed == 0 { + continue; + } + + let term = if failed > 1 { "tests" } else { "test" }; + shell::println(format!("Encountered {failed} failing {term} in {suite_name}"))?; + for (name, result) in suite.failures() { + shell::println(result.short_result(name))?; + } + shell::println("")?; + } + let successes = outcome.passed(); + shell::println(format!( + "Encountered a total of {} failing tests, {} tests succeeded", + failures.to_string().red(), + successes.to_string().green() + ))?; + + // TODO: Avoid process::exit + std::process::exit(1); + } +} + +/// A set of test results for a single test suite, which is all the tests in a single contract. +#[derive(Clone, Debug, Serialize)] pub struct SuiteResult { - /// Total duration of the test run for this block of tests + /// Wall clock time it took to execute all tests in this suite. + #[serde(with = "humantime_serde")] pub duration: Duration, - /// Individual test results. `test fn signature -> TestResult` + /// Individual test results: `test fn signature -> TestResult`. pub test_results: BTreeMap, - /// Warnings + /// Generated warnings. pub warnings: Vec, + /// Libraries used to link test contract. + pub libraries: Libraries, + /// Contracts linked with correct libraries. + #[serde(skip)] + pub known_contracts: Arc, } impl SuiteResult { @@ -34,20 +210,42 @@ impl SuiteResult { duration: Duration, test_results: BTreeMap, warnings: Vec, + libraries: Libraries, + known_contracts: Arc, ) -> Self { - Self { duration, test_results, warnings } + Self { duration, test_results, warnings, libraries, known_contracts } } - /// Iterator over all succeeding tests and their names + /// Returns an iterator over all individual succeeding tests and their names. pub fn successes(&self) -> impl Iterator { self.tests().filter(|(_, t)| t.status == TestStatus::Success) } - /// Iterator over all failing tests and their names + /// Returns an iterator over all individual skipped tests and their names. + pub fn skips(&self) -> impl Iterator { + self.tests().filter(|(_, t)| t.status == TestStatus::Skipped) + } + + /// Returns an iterator over all individual failing tests and their names. pub fn failures(&self) -> impl Iterator { self.tests().filter(|(_, t)| t.status == TestStatus::Failure) } + /// Returns the number of tests that passed. + pub fn passed(&self) -> usize { + self.successes().count() + } + + /// Returns the number of tests that were skipped. + pub fn skipped(&self) -> usize { + self.skips().count() + } + + /// Returns the number of tests that failed. + pub fn failed(&self) -> usize { + self.failures().count() + } + /// Iterator over all tests and their names pub fn tests(&self) -> impl Iterator { self.test_results.iter() @@ -62,8 +260,62 @@ impl SuiteResult { pub fn len(&self) -> usize { self.test_results.len() } + + /// Sums up all the durations of all individual tests in this suite. + /// + /// Note that this is not necessarily the wall clock time of the entire test suite. + pub fn total_time(&self) -> Duration { + self.test_results.values().map(|result| result.duration).sum() + } + + /// Returns the summary of a single test suite. + pub fn summary(&self) -> String { + let failed = self.failed(); + let result = if failed == 0 { "ok".green() } else { "FAILED".red() }; + format!( + "Suite result: {}. {} passed; {} failed; {} skipped; finished in {:.2?} ({:.2?} CPU time)", + result, + self.passed().green(), + failed.red(), + self.skipped().yellow(), + self.duration, + self.total_time(), + ) + } +} + +/// The result of a single test in a test suite. +/// +/// This is flattened from a [`TestOutcome`]. +#[derive(Clone, Debug)] +pub struct SuiteTestResult { + /// The identifier of the artifact/contract in the form: + /// `:`. + pub artifact_id: String, + /// The function signature of the Solidity test. + pub signature: String, + /// The result of the executed test. + pub result: TestResult, +} + +impl SuiteTestResult { + /// Returns the gas used by the test. + pub fn gas_used(&self) -> u64 { + self.result.kind.report().gas() + } + + /// Returns the contract name of the artifact ID. + pub fn contract_name(&self) -> &str { + get_contract_name(&self.artifact_id) + } + + /// Returns the file name of the artifact ID. + pub fn file_name(&self) -> &str { + get_file_name(&self.artifact_id) + } } +/// The status of a test. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum TestStatus { Success, @@ -92,7 +344,7 @@ impl TestStatus { } } -/// The result of an executed solidity test +/// The result of an executed test. #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct TestResult { /// The test status, indicating whether the test case succeeded, failed, or was marked as @@ -119,18 +371,25 @@ pub struct TestResult { pub kind: TestKind, /// Traces + #[serde(skip)] pub traces: Traces, + /// Additional traces to use for gas report. + #[serde(skip)] + pub gas_report_traces: Vec>, + /// Raw coverage info #[serde(skip)] pub coverage: Option, /// Labeled addresses - pub labeled_addresses: BTreeMap, + pub labeled_addresses: HashMap, /// The debug nodes of the call pub debug: Option, + pub duration: Duration, + /// pc breakpoint char map pub breakpoints: Breakpoints, } @@ -138,8 +397,8 @@ pub struct TestResult { impl fmt::Display for TestResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.status { - TestStatus::Success => Paint::green("[PASS]").fmt(f), - TestStatus::Skipped => Paint::yellow("[SKIP]").fmt(f), + TestStatus::Success => "[PASS]".green().fmt(f), + TestStatus::Skipped => "[SKIP]".yellow().fmt(f), TestStatus::Failure => { let mut s = String::from("[FAIL. Reason: "); @@ -162,7 +421,7 @@ impl fmt::Display for TestResult { s.push(']'); } - Paint::red(s).fmt(f) + s.red().fmt(f) } } } @@ -177,10 +436,15 @@ impl TestResult { pub fn is_fuzz(&self) -> bool { matches!(self.kind, TestKind::Fuzz { .. }) } + + /// Formats the test result into a string (for printing). + pub fn short_result(&self, name: &str) -> String { + format!("{self} {name} {}", self.kind.report()) + } } /// Data report by a test. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum TestKindReport { Standard { gas: u64 }, Fuzz { runs: usize, mean_gas: u64, median_gas: u64 }, @@ -265,11 +529,13 @@ pub struct TestSetup { /// Call traces of the setup pub traces: Traces, /// Addresses labeled during setup - pub labeled_addresses: BTreeMap, + pub labeled_addresses: HashMap, /// The reason the setup failed, if it did pub reason: Option, /// Coverage info during setup pub coverage: Option, + /// Defined fuzz test fixtures + pub fuzz_fixtures: FuzzFixtures, } impl TestSetup { @@ -277,14 +543,14 @@ impl TestSetup { error: EvmError, mut logs: Vec, mut traces: Traces, - mut labeled_addresses: BTreeMap, + mut labeled_addresses: HashMap, ) -> Self { match error { EvmError::Execution(err) => { // force the tracekind to be setup so a trace is shown. - traces.extend(err.traces.map(|traces| (TraceKind::Setup, traces))); - logs.extend(err.logs); - labeled_addresses.extend(err.labels); + traces.extend(err.raw.traces.map(|traces| (TraceKind::Setup, traces))); + logs.extend(err.raw.logs); + labeled_addresses.extend(err.raw.labels); Self::failed_with(logs, traces, labeled_addresses, err.reason) } e => Self::failed_with( @@ -300,16 +566,17 @@ impl TestSetup { address: Address, logs: Vec, traces: Traces, - labeled_addresses: BTreeMap, + labeled_addresses: HashMap, coverage: Option, + fuzz_fixtures: FuzzFixtures, ) -> Self { - Self { address, logs, traces, labeled_addresses, reason: None, coverage } + Self { address, logs, traces, labeled_addresses, reason: None, coverage, fuzz_fixtures } } pub fn failed_with( logs: Vec, traces: Traces, - labeled_addresses: BTreeMap, + labeled_addresses: HashMap, reason: String, ) -> Self { Self { @@ -319,6 +586,7 @@ impl TestSetup { labeled_addresses, reason: Some(reason), coverage: None, + fuzz_fixtures: FuzzFixtures::default(), } } diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 0a9a121c2aae2..b29309bb0ac45 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -1,11 +1,13 @@ //! The Forge test runner. use crate::{ + multi_runner::{is_matching_test, TestContract}, result::{SuiteResult, TestKind, TestResult, TestSetup, TestStatus}, TestFilter, TestOptions, }; -use alloy_json_abi::{Function, JsonAbi as Abi}; -use alloy_primitives::{Address, Bytes, U256}; +use alloy_dyn_abi::DynSolValue; +use alloy_json_abi::Function; +use alloy_primitives::{Address, U256}; use eyre::Result; use foundry_common::{ contracts::{ContractsByAddress, ContractsByArtifact}, @@ -15,36 +17,37 @@ use foundry_config::{FuzzConfig, InvariantConfig}; use foundry_evm::{ constants::CALLER, coverage::HitMaps, - decode::decode_console_logs, + decode::{decode_console_logs, RevertDecoder}, executors::{ fuzz::{CaseOutcome, CounterExampleOutcome, FuzzOutcome, FuzzedExecutor}, - invariant::{replay_run, InvariantExecutor, InvariantFuzzError, InvariantFuzzTestResult}, - CallResult, EvmError, ExecutionErr, Executor, + invariant::{ + replay_error, replay_run, InvariantExecutor, InvariantFuzzError, + InvariantFuzzTestResult, + }, + CallResult, EvmError, ExecutionErr, Executor, RawCallResult, }, - fuzz::{invariant::InvariantContract, CounterExample}, + fuzz::{fixture_name, invariant::InvariantContract, CounterExample, FuzzFixtures}, traces::{load_contracts, TraceKind}, }; -use proptest::test_runner::{TestError, TestRunner}; +use proptest::test_runner::TestRunner; use rayon::prelude::*; use std::{ + borrow::Cow, collections::{BTreeMap, HashMap}, + sync::Arc, time::Instant, }; /// A type that executes all tests of a contract -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ContractRunner<'a> { pub name: &'a str, + /// The data of the contract being ran. + pub contract: &'a TestContract, /// The executor used by the runner. pub executor: Executor, - /// Library contracts to be deployed before the test contract - pub predeploy_libs: &'a [Bytes], - /// The deployed contract's code - pub code: Bytes, - /// The test contract's ABI - pub contract: &'a Abi, - /// All known errors, used to decode reverts - pub errors: Option<&'a Abi>, + /// Revert decoder. Contains all known errors. + pub revert_decoder: &'a RevertDecoder, /// The initial balance of the test contract pub initial_balance: U256, /// The address which will be used as the `from` field in all EVM calls @@ -54,27 +57,22 @@ pub struct ContractRunner<'a> { } impl<'a> ContractRunner<'a> { - #[allow(clippy::too_many_arguments)] pub fn new( name: &'a str, executor: Executor, - contract: &'a Abi, - code: Bytes, + contract: &'a TestContract, initial_balance: U256, sender: Option
, - errors: Option<&'a Abi>, - predeploy_libs: &'a [Bytes], + revert_decoder: &'a RevertDecoder, debug: bool, ) -> Self { Self { name, executor, contract, - code, initial_balance, sender: sender.unwrap_or_default(), - errors, - predeploy_libs, + revert_decoder, debug, } } @@ -102,12 +100,17 @@ impl<'a> ContractRunner<'a> { // Deploy libraries let mut logs = Vec::new(); - let mut traces = Vec::with_capacity(self.predeploy_libs.len()); - for code in self.predeploy_libs.iter() { - match self.executor.deploy(self.sender, code.clone(), U256::ZERO, self.errors) { + let mut traces = Vec::with_capacity(self.contract.libs_to_deploy.len()); + for code in self.contract.libs_to_deploy.iter() { + match self.executor.deploy( + self.sender, + code.clone(), + U256::ZERO, + Some(self.revert_decoder), + ) { Ok(d) => { - logs.extend(d.logs); - traces.extend(d.traces.map(|traces| (TraceKind::Deployment, traces))); + logs.extend(d.raw.logs); + traces.extend(d.raw.traces.map(|traces| (TraceKind::Deployment, traces))); } Err(e) => { return Ok(TestSetup::from_evm_error_with(e, logs, traces, Default::default())) @@ -115,22 +118,30 @@ impl<'a> ContractRunner<'a> { } } - // Deploy the test contract - let address = - match self.executor.deploy(self.sender, self.code.clone(), U256::ZERO, self.errors) { - Ok(d) => { - logs.extend(d.logs); - traces.extend(d.traces.map(|traces| (TraceKind::Deployment, traces))); - d.address - } - Err(e) => { - return Ok(TestSetup::from_evm_error_with(e, logs, traces, Default::default())) - } - }; + let address = self.sender.create(self.executor.get_nonce(self.sender)?); - // Now we set the contracts initial balance, and we also reset `self.sender`s and `CALLER`s - // balance to the initial balance we want + // Set the contracts initial balance before deployment, so it is available during + // construction self.executor.set_balance(address, self.initial_balance)?; + + // Deploy the test contract + match self.executor.deploy( + self.sender, + self.contract.bytecode.clone(), + U256::ZERO, + Some(self.revert_decoder), + ) { + Ok(d) => { + logs.extend(d.raw.logs); + traces.extend(d.raw.traces.map(|traces| (TraceKind::Deployment, traces))); + d.address + } + Err(e) => { + return Ok(TestSetup::from_evm_error_with(e, logs, traces, Default::default())) + } + }; + + // Reset `self.sender`s and `CALLER`s balance to the initial balance we want self.executor.set_balance(self.sender, self.initial_balance)?; self.executor.set_balance(CALLER, self.initial_balance)?; @@ -139,48 +150,117 @@ impl<'a> ContractRunner<'a> { // Optionally call the `setUp` function let setup = if setup { trace!("setting up"); - let (setup_logs, setup_traces, labeled_addresses, reason, coverage) = match self - .executor - .setup(None, address) - { - Ok(CallResult { traces, labels, logs, coverage, .. }) => { + let res = self.executor.setup(None, address, Some(self.revert_decoder)); + let (setup_logs, setup_traces, labeled_addresses, reason, coverage) = match res { + Ok(RawCallResult { traces, labels, logs, coverage, .. }) => { trace!(contract=%address, "successfully setUp test"); (logs, traces, labels, None, coverage) } Err(EvmError::Execution(err)) => { - let ExecutionErr { traces, labels, logs, reason, .. } = *err; - error!(reason=%reason, contract=%address, "setUp failed"); - (logs, traces, labels, Some(format!("setup failed: {reason}")), None) + let ExecutionErr { + raw: RawCallResult { traces, labels, logs, coverage, .. }, + reason, + } = *err; + (logs, traces, labels, Some(format!("setup failed: {reason}")), coverage) } Err(err) => { - error!(reason=%err, contract=%address, "setUp failed"); - (Vec::new(), None, BTreeMap::new(), Some(format!("setup failed: {err}")), None) + (Vec::new(), None, HashMap::new(), Some(format!("setup failed: {err}")), None) } }; traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces))); logs.extend(setup_logs); - TestSetup { address, logs, traces, labeled_addresses, reason, coverage } + TestSetup { + address, + logs, + traces, + labeled_addresses, + reason, + coverage, + fuzz_fixtures: self.fuzz_fixtures(address), + } } else { - TestSetup::success(address, logs, traces, Default::default(), None) + TestSetup::success( + address, + logs, + traces, + Default::default(), + None, + self.fuzz_fixtures(address), + ) }; Ok(setup) } + /// Collect fixtures from test contract. + /// + /// Fixtures can be defined: + /// - as storage arrays in test contract, prefixed with `fixture` + /// - as functions prefixed with `fixture` and followed by parameter name to be + /// fuzzed + /// + /// Storage array fixtures: + /// `uint256[] public fixture_amount = [1, 2, 3];` + /// define an array of uint256 values to be used for fuzzing `amount` named parameter in scope + /// of the current test. + /// + /// Function fixtures: + /// `function fixture_owner() public returns (address[] memory){}` + /// returns an array of addresses to be used for fuzzing `owner` named parameter in scope of the + /// current test. + fn fuzz_fixtures(&mut self, address: Address) -> FuzzFixtures { + let mut fixtures = HashMap::new(); + self.contract.abi.functions().filter(|func| func.is_fixture()).for_each(|func| { + if func.inputs.is_empty() { + // Read fixtures declared as functions. + if let Ok(CallResult { raw: _, decoded_result }) = + self.executor.call(CALLER, address, func, &[], U256::ZERO, None) + { + fixtures.insert(fixture_name(func.name.clone()), decoded_result); + } + } else { + // For reading fixtures from storage arrays we collect values by calling the + // function with incremented indexes until there's an error. + let mut vals = Vec::new(); + let mut index = 0; + loop { + if let Ok(CallResult { raw: _, decoded_result }) = self.executor.call( + CALLER, + address, + func, + &[DynSolValue::Uint(U256::from(index), 256)], + U256::ZERO, + None, + ) { + vals.push(decoded_result); + } else { + // No result returned for this index, we reached the end of storage + // array or the function is not a valid fixture. + break; + } + index += 1; + } + fixtures.insert(fixture_name(func.name.clone()), DynSolValue::Array(vals)); + }; + }); + + FuzzFixtures::new(fixtures) + } + /// Runs all tests for a contract whose names match the provided regular expression pub fn run_tests( mut self, - filter: impl TestFilter, - test_options: TestOptions, - known_contracts: Option<&ContractsByArtifact>, + filter: &dyn TestFilter, + test_options: &TestOptions, + known_contracts: Arc, ) -> SuiteResult { info!("starting tests"); let start = Instant::now(); let mut warnings = Vec::new(); let setup_fns: Vec<_> = - self.contract.functions().filter(|func| func.name.is_setup()).collect(); + self.contract.abi.functions().filter(|func| func.name.is_setup()).collect(); let needs_setup = setup_fns.len() == 1 && setup_fns[0].name == "setUp"; @@ -201,10 +281,12 @@ impl<'a> ContractRunner<'a> { [("setUp()".to_string(), TestResult::fail("multiple setUp functions".to_string()))] .into(), warnings, + self.contract.libraries.clone(), + known_contracts, ) } - let has_invariants = self.contract.functions().any(|func| func.is_invariant_test()); + let has_invariants = self.contract.abi.functions().any(|func| func.is_invariant_test()); // Invariant testing requires tracing to figure out what contracts were created. let tmp_tracing = self.executor.inspector.tracer.is_none() && has_invariants && needs_setup; @@ -237,61 +319,77 @@ impl<'a> ContractRunner<'a> { )] .into(), warnings, + self.contract.libraries.clone(), + known_contracts, ) } - let functions: Vec<_> = self.contract.functions().collect(); - let mut test_results = functions + // Filter out functions sequentially since it's very fast and there is no need to do it + // in parallel. + let find_timer = Instant::now(); + let functions = self + .contract + .abi + .functions() + .filter(|func| is_matching_test(func, filter)) + .collect::>(); + let find_time = find_timer.elapsed(); + debug!( + "Found {} test functions out of {} in {:?}", + functions.len(), + self.contract.abi.functions().count(), + find_time, + ); + + let identified_contracts = + has_invariants.then(|| load_contracts(setup.traces.clone(), &known_contracts)); + let test_results = functions .par_iter() - .filter(|&&func| func.is_test() && filter.matches_test(func.signature())) .map(|&func| { + let sig = func.signature(); + + let setup = setup.clone(); let should_fail = func.is_test_fail(); - let res = if func.is_fuzz_test() { + let res = if func.is_invariant_test() { + let runner = test_options.invariant_runner(self.name, &func.name); + let invariant_config = test_options.invariant_config(self.name, &func.name); + self.run_invariant_test( + runner, + setup, + *invariant_config, + func, + &known_contracts, + identified_contracts.as_ref().unwrap(), + ) + } else if func.is_fuzz_test() { + debug_assert!(func.is_test()); let runner = test_options.fuzz_runner(self.name, &func.name); let fuzz_config = test_options.fuzz_config(self.name, &func.name); - self.run_fuzz_test(func, should_fail, runner, setup.clone(), *fuzz_config) + self.run_fuzz_test(func, should_fail, runner, setup, fuzz_config.clone()) } else { - self.run_test(func, should_fail, setup.clone()) + debug_assert!(func.is_test()); + self.run_test(func, should_fail, setup) }; - (func.signature(), res) + + (sig, res) }) .collect::>(); - if has_invariants { - let identified_contracts = load_contracts(setup.traces.clone(), known_contracts); - let results: Vec<_> = functions - .par_iter() - .filter(|&&func| func.is_invariant_test() && filter.matches_test(func.signature())) - .map(|&func| { - let runner = test_options.invariant_runner(self.name, &func.name); - let invariant_config = test_options.invariant_config(self.name, &func.name); - let res = self.run_invariant_test( - runner, - setup.clone(), - *invariant_config, - func, - known_contracts, - &identified_contracts, - ); - (func.signature(), res) - }) - .collect(); - test_results.extend(results); - } - let duration = start.elapsed(); - if !test_results.is_empty() { - let successful = - test_results.iter().filter(|(_, tst)| tst.status == TestStatus::Success).count(); - info!( - duration = ?duration, - "done. {}/{} successful", - successful, - test_results.len() - ); - } - - SuiteResult::new(duration, test_results, warnings) + let suite_result = SuiteResult::new( + duration, + test_results, + warnings, + self.contract.libraries.clone(), + known_contracts, + ); + info!( + duration=?suite_result.duration, + "done. {}/{} successful", + suite_result.passed(), + suite_result.test_results.len() + ); + suite_result } /// Runs a single test @@ -300,100 +398,92 @@ impl<'a> ContractRunner<'a> { /// /// State modifications are not committed to the evm database but discarded after the call, /// similar to `eth_call`. - #[instrument(name = "test", skip_all, fields(name = %func.signature(), %should_fail))] pub fn run_test(&self, func: &Function, should_fail: bool, setup: TestSetup) -> TestResult { + let span = info_span!("test", %should_fail); + if !span.is_disabled() { + let sig = &func.signature()[..]; + if enabled!(tracing::Level::TRACE) { + span.record("sig", sig); + } else { + span.record("sig", sig.split('(').next().unwrap()); + } + } + let _guard = span.enter(); + let TestSetup { address, mut logs, mut traces, mut labeled_addresses, mut coverage, .. } = setup; // Run unit test let mut executor = self.executor.clone(); - let start = Instant::now(); - let debug_arena; - let (reverted, reason, gas, stipend, coverage, state_changeset, breakpoints) = - match executor.execute_test::<_, _>( - self.sender, - address, - func.clone(), - vec![], - U256::ZERO, - self.errors, - ) { - Ok(CallResult { - reverted, - gas_used: gas, - stipend, - logs: execution_logs, - traces: execution_trace, - coverage: execution_coverage, - labels: new_labels, - state_changeset, - debug, - breakpoints, - .. - }) => { - traces.extend(execution_trace.map(|traces| (TraceKind::Execution, traces))); - labeled_addresses.extend(new_labels); - logs.extend(execution_logs); - debug_arena = debug; - coverage = merge_coverages(coverage, execution_coverage); - - (reverted, None, gas, stipend, coverage, state_changeset, breakpoints) - } - Err(EvmError::Execution(err)) => { - traces.extend(err.traces.map(|traces| (TraceKind::Execution, traces))); - labeled_addresses.extend(err.labels); - logs.extend(err.logs); - debug_arena = err.debug; - ( - err.reverted, - Some(err.reason), - err.gas_used, - err.stipend, - None, - err.state_changeset, - HashMap::new(), - ) - } - Err(EvmError::SkipError) => { - return TestResult { - status: TestStatus::Skipped, - reason: None, - decoded_logs: decode_console_logs(&logs), - traces, - labeled_addresses, - kind: TestKind::Standard(0), - ..Default::default() - } + let start: Instant = Instant::now(); + let (raw_call_result, reason) = match executor.execute_test( + self.sender, + address, + func, + &[], + U256::ZERO, + Some(self.revert_decoder), + ) { + Ok(res) => (res.raw, None), + Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)), + Err(EvmError::SkipError) => { + return TestResult { + status: TestStatus::Skipped, + reason: None, + decoded_logs: decode_console_logs(&logs), + traces, + labeled_addresses, + kind: TestKind::Standard(0), + duration: start.elapsed(), + ..Default::default() } - Err(err) => { - return TestResult { - status: TestStatus::Failure, - reason: Some(err.to_string()), - decoded_logs: decode_console_logs(&logs), - traces, - labeled_addresses, - kind: TestKind::Standard(0), - ..Default::default() - } + } + Err(err) => { + return TestResult { + status: TestStatus::Failure, + reason: Some(err.to_string()), + decoded_logs: decode_console_logs(&logs), + traces, + labeled_addresses, + kind: TestKind::Standard(0), + duration: start.elapsed(), + ..Default::default() } - }; + } + }; + + let RawCallResult { + reverted, + gas_used: gas, + stipend, + logs: execution_logs, + traces: execution_trace, + coverage: execution_coverage, + labels: new_labels, + state_changeset, + debug, + cheatcodes, + .. + } = raw_call_result; + + let breakpoints = cheatcodes.map(|c| c.breakpoints).unwrap_or_default(); + let debug_arena = debug; + traces.extend(execution_trace.map(|traces| (TraceKind::Execution, traces))); + labeled_addresses.extend(new_labels); + logs.extend(execution_logs); + coverage = merge_coverages(coverage, execution_coverage); let success = executor.is_success( setup.address, reverted, - state_changeset.expect("we should have a state changeset"), + Cow::Owned(state_changeset.unwrap()), should_fail, ); // Record test execution time - debug!( - duration = ?start.elapsed(), - gas, - reverted, - should_fail, - success, - ); + let duration = start.elapsed(); + trace!(?duration, gas, reverted, should_fail, success); TestResult { status: match success { @@ -410,32 +500,34 @@ impl<'a> ContractRunner<'a> { labeled_addresses, debug: debug_arena, breakpoints, + duration, + gas_report_traces: Vec::new(), } } - #[instrument(name = "invariant-test", skip_all)] + #[instrument(name = "invariant_test", skip_all)] pub fn run_invariant_test( &self, runner: TestRunner, setup: TestSetup, invariant_config: InvariantConfig, func: &Function, - known_contracts: Option<&ContractsByArtifact>, + known_contracts: &ContractsByArtifact, identified_contracts: &ContractsByAddress, ) -> TestResult { trace!(target: "forge::test::fuzz", "executing invariant test for {:?}", func.name); - let empty = ContractsByArtifact::default(); - let project_contracts = known_contracts.unwrap_or(&empty); - let TestSetup { address, logs, traces, labeled_addresses, coverage, .. } = setup; + let TestSetup { address, logs, traces, labeled_addresses, coverage, fuzz_fixtures, .. } = + setup; // First, run the test normally to see if it needs to be skipped. - if let Err(EvmError::SkipError) = self.executor.clone().execute_test::<_, _>( + let start = Instant::now(); + if let Err(EvmError::SkipError) = self.executor.clone().execute_test( self.sender, address, - func.clone(), - vec![], + func, + &[], U256::ZERO, - self.errors, + Some(self.revert_decoder), ) { return TestResult { status: TestStatus::Skipped, @@ -445,6 +537,7 @@ impl<'a> ContractRunner<'a> { labeled_addresses, kind: TestKind::Invariant { runs: 1, calls: 1, reverts: 1 }, coverage, + duration: start.elapsed(), ..Default::default() } }; @@ -454,58 +547,67 @@ impl<'a> ContractRunner<'a> { runner, invariant_config, identified_contracts, - project_contracts, + known_contracts, ); let invariant_contract = - InvariantContract { address, invariant_function: func, abi: self.contract }; + InvariantContract { address, invariant_function: func, abi: &self.contract.abi }; - let InvariantFuzzTestResult { error, cases, reverts, last_run_inputs } = match evm - .invariant_fuzz(invariant_contract.clone()) - { - Ok(x) => x, - Err(e) => { - return TestResult { - status: TestStatus::Failure, - reason: Some(format!("failed to set up invariant testing environment: {e}")), - decoded_logs: decode_console_logs(&logs), - traces, - labeled_addresses, - kind: TestKind::Invariant { runs: 0, calls: 0, reverts: 0 }, - ..Default::default() + let InvariantFuzzTestResult { error, cases, reverts, last_run_inputs, gas_report_traces } = + match evm.invariant_fuzz(invariant_contract.clone(), &fuzz_fixtures) { + Ok(x) => x, + Err(e) => { + return TestResult { + status: TestStatus::Failure, + reason: Some(format!( + "failed to set up invariant testing environment: {e}" + )), + decoded_logs: decode_console_logs(&logs), + traces, + labeled_addresses, + kind: TestKind::Invariant { runs: 0, calls: 0, reverts: 0 }, + duration: start.elapsed(), + ..Default::default() + } } - } - }; + }; let mut counterexample = None; let mut logs = logs.clone(); let mut traces = traces.clone(); let success = error.is_none(); - let reason = error - .as_ref() - .and_then(|err| (!err.revert_reason.is_empty()).then(|| err.revert_reason.clone())); + let reason = error.as_ref().and_then(|err| err.revert_reason()); let mut coverage = coverage.clone(); match error { // If invariants were broken, replay the error to collect logs and traces - Some(error @ InvariantFuzzError { test_error: TestError::Fail(_, _), .. }) => { - match error.replay( - self.executor.clone(), - known_contracts, - identified_contracts.clone(), - &mut logs, - &mut traces, - ) { - Ok(c) => counterexample = c, - Err(err) => { - error!(%err, "Failed to replay invariant error"); - } - }; - } + Some(error) => match error { + InvariantFuzzError::BrokenInvariant(case_data) | + InvariantFuzzError::Revert(case_data) => { + // Replay error to create counterexample and to collect logs, traces and + // coverage. + match replay_error( + &case_data, + &invariant_contract, + self.executor.clone(), + known_contracts, + identified_contracts.clone(), + &mut logs, + &mut traces, + &mut coverage, + ) { + Ok(c) => counterexample = c, + Err(err) => { + error!(%err, "Failed to replay invariant error"); + } + }; + } + InvariantFuzzError::MaxAssumeRejects(_) => {} + }, // If invariants ran successfully, replay the last run to collect logs and // traces. _ => { - replay_run( + if let Err(err) = replay_run( &invariant_contract, self.executor.clone(), known_contracts, @@ -513,18 +615,13 @@ impl<'a> ContractRunner<'a> { &mut logs, &mut traces, &mut coverage, - func.clone(), last_run_inputs.clone(), - ); + ) { + error!(%err, "Failed to replay last invariant run"); + } } } - let kind = TestKind::Invariant { - runs: cases.len(), - calls: cases.iter().map(|sequence| sequence.cases().len()).sum(), - reverts, - }; - TestResult { status: match success { true => TestStatus::Success, @@ -534,15 +631,21 @@ impl<'a> ContractRunner<'a> { counterexample, decoded_logs: decode_console_logs(&logs), logs, - kind, + kind: TestKind::Invariant { + runs: cases.len(), + calls: cases.iter().map(|sequence| sequence.cases().len()).sum(), + reverts, + }, coverage, traces, labeled_addresses: labeled_addresses.clone(), + duration: start.elapsed(), + gas_report_traces, ..Default::default() // TODO collect debug traces on the last run or error } } - #[instrument(name = "fuzz-test", skip_all, fields(name = %func.signature(), %should_fail))] + #[instrument(name = "fuzz_test", skip_all, fields(name = %func.signature(), %should_fail))] pub fn run_fuzz_test( &self, func: &Function, @@ -551,16 +654,37 @@ impl<'a> ContractRunner<'a> { setup: TestSetup, fuzz_config: FuzzConfig, ) -> TestResult { + let span = info_span!("fuzz_test", %should_fail); + if !span.is_disabled() { + let sig = &func.signature()[..]; + if enabled!(tracing::Level::TRACE) { + span.record("test", sig); + } else { + span.record("test", sig.split('(').next().unwrap()); + } + } + let _guard = span.enter(); + let TestSetup { - address, mut logs, mut traces, mut labeled_addresses, mut coverage, .. + address, + mut logs, + mut traces, + mut labeled_addresses, + mut coverage, + fuzz_fixtures, + .. } = setup; // Run fuzz test let start = Instant::now(); - let fuzzed_executor = - FuzzedExecutor::new(self.executor.clone(), runner.clone(), self.sender, fuzz_config); - let state = fuzzed_executor.build_fuzz_state(); - let mut result = fuzzed_executor.fuzz(func, address, should_fail, self.errors); + let fuzzed_executor = FuzzedExecutor::new( + self.executor.clone(), + runner.clone(), + self.sender, + fuzz_config.clone(), + ); + let result = + fuzzed_executor.fuzz(func, &fuzz_fixtures, address, should_fail, self.revert_decoder); let mut debug = Default::default(); let mut breakpoints = Default::default(); @@ -578,6 +702,7 @@ impl<'a> ContractRunner<'a> { debug, breakpoints, coverage, + duration: start.elapsed(), ..Default::default() } } @@ -597,13 +722,12 @@ impl<'a> ContractRunner<'a> { result.first_case.calldata.clone() }; // rerun the last relevant test with traces - let debug_result = FuzzedExecutor::new( - debug_executor, - runner, - self.sender, - fuzz_config, - ) - .single_fuzz(&state, address, should_fail, calldata); + let debug_result = + FuzzedExecutor::new(debug_executor, runner, self.sender, fuzz_config).single_fuzz( + address, + should_fail, + calldata, + ); (debug, breakpoints) = match debug_result { Ok(fuzz_outcome) => match fuzz_outcome { @@ -628,16 +752,14 @@ impl<'a> ContractRunner<'a> { }; // Record logs, labels and traces - logs.append(&mut result.logs); - labeled_addresses.append(&mut result.labeled_addresses); + logs.extend(result.logs); + labeled_addresses.extend(result.labeled_addresses); traces.extend(result.traces.map(|traces| (TraceKind::Execution, traces))); coverage = merge_coverages(coverage, result.coverage); // Record test execution time - debug!( - duration = ?start.elapsed(), - success = %result.success - ); + let duration = start.elapsed(); + trace!(?duration, success = %result.success); TestResult { status: match result.success { @@ -654,6 +776,8 @@ impl<'a> ContractRunner<'a> { labeled_addresses, debug, breakpoints, + duration, + gas_report_traces: result.gas_report_traces.into_iter().map(|t| vec![t]).collect(), } } } diff --git a/crates/forge/tests/cli/build.rs b/crates/forge/tests/cli/build.rs index 2e97cb7491a72..668e4be5e1deb 100644 --- a/crates/forge/tests/cli/build.rs +++ b/crates/forge/tests/cli/build.rs @@ -24,3 +24,19 @@ contract Dummy { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/compile_json.stdout"), ); }); + +// tests build output is as expected +forgetest_init!(exact_build_output, |prj, cmd| { + cmd.args(["build", "--force"]); + let stdout = cmd.stdout_lossy(); + assert!(stdout.contains("Compiling"), "\n{stdout}"); +}); + +// tests build output is as expected +forgetest_init!(build_sizes_no_forge_std, |prj, cmd| { + cmd.args(["build", "--sizes"]); + let stdout = cmd.stdout_lossy(); + assert!(!stdout.contains("console"), "\n{stdout}"); + assert!(!stdout.contains("std"), "\n{stdout}"); + assert!(stdout.contains("Counter"), "\n{stdout}"); +}); diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index b43617078fa85..a4deb8bcef3de 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -2,15 +2,18 @@ use crate::constants::*; use foundry_compilers::{artifacts::Metadata, remappings::Remapping, ConfigurableContractArtifact}; -use foundry_config::{parse_with_profile, BasicConfig, Chain, Config, SolidityErrorCode}; +use foundry_config::{ + parse_with_profile, BasicConfig, Chain, Config, FuzzConfig, SolidityErrorCode, +}; use foundry_test_utils::{ foundry_compilers::PathStyle, + rpc::next_etherscan_api_key, util::{pretty_err, read_string, OutputExt, TestCommand}, }; use semver::Version; use std::{ env, fs, - path::PathBuf, + path::{Path, PathBuf}, process::{Command, Stdio}, str::FromStr, }; @@ -393,7 +396,7 @@ forgetest!(can_init_vscode, |prj, cmd| { let remappings = prj.root().join("remappings.txt"); assert!(remappings.is_file()); let content = std::fs::read_to_string(remappings).unwrap(); - assert_eq!(content, "ds-test/=lib/forge-std/lib/ds-test/src/\nforge-std/=lib/forge-std/src/",); + assert_eq!(content, "forge-std/=lib/forge-std/src/",); }); // checks that forge can init with template @@ -432,6 +435,83 @@ forgetest!(fail_init_nonexistent_template, |prj, cmd| { cmd.assert_non_empty_stderr(); }); +// checks that clone works +forgetest!(can_clone, |prj, cmd| { + prj.wipe(); + + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(!foundry_toml.exists()); + + cmd.args([ + "clone", + "--etherscan-api-key", + next_etherscan_api_key().as_str(), + "0x044b75f554b886A065b9567891e45c79542d7357", + ]) + .arg(prj.root()); + cmd.assert_non_empty_stdout(); + + let s = read_string(&foundry_toml); + let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; +}); + +// Checks that quiet mode does not print anything for clone +forgetest!(can_clone_quiet, |prj, cmd| { + prj.wipe(); + + cmd.args([ + "clone", + "--etherscan-api-key", + next_etherscan_api_key().as_str(), + "--quiet", + "0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec", + ]) + .arg(prj.root()); + cmd.assert_empty_stdout(); +}); + +// checks that clone works with --no-remappings-txt +forgetest!(can_clone_no_remappings_txt, |prj, cmd| { + prj.wipe(); + + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(!foundry_toml.exists()); + + cmd.args([ + "clone", + "--etherscan-api-key", + next_etherscan_api_key().as_str(), + "--no-remappings-txt", + "0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf", + ]) + .arg(prj.root()); + cmd.assert_non_empty_stdout(); + + let s = read_string(&foundry_toml); + let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; +}); + +// checks that clone works with --keep-directory-structure +forgetest!(can_clone_keep_directory_structure, |prj, cmd| { + prj.wipe(); + + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(!foundry_toml.exists()); + + cmd.args([ + "clone", + "--etherscan-api-key", + next_etherscan_api_key().as_str(), + "--keep-directory-structure", + "0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf", + ]) + .arg(prj.root()); + cmd.assert_non_empty_stdout(); + + let s = read_string(&foundry_toml); + let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; +}); + // checks that `clean` removes dapptools style paths forgetest!(can_clean, |prj, cmd| { prj.assert_create_dirs_exists(); @@ -466,6 +546,20 @@ forgetest_init!(can_clean_config, |prj, cmd| { assert!(!artifact.exists()); }); +// checks that `clean` removes fuzz cache dir +forgetest_init!(can_clean_fuzz_cache, |prj, cmd| { + let config = Config { fuzz: FuzzConfig::new("cache/fuzz".into()), ..Default::default() }; + prj.write_config(config); + // default test contract is written in custom out directory + let cache_dir = prj.root().join("cache/fuzz"); + let _ = fs::create_dir(cache_dir.clone()); + assert!(cache_dir.exists()); + + cmd.forge_fuse().arg("clean"); + cmd.output(); + assert!(!cache_dir.exists()); +}); + // checks that extra output works forgetest_init!(can_emit_extra_output, |prj, cmd| { cmd.args(["build", "--extra-output", "metadata"]); @@ -536,31 +630,10 @@ contract Greeter { cmd.arg("build"); let output = cmd.stdout_lossy(); - assert!(output.contains( - " -Compiler run successful with warnings: -Warning (5667): Warning: Unused function parameter. Remove or comment out the variable name to silence this warning. -", - )); + assert!(output.contains("Warning"), "{output}"); }); // Tests that direct import paths are handled correctly -// -// NOTE(onbjerg): Disabled for Windows -- for some reason solc fails with a bogus error message -// here: error[9553]: TypeError: Invalid type for argument in function call. Invalid implicit -// conversion from struct Bar memory to struct Bar memory requested. --> src\Foo.sol:12:22: -// | -// 12 | FooLib.check(b); -// | ^ -// -// -// -// error[9553]: TypeError: Invalid type for argument in function call. Invalid implicit conversion -// from contract Foo to contract Foo requested. --> src\Foo.sol:15:23: -// | -// 15 | FooLib.check2(this); -// | ^^^^ -#[cfg(not(target_os = "windows"))] forgetest!(can_handle_direct_imports_into_src, |prj, cmd| { prj.add_source( "Foo", @@ -635,15 +708,12 @@ contract Foo { }); // test that `forge snapshot` commands work -forgetest!( - #[serial_test::serial] - can_check_snapshot, - |prj, cmd| { - prj.insert_ds_test(); +forgetest!(can_check_snapshot, |prj, cmd| { + prj.insert_ds_test(); - prj.add_source( - "ATest.t.sol", - r#" + prj.add_source( + "ATest.t.sol", + r#" import "./test.sol"; contract ATest is DSTest { function testExample() public { @@ -651,20 +721,54 @@ contract ATest is DSTest { } } "#, - ) - .unwrap(); + ) + .unwrap(); + + cmd.arg("snapshot"); - cmd.arg("snapshot"); + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/can_check_snapshot.stdout"), + ); - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_check_snapshot.stdout"), - ); + cmd.arg("--check"); + let _ = cmd.output(); +}); - cmd.arg("--check"); - let _ = cmd.output(); - } -); +// test that `forge build` does not print `(with warnings)` if file path is ignored +forgetest!(can_compile_without_warnings_ignored_file_paths, |prj, cmd| { + // Ignoring path and setting empty error_codes as default would set would set some error codes + prj.write_config(Config { + ignored_file_paths: vec![Path::new("src").to_path_buf()], + ignored_error_codes: vec![], + ..Default::default() + }); + + prj.add_raw_source( + "src/example.sol", + r" +pragma solidity *; +contract A { + function testExample() public {} +} +", + ) + .unwrap(); + + cmd.args(["build", "--force"]); + let out = cmd.stdout_lossy(); + // expect no warning as path is ignored + assert!(out.contains("Compiler run successful!")); + assert!(!out.contains("Compiler run successful with warnings:")); + + // Reconfigure without ignored paths or error codes and check for warnings + // need to reset empty error codes as default would set some error codes + prj.write_config(Config { ignored_error_codes: vec![], ..Default::default() }); + + let out = cmd.stdout_lossy(); + // expect warnings as path is not ignored + assert!(out.contains("Compiler run successful with warnings:"), "{out}"); + assert!(out.contains("Warning") && out.contains("SPDX-License-Identifier"), "{out}"); +}); // test that `forge build` does not print `(with warnings)` if there arent any forgetest!(can_compile_without_warnings, |prj, cmd| { @@ -750,7 +854,7 @@ forgetest!( .to_string(); println!("project root: \"{root}\""); - let eth_rpc_url = foundry_common::rpc::next_http_archive_rpc_endpoint(); + let eth_rpc_url = foundry_test_utils::rpc::next_http_archive_rpc_endpoint(); let dss_exec_lib = "src/DssSpell.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4"; cmd.args([ @@ -895,6 +999,23 @@ forgetest!(can_install_and_remove, |prj, cmd| { remove(&mut cmd, "lib/forge-std"); }); +// test to check we can run `forge install` in an empty dir +forgetest!(can_install_empty, |prj, cmd| { + // create + cmd.git_init(); + cmd.forge_fuse().args(["install"]); + cmd.assert_empty_stdout(); + + // create initial commit + fs::write(prj.root().join("README.md"), "Initial commit").unwrap(); + + cmd.git_add().unwrap(); + cmd.git_commit("Initial commit").unwrap(); + + cmd.forge_fuse().args(["install"]); + cmd.assert_empty_stdout(); +}); + // test to check that package can be reinstalled after manually removing the directory forgetest!(can_reinstall_after_manual_remove, |prj, cmd| { cmd.git_init(); @@ -1238,36 +1359,34 @@ contract ContractThreeTest is DSTest { .unwrap(); // report for One - prj.write_config(Config { - gas_reports: (vec!["ContractOne".to_string()]), - gas_reports_ignore: (vec![]), - ..Default::default() - }); + prj.write_config(Config { gas_reports: vec!["ContractOne".to_string()], ..Default::default() }); cmd.forge_fuse(); let first_out = cmd.arg("test").arg("--gas-report").stdout_lossy(); - assert!(first_out.contains("foo") && !first_out.contains("bar") && !first_out.contains("baz")); + assert!( + first_out.contains("foo") && !first_out.contains("bar") && !first_out.contains("baz"), + "foo:\n{first_out}" + ); // report for Two - cmd.forge_fuse(); - prj.write_config(Config { - gas_reports: (vec!["ContractTwo".to_string()]), - ..Default::default() - }); + prj.write_config(Config { gas_reports: vec!["ContractTwo".to_string()], ..Default::default() }); cmd.forge_fuse(); let second_out = cmd.arg("test").arg("--gas-report").stdout_lossy(); assert!( - !second_out.contains("foo") && second_out.contains("bar") && !second_out.contains("baz") + !second_out.contains("foo") && second_out.contains("bar") && !second_out.contains("baz"), + "bar:\n{second_out}" ); // report for Three - cmd.forge_fuse(); prj.write_config(Config { - gas_reports: (vec!["ContractThree".to_string()]), + gas_reports: vec!["ContractThree".to_string()], ..Default::default() }); cmd.forge_fuse(); let third_out = cmd.arg("test").arg("--gas-report").stdout_lossy(); - assert!(!third_out.contains("foo") && !third_out.contains("bar") && third_out.contains("baz")); + assert!( + !third_out.contains("foo") && !third_out.contains("bar") && third_out.contains("baz"), + "baz:\n{third_out}" + ); }); forgetest!(gas_ignore_some_contracts, |prj, cmd| { @@ -1521,13 +1640,17 @@ forgetest_init!(can_install_missing_deps_build, |prj, cmd| { cmd.arg("build"); let output = cmd.stdout_lossy(); - assert!(output.contains("Missing dependencies found. Installing now"), "{}", output); - assert!(output.contains("No files changed, compilation skipped"), "{}", output); + assert!(output.contains("Missing dependencies found. Installing now"), "{output}"); + + // re-run + let output = cmd.stdout_lossy(); + assert!(!output.contains("Missing dependencies found. Installing now"), "{output}"); + assert!(output.contains("No files changed, compilation skipped"), "{output}"); }); // checks that extra output works forgetest_init!(can_build_skip_contracts, |prj, cmd| { - prj.clear_cache(); + prj.clear(); // only builds the single template contract `src/*` cmd.args(["build", "--skip", "tests", "--skip", "scripts"]); @@ -1544,8 +1667,6 @@ forgetest_init!(can_build_skip_contracts, |prj, cmd| { }); forgetest_init!(can_build_skip_glob, |prj, cmd| { - prj.clear_cache(); - prj.add_test( "Foo", r" @@ -1556,7 +1677,13 @@ function test_run() external {} .unwrap(); // only builds the single template contract `src/*` even if `*.t.sol` or `.s.sol` is absent - cmd.args(["build", "--skip", "*/test/**", "--skip", "*/script/**"]); + prj.clear(); + cmd.args(["build", "--skip", "*/test/**", "--skip", "*/script/**", "--force"]); + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/can_build_skip_glob.stdout"), + ); + + cmd.forge_fuse().args(["build", "--skip", "./test/**", "--skip", "./script/**", "--force"]); cmd.unchecked_output().stdout_matches_path( PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/can_build_skip_glob.stdout"), ); @@ -1594,3 +1721,17 @@ forgetest_init!(can_build_names_repeatedly, |prj, cmd| { let unchanged = cmd.stdout_lossy(); assert!(unchanged.contains(list), "{}", list); }); + +// +forgetest_init!(can_inspect_counter_pretty, |prj, cmd| { + cmd.args(["inspect", "src/Counter.sol:Counter", "abi", "--pretty"]); + let output = cmd.stdout_lossy(); + assert_eq!( + output.trim(), + "interface Counter { + function increment() external; + function number() external view returns (uint256); + function setNumber(uint256 newNumber) external; +}" + ); +}); diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 30fa8f4d94e94..f8fd6e3ef234c 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -2,10 +2,11 @@ use alloy_primitives::{Address, B256, U256}; use foundry_cli::utils as forge_utils; -use foundry_compilers::artifacts::{OptimizerDetails, RevertStrings, YulDetails}; +use foundry_compilers::artifacts::{BytecodeHash, OptimizerDetails, RevertStrings, YulDetails}; use foundry_config::{ cache::{CachedChains, CachedEndpoints, StorageCachingConfig}, - Config, FuzzConfig, InvariantConfig, SolcReq, + fs_permissions::{FsAccessPermission, PathPermission}, + Config, FsPermissions, FuzzConfig, InvariantConfig, SolcReq, }; use foundry_evm::opts::EvmOpts; use foundry_test_utils::{ @@ -14,7 +15,11 @@ use foundry_test_utils::{ }; use path_slash::PathBufExt; use pretty_assertions::assert_eq; -use std::{fs, path::PathBuf, str::FromStr}; +use std::{ + fs, + path::{Path, PathBuf}, + str::FromStr, +}; // tests all config values that are in use forgetest!(can_extract_config_values, |prj, cmd| { @@ -60,10 +65,14 @@ forgetest!(can_extract_config_values, |prj, cmd| { runs: 1000, max_test_rejects: 100203, seed: Some(U256::from(1000)), + failure_persist_dir: Some("test-cache/fuzz".into()), + failure_persist_file: Some("failures".to_string()), ..Default::default() }, invariant: InvariantConfig { runs: 256, ..Default::default() }, ffi: true, + always_use_create_2_factory: false, + prompt_timeout: 0, sender: "00a329c0648769A73afAc7F9381D08FB43dBEA72".parse().unwrap(), tx_origin: "00a329c0648769A73afAc7F9F81E08FB43dBEA72".parse().unwrap(), initial_balance: U256::from(0xffffffffffffffffffffffffu128), @@ -79,6 +88,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { block_difficulty: 10, block_prevrandao: B256::random(), block_gas_limit: Some(100u64.into()), + disable_block_gas_limit: false, memory_limit: 1 << 27, eth_rpc_url: Some("localhost".to_string()), eth_rpc_jwt: None, @@ -90,8 +100,10 @@ forgetest!(can_extract_config_values, |prj, cmd| { "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6".to_string() ], ignored_error_codes: vec![], + ignored_file_paths: vec![], deny_warnings: false, via_ir: true, + ast: false, rpc_storage_caching: StorageCachingConfig { chains: CachedChains::None, endpoints: CachedEndpoints::Remote, @@ -111,8 +123,11 @@ forgetest!(can_extract_config_values, |prj, cmd| { fmt: Default::default(), doc: Default::default(), fs_permissions: Default::default(), + labels: Default::default(), cancun: true, prague: true, + isolate: true, + unchecked_cheatcode_artifacts: false, __non_exhaustive: (), __warnings: vec![], }; @@ -122,200 +137,179 @@ forgetest!(can_extract_config_values, |prj, cmd| { }); // tests config gets printed to std out -forgetest!( - #[serial_test::serial] - can_show_config, - |prj, cmd| { - cmd.arg("config"); - let expected = - Config::load_with_root(prj.root()).to_string_pretty().unwrap().trim().to_string(); - assert_eq!(expected, cmd.stdout_lossy().trim().to_string()); - } -); +forgetest!(can_show_config, |prj, cmd| { + cmd.arg("config"); + let expected = + Config::load_with_root(prj.root()).to_string_pretty().unwrap().trim().to_string(); + assert_eq!(expected, cmd.stdout_lossy().trim().to_string()); +}); // checks that config works // - foundry.toml is properly generated // - paths are resolved properly // - config supports overrides from env, and cli -forgetest_init!( - #[serial_test::serial] - can_override_config, - |prj, cmd| { - cmd.set_current_dir(prj.root()); - let foundry_toml = prj.root().join(Config::FILE_NAME); - assert!(foundry_toml.exists()); - - let profile = Config::load_with_root(prj.root()); - // ensure that the auto-generated internal remapping for forge-std's ds-test exists - assert_eq!(profile.remappings.len(), 2); - assert_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", profile.remappings[0].to_string()); - - // ensure remappings contain test - assert_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", profile.remappings[0].to_string()); - // the loaded config has resolved, absolute paths - assert_eq!( - "ds-test/=lib/forge-std/lib/ds-test/src/", - Remapping::from(profile.remappings[0].clone()).to_string() - ); - - cmd.arg("config"); - let expected = profile.to_string_pretty().unwrap(); - assert_eq!(expected.trim().to_string(), cmd.stdout_lossy().trim().to_string()); - - // remappings work - let remappings_txt = - prj.create_file("remappings.txt", "ds-test/=lib/forge-std/lib/ds-test/from-file/"); - let config = forge_utils::load_config_with_root(Some(prj.root().into())); - assert_eq!( - format!( - "ds-test/={}/", - prj.root().join("lib/forge-std/lib/ds-test/from-file").to_slash_lossy() - ), - Remapping::from(config.remappings[0].clone()).to_string() - ); - - // env vars work - std::env::set_var("DAPP_REMAPPINGS", "ds-test/=lib/forge-std/lib/ds-test/from-env/"); - let config = forge_utils::load_config_with_root(Some(prj.root().into())); - assert_eq!( - format!( - "ds-test/={}/", - prj.root().join("lib/forge-std/lib/ds-test/from-env").to_slash_lossy() - ), - Remapping::from(config.remappings[0].clone()).to_string() - ); - - let config = - prj.config_from_output(["--remappings", "ds-test/=lib/forge-std/lib/ds-test/from-cli"]); - assert_eq!( - format!( - "ds-test/={}/", - prj.root().join("lib/forge-std/lib/ds-test/from-cli").to_slash_lossy() - ), - Remapping::from(config.remappings[0].clone()).to_string() - ); - - let config = prj.config_from_output(["--remappings", "other-key/=lib/other/"]); - assert_eq!(config.remappings.len(), 3); - assert_eq!( - format!("other-key/={}/", prj.root().join("lib/other").to_slash_lossy()), - // As CLI has the higher priority, it'll be found at the first slot. - Remapping::from(config.remappings[0].clone()).to_string() - ); - - std::env::remove_var("DAPP_REMAPPINGS"); - pretty_err(&remappings_txt, fs::remove_file(&remappings_txt)); - - cmd.set_cmd(prj.forge_bin()).args(["config", "--basic"]); - let expected = profile.into_basic().to_string_pretty().unwrap(); - assert_eq!(expected.trim().to_string(), cmd.stdout_lossy().trim().to_string()); - } -); - -forgetest_init!( - #[serial_test::serial] - can_parse_remappings_correctly, - |prj, cmd| { - cmd.set_current_dir(prj.root()); - let foundry_toml = prj.root().join(Config::FILE_NAME); - assert!(foundry_toml.exists()); - - let profile = Config::load_with_root(prj.root()); - // ensure that the auto-generated internal remapping for forge-std's ds-test exists - assert_eq!(profile.remappings.len(), 2); - let [r, _] = &profile.remappings[..] else { unreachable!() }; - assert_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", r.to_string()); - - // the loaded config has resolved, absolute paths - assert_eq!( - "ds-test/=lib/forge-std/lib/ds-test/src/", - Remapping::from(r.clone()).to_string() - ); - - cmd.arg("config"); - let expected = profile.to_string_pretty().unwrap(); - assert_eq!(expected.trim().to_string(), cmd.stdout_lossy().trim().to_string()); - - let install = |cmd: &mut TestCommand, dep: &str| { - cmd.forge_fuse().args(["install", dep, "--no-commit"]); - cmd.assert_non_empty_stdout(); - }; - - install(&mut cmd, "transmissions11/solmate"); - let profile = Config::load_with_root(prj.root()); - // remappings work - let remappings_txt = prj.create_file( - "remappings.txt", - "solmate/=lib/solmate/src/\nsolmate-contracts/=lib/solmate/src/", - ); - let config = forge_utils::load_config_with_root(Some(prj.root().into())); - // trailing slashes are removed on windows `to_slash_lossy` - let path = prj.root().join("lib/solmate/src/").to_slash_lossy().into_owned(); - #[cfg(windows)] - let path = path + "/"; - assert_eq!( - format!("solmate/={path}"), - Remapping::from(config.remappings[0].clone()).to_string() - ); - // As this is an user-generated remapping, it is not removed, even if it points to the same - // location. - assert_eq!( - format!("solmate-contracts/={path}"), - Remapping::from(config.remappings[1].clone()).to_string() - ); - pretty_err(&remappings_txt, fs::remove_file(&remappings_txt)); - - cmd.set_cmd(prj.forge_bin()).args(["config", "--basic"]); - let expected = profile.into_basic().to_string_pretty().unwrap(); - assert_eq!(expected.trim().to_string(), cmd.stdout_lossy().trim().to_string()); - } -); - -forgetest_init!( - #[serial_test::serial] - can_detect_config_vals, - |prj, _cmd| { - let url = "http://127.0.0.1:8545"; - let config = prj.config_from_output(["--no-auto-detect", "--rpc-url", url]); - assert!(!config.auto_detect_solc); - assert_eq!(config.eth_rpc_url, Some(url.to_string())); - - let mut config = Config::load_with_root(prj.root()); - config.eth_rpc_url = Some("http://127.0.0.1:8545".to_string()); - config.auto_detect_solc = false; - // write to `foundry.toml` - prj.create_file( - Config::FILE_NAME, - &config.to_string_pretty().unwrap().replace("eth_rpc_url", "eth-rpc-url"), - ); - let config = prj.config_from_output(["--force"]); - assert!(!config.auto_detect_solc); - assert_eq!(config.eth_rpc_url, Some(url.to_string())); - } -); +forgetest_init!(can_override_config, |prj, cmd| { + cmd.set_current_dir(prj.root()); + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(foundry_toml.exists()); + + let profile = Config::load_with_root(prj.root()); + // ensure that the auto-generated internal remapping for forge-std's ds-test exists + assert_eq!(profile.remappings.len(), 1); + assert_eq!("forge-std/=lib/forge-std/src/", profile.remappings[0].to_string()); + + // ensure remappings contain test + assert_eq!("forge-std/=lib/forge-std/src/", profile.remappings[0].to_string()); + // the loaded config has resolved, absolute paths + assert_eq!( + "forge-std/=lib/forge-std/src/", + Remapping::from(profile.remappings[0].clone()).to_string() + ); + + cmd.arg("config"); + let expected = profile.to_string_pretty().unwrap(); + assert_eq!(expected.trim().to_string(), cmd.stdout_lossy().trim().to_string()); + + // remappings work + let remappings_txt = + prj.create_file("remappings.txt", "ds-test/=lib/forge-std/lib/ds-test/from-file/"); + let config = forge_utils::load_config_with_root(Some(prj.root().into())); + assert_eq!( + format!( + "ds-test/={}/", + prj.root().join("lib/forge-std/lib/ds-test/from-file").to_slash_lossy() + ), + Remapping::from(config.remappings[0].clone()).to_string() + ); + + // env vars work + std::env::set_var("DAPP_REMAPPINGS", "ds-test/=lib/forge-std/lib/ds-test/from-env/"); + let config = forge_utils::load_config_with_root(Some(prj.root().into())); + assert_eq!( + format!( + "ds-test/={}/", + prj.root().join("lib/forge-std/lib/ds-test/from-env").to_slash_lossy() + ), + Remapping::from(config.remappings[0].clone()).to_string() + ); + + let config = + prj.config_from_output(["--remappings", "ds-test/=lib/forge-std/lib/ds-test/from-cli"]); + assert_eq!( + format!( + "ds-test/={}/", + prj.root().join("lib/forge-std/lib/ds-test/from-cli").to_slash_lossy() + ), + Remapping::from(config.remappings[0].clone()).to_string() + ); + + let config = prj.config_from_output(["--remappings", "other-key/=lib/other/"]); + assert_eq!(config.remappings.len(), 3); + assert_eq!( + format!("other-key/={}/", prj.root().join("lib/other").to_slash_lossy()), + // As CLI has the higher priority, it'll be found at the first slot. + Remapping::from(config.remappings[0].clone()).to_string() + ); + + std::env::remove_var("DAPP_REMAPPINGS"); + pretty_err(&remappings_txt, fs::remove_file(&remappings_txt)); + + cmd.set_cmd(prj.forge_bin()).args(["config", "--basic"]); + let expected = profile.into_basic().to_string_pretty().unwrap(); + assert_eq!(expected.trim().to_string(), cmd.stdout_lossy().trim().to_string()); +}); + +forgetest_init!(can_parse_remappings_correctly, |prj, cmd| { + cmd.set_current_dir(prj.root()); + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(foundry_toml.exists()); + + let profile = Config::load_with_root(prj.root()); + // ensure that the auto-generated internal remapping for forge-std's ds-test exists + assert_eq!(profile.remappings.len(), 1); + let r = &profile.remappings[0]; + assert_eq!("forge-std/=lib/forge-std/src/", r.to_string()); + + // the loaded config has resolved, absolute paths + assert_eq!("forge-std/=lib/forge-std/src/", Remapping::from(r.clone()).to_string()); + + cmd.arg("config"); + let expected = profile.to_string_pretty().unwrap(); + assert_eq!(expected.trim().to_string(), cmd.stdout_lossy().trim().to_string()); + + let install = |cmd: &mut TestCommand, dep: &str| { + cmd.forge_fuse().args(["install", dep, "--no-commit"]); + cmd.assert_non_empty_stdout(); + }; + + install(&mut cmd, "transmissions11/solmate"); + let profile = Config::load_with_root(prj.root()); + // remappings work + let remappings_txt = prj.create_file( + "remappings.txt", + "solmate/=lib/solmate/src/\nsolmate-contracts/=lib/solmate/src/", + ); + let config = forge_utils::load_config_with_root(Some(prj.root().into())); + // trailing slashes are removed on windows `to_slash_lossy` + let path = prj.root().join("lib/solmate/src/").to_slash_lossy().into_owned(); + #[cfg(windows)] + let path = path + "/"; + assert_eq!( + format!("solmate/={path}"), + Remapping::from(config.remappings[0].clone()).to_string() + ); + // As this is an user-generated remapping, it is not removed, even if it points to the same + // location. + assert_eq!( + format!("solmate-contracts/={path}"), + Remapping::from(config.remappings[1].clone()).to_string() + ); + pretty_err(&remappings_txt, fs::remove_file(&remappings_txt)); + + cmd.set_cmd(prj.forge_bin()).args(["config", "--basic"]); + let expected = profile.into_basic().to_string_pretty().unwrap(); + assert_eq!(expected.trim().to_string(), cmd.stdout_lossy().trim().to_string()); +}); + +forgetest_init!(can_detect_config_vals, |prj, _cmd| { + let url = "http://127.0.0.1:8545"; + let config = prj.config_from_output(["--no-auto-detect", "--rpc-url", url]); + assert!(!config.auto_detect_solc); + assert_eq!(config.eth_rpc_url, Some(url.to_string())); + + let mut config = Config::load_with_root(prj.root()); + config.eth_rpc_url = Some("http://127.0.0.1:8545".to_string()); + config.auto_detect_solc = false; + // write to `foundry.toml` + prj.create_file( + Config::FILE_NAME, + &config.to_string_pretty().unwrap().replace("eth_rpc_url", "eth-rpc-url"), + ); + let config = prj.config_from_output(["--force"]); + assert!(!config.auto_detect_solc); + assert_eq!(config.eth_rpc_url, Some(url.to_string())); +}); // checks that `clean` removes dapptools style paths -forgetest_init!( - #[serial_test::serial] - can_get_evm_opts, - |prj, _cmd| { - let url = "http://127.0.0.1:8545"; - let config = prj.config_from_output(["--rpc-url", url, "--ffi"]); - assert_eq!(config.eth_rpc_url, Some(url.to_string())); - assert!(config.ffi); - - std::env::set_var("FOUNDRY_ETH_RPC_URL", url); - let figment = Config::figment_with_root(prj.root()).merge(("debug", false)); - let evm_opts: EvmOpts = figment.extract().unwrap(); - assert_eq!(evm_opts.fork_url, Some(url.to_string())); - std::env::remove_var("FOUNDRY_ETH_RPC_URL"); - } -); +forgetest_init!(can_get_evm_opts, |prj, _cmd| { + let url = "http://127.0.0.1:8545"; + let config = prj.config_from_output(["--rpc-url", url, "--ffi"]); + assert_eq!(config.eth_rpc_url, Some(url.to_string())); + assert!(config.ffi); + + std::env::set_var("FOUNDRY_ETH_RPC_URL", url); + let figment = Config::figment_with_root(prj.root()).merge(("debug", false)); + let evm_opts: EvmOpts = figment.extract().unwrap(); + assert_eq!(evm_opts.fork_url, Some(url.to_string())); + std::env::remove_var("FOUNDRY_ETH_RPC_URL"); +}); // checks that we can set various config values forgetest_init!(can_set_config_values, |prj, _cmd| { - let config = prj.config_from_output(["--via-ir"]); + let config = prj.config_from_output(["--via-ir", "--no-metadata"]); assert!(config.via_ir); + assert_eq!(config.cbor_metadata, false); + assert_eq!(config.bytecode_hash, BytecodeHash::None); }); // tests that solc can be explicitly set @@ -374,7 +368,7 @@ contract Foo {} // test to ensure yul optimizer can be set as intended forgetest!(can_set_yul_optimizer, |prj, cmd| { prj.add_source( - "Foo", + "foo.sol", r" contract Foo { function bar() public pure { @@ -399,17 +393,12 @@ contract Foo { ..Default::default() }; prj.write_config(config); - - assert!(cmd.stdout_lossy().ends_with( - " -Compiler run successful! -", - )); + cmd.assert_success(); }); // tests that the lib triple can be parsed forgetest_init!(can_parse_dapp_libraries, |_prj, cmd| { - cmd.set_env( + cmd.env( "DAPP_LIBRARIES", "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6", ); @@ -450,11 +439,11 @@ forgetest!(can_set_gas_price, |prj, cmd| { forgetest_init!(can_detect_lib_foundry_toml, |prj, cmd| { let config = cmd.config(); let remappings = config.remappings.iter().cloned().map(Remapping::from).collect::>(); + dbg!(&remappings); pretty_assertions::assert_eq!( remappings, vec![ // global - "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), "forge-std/=lib/forge-std/src/".parse().unwrap(), ] ); @@ -473,7 +462,6 @@ forgetest_init!(can_detect_lib_foundry_toml, |prj, cmd| { remappings, vec![ // default - "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), "forge-std/=lib/forge-std/src/".parse().unwrap(), // remapping is local to the lib "nested-lib/=lib/nested-lib/src/".parse().unwrap(), @@ -499,7 +487,6 @@ forgetest_init!(can_detect_lib_foundry_toml, |prj, cmd| { // local to the lib "another-lib/=lib/nested-lib/lib/another-lib/src/".parse().unwrap(), // global - "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), "forge-std/=lib/forge-std/src/".parse().unwrap(), "nested-lib/=lib/nested-lib/src/".parse().unwrap(), // remappings local to the lib @@ -518,7 +505,6 @@ forgetest_init!(can_detect_lib_foundry_toml, |prj, cmd| { // local to the lib "another-lib/=lib/nested-lib/lib/another-lib/custom-source-dir/".parse().unwrap(), // global - "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), "forge-std/=lib/forge-std/src/".parse().unwrap(), "nested-lib/=lib/nested-lib/src/".parse().unwrap(), // remappings local to the lib @@ -530,33 +516,27 @@ forgetest_init!(can_detect_lib_foundry_toml, |prj, cmd| { // test remappings with closer paths are prioritised // so that `dep/=lib/a/src` will take precedent over `dep/=lib/a/lib/b/src` -forgetest_init!( - #[serial_test::serial] - can_prioritise_closer_lib_remappings, - |prj, cmd| { - let config = cmd.config(); - - // create a new lib directly in the `lib` folder with conflicting remapping `forge-std/` - let mut config = config; - config.remappings = - vec![Remapping::from_str("forge-std/=lib/forge-std/src/").unwrap().into()]; - let nested = prj.paths().libraries[0].join("dep1"); - pretty_err(&nested, fs::create_dir_all(&nested)); - let toml_file = nested.join("foundry.toml"); - pretty_err(&toml_file, fs::write(&toml_file, config.to_string_pretty().unwrap())); - - let config = cmd.config(); - let remappings = config.get_all_remappings(); - pretty_assertions::assert_eq!( - remappings, - vec![ - "dep1/=lib/dep1/src/".parse().unwrap(), - "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), - "forge-std/=lib/forge-std/src/".parse().unwrap() - ] - ); - } -); +forgetest_init!(can_prioritise_closer_lib_remappings, |prj, cmd| { + let config = cmd.config(); + + // create a new lib directly in the `lib` folder with conflicting remapping `forge-std/` + let mut config = config; + config.remappings = vec![Remapping::from_str("forge-std/=lib/forge-std/src/").unwrap().into()]; + let nested = prj.paths().libraries[0].join("dep1"); + pretty_err(&nested, fs::create_dir_all(&nested)); + let toml_file = nested.join("foundry.toml"); + pretty_err(&toml_file, fs::write(&toml_file, config.to_string_pretty().unwrap())); + + let config = cmd.config(); + let remappings = config.get_all_remappings().collect::>(); + pretty_assertions::assert_eq!( + remappings, + vec![ + "dep1/=lib/dep1/src/".parse().unwrap(), + "forge-std/=lib/forge-std/src/".parse().unwrap() + ] + ); +}); // test to check that foundry.toml libs section updates on install forgetest!(can_update_libs_section, |prj, cmd| { @@ -604,10 +584,10 @@ forgetest!(config_emit_warnings, |prj, cmd| { assert_eq!( String::from_utf8_lossy(&output.stderr) .lines() - .filter(|line| { line.contains("Unknown section [default]") }) + .filter(|line| line.contains("unknown config section") && line.contains("[default]")) .count(), - 1 - ) + 1, + ); }); forgetest_init!(can_skip_remappings_auto_detection, |prj, cmd| { @@ -625,3 +605,83 @@ forgetest_init!(can_skip_remappings_auto_detection, |prj, cmd| { assert_eq!(config.remappings.len(), 1); assert_eq!("remapping/=lib/remapping/", config.remappings[0].to_string()); }); + +forgetest_init!(can_parse_default_fs_permissions, |_prj, cmd| { + let config = cmd.config(); + + assert_eq!(config.fs_permissions.len(), 1); + let out_permission = config.fs_permissions.find_permission(Path::new("out")).unwrap(); + assert_eq!(FsAccessPermission::Read, out_permission); +}); + +forgetest_init!(can_parse_custom_fs_permissions, |prj, cmd| { + // explicitly set fs permissions + let custom_permissions = FsPermissions::new(vec![ + PathPermission::read("./read"), + PathPermission::write("./write"), + PathPermission::read_write("./write/contracts"), + ]); + + let config = Config { fs_permissions: custom_permissions, ..Default::default() }; + prj.write_config(config); + + let config = cmd.config(); + + assert_eq!(config.fs_permissions.len(), 3); + + // check read permission + let permission = config.fs_permissions.find_permission(Path::new("./read")).unwrap(); + assert_eq!(permission, FsAccessPermission::Read); + // check nested write permission + let permission = + config.fs_permissions.find_permission(Path::new("./write/MyContract.sol")).unwrap(); + assert_eq!(permission, FsAccessPermission::Write); + // check nested read-write permission + let permission = config + .fs_permissions + .find_permission(Path::new("./write/contracts/MyContract.sol")) + .unwrap(); + assert_eq!(permission, FsAccessPermission::ReadWrite); + // check no permission + let permission = + config.fs_permissions.find_permission(Path::new("./bogus")).unwrap_or_default(); + assert_eq!(permission, FsAccessPermission::None); +}); + +#[cfg(not(target_os = "windows"))] +forgetest_init!(can_resolve_symlink_fs_permissions, |prj, cmd| { + // write config in packages/files/config.json + let config_path = prj.root().join("packages").join("files"); + fs::create_dir_all(&config_path).unwrap(); + fs::write(config_path.join("config.json"), "{ enabled: true }").unwrap(); + + // symlink packages/files dir as links/ + std::os::unix::fs::symlink( + Path::new("./packages/../packages/../packages/files"), + prj.root().join("links"), + ) + .unwrap(); + + // write config, give read access to links/ symlink to packages/files/ + let permissions = + FsPermissions::new(vec![PathPermission::read(Path::new("./links/config.json"))]); + let config = Config { fs_permissions: permissions, ..Default::default() }; + prj.write_config(config); + + let config = cmd.config(); + let mut fs_permissions = config.fs_permissions; + fs_permissions.join_all(prj.root()); + assert_eq!(fs_permissions.len(), 1); + + // read permission to file should be granted through symlink + let permission = fs_permissions.find_permission(&config_path.join("config.json")).unwrap(); + assert_eq!(permission, FsAccessPermission::Read); +}); + +// tests if evm version is normalized for config output +forgetest!(normalize_config_evm_version, |_prj, cmd| { + cmd.args(["config", "--use", "0.8.0", "--json"]); + let output = cmd.stdout_lossy(); + let config: Config = serde_json::from_str(&output).unwrap(); + assert_eq!(config.evm_version, EvmVersion::Istanbul); +}); diff --git a/crates/forge/tests/cli/context.rs b/crates/forge/tests/cli/context.rs new file mode 100644 index 0000000000000..34a7598a37cd9 --- /dev/null +++ b/crates/forge/tests/cli/context.rs @@ -0,0 +1,81 @@ +//! Contains tests for checking forge execution context cheatcodes +const FORGE_TEST_CONTEXT_CONTRACT: &str = r#" +import "./test.sol"; +interface Vm { + enum ForgeContext { TestGroup, Test, Coverage, Snapshot, ScriptGroup, ScriptDryRun, ScriptBroadcast, ScriptResume, Unknown } + function isContext(ForgeContext context) external view returns (bool isContext); +} + +contract ForgeContextTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testForgeTestContext() external view { + require(vm.isContext(Vm.ForgeContext.TestGroup) && !vm.isContext(Vm.ForgeContext.ScriptGroup), "wrong context"); + require(vm.isContext(Vm.ForgeContext.Test), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Coverage), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Snapshot), "wrong context"); + } + function testForgeSnapshotContext() external view { + require(vm.isContext(Vm.ForgeContext.TestGroup) && !vm.isContext(Vm.ForgeContext.ScriptGroup), "wrong context"); + require(vm.isContext(Vm.ForgeContext.Snapshot), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Test), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Coverage), "wrong context"); + } + function testForgeCoverageContext() external view { + require(vm.isContext(Vm.ForgeContext.TestGroup) && !vm.isContext(Vm.ForgeContext.ScriptGroup), "wrong context"); + require(vm.isContext(Vm.ForgeContext.Coverage), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Test), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Snapshot), "wrong context"); + } + + function runDryRun() external view { + require(vm.isContext(Vm.ForgeContext.ScriptGroup) && !vm.isContext(Vm.ForgeContext.TestGroup), "wrong context"); + require(vm.isContext(Vm.ForgeContext.ScriptDryRun), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.ScriptBroadcast), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.ScriptResume), "wrong context"); + } + function runBroadcast() external view { + require(vm.isContext(Vm.ForgeContext.ScriptGroup) && !vm.isContext(Vm.ForgeContext.TestGroup), "wrong context"); + require(vm.isContext(Vm.ForgeContext.ScriptBroadcast), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.ScriptDryRun), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.ScriptResume), "wrong context"); + } +} + "#; + +// tests that context properly set for `forge test` command +forgetest!(can_set_forge_test_standard_context, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source("ForgeContextTest.t.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + cmd.args(["test", "--match-test", "testForgeTestContext"]).assert_success(); +}); + +// tests that context properly set for `forge snapshot` command +forgetest!(can_set_forge_test_snapshot_context, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source("ForgeContextTest.t.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + cmd.args(["snapshot", "--match-test", "testForgeSnapshotContext"]).assert_success(); +}); + +// tests that context properly set for `forge coverage` command +forgetest!(can_set_forge_test_coverage_context, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source("ForgeContextTest.t.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + cmd.args(["coverage", "--match-test", "testForgeCoverageContext"]).assert_success(); +}); + +// tests that context properly set for `forge script` command +forgetest!(can_set_forge_script_dry_run_context, |prj, cmd| { + prj.insert_ds_test(); + let script = + prj.add_source("ForgeScriptContextTest.s.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + cmd.arg("script").arg(script).args(["--sig", "runDryRun()"]).assert_success(); +}); + +// tests that context properly set for `forge script --broadcast` command +forgetest!(can_set_forge_script_broadcast_context, |prj, cmd| { + prj.insert_ds_test(); + let script = + prj.add_source("ForgeScriptContextTest.s.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + cmd.arg("script").arg(script).args(["--broadcast", "--sig", "runBroadcast()"]).assert_success(); +}); diff --git a/crates/forge/tests/cli/create.rs b/crates/forge/tests/cli/create.rs index 9cb15954989fe..20f0aa7cca5a4 100644 --- a/crates/forge/tests/cli/create.rs +++ b/crates/forge/tests/cli/create.rs @@ -129,98 +129,87 @@ forgetest!(can_create_oracle_on_mumbai, |prj, cmd| { }); // tests that we can deploy the template contract -forgetest_async!( - #[serial_test::serial] - can_create_template_contract, - |prj, cmd| { - foundry_test_utils::util::initialize(prj.root()); - - let (_api, handle) = spawn(NodeConfig::test()).await; - let rpc = handle.http_endpoint(); - let wallet = handle.dev_wallets().next().unwrap(); - let pk = hex::encode(wallet.signer().to_bytes()); - - // explicitly byte code hash for consistent checks - let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; - prj.write_config(config); - - cmd.forge_fuse().args([ - "create", - format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), - "--rpc-url", - rpc.as_str(), - "--private-key", - pk.as_str(), - ]); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_template_contract.stdout"), - ); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_template_contract-2nd.stdout"), - ); - } -); +forgetest_async!(can_create_template_contract, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let (_api, handle) = spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + let wallet = handle.dev_wallets().next().unwrap(); + let pk = hex::encode(wallet.signer().to_bytes()); + + // explicitly byte code hash for consistent checks + let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; + prj.write_config(config); + + cmd.forge_fuse().args([ + "create", + format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + ]); + + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/can_create_template_contract.stdout"), + ); + + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/can_create_template_contract-2nd.stdout"), + ); +}); // tests that we can deploy the template contract -forgetest_async!( - #[serial_test::serial] - can_create_using_unlocked, - |prj, cmd| { - foundry_test_utils::util::initialize(prj.root()); - - let (_api, handle) = spawn(NodeConfig::test()).await; - let rpc = handle.http_endpoint(); - let dev = handle.dev_accounts().next().unwrap(); - - // explicitly byte code hash for consistent checks - let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; - prj.write_config(config); - - cmd.forge_fuse().args([ - "create", - format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), - "--rpc-url", - rpc.as_str(), - "--from", - format!("{dev:?}").as_str(), - "--unlocked", - ]); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_using_unlocked.stdout"), - ); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_using_unlocked-2nd.stdout"), - ); - } -); +forgetest_async!(can_create_using_unlocked, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let (_api, handle) = spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + let dev = handle.dev_accounts().next().unwrap(); + + // explicitly byte code hash for consistent checks + let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; + prj.write_config(config); + + cmd.forge_fuse().args([ + "create", + format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), + "--rpc-url", + rpc.as_str(), + "--from", + format!("{dev:?}").as_str(), + "--unlocked", + ]); + + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/can_create_using_unlocked.stdout"), + ); + + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/can_create_using_unlocked-2nd.stdout"), + ); +}); // tests that we can deploy with constructor args -forgetest_async!( - #[serial_test::serial] - can_create_with_constructor_args, - |prj, cmd| { - foundry_test_utils::util::initialize(prj.root()); - - let (_api, handle) = spawn(NodeConfig::test()).await; - let rpc = handle.http_endpoint(); - let wallet = handle.dev_wallets().next().unwrap(); - let pk = hex::encode(wallet.signer().to_bytes()); - - // explicitly byte code hash for consistent checks - let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; - prj.write_config(config); - - prj.add_source( - "ConstructorContract", - r#" +forgetest_async!(can_create_with_constructor_args, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let (_api, handle) = spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + let wallet = handle.dev_wallets().next().unwrap(); + let pk = hex::encode(wallet.signer().to_bytes()); + + // explicitly byte code hash for consistent checks + let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; + prj.write_config(config); + + prj.add_source( + "ConstructorContract", + r#" contract ConstructorContract { string public name; @@ -229,28 +218,28 @@ contract ConstructorContract { } } "#, - ) - .unwrap(); - - cmd.forge_fuse().args([ - "create", - "./src/ConstructorContract.sol:ConstructorContract", - "--rpc-url", - rpc.as_str(), - "--private-key", - pk.as_str(), - "--constructor-args", - "My Constructor", - ]); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_with_constructor_args.stdout"), - ); - - prj.add_source( - "TupleArrayConstructorContract", - r#" + ) + .unwrap(); + + cmd.forge_fuse().args([ + "create", + "./src/ConstructorContract.sol:ConstructorContract", + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + "--constructor-args", + "My Constructor", + ]); + + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/can_create_with_constructor_args.stdout"), + ); + + prj.add_source( + "TupleArrayConstructorContract", + r#" struct Point { uint256 x; uint256 y; @@ -260,46 +249,42 @@ contract TupleArrayConstructorContract { constructor(Point[] memory _points) {} } "#, - ) - .unwrap(); - - cmd.forge_fuse().args([ - "create", - "./src/TupleArrayConstructorContract.sol:TupleArrayConstructorContract", - "--rpc-url", - rpc.as_str(), - "--private-key", - pk.as_str(), - "--constructor-args", - "[(1,2), (2,3), (3,4)]", - ]); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_with_tuple_constructor_args.stdout"), - ); - } -); + ) + .unwrap(); + + cmd.forge_fuse().args([ + "create", + "./src/TupleArrayConstructorContract.sol:TupleArrayConstructorContract", + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + "--constructor-args", + "[(1,2), (2,3), (3,4)]", + ]); + + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/can_create_with_tuple_constructor_args.stdout"), + ); +}); // -forgetest_async!( - #[serial_test::serial] - can_create_and_call, - |prj, cmd| { - foundry_test_utils::util::initialize(prj.root()); - - let (_api, handle) = spawn(NodeConfig::test()).await; - let rpc = handle.http_endpoint(); - let wallet = handle.dev_wallets().next().unwrap(); - let pk = hex::encode(wallet.signer().to_bytes()); - - // explicitly byte code hash for consistent checks - let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; - prj.write_config(config); - - prj.add_source( - "UniswapV2Swap", - r#" +forgetest_async!(can_create_and_call, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let (_api, handle) = spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + let wallet = handle.dev_wallets().next().unwrap(); + let pk = hex::encode(wallet.signer().to_bytes()); + + // explicitly byte code hash for consistent checks + let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; + prj.write_config(config); + + prj.add_source( + "UniswapV2Swap", + r#" contract UniswapV2Swap { function pairInfo() public view returns (uint reserveA, uint reserveB, uint totalSupply) { @@ -308,19 +293,18 @@ contract UniswapV2Swap { } "#, - ) - .unwrap(); - - cmd.forge_fuse().args([ - "create", - "./src/UniswapV2Swap.sol:UniswapV2Swap", - "--rpc-url", - rpc.as_str(), - "--private-key", - pk.as_str(), - ]); - - let (stdout, _) = cmd.output_lossy(); - assert!(stdout.contains("Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3")); - } -); + ) + .unwrap(); + + cmd.forge_fuse().args([ + "create", + "./src/UniswapV2Swap.sol:UniswapV2Swap", + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + ]); + + let (stdout, _) = cmd.output_lossy(); + assert!(stdout.contains("Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3")); +}); diff --git a/crates/forge/tests/cli/debug.rs b/crates/forge/tests/cli/debug.rs new file mode 100644 index 0000000000000..3e3d08c7e2ad7 --- /dev/null +++ b/crates/forge/tests/cli/debug.rs @@ -0,0 +1,94 @@ +use itertools::Itertools; +use std::path::Path; + +// Sets up a debuggable test case. +// Run with `cargo test-debugger`. +forgetest_async!( + #[ignore = "ran manually"] + manual_debug_setup, + |prj, cmd| { + cmd.args(["init", "--force"]).arg(prj.root()).assert_non_empty_stdout(); + cmd.forge_fuse(); + + prj.add_source("Counter2.sol", r#" +contract A { + address public a; + uint public b; + int public c; + bytes32 public d; + bool public e; + bytes public f; + string public g; + + constructor(address _a, uint _b, int _c, bytes32 _d, bool _e, bytes memory _f, string memory _g) { + a = _a; + b = _b; + c = _c; + d = _d; + e = _e; + f = _f; + g = _g; + } + + function getA() public view returns (address) { + return a; + } + + function setA(address _a) public { + a = _a; + } +}"#, + ) + .unwrap(); + + let script = prj.add_script("Counter.s.sol", r#" +import "../src/Counter2.sol"; +import "forge-std/Script.sol"; +import "forge-std/Test.sol"; + +contract B is A { + A public other; + address public self = address(this); + + constructor(address _a, uint _b, int _c, bytes32 _d, bool _e, bytes memory _f, string memory _g) + A(_a, _b, _c, _d, _e, _f, _g) + { + other = new A(_a, _b, _c, _d, _e, _f, _g); + } +} + +contract Script0 is Script, Test { + function run() external { + assertEq(uint256(1), uint256(1)); + + vm.startBroadcast(); + B b = new B(msg.sender, 2 ** 32, -1 * (2 ** 32), keccak256(abi.encode(1)), true, "abcdef", "hello"); + assertEq(b.getA(), msg.sender); + b.setA(tx.origin); + assertEq(b.getA(), tx.origin); + address _b = b.self(); + bytes32 _d = b.d(); + bytes32 _d2 = b.other().d(); + } +}"#, + ) + .unwrap(); + + cmd.args(["build"]).assert_success(); + cmd.forge_fuse(); + + cmd.args([ + "script", + script.to_str().unwrap(), + "--root", + prj.root().to_str().unwrap(), + "--tc=Script0", + "--debug", + ]); + eprintln!("root: {}", prj.root().display()); + let cmd_path = Path::new(cmd.cmd().get_program()).canonicalize().unwrap(); + let args = cmd.cmd().get_args().map(|s| s.to_str().unwrap()).format(" "); + eprintln!(" cmd: {} {args}", cmd_path.display()); + std::mem::forget(prj); + } +); diff --git a/crates/forge/tests/cli/ext_integration.rs b/crates/forge/tests/cli/ext_integration.rs index 55c128b07f500..3483708f2c37f 100644 --- a/crates/forge/tests/cli/ext_integration.rs +++ b/crates/forge/tests/cli/ext_integration.rs @@ -1,32 +1,127 @@ -forgetest_external!(solmate, "transmissions11/solmate"); -forgetest_external!(prb_math, "PaulRBerg/prb-math"); -forgetest_external!(prb_proxy, "PaulRBerg/prb-proxy"); -forgetest_external!(solady, "Vectorized/solady"); -forgetest_external!( - #[cfg_attr(windows, ignore = "weird git fail")] - geb, - "reflexer-labs/geb", - &["--chain-id", "99", "--sender", "0x00a329c0648769A73afAc7F9381E08FB43dBEA72"] -); -forgetest_external!(stringutils, "Arachnid/solidity-stringutils"); -forgetest_external!(lootloose, "gakonst/lootloose"); -forgetest_external!(lil_web3, "m1guelpf/lil-web3"); -forgetest_external!( - // https://github.com/foundry-rs/foundry/pull/6280 - // `run: pnpm --version` is ok, `Command::new("pnpm")` isn't. Good job Windows. - #[cfg_attr(windows, ignore = "Windows cannot find installed programs")] - snekmate, - "pcaversaccio/snekmate" -); +use foundry_test_utils::util::ExtTester; + +#[test] +fn forge_std() { + ExtTester::new("foundry-rs", "forge-std", "1d0766bc5d814f117c7b1e643828f7d85024fb51") + // Skip fork tests. + .args(["--nmc", "Fork"]) + .run(); +} + +#[test] +fn solmate() { + ExtTester::new("transmissions11", "solmate", "c892309933b25c03d32b1b0d674df7ae292ba925").run(); +} + +#[test] +#[cfg_attr(windows, ignore = "Windows cannot find installed programs")] +fn prb_math() { + ExtTester::new("PaulRBerg", "prb-math", "5b6279a0cf7c1b1b6a5cc96082811f7ef620cf60") + .install_command(&["bun", "install", "--prefer-offline"]) + // Try npm if bun fails / is not installed. + .install_command(&["npm", "install", "--prefer-offline"]) + .run(); +} + +#[test] +#[cfg_attr(windows, ignore = "Windows cannot find installed programs")] +fn prb_proxy() { + ExtTester::new("PaulRBerg", "prb-proxy", "fa13cf09fbf544a2d575b45884b8e94a79a02c06") + .install_command(&["bun", "install", "--prefer-offline"]) + // Try npm if bun fails / is not installed. + .install_command(&["npm", "install", "--prefer-offline"]) + .run(); +} + +#[test] +#[cfg_attr(windows, ignore = "Windows cannot find installed programs")] +fn sablier_v2() { + ExtTester::new("sablier-labs", "v2-core", "84758a40077bf3ccb1c8f7bb8d00278e672fbfef") + // Skip fork tests. + .args(["--nmc", "Fork"]) + // Run tests without optimizations. + .env("FOUNDRY_PROFILE", "lite") + .install_command(&["bun", "install", "--prefer-offline"]) + // Try npm if bun fails / is not installed. + .install_command(&["npm", "install", "--prefer-offline"]) + .run(); +} + +#[test] +fn solady() { + ExtTester::new("Vectorized", "solady", "54ea1543a229b88b44ccb6ec5ea570135811a7d9").run(); +} + +#[test] +#[cfg_attr(windows, ignore = "weird git fail")] +fn geb() { + ExtTester::new("reflexer-labs", "geb", "1a59f16a377386c49f520006ed0f7fd9d128cb09") + .args(["--chain-id", "99", "--sender", "0x00a329c0648769A73afAc7F9381E08FB43dBEA72"]) + .run(); +} + +#[test] +fn stringutils() { + ExtTester::new("Arachnid", "solidity-stringutils", "4b2fcc43fa0426e19ce88b1f1ec16f5903a2e461") + .run(); +} + +#[test] +fn lootloose() { + ExtTester::new("gakonst", "lootloose", "7b639efe97836155a6a6fc626bf1018d4f8b2495") + .install_command(&["make", "install"]) + .run(); +} + +#[test] +fn lil_web3() { + ExtTester::new("m1guelpf", "lil-web3", "7346bd28c2586da3b07102d5290175a276949b15").run(); +} + +#[test] +#[cfg_attr(windows, ignore = "Windows cannot find installed programs")] +fn snekmate() { + ExtTester::new("pcaversaccio", "snekmate", "ed49a0454393673cdf9a4250dd7051c28e6ac35f") + .install_command(&["pnpm", "install", "--prefer-offline"]) + // Try npm if pnpm fails / is not installed. + .install_command(&["npm", "install", "--prefer-offline"]) + .run(); +} + +#[test] +fn makerdao_multicall() { + ExtTester::new("makerdao", "multicall", "103a8a28e4e372d582d6539b30031bda4cd48e21").run(); +} + +#[test] +fn mds1_multicall() { + ExtTester::new("mds1", "multicall", "263ef67f29ab9e450142b42dde617ad69adbf211").run(); +} // Forking tests -forgetest_external!(multicall, "makerdao/multicall", &["--block-number", "1"]); -forgetest_external!( - drai, - "mds1/drai", - 13633752, - &["--chain-id", "99", "--sender", "0x00a329c0648769A73afAc7F9381E08FB43dBEA72"] -); -forgetest_external!(gunilev, "hexonaut/guni-lev", 13633752); -forgetest_external!(convex, "mds1/convex-shutdown-simulation", 14445961); +#[test] +fn drai() { + ExtTester::new("mds1", "drai", "f31ce4fb15bbb06c94eefea2a3a43384c75b95cf") + .args(["--chain-id", "99", "--sender", "0x00a329c0648769A73afAc7F9381E08FB43dBEA72"]) + .fork_block(13633752) + .run(); +} + +#[test] +fn gunilev() { + ExtTester::new("hexonaut", "guni-lev", "15ee8b4c2d28e553c5cd5ba9a2a274af97563bc4") + .fork_block(13633752) + .run(); +} + +#[test] +fn convex_shutdown_simulation() { + ExtTester::new( + "mds1", + "convex-shutdown-simulation", + "2537cdebce4396753225c5e616c8e00547d2fcea", + ) + .fork_block(14445961) + .run(); +} diff --git a/crates/forge/tests/cli/heavy_integration.rs b/crates/forge/tests/cli/heavy_integration.rs deleted file mode 100644 index 1b21eb85f9ebb..0000000000000 --- a/crates/forge/tests/cli/heavy_integration.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Heavy integration tests that can take an hour to run or more. - -forgetest_external!(maple, "maple-labs/maple-core-v2"); diff --git a/crates/forge/tests/cli/main.rs b/crates/forge/tests/cli/main.rs index f8c9dcbbf0cad..543b84dc7d9c0 100644 --- a/crates/forge/tests/cli/main.rs +++ b/crates/forge/tests/cli/main.rs @@ -8,8 +8,10 @@ mod build; mod cache; mod cmd; mod config; +mod context; mod coverage; mod create; +mod debug; mod doc; mod multi_script; mod script; @@ -18,6 +20,3 @@ mod test_cmd; mod verify; mod ext_integration; - -#[cfg(feature = "heavy-integration-tests")] -mod heavy_integration; diff --git a/crates/forge/tests/cli/multi_script.rs b/crates/forge/tests/cli/multi_script.rs index fb052b40b8a8d..d6f7628da1694 100644 --- a/crates/forge/tests/cli/multi_script.rs +++ b/crates/forge/tests/cli/multi_script.rs @@ -1,6 +1,6 @@ //! Contains various tests related to forge script use anvil::{spawn, NodeConfig}; -use foundry_common::types::ToEthers; + use foundry_test_utils::{ScriptOutcome, ScriptTester}; forgetest_async!(can_deploy_multi_chain_script_without_lib, |prj, cmd| { @@ -15,23 +15,11 @@ forgetest_async!(can_deploy_multi_chain_script_without_lib, |prj, cmd| { .args(&[&handle1.http_endpoint(), &handle2.http_endpoint()]) .broadcast(ScriptOutcome::OkBroadcast); - assert_eq!( - api1.transaction_count(tester.accounts_pub[0].to_ethers(), None).await.unwrap().as_u32(), - 1 - ); - assert_eq!( - api1.transaction_count(tester.accounts_pub[1].to_ethers(), None).await.unwrap().as_u32(), - 1 - ); - - assert_eq!( - api2.transaction_count(tester.accounts_pub[0].to_ethers(), None).await.unwrap().as_u32(), - 2 - ); - assert_eq!( - api2.transaction_count(tester.accounts_pub[1].to_ethers(), None).await.unwrap().as_u32(), - 3 - ); + assert_eq!(api1.transaction_count(tester.accounts_pub[0], None).await.unwrap().to::(), 1); + assert_eq!(api1.transaction_count(tester.accounts_pub[1], None).await.unwrap().to::(), 1); + + assert_eq!(api2.transaction_count(tester.accounts_pub[0], None).await.unwrap().to::(), 2); + assert_eq!(api2.transaction_count(tester.accounts_pub[1], None).await.unwrap().to::(), 3); }); forgetest_async!(can_not_deploy_multi_chain_script_with_lib, |prj, cmd| { @@ -61,3 +49,18 @@ forgetest_async!(can_not_change_fork_during_broadcast, |prj, cmd| { .args(&[&handle1.http_endpoint(), &handle2.http_endpoint()]) .broadcast(ScriptOutcome::ErrorSelectForkOnBroadcast); }); + +forgetest_async!(can_resume_multi_chain_script, |prj, cmd| { + let (_, handle1) = spawn(NodeConfig::test()).await; + let (_, handle2) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); + + tester + .add_sig("MultiChainBroadcastNoLink", "deploy(string memory,string memory)") + .args(&[&handle1.http_endpoint(), &handle2.http_endpoint()]) + .broadcast(ScriptOutcome::MissingWallet) + .load_private_keys(&[0, 1]) + .await + .arg("--multi") + .resume(ScriptOutcome::OkBroadcast); +}); diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 6fd5dcd06a082..70b97b58fe10e 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -1,10 +1,9 @@ //! Contains various tests related to `forge script`. use crate::constants::TEMPLATE_CONTRACT; -use alloy_primitives::Address; +use alloy_primitives::{Address, Bytes}; use anvil::{spawn, NodeConfig}; -use foundry_common::{rpc, types::ToEthers}; -use foundry_test_utils::{util::OutputExt, ScriptOutcome, ScriptTester}; +use foundry_test_utils::{rpc, util::OutputExt, ScriptOutcome, ScriptTester}; use regex::Regex; use serde_json::Value; use std::{env, path::PathBuf, str::FromStr}; @@ -32,7 +31,7 @@ contract ContractScript is Script { ) .unwrap(); - let rpc = foundry_common::rpc::next_http_rpc_endpoint(); + let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint(); cmd.arg("script").arg(script).args(["--fork-url", rpc.as_str(), "-vvvvv"]).assert_success(); } @@ -107,6 +106,47 @@ contract Demo { ); }); +static FAILING_SCRIPT: &str = r#" +import "forge-std/Script.sol"; + +contract FailingScript is Script { + function run() external { + revert("failed"); + } +} +"#; + +// Tests that execution throws upon encountering a revert in the script. +forgetest_async!(assert_exit_code_error_on_failure_script, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + let script = prj.add_source("FailingScript", FAILING_SCRIPT).unwrap(); + + // set up command + cmd.arg("script").arg(script); + + // run command and assert error exit code + cmd.assert_err(); + + let output = cmd.stderr_lossy(); + assert!(output.contains("script failed: revert: failed")); +}); + +// Tests that execution throws upon encountering a revert in the script with --json option. +// +forgetest_async!(assert_exit_code_error_on_failure_script_with_json, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + let script = prj.add_source("FailingScript", FAILING_SCRIPT).unwrap(); + + // set up command + cmd.arg("script").arg(script).arg("--json"); + + // run command and assert error exit code + cmd.assert_err(); + + let output = cmd.stderr_lossy(); + assert!(output.contains("script failed: revert: failed")); +}); + // Tests that the manually specified gas limit is used when using the --unlocked option forgetest_async!(can_execute_script_command_with_manual_gas_limit_unlocked, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); @@ -403,97 +443,75 @@ forgetest_async!(can_deploy_script_with_lib, |prj, cmd| { .await; }); -forgetest_async!( - #[serial_test::serial] - can_deploy_script_private_key, - |prj, cmd| { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .load_addresses(&[ - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap() - ]) - .await - .add_sig("BroadcastTest", "deployPrivateKey()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment_addresses(&[( - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), - 3, - )]) - .await; - } -); +forgetest_async!(can_deploy_script_private_key, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); -forgetest_async!( - #[serial_test::serial] - can_deploy_unlocked, - |prj, cmd| { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .sender("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap()) - .unlocked() - .add_sig("BroadcastTest", "deployOther()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast); - } -); + tester + .load_addresses(&[Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap()]) + .await + .add_sig("BroadcastTest", "deployPrivateKey()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment_addresses(&[( + Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), + 3, + )]) + .await; +}); -forgetest_async!( - #[serial_test::serial] - can_deploy_script_remember_key, - |prj, cmd| { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .load_addresses(&[ - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap() - ]) - .await - .add_sig("BroadcastTest", "deployRememberKey()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment_addresses(&[( - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), - 2, - )]) - .await; - } -); +forgetest_async!(can_deploy_unlocked, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); -forgetest_async!( - #[serial_test::serial] - can_deploy_script_remember_key_and_resume, - |prj, cmd| { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .add_deployer(0) - .load_addresses(&[ - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap() - ]) - .await - .add_sig("BroadcastTest", "deployRememberKeyResume()") - .simulate(ScriptOutcome::OkSimulation) - .resume(ScriptOutcome::MissingWallet) - // load missing wallet - .load_private_keys(&[0]) - .await - .run(ScriptOutcome::OkBroadcast) - .assert_nonce_increment_addresses(&[( - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), - 1, - )]) - .await - .assert_nonce_increment(&[(0, 2)]) - .await; - } -); + tester + .sender("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap()) + .unlocked() + .add_sig("BroadcastTest", "deployOther()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast); +}); + +forgetest_async!(can_deploy_script_remember_key, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .load_addresses(&[Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap()]) + .await + .add_sig("BroadcastTest", "deployRememberKey()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment_addresses(&[( + Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), + 2, + )]) + .await; +}); + +forgetest_async!(can_deploy_script_remember_key_and_resume, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .add_deployer(0) + .load_addresses(&[Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap()]) + .await + .add_sig("BroadcastTest", "deployRememberKeyResume()") + .simulate(ScriptOutcome::OkSimulation) + .resume(ScriptOutcome::MissingWallet) + // load missing wallet + .load_private_keys(&[0]) + .await + .run(ScriptOutcome::OkBroadcast) + .assert_nonce_increment_addresses(&[( + Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), + 1, + )]) + .await + .assert_nonce_increment(&[(0, 2)]) + .await; +}); forgetest_async!(can_resume_script, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; @@ -561,10 +579,8 @@ forgetest_async!(can_deploy_with_create2, |prj, cmd| { // Prepare CREATE2 Deployer api.anvil_set_code( - foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER.to_ethers(), - ethers_core::types::Bytes::from_static( - foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE, - ), + foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER, + Bytes::from_static(foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE), ) .await .unwrap(); @@ -582,41 +598,33 @@ forgetest_async!(can_deploy_with_create2, |prj, cmd| { .run(ScriptOutcome::ScriptFailed); }); -forgetest_async!( - #[serial_test::serial] - can_deploy_and_simulate_25_txes_concurrently, - |prj, cmd| { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .load_private_keys(&[0]) - .await - .add_sig("BroadcastTestNoLinking", "deployMany()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment(&[(0, 25)]) - .await; - } -); +forgetest_async!(can_deploy_and_simulate_25_txes_concurrently, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); -forgetest_async!( - #[serial_test::serial] - can_deploy_and_simulate_mixed_broadcast_modes, - |prj, cmd| { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .load_private_keys(&[0]) - .await - .add_sig("BroadcastMix", "deployMix()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment(&[(0, 15)]) - .await; - } -); + tester + .load_private_keys(&[0]) + .await + .add_sig("BroadcastTestNoLinking", "deployMany()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 25)]) + .await; +}); + +forgetest_async!(can_deploy_and_simulate_mixed_broadcast_modes, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .load_private_keys(&[0]) + .await + .add_sig("BroadcastMix", "deployMix()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 15)]) + .await; +}); forgetest_async!(deploy_with_setup, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; @@ -643,81 +651,76 @@ forgetest_async!(fail_broadcast_staticcall, |prj, cmd| { .simulate(ScriptOutcome::StaticCallNotAllowed); }); -forgetest_async!( - #[serial_test::serial] - check_broadcast_log, - |prj, cmd| { - let (api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - // Prepare CREATE2 Deployer - let addr = Address::from_str("0x4e59b44847b379578588920ca78fbf26c0b4956c").unwrap(); - let code = hex::decode("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3").expect("Could not decode create2 deployer init_code").into(); - api.anvil_set_code(addr.to_ethers(), code).await.unwrap(); - - tester - .load_private_keys(&[0]) - .await - .add_sig("BroadcastTestSetup", "run()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment(&[(0, 6)]) - .await; - - // Uncomment to recreate the broadcast log - // std::fs::copy( - // "broadcast/Broadcast.t.sol/31337/run-latest.json", - // PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata/fixtures/broadcast. - // log. json" ), ); - - // Check broadcast logs - // Ignore timestamp, blockHash, blockNumber, cumulativeGasUsed, effectiveGasPrice, - // transactionIndex and logIndex values since they can change inbetween runs - let re = Regex::new(r#"((timestamp":).[0-9]*)|((blockHash":).*)|((blockNumber":).*)|((cumulativeGasUsed":).*)|((effectiveGasPrice":).*)|((transactionIndex":).*)|((logIndex":).*)"#).unwrap(); - - let fixtures_log = std::fs::read_to_string( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../../testdata/fixtures/broadcast.log.json"), - ) - .unwrap(); - let _fixtures_log = re.replace_all(&fixtures_log, ""); +forgetest_async!(check_broadcast_log, |prj, cmd| { + let (api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - let run_log = - std::fs::read_to_string("broadcast/Broadcast.t.sol/31337/run-latest.json").unwrap(); - let _run_log = re.replace_all(&run_log, ""); + // Prepare CREATE2 Deployer + let addr = Address::from_str("0x4e59b44847b379578588920ca78fbf26c0b4956c").unwrap(); + let code = hex::decode("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3").expect("Could not decode create2 deployer init_code").into(); + api.anvil_set_code(addr, code).await.unwrap(); - // pretty_assertions::assert_eq!(fixtures_log, run_log); + tester + .load_private_keys(&[0]) + .await + .add_sig("BroadcastTestSetup", "run()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 6)]) + .await; - // Uncomment to recreate the sensitive log - // std::fs::copy( - // "cache/Broadcast.t.sol/31337/run-latest.json", - // PathBuf::from(env!("CARGO_MANIFEST_DIR")) - // .join("../../testdata/fixtures/broadcast.sensitive.log.json"), - // ); + // Uncomment to recreate the broadcast log + // std::fs::copy( + // "broadcast/Broadcast.t.sol/31337/run-latest.json", + // PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata/fixtures/broadcast. + // log. json" ), ); - // Check sensitive logs - // Ignore port number since it can change inbetween runs - let re = Regex::new(r":[0-9]+").unwrap(); + // Check broadcast logs + // Ignore timestamp, blockHash, blockNumber, cumulativeGasUsed, effectiveGasPrice, + // transactionIndex and logIndex values since they can change inbetween runs + let re = Regex::new(r#"((timestamp":).[0-9]*)|((blockHash":).*)|((blockNumber":).*)|((cumulativeGasUsed":).*)|((effectiveGasPrice":).*)|((transactionIndex":).*)|((logIndex":).*)"#).unwrap(); - let fixtures_log = std::fs::read_to_string( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../../testdata/fixtures/broadcast.sensitive.log.json"), - ) - .unwrap(); - let fixtures_log = re.replace_all(&fixtures_log, ""); + let fixtures_log = std::fs::read_to_string( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../testdata/fixtures/broadcast.log.json"), + ) + .unwrap(); + let _fixtures_log = re.replace_all(&fixtures_log, ""); - let run_log = - std::fs::read_to_string("cache/Broadcast.t.sol/31337/run-latest.json").unwrap(); - let run_log = re.replace_all(&run_log, ""); + let run_log = + std::fs::read_to_string("broadcast/Broadcast.t.sol/31337/run-latest.json").unwrap(); + let _run_log = re.replace_all(&run_log, ""); - // Clean up carriage return OS differences - let re = Regex::new(r"\r\n").unwrap(); - let fixtures_log = re.replace_all(&fixtures_log, "\n"); - let run_log = re.replace_all(&run_log, "\n"); + // pretty_assertions::assert_eq!(fixtures_log, run_log); - pretty_assertions::assert_eq!(fixtures_log, run_log); - } -); + // Uncomment to recreate the sensitive log + // std::fs::copy( + // "cache/Broadcast.t.sol/31337/run-latest.json", + // PathBuf::from(env!("CARGO_MANIFEST_DIR")) + // .join("../../testdata/fixtures/broadcast.sensitive.log.json"), + // ); + + // Check sensitive logs + // Ignore port number since it can change inbetween runs + let re = Regex::new(r":[0-9]+").unwrap(); + + let fixtures_log = std::fs::read_to_string( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../testdata/fixtures/broadcast.sensitive.log.json"), + ) + .unwrap(); + let fixtures_log = re.replace_all(&fixtures_log, ""); + + let run_log = std::fs::read_to_string("cache/Broadcast.t.sol/31337/run-latest.json").unwrap(); + let run_log = re.replace_all(&run_log, ""); + + // Clean up carriage return OS differences + let re = Regex::new(r"\r\n").unwrap(); + let fixtures_log = re.replace_all(&fixtures_log, "\n"); + let run_log = re.replace_all(&run_log, "\n"); + + pretty_assertions::assert_eq!(fixtures_log, run_log); +}); forgetest_async!(test_default_sender_balance, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; @@ -1076,6 +1079,28 @@ interface Interface {} assert!(cmd.stdout_lossy().contains("Script ran successfully.")); }); +forgetest_async!(assert_can_detect_unlinked_target_with_libraries, |prj, cmd| { + let script = prj + .add_script( + "ScriptWithExtLib.s.sol", + r#" +library Lib { + function f() public {} +} + +contract Script { + function run() external { + Lib.f(); + } +} + "#, + ) + .unwrap(); + + cmd.arg("script").arg(script); + assert!(cmd.stdout_lossy().contains("Script ran successfully.")); +}); + forgetest_async!(assert_can_resume_with_additional_contracts, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); @@ -1088,3 +1113,211 @@ forgetest_async!(assert_can_resume_with_additional_contracts, |prj, cmd| { .await .resume(ScriptOutcome::OkBroadcast); }); + +forgetest_async!(can_detect_contract_when_multiple_versions, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + prj.add_script( + "A.sol", + r#"pragma solidity 0.8.20; +import "./B.sol"; + +contract ScriptA {} +"#, + ) + .unwrap(); + + prj.add_script( + "B.sol", + r#"pragma solidity >=0.8.5 <=0.8.20; +import 'forge-std/Script.sol'; + +contract ScriptB is Script { + function run() external { + vm.broadcast(); + address(0).call(""); + } +} +"#, + ) + .unwrap(); + + prj.add_script( + "C.sol", + r#"pragma solidity 0.8.5; +import "./B.sol"; + +contract ScriptC {} +"#, + ) + .unwrap(); + + let mut tester = ScriptTester::new(cmd, None, prj.root(), "script/B.sol"); + tester.cmd.forge_fuse().args(["script", "script/B.sol"]); + tester.simulate(ScriptOutcome::OkNoEndpoint); +}); + +forgetest_async!(can_sign_with_script_wallet_single, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); + tester + .add_sig("ScriptSign", "run()") + .load_private_keys(&[0]) + .await + .simulate(ScriptOutcome::OkNoEndpoint); +}); + +forgetest_async!(can_sign_with_script_wallet_multiple, |prj, cmd| { + let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); + let acc = tester.accounts_pub[0].to_checksum(None); + tester + .add_sig("ScriptSign", "run(address)") + .arg(&acc) + .load_private_keys(&[0, 1, 2]) + .await + .simulate(ScriptOutcome::OkRun); +}); + +forgetest_async!(fails_with_function_name_and_overloads, |prj, cmd| { + let script = prj + .add_script( + "Sctipt.s.sol", + r#" +contract Script { + function run() external {} + + function run(address,uint256) external {} +} + "#, + ) + .unwrap(); + + cmd.arg("script").args([&script.to_string_lossy(), "--sig", "run"]); + assert!(cmd.stderr_lossy().contains("Multiple functions with the same name")); +}); + +forgetest_async!(can_decode_custom_errors, |prj, cmd| { + cmd.args(["init", "--force"]).arg(prj.root()); + cmd.assert_non_empty_stdout(); + cmd.forge_fuse(); + + let script = prj + .add_script( + "CustomErrorScript.s.sol", + r#" +import { Script } from "forge-std/Script.sol"; + +contract ContractWithCustomError { + error CustomError(); + + constructor() { + revert CustomError(); + } +} + +contract CustomErrorScript is Script { + ContractWithCustomError test; + + function run() public { + test = new ContractWithCustomError(); + } +} +"#, + ) + .unwrap(); + + cmd.arg("script").arg(script).args(["--tc", "CustomErrorScript"]); + assert!(cmd.stderr_lossy().contains("script failed: CustomError()")); +}); + +// https://github.com/foundry-rs/foundry/issues/7620 +forgetest_async!(can_run_zero_base_fee, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract SimpleScript is Script { + function run() external { + vm.startBroadcast(); + address(0).call(""); + } +} + "#, + ) + .unwrap(); + + let node_config = NodeConfig::test().with_base_fee(Some(0)); + let (_api, handle) = spawn(node_config).await; + let dev = handle.dev_accounts().next().unwrap(); + + // Firstly run script with non-zero gas prices to ensure that eth_feeHistory contains non-zero + // values. + cmd.args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--sender", + format!("{dev:?}").as_str(), + "--broadcast", + "--unlocked", + "--with-gas-price", + "2000000", + "--priority-gas-price", + "100000", + ]); + + let output = cmd.stdout_lossy(); + assert!(output.contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL")); + + // Ensure that we can correctly estimate gas when base fee is zero but priority fee is not. + cmd.forge_fuse().args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--sender", + format!("{dev:?}").as_str(), + "--broadcast", + "--unlocked", + ]); + + let output = cmd.stdout_lossy(); + assert!(output.contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL")); +}); + +// https://github.com/foundry-rs/foundry/pull/7742 +forgetest_async!(unlocked_no_sender, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract SimpleScript is Script { + function run() external { + vm.startBroadcast(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + address(0).call(""); + } +} + "#, + ) + .unwrap(); + + let (_api, handle) = spawn(NodeConfig::test()).await; + + cmd.args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--broadcast", + "--unlocked", + ]); + + let output = cmd.stdout_lossy(); + assert!(output.contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL")); +}); diff --git a/crates/forge/tests/cli/svm.rs b/crates/forge/tests/cli/svm.rs index 08df1e3f815a6..cbdd56f9d787e 100644 --- a/crates/forge/tests/cli/svm.rs +++ b/crates/forge/tests/cli/svm.rs @@ -8,9 +8,10 @@ use svm::Platform; /// Solc to Foundry release process: /// 1. new solc release /// 2. svm updated with all build info -/// 3. svm bumped in ethers-rs -/// 4. ethers bumped in foundry + update the `LATEST_SOLC` -const LATEST_SOLC: Version = Version::new(0, 8, 23); +/// 3. svm bumped in foundry-compilers +/// 4. foundry-compilers update with any breaking changes +/// 5. upgrade the `LATEST_SOLC` +const LATEST_SOLC: Version = Version::new(0, 8, 25); macro_rules! ensure_svm_releases { ($($test:ident => $platform:ident),* $(,)?) => {$( diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index 97d428e3d0ee3..2b0784d6a27d0 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -1,8 +1,11 @@ -//! Contains various tests for checking `forge test` -use foundry_common::rpc; +//! Contains various tests for `forge test`. + use foundry_config::Config; -use foundry_test_utils::util::{OutputExt, OTHER_SOLC_VERSION, SOLC_VERSION}; -use std::{path::PathBuf, process::Command, str::FromStr}; +use foundry_test_utils::{ + rpc, + util::{OutputExt, OTHER_SOLC_VERSION, SOLC_VERSION}, +}; +use std::{path::PathBuf, str::FromStr}; // tests that test filters are handled correctly forgetest!(can_set_filter_values, |prj, cmd| { @@ -259,31 +262,6 @@ contract ContractTest is DSTest { ); }); -// checks that we can test forge std successfully -// `forgetest_init!` will install with `forge-std` under `lib/forge-std` -forgetest_init!( - #[serial_test::serial] - can_test_forge_std, - |prj, cmd| { - let forge_std_dir = prj.root().join("lib/forge-std"); - let status = Command::new("git") - .current_dir(&forge_std_dir) - .args(["pull", "origin", "master"]) - .status() - .unwrap(); - if !status.success() { - panic!("failed to update forge-std"); - } - - // execute in subdir - cmd.cmd().current_dir(forge_std_dir); - cmd.args(["test", "--root", "."]); - let stdout = cmd.stdout_lossy(); - assert!(stdout.contains("[PASS]"), "No tests passed:\n{stdout}"); - assert!(!stdout.contains("[FAIL]"), "Tests failed:\n{stdout}"); - } -); - // tests that libraries are handled correctly in multiforking mode forgetest_init!(can_use_libs_in_multi_fork, |prj, cmd| { prj.wipe_contracts(); @@ -367,3 +345,202 @@ forgetest_init!(exit_code_error_on_fail_fast_with_json, |prj, cmd| { // run command and assert error exit code cmd.assert_err(); }); + +// +forgetest_init!(repro_6531, |prj, cmd| { + prj.wipe_contracts(); + + let endpoint = rpc::next_http_archive_rpc_endpoint(); + + prj.add_test( + "Contract.t.sol", + &r#" +import {Test} from "forge-std/Test.sol"; + +interface IERC20 { + function name() external view returns (string memory); +} + +contract USDTCallingTest is Test { + function test() public { + vm.createSelectFork(""); + IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7).name(); + } +} + "# + .replace("", &endpoint), + ) + .unwrap(); + + let expected = std::fs::read_to_string( + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/repro_6531.stdout"), + ) + .unwrap() + .replace("", &endpoint); + + cmd.args(["test", "-vvvv"]).unchecked_output().stdout_matches_content(&expected); +}); + +// +forgetest_init!(include_custom_types_in_traces, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "Contract.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +error PoolNotInitialized(); +event MyEvent(uint256 a); + +contract CustomTypesTest is Test { + function testErr() public pure { + revert PoolNotInitialized(); + } + function testEvent() public { + emit MyEvent(100); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "-vvvv"]).unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/include_custom_types_in_traces.stdout"), + ); +}); + +forgetest_init!(can_test_selfdestruct_with_isolation, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "Contract.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Destructing { + function destruct() public { + selfdestruct(payable(address(0))); + } +} + +contract SelfDestructTest is Test { + function test() public { + Destructing d = new Destructing(); + vm.store(address(d), bytes32(0), bytes32(uint256(1))); + d.destruct(); + assertEq(address(d).code.length, 0); + assertEq(vm.load(address(d), bytes32(0)), bytes32(0)); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "-vvvv", "--isolate"]).assert_success(); +}); + +forgetest_init!(can_test_transient_storage_with_isolation, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "Contract.t.sol", + r#"pragma solidity 0.8.24; +import {Test} from "forge-std/Test.sol"; + +contract TransientTester { + function locked() public view returns (bool isLocked) { + assembly { + isLocked := tload(0) + } + } + + modifier lock() { + require(!locked(), "locked"); + assembly { + tstore(0, 1) + } + _; + } + + function maybeReentrant(address target, bytes memory data) public lock { + (bool success, bytes memory ret) = target.call(data); + if (!success) { + // forwards revert reason + assembly { + let ret_size := mload(ret) + revert(add(32, ret), ret_size) + } + } + } +} + +contract TransientTest is Test { + function test() public { + TransientTester t = new TransientTester(); + vm.expectRevert(bytes("locked")); + t.maybeReentrant(address(t), abi.encodeCall(TransientTester.maybeReentrant, (address(0), new bytes(0)))); + + t.maybeReentrant(address(0), new bytes(0)); + assertEq(t.locked(), false); + } +} + + "#, + ) + .unwrap(); + + cmd.args(["test", "-vvvv", "--isolate", "--evm-version", "cancun"]).assert_success(); +}); + +forgetest_init!(can_disable_block_gas_limit, |prj, cmd| { + prj.wipe_contracts(); + + let endpoint = rpc::next_http_archive_rpc_endpoint(); + + prj.add_test( + "Contract.t.sol", + &r#"pragma solidity 0.8.24; +import {Test} from "forge-std/Test.sol"; + +contract C is Test {} + +contract GasWaster { + function waste() public { + for (uint256 i = 0; i < 100; i++) { + new C(); + } + } +} + +contract GasLimitTest is Test { + function test() public { + vm.createSelectFork(""); + + GasWaster waster = new GasWaster(); + waster.waste(); + } +} + "# + .replace("", &endpoint), + ) + .unwrap(); + + cmd.args(["test", "-vvvv", "--isolate", "--disable-block-gas-limit"]).assert_success(); +}); + +forgetest!(test_match_path, |prj, cmd| { + prj.add_source( + "dummy", + r" +contract Dummy { + function testDummy() public {} +} +", + ) + .unwrap(); + + cmd.args(["test", "--match-path", "src/dummy.sol"]); + cmd.assert_success() +}); diff --git a/crates/forge/tests/cli/utils.rs b/crates/forge/tests/cli/utils.rs index 12b0fe13470f4..75523e50a9d8b 100644 --- a/crates/forge/tests/cli/utils.rs +++ b/crates/forge/tests/cli/utils.rs @@ -1,7 +1,8 @@ //! Various helper functions -use ethers_core::types::{Address, Chain}; -use ethers_signers::{LocalWallet, Signer}; +use alloy_chains::NamedChain; +use alloy_primitives::Address; +use alloy_signer_wallet::LocalWallet; /// Returns the current millis since unix epoch. /// @@ -13,12 +14,12 @@ pub fn millis_since_epoch() -> u128 { .as_millis() } -pub fn etherscan_key(chain: Chain) -> Option { +pub fn etherscan_key(chain: NamedChain) -> Option { match chain { - Chain::Fantom | Chain::FantomTestnet => { + NamedChain::Fantom | NamedChain::FantomTestnet => { std::env::var("FTMSCAN_API_KEY").or_else(|_| std::env::var("FANTOMSCAN_API_KEY")).ok() } - Chain::OptimismKovan => std::env::var("OP_KOVAN_API_KEY").ok(), + NamedChain::OptimismKovan => std::env::var("OP_KOVAN_API_KEY").ok(), _ => std::env::var("ETHERSCAN_API_KEY").ok(), } } @@ -35,14 +36,13 @@ pub fn network_private_key(chain: &str) -> Option { /// Represents external input required for executing verification requests pub struct EnvExternalities { - pub chain: Chain, + pub chain: NamedChain, pub rpc: String, pub pk: String, pub etherscan: String, pub verifier: String, } -#[allow(dead_code)] impl EnvExternalities { pub fn address(&self) -> Option
{ let pk: LocalWallet = self.pk.parse().ok()?; @@ -51,60 +51,60 @@ impl EnvExternalities { pub fn goerli() -> Option { Some(Self { - chain: Chain::Goerli, + chain: NamedChain::Goerli, rpc: network_rpc_key("goerli")?, pk: network_private_key("goerli")?, - etherscan: etherscan_key(Chain::Goerli)?, + etherscan: etherscan_key(NamedChain::Goerli)?, verifier: "etherscan".to_string(), }) } pub fn ftm_testnet() -> Option { Some(Self { - chain: Chain::FantomTestnet, + chain: NamedChain::FantomTestnet, rpc: network_rpc_key("ftm_testnet")?, pk: network_private_key("ftm_testnet")?, - etherscan: etherscan_key(Chain::FantomTestnet)?, + etherscan: etherscan_key(NamedChain::FantomTestnet)?, verifier: "etherscan".to_string(), }) } pub fn optimism_kovan() -> Option { Some(Self { - chain: Chain::OptimismKovan, + chain: NamedChain::OptimismKovan, rpc: network_rpc_key("op_kovan")?, pk: network_private_key("op_kovan")?, - etherscan: etherscan_key(Chain::OptimismKovan)?, + etherscan: etherscan_key(NamedChain::OptimismKovan)?, verifier: "etherscan".to_string(), }) } pub fn arbitrum_goerli() -> Option { Some(Self { - chain: Chain::ArbitrumGoerli, + chain: NamedChain::ArbitrumGoerli, rpc: network_rpc_key("arbitrum-goerli")?, pk: network_private_key("arbitrum-goerli")?, - etherscan: etherscan_key(Chain::ArbitrumGoerli)?, + etherscan: etherscan_key(NamedChain::ArbitrumGoerli)?, verifier: "blockscout".to_string(), }) } pub fn mumbai() -> Option { Some(Self { - chain: Chain::PolygonMumbai, + chain: NamedChain::PolygonMumbai, rpc: network_rpc_key("mumbai")?, pk: network_private_key("mumbai")?, - etherscan: etherscan_key(Chain::PolygonMumbai)?, + etherscan: etherscan_key(NamedChain::PolygonMumbai)?, verifier: "etherscan".to_string(), }) } pub fn sepolia() -> Option { Some(Self { - chain: Chain::Sepolia, + chain: NamedChain::Sepolia, rpc: network_rpc_key("sepolia")?, pk: network_private_key("sepolia")?, - etherscan: etherscan_key(Chain::Sepolia)?, + etherscan: etherscan_key(NamedChain::Sepolia)?, verifier: "etherscan".to_string(), }) } diff --git a/crates/forge/tests/cli/verify.rs b/crates/forge/tests/cli/verify.rs index c35640a147504..deedcc9abf83a 100644 --- a/crates/forge/tests/cli/verify.rs +++ b/crates/forge/tests/cli/verify.rs @@ -39,12 +39,47 @@ function doStuff() external {} .unwrap(); } +fn add_single_verify_target_file(prj: &TestProject) { + let timestamp = utils::millis_since_epoch(); + let contract = format!( + r#" +contract Unique {{ + uint public _timestamp = {timestamp}; +}} +contract Verify is Unique {{ +function doStuff() external {{}} +}} +"# + ); + + prj.add_source("Verify.sol", &contract).unwrap(); +} + +fn add_verify_target_with_constructor(prj: &TestProject) { + prj.add_source( + "Verify.sol", + r#" +import {Unique} from "./unique.sol"; +contract Verify is Unique { + struct SomeStruct { + uint256 a; + string str; + } + + constructor(SomeStruct memory st, address owner) {} +} +"#, + ) + .unwrap(); +} + fn parse_verification_result(cmd: &mut TestCommand, retries: u32) -> eyre::Result<()> { // give etherscan some time to verify the contract let retry = Retry::new(retries, Some(Duration::from_secs(30))); retry.run(|| -> eyre::Result<()> { let output = cmd.unchecked_output(); let out = String::from_utf8_lossy(&output.stdout); + println!("{}", out); if out.contains("Contract successfully verified") { return Ok(()) } @@ -56,6 +91,39 @@ fn parse_verification_result(cmd: &mut TestCommand, retries: u32) -> eyre::Resul }) } +fn await_verification_response(info: EnvExternalities, mut cmd: TestCommand) { + let guid = { + // give etherscan some time to detect the transaction + let retry = Retry::new(5, Some(Duration::from_secs(60))); + retry + .run(|| -> eyre::Result { + let output = cmd.unchecked_output(); + let out = String::from_utf8_lossy(&output.stdout); + utils::parse_verification_guid(&out).ok_or_else(|| { + eyre::eyre!( + "Failed to get guid, stdout: {}, stderr: {}", + out, + String::from_utf8_lossy(&output.stderr) + ) + }) + }) + .expect("Failed to get verify guid") + }; + + // verify-check + cmd.forge_fuse() + .arg("verify-check") + .arg(guid) + .arg("--chain-id") + .arg(info.chain.to_string()) + .arg("--etherscan-api-key") + .arg(info.etherscan) + .arg("--verifier") + .arg(info.verifier); + + parse_verification_result(&mut cmd, 6).expect("Failed to verify check") +} + fn verify_on_chain(info: Option, prj: TestProject, mut cmd: TestCommand) { // only execute if keys present if let Some(info) = info { @@ -75,42 +143,67 @@ fn verify_on_chain(info: Option, prj: TestProject, mut cmd: Te info.chain.to_string(), address, contract_path.to_string(), + "--etherscan-api-key".to_string(), info.etherscan.to_string(), "--verifier".to_string(), info.verifier.to_string(), ]); - // `verify-contract` - let guid = { - // give etherscan some time to detect the transaction - let retry = Retry::new(5, Some(Duration::from_secs(60))); - retry - .run(|| -> eyre::Result { - let output = cmd.unchecked_output(); - let out = String::from_utf8_lossy(&output.stdout); - utils::parse_verification_guid(&out).ok_or_else(|| { - eyre::eyre!( - "Failed to get guid, stdout: {}, stderr: {}", - out, - String::from_utf8_lossy(&output.stderr) - ) - }) - }) - .expect("Failed to get verify guid") - }; - - // verify-check - cmd.forge_fuse() - .arg("verify-check") - .arg(guid) - .arg("--chain-id") - .arg(info.chain.to_string()) - .arg("--etherscan-key") - .arg(info.etherscan) - .arg("--verifier") - .arg(info.verifier); - - parse_verification_result(&mut cmd, 6).expect("Failed to verify check") + await_verification_response(info, cmd) + } +} + +fn guess_constructor_args(info: Option, prj: TestProject, mut cmd: TestCommand) { + // only execute if keys present + if let Some(info) = info { + println!("verifying on {}", info.chain); + add_unique(&prj); + add_verify_target_with_constructor(&prj); + + let contract_path = "src/Verify.sol:Verify"; + cmd.arg("create").args(info.create_args()).arg(contract_path).args(vec![ + "--constructor-args", + "(239,SomeString)", + "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", + ]); + + let out = cmd.stdout_lossy(); + let address = utils::parse_deployed_address(out.as_str()) + .unwrap_or_else(|| panic!("Failed to parse deployer {out}")); + + cmd.forge_fuse().arg("verify-contract").root_arg().args([ + "--rpc-url".to_string(), + info.rpc.to_string(), + address, + contract_path.to_string(), + "--etherscan-api-key".to_string(), + info.etherscan.to_string(), + "--verifier".to_string(), + info.verifier.to_string(), + "--guess-constructor-args".to_string(), + ]); + + await_verification_response(info, cmd) + } +} + +/// Executes create --verify on the given chain +fn create_verify_on_chain(info: Option, prj: TestProject, mut cmd: TestCommand) { + // only execute if keys present + if let Some(info) = info { + println!("verifying on {}", info.chain); + add_single_verify_target_file(&prj); + + let contract_path = "src/Verify.sol:Verify"; + cmd.arg("create").args(info.create_args()).args([ + contract_path, + "--etherscan-api-key", + info.etherscan.as_str(), + "--verify", + ]); + + let out = cmd.stdout_lossy(); + assert!(out.contains("Contract successfully verified"), "{}", out); } } @@ -123,3 +216,22 @@ forgetest!(can_verify_random_contract_fantom_testnet, |prj, cmd| { forgetest!(can_verify_random_contract_optimism_kovan, |prj, cmd| { verify_on_chain(EnvExternalities::optimism_kovan(), prj, cmd); }); + +// tests `create && contract-verify && verify-check` on Sepolia testnet if correct env vars are set +forgetest!(can_verify_random_contract_sepolia, |prj, cmd| { + verify_on_chain(EnvExternalities::sepolia(), prj, cmd); +}); + +// tests `create --verify on Sepolia testnet if correct env vars are set +// SEPOLIA_RPC_URL=https://rpc.sepolia.org +// TEST_PRIVATE_KEY=0x... +// ETHERSCAN_API_KEY= +forgetest!(can_create_verify_random_contract_sepolia, |prj, cmd| { + create_verify_on_chain(EnvExternalities::sepolia(), prj, cmd); +}); + +// tests `create && contract-verify --guess-constructor-args && verify-check` on Goerli testnet if +// correct env vars are set +forgetest!(can_guess_constructor_args, |prj, cmd| { + guess_constructor_args(EnvExternalities::goerli(), prj, cmd); +}); diff --git a/crates/forge/tests/fixtures/can_check_snapshot.stdout b/crates/forge/tests/fixtures/can_check_snapshot.stdout index dffc5df496344..bce1c6972521f 100644 --- a/crates/forge/tests/fixtures/can_check_snapshot.stdout +++ b/crates/forge/tests/fixtures/can_check_snapshot.stdout @@ -2,8 +2,8 @@ Compiling 2 files with 0.8.23 Solc 0.8.23 finished in 424.55ms Compiler run successful! -Running 1 test for src/ATest.t.sol:ATest +Ran 1 test for src/ATest.t.sol:ATest [PASS] testExample() (gas: 168) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.42ms - -Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.42ms + +Ran 1 test suite: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/fixtures/can_create_template_contract.stdout b/crates/forge/tests/fixtures/can_create_template_contract.stdout index 622c81ac4a888..533c927275012 100644 --- a/crates/forge/tests/fixtures/can_create_template_contract.stdout +++ b/crates/forge/tests/fixtures/can_create_template_contract.stdout @@ -1,4 +1,4 @@ -Compiling 24 files with 0.8.23 +Compiling 1 files with 0.8.23 Solc 0.8.23 finished in 2.27s Compiler run successful! Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 diff --git a/crates/forge/tests/fixtures/can_create_using_unlocked.stdout b/crates/forge/tests/fixtures/can_create_using_unlocked.stdout index a4132c617c0a1..1f8b60d6f40e1 100644 --- a/crates/forge/tests/fixtures/can_create_using_unlocked.stdout +++ b/crates/forge/tests/fixtures/can_create_using_unlocked.stdout @@ -1,4 +1,4 @@ -Compiling 24 files with 0.8.23 +Compiling 1 files with 0.8.23 Solc 0.8.23 finished in 1.95s Compiler run successful! Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 diff --git a/crates/forge/tests/fixtures/can_create_with_constructor_args.stdout b/crates/forge/tests/fixtures/can_create_with_constructor_args.stdout index 8cb09c22ced0b..299ad2f2d85f9 100644 --- a/crates/forge/tests/fixtures/can_create_with_constructor_args.stdout +++ b/crates/forge/tests/fixtures/can_create_with_constructor_args.stdout @@ -1,4 +1,4 @@ -Compiling 25 files with 0.8.23 +Compiling 1 files with 0.8.23 Solc 0.8.23 finished in 2.82s Compiler run successful! Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 diff --git a/crates/forge/tests/fixtures/can_run_test_in_custom_test_folder.stdout b/crates/forge/tests/fixtures/can_run_test_in_custom_test_folder.stdout index 5cf274ebc7de2..cd92d6ebeed8b 100644 --- a/crates/forge/tests/fixtures/can_run_test_in_custom_test_folder.stdout +++ b/crates/forge/tests/fixtures/can_run_test_in_custom_test_folder.stdout @@ -2,8 +2,8 @@ Compiling 2 files with 0.8.23 Solc 0.8.23 finished in 185.25ms Compiler run successful! -Running 1 test for src/nested/forge-tests/MyTest.t.sol:MyTest +Ran 1 test for src/nested/forge-tests/MyTest.t.sol:MyTest [PASS] testTrue() (gas: 168) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.93ms - -Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.93ms + +Ran 1 test suite: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/fixtures/can_set_yul_optimizer.stderr b/crates/forge/tests/fixtures/can_set_yul_optimizer.stderr index c7c847bf96e18..0dd4db95b6eb7 100644 --- a/crates/forge/tests/fixtures/can_set_yul_optimizer.stderr +++ b/crates/forge/tests/fixtures/can_set_yul_optimizer.stderr @@ -1,9 +1,8 @@ Error: Compiler run failed: -Error (6553): SyntaxError: The msize instruction cannot be used when the Yul optimizer is activated because it can change its semantics. Either disable the Yul optimizer or do not use the instruction. +Error (6553): The msize instruction cannot be used when the Yul optimizer is activated because it can change its semantics. Either disable the Yul optimizer or do not use the instruction. --> src/Foo.sol:6:8: | 6 | assembly { | ^ (Relevant source part starts here and spans across multiple lines). - diff --git a/crates/forge/tests/fixtures/can_test_repeatedly.stdout b/crates/forge/tests/fixtures/can_test_repeatedly.stdout index 6d645a5606b55..7095a50f0305c 100644 --- a/crates/forge/tests/fixtures/can_test_repeatedly.stdout +++ b/crates/forge/tests/fixtures/can_test_repeatedly.stdout @@ -1,8 +1,8 @@ No files changed, compilation skipped -Running 2 tests for test/Counter.t.sol:CounterTest +Ran 2 tests for test/Counter.t.sol:CounterTest [PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 26521, ~: 28387) -[PASS] test_Increment() (gas: 28379) -Test result: ok. 2 passed; 0 failed; 0 skipped; finished in 9.42ms - -Ran 1 test suites: 2 tests passed, 0 failed, 0 skipped (2 total tests) +[PASS] test_Increment() (gas: 31325) +Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 9.42ms + +Ran 1 test suite: 2 tests passed, 0 failed, 0 skipped (2 total tests) diff --git a/crates/forge/tests/fixtures/can_use_libs_in_multi_fork.stdout b/crates/forge/tests/fixtures/can_use_libs_in_multi_fork.stdout index 4c954e6c37c9c..70c72887aaea4 100644 --- a/crates/forge/tests/fixtures/can_use_libs_in_multi_fork.stdout +++ b/crates/forge/tests/fixtures/can_use_libs_in_multi_fork.stdout @@ -2,8 +2,8 @@ Compiling 2 files with 0.8.23 Solc 0.8.23 finished in 1.95s Compiler run successful! -Running 1 test for test/Contract.t.sol:ContractTest +Ran 1 test for test/Contract.t.sol:ContractTest [PASS] test() (gas: 70360) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.21s - -Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.21s + +Ran 1 test suite: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/fixtures/include_custom_types_in_traces.stdout b/crates/forge/tests/fixtures/include_custom_types_in_traces.stdout new file mode 100644 index 0000000000000..571cc69274595 --- /dev/null +++ b/crates/forge/tests/fixtures/include_custom_types_in_traces.stdout @@ -0,0 +1,25 @@ +Compiling 1 files with 0.8.23 +Solc 0.8.23 finished in 798.51ms +Compiler run successful! + +Ran 2 tests for test/Contract.t.sol:CustomTypesTest +[FAIL. Reason: PoolNotInitialized()] testErr() (gas: 231) +Traces: + [231] CustomTypesTest::testErr() + └─ ← [Revert] PoolNotInitialized() + +[PASS] testEvent() (gas: 1312) +Traces: + [1312] CustomTypesTest::testEvent() + ├─ emit MyEvent(a: 100) + └─ ← [Stop] + +Suite result: FAILED. 1 passed; 1 failed; 0 skipped; finished in 3.88ms + +Ran 1 test suite: 1 tests passed, 1 failed, 0 skipped (2 total tests) + +Failing tests: +Encountered 1 failing test in test/Contract.t.sol:CustomTypesTest +[FAIL. Reason: PoolNotInitialized()] testErr() (gas: 231) + +Encountered a total of 1 failing tests, 1 tests succeeded diff --git a/crates/forge/tests/fixtures/repro_6531.stdout b/crates/forge/tests/fixtures/repro_6531.stdout new file mode 100644 index 0000000000000..35c27c9483e17 --- /dev/null +++ b/crates/forge/tests/fixtures/repro_6531.stdout @@ -0,0 +1,17 @@ +Compiling 1 files with 0.8.23 + +Compiler run successful! + +Ran 1 test for test/Contract.t.sol:USDTCallingTest +[PASS] test() (gas: 9559) +Traces: + [9559] USDTCallingTest::test() + ├─ [0] VM::createSelectFork("") + │ └─ ← [Return] 0 + ├─ [3110] 0xdAC17F958D2ee523a2206206994597C13D831ec7::name() [staticcall] + │ └─ ← [Return] "Tether USD" + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.43s + +Ran 1 test suite: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.1.stdout b/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.1.stdout index aee6fb691ce3e..c98d9f93e42f4 100644 --- a/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.1.stdout +++ b/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.1.stdout @@ -2,8 +2,8 @@ Compiling 2 files with 0.8.23 Solc 0.8.23 finished in 185.25ms Compiler run successful! -Running 1 test for src/Contract.t.sol:ContractTest +Ran 1 test for src/Contract.t.sol:ContractTest [PASS] testExample() (gas: 190) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.89ms - -Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.89ms + +Ran 1 test suite: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.2.stdout b/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.2.stdout index 691af81679df1..abfd712db4c1e 100644 --- a/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.2.stdout +++ b/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.2.stdout @@ -2,8 +2,8 @@ Compiling 2 files with 0.8.22 Solc 0.8.22 finished in 185.25ms Compiler run successful! -Running 1 test for src/Contract.t.sol:ContractTest +Ran 1 test for src/Contract.t.sol:ContractTest [PASS] testExample() (gas: 190) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.89ms - -Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests) +Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.89ms + +Ran 1 test suite: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/fixtures/suggest_when_no_tests_match.stdout b/crates/forge/tests/fixtures/suggest_when_no_tests_match.stdout index 1c27d8005cdcd..1cf6ad73f8952 100644 --- a/crates/forge/tests/fixtures/suggest_when_no_tests_match.stdout +++ b/crates/forge/tests/fixtures/suggest_when_no_tests_match.stdout @@ -1,7 +1,3 @@ -Compiling 1 files with 0.8.23 -Solc 0.8.23 finished in 185.25ms -Compiler run successful! - No tests match the provided pattern: match-test: `testA.*` no-match-test: `testB.*` diff --git a/crates/forge/tests/fixtures/warn_no_tests.stdout b/crates/forge/tests/fixtures/warn_no_tests.stdout index 9b2b8bff47481..a9a7e7fc67111 100644 --- a/crates/forge/tests/fixtures/warn_no_tests.stdout +++ b/crates/forge/tests/fixtures/warn_no_tests.stdout @@ -1,5 +1 @@ -Compiling 1 files with 0.8.23 -Solc 0.8.23 -Compiler run successful! - No tests found in project! Forge looks for functions that starts with `test`. diff --git a/crates/forge/tests/fixtures/warn_no_tests_match.stdout b/crates/forge/tests/fixtures/warn_no_tests_match.stdout index 56f068238d266..4b4080f15faec 100644 --- a/crates/forge/tests/fixtures/warn_no_tests_match.stdout +++ b/crates/forge/tests/fixtures/warn_no_tests_match.stdout @@ -1,7 +1,3 @@ -Compiling 1 files with 0.8.23 -Solc 0.8.23 -Compiler run successful! - No tests match the provided pattern: match-test: `testA.*` no-match-test: `testB.*` diff --git a/crates/forge/tests/it/cheats.rs b/crates/forge/tests/it/cheats.rs index c0265c3d6c742..47d6ebbb9ec5b 100644 --- a/crates/forge/tests/it/cheats.rs +++ b/crates/forge/tests/it/cheats.rs @@ -1,24 +1,60 @@ -//! forge tests for cheat codes +//! Forge tests for cheatcodes. use crate::{ config::*, - test_helpers::{PROJECT, RE_PATH_SEPARATOR}, + test_helpers::{ + ForgeTestData, RE_PATH_SEPARATOR, TEST_DATA_CANCUN, TEST_DATA_DEFAULT, + TEST_DATA_MULTI_VERSION, + }, }; -use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; +use foundry_config::{fs_permissions::PathPermission, FsPermissions}; use foundry_test_utils::Filter; -/// Executes all cheat code tests but not fork cheat codes -#[tokio::test(flavor = "multi_thread")] -async fn test_cheats_local() { - let mut config = Config::with_root(PROJECT.root()); +/// Executes all cheat code tests but not fork cheat codes or tests that require isolation mode +async fn test_cheats_local(test_data: &ForgeTestData) { + let mut filter = Filter::new(".*", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}*")) + .exclude_paths("Fork") + .exclude_contracts("Isolated"); + + // Exclude FFI tests on Windows because no `echo`, and file tests that expect certain file paths + if cfg!(windows) { + filter = filter.exclude_tests("(Ffi|File|Line|Root)"); + } + + let mut config = test_data.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); - let runner = runner_with_config(config); - let filter = - Filter::new(".*", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}*")).exclude_paths("Fork"); + let runner = test_data.runner_with_config(config); + + TestConfig::with_filter(runner, filter).run().await; +} + +/// Executes subset of all cheat code tests in isolation mode +async fn test_cheats_local_isolated(test_data: &ForgeTestData) { + let filter = Filter::new(".*", ".*(Isolated)", &format!(".*cheats{RE_PATH_SEPARATOR}*")); + + let mut config = test_data.config.clone(); + config.isolate = true; + let runner = test_data.runner_with_config(config); + + TestConfig::with_filter(runner, filter).run().await; +} - // on windows exclude ffi tests since no echo and file test that expect a certain file path - #[cfg(windows)] - let filter = filter.exclude_tests("(Ffi|File|Line|Root)"); +#[tokio::test(flavor = "multi_thread")] +async fn test_cheats_local_default() { + test_cheats_local(&TEST_DATA_DEFAULT).await +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_cheats_local_default_isolated() { + test_cheats_local_isolated(&TEST_DATA_DEFAULT).await +} - TestConfig::with_filter(runner.await, filter).run().await; +#[tokio::test(flavor = "multi_thread")] +async fn test_cheats_local_multi_version() { + test_cheats_local(&TEST_DATA_MULTI_VERSION).await +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_cheats_local_cancun() { + test_cheats_local(&TEST_DATA_CANCUN).await } diff --git a/crates/forge/tests/it/config.rs b/crates/forge/tests/it/config.rs index cfb706d33fc3e..1b2a1398d1f6f 100644 --- a/crates/forge/tests/it/config.rs +++ b/crates/forge/tests/it/config.rs @@ -1,27 +1,24 @@ -//! Test setup +//! Test config. -use crate::test_helpers::{COMPILED, EVM_OPTS, PROJECT}; use forge::{ result::{SuiteResult, TestStatus}, - MultiContractRunner, MultiContractRunnerBuilder, TestOptions, TestOptionsBuilder, -}; -use foundry_config::{ - fs_permissions::PathPermission, Config, FsPermissions, FuzzConfig, FuzzDictionaryConfig, - InvariantConfig, RpcEndpoint, RpcEndpoints, + MultiContractRunner, }; use foundry_evm::{ - decode::decode_console_logs, inspectors::CheatsConfig, revm::primitives::SpecId, + decode::decode_console_logs, + revm::primitives::SpecId, + traces::{render_trace_arena, CallTraceDecoderBuilder}, }; use foundry_test_utils::{init_tracing, Filter}; +use futures::future::join_all; use itertools::Itertools; -use std::{collections::BTreeMap, path::Path}; +use std::collections::BTreeMap; -/// How to execute a a test run +/// How to execute a test run. pub struct TestConfig { pub runner: MultiContractRunner, pub should_fail: bool, pub filter: Filter, - pub opts: TestOptions, } impl TestConfig { @@ -29,13 +26,9 @@ impl TestConfig { Self::with_filter(runner, Filter::matches_all()) } - pub async fn filter(filter: Filter) -> Self { - Self::with_filter(runner().await, filter) - } - pub fn with_filter(runner: MultiContractRunner, filter: Filter) -> Self { init_tracing(); - Self { runner, should_fail: false, filter, opts: test_opts() } + Self { runner, should_fail: false, filter } } pub fn evm_spec(mut self, spec: SpecId) -> Self { @@ -53,8 +46,8 @@ impl TestConfig { } /// Executes the test runner - pub async fn test(&mut self) -> BTreeMap { - self.runner.test(&self.filter, None, self.opts.clone()).await + pub fn test(&mut self) -> BTreeMap { + self.runner.test_collect(&self.filter) } pub async fn run(&mut self) { @@ -67,7 +60,7 @@ impl TestConfig { /// * filter matched 0 test cases /// * a test results deviates from the configured `should_fail` setting pub async fn try_run(&mut self) -> eyre::Result<()> { - let suite_result = self.test().await; + let suite_result = self.test(); if suite_result.is_empty() { eyre::bail!("empty test result"); } @@ -78,14 +71,25 @@ impl TestConfig { { let logs = decode_console_logs(&result.logs); let outcome = if self.should_fail { "fail" } else { "pass" }; - + let call_trace_decoder = CallTraceDecoderBuilder::default().build(); + let decoded_traces = join_all( + result + .traces + .iter() + .map(|(_, a)| render_trace_arena(a, &call_trace_decoder)) + .collect::>(), + ) + .await + .into_iter() + .map(|x| x.unwrap()) + .collect::>(); eyre::bail!( "Test {} did not {} as expected.\nReason: {:?}\nLogs:\n{}\n\nTraces:\n{}", test_name, outcome, result.reason, logs.join("\n"), - result.traces.iter().map(|(_, a)| a).format("\n"), + decoded_traces.into_iter().format("\n"), ) } } @@ -95,122 +99,6 @@ impl TestConfig { } } -/// Returns the [`TestOptions`] used by the tests. -pub fn test_opts() -> TestOptions { - TestOptionsBuilder::default() - .fuzz(FuzzConfig { - runs: 256, - max_test_rejects: 65536, - seed: None, - dictionary: FuzzDictionaryConfig { - include_storage: true, - include_push_bytes: true, - dictionary_weight: 40, - max_fuzz_dictionary_addresses: 10_000, - max_fuzz_dictionary_values: 10_000, - }, - }) - .invariant(InvariantConfig { - runs: 256, - depth: 15, - fail_on_revert: false, - call_override: false, - dictionary: FuzzDictionaryConfig { - dictionary_weight: 80, - include_storage: true, - include_push_bytes: true, - max_fuzz_dictionary_addresses: 10_000, - max_fuzz_dictionary_values: 10_000, - }, - shrink_sequence: true, - }) - .build(&COMPILED, &PROJECT.paths.root) - .expect("Config loaded") -} - -pub fn manifest_root() -> &'static Path { - let mut root = Path::new(env!("CARGO_MANIFEST_DIR")); - // need to check here where we're executing the test from, if in `forge` we need to also allow - // `testdata` - if root.ends_with("forge") { - root = root.parent().unwrap(); - } - root -} - -/// Builds a base runner -pub fn base_runner() -> MultiContractRunnerBuilder { - init_tracing(); - MultiContractRunnerBuilder::default().sender(EVM_OPTS.sender) -} - -/// Builds a non-tracing runner -pub async fn runner() -> MultiContractRunner { - let mut config = Config::with_root(PROJECT.root()); - config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write(manifest_root())]); - runner_with_config(config).await -} - -/// Builds a non-tracing runner -pub async fn runner_with_config(mut config: Config) -> MultiContractRunner { - config.rpc_endpoints = rpc_endpoints(); - config.allow_paths.push(manifest_root().to_path_buf()); - - let root = &PROJECT.paths.root; - let opts = &*EVM_OPTS; - let env = opts.evm_env().await.expect("could not instantiate fork environment"); - let output = COMPILED.clone(); - base_runner() - .with_test_options(test_opts()) - .with_cheats_config(CheatsConfig::new(&config, opts.clone())) - .sender(config.sender) - .build(root, output, env, opts.clone()) - .unwrap() -} - -/// Builds a tracing runner -pub async fn tracing_runner() -> MultiContractRunner { - let mut opts = EVM_OPTS.clone(); - opts.verbosity = 5; - base_runner() - .build( - &PROJECT.paths.root, - (*COMPILED).clone(), - EVM_OPTS.evm_env().await.expect("Could not instantiate fork environment"), - opts, - ) - .unwrap() -} - -// Builds a runner that runs against forked state -pub async fn forked_runner(rpc: &str) -> MultiContractRunner { - let mut opts = EVM_OPTS.clone(); - - opts.env.chain_id = None; // clear chain id so the correct one gets fetched from the RPC - opts.fork_url = Some(rpc.to_string()); - - let env = opts.evm_env().await.expect("Could not instantiate fork environment"); - let fork = opts.get_fork(&Default::default(), env.clone()); - - base_runner() - .with_fork(fork) - .build(&PROJECT.paths.root, (*COMPILED).clone(), env, opts) - .unwrap() -} - -/// the RPC endpoints used during tests -pub fn rpc_endpoints() -> RpcEndpoints { - RpcEndpoints::new([ - ( - "rpcAlias", - RpcEndpoint::Url( - "https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf".to_string(), - ), - ), - ("rpcEnvAlias", RpcEndpoint::Env("${RPC_ENV_ALIAS}".to_string())), - ]) -} - /// A helper to assert the outcome of multiple tests with helpful assert messages #[track_caller] #[allow(clippy::type_complexity)] diff --git a/crates/forge/tests/it/core.rs b/crates/forge/tests/it/core.rs index 07b89a6c14a7f..34cdc0d179287 100644 --- a/crates/forge/tests/it/core.rs +++ b/crates/forge/tests/it/core.rs @@ -1,6 +1,6 @@ -//! forge tests for core functionality +//! Forge tests for core functionality. -use crate::config::*; +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; use forge::result::SuiteResult; use foundry_evm::traces::TraceKind; use foundry_test_utils::Filter; @@ -8,14 +8,15 @@ use std::{collections::BTreeMap, env}; #[tokio::test(flavor = "multi_thread")] async fn test_core() { - let mut runner = runner().await; - let results = runner.test(&Filter::new(".*", ".*", ".*core"), None, test_opts()).await; + let filter = Filter::new(".*", ".*", ".*core"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([ ( - "core/FailingSetup.t.sol:FailingSetupTest", + "default/core/FailingSetup.t.sol:FailingSetupTest", vec![( "setUp()", false, @@ -25,7 +26,7 @@ async fn test_core() { )], ), ( - "core/MultipleSetup.t.sol:MultipleSetup", + "default/core/MultipleSetup.t.sol:MultipleSetup", vec![( "setUp()", false, @@ -35,34 +36,37 @@ async fn test_core() { )], ), ( - "core/Reverting.t.sol:RevertingTest", + "default/core/Reverting.t.sol:RevertingTest", vec![("testFailRevert()", true, None, None, None)], ), ( - "core/SetupConsistency.t.sol:SetupConsistencyCheck", + "default/core/SetupConsistency.t.sol:SetupConsistencyCheck", vec![ ("testAdd()", true, None, None, None), ("testMultiply()", true, None, None, None), ], ), ( - "core/DSStyle.t.sol:DSStyleTest", + "default/core/DSStyle.t.sol:DSStyleTest", vec![("testFailingAssertions()", true, None, None, None)], ), ( - "core/ContractEnvironment.t.sol:ContractEnvironmentTest", + "default/core/ContractEnvironment.t.sol:ContractEnvironmentTest", vec![ ("testAddresses()", true, None, None, None), ("testEnvironment()", true, None, None, None), ], ), ( - "core/PaymentFailure.t.sol:PaymentFailureTest", + "default/core/PaymentFailure.t.sol:PaymentFailureTest", vec![("testCantPay()", false, Some("EvmError: Revert".to_string()), None, None)], ), - ("core/Abstract.t.sol:AbstractTest", vec![("testSomething()", true, None, None, None)]), ( - "core/FailingTestAfterFailedSetup.t.sol:FailingTestAfterFailedSetupTest", + "default/core/Abstract.t.sol:AbstractTest", + vec![("testSomething()", true, None, None, None)], + ), + ( + "default/core/FailingTestAfterFailedSetup.t.sol:FailingTestAfterFailedSetupTest", vec![( "setUp()", false, @@ -77,25 +81,26 @@ async fn test_core() { #[tokio::test(flavor = "multi_thread")] async fn test_linking() { - let mut runner = runner().await; - let results = runner.test(&Filter::new(".*", ".*", ".*linking"), None, test_opts()).await; + let filter = Filter::new(".*", ".*", ".*linking"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([ ( - "linking/simple/Simple.t.sol:SimpleLibraryLinkingTest", + "default/linking/simple/Simple.t.sol:SimpleLibraryLinkingTest", vec![("testCall()", true, None, None, None)], ), ( - "linking/nested/Nested.t.sol:NestedLibraryLinkingTest", + "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest", vec![ ("testDirect()", true, None, None, None), ("testNested()", true, None, None, None), ], ), ( - "linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest", + "default/linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest", vec![ ("testA()", true, None, None, None), ("testB()", true, None, None, None), @@ -110,14 +115,15 @@ async fn test_linking() { #[tokio::test(flavor = "multi_thread")] async fn test_logs() { - let mut runner = runner().await; - let results = runner.test(&Filter::new(".*", ".*", ".*logs"), None, test_opts()).await; + let filter = Filter::new(".*", ".*", ".*logs"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([ ( - "logs/DebugLogs.t.sol:DebugLogsTest", + "default/logs/DebugLogs.t.sol:DebugLogsTest", vec![ ( "test1()", @@ -286,7 +292,7 @@ async fn test_logs() { ], ), ( - "logs/HardhatLogs.t.sol:HardhatLogsTest", + "default/logs/HardhatLogs.t.sol:HardhatLogsTest", vec![ ( "testInts()", @@ -670,35 +676,31 @@ async fn test_logs() { #[tokio::test(flavor = "multi_thread")] async fn test_env_vars() { - let mut runner = runner().await; - - // test `setEnv` first, and confirm that it can correctly set environment variables, - // so that we can use it in subsequent `env*` tests - runner.test(&Filter::new("testSetEnv", ".*", ".*"), None, test_opts()).await; let env_var_key = "_foundryCheatcodeSetEnvTestKey"; let env_var_val = "_foundryCheatcodeSetEnvTestVal"; - let res = env::var(env_var_key); - assert!( - res.is_ok() && res.unwrap() == env_var_val, - "Test `testSetEnv` did not pass as expected. -Reason: `setEnv` failed to set an environment variable `{env_var_key}={env_var_val}`" - ); + env::remove_var(env_var_key); + + let filter = Filter::new("testSetEnv", ".*", ".*"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let _ = runner.test_collect(&filter); + + assert_eq!(env::var(env_var_key).unwrap(), env_var_val); } #[tokio::test(flavor = "multi_thread")] async fn test_doesnt_run_abstract_contract() { - let mut runner = runner().await; - let results = runner - .test(&Filter::new(".*", ".*", ".*Abstract.t.sol".to_string().as_str()), None, test_opts()) - .await; - assert!(results.get("core/Abstract.t.sol:AbstractTestBase").is_none()); - assert!(results.get("core/Abstract.t.sol:AbstractTest").is_some()); + let filter = Filter::new(".*", ".*", ".*Abstract.t.sol".to_string().as_str()); + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&filter); + assert!(!results.contains_key("default/core/Abstract.t.sol:AbstractTestBase")); + assert!(results.contains_key("default/core/Abstract.t.sol:AbstractTest")); } #[tokio::test(flavor = "multi_thread")] async fn test_trace() { - let mut runner = tracing_runner().await; - let suite_result = runner.test(&Filter::new(".*", ".*", ".*trace"), None, test_opts()).await; + let filter = Filter::new(".*", ".*", ".*trace"); + let mut runner = TEST_DATA_DEFAULT.tracing_runner(); + let suite_result = runner.test_collect(&filter); // TODO: This trace test is very basic - it is probably a good candidate for snapshot // testing. diff --git a/crates/forge/tests/it/fork.rs b/crates/forge/tests/it/fork.rs index e66e975fdc86c..d3f81ff67ea13 100644 --- a/crates/forge/tests/it/fork.rs +++ b/crates/forge/tests/it/fork.rs @@ -1,28 +1,23 @@ -//! forge tests for cheat codes +//! Forge forking tests. use crate::{ config::*, - test_helpers::{PROJECT, RE_PATH_SEPARATOR}, + test_helpers::{RE_PATH_SEPARATOR, TEST_DATA_DEFAULT}, }; use forge::result::SuiteResult; -use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; +use foundry_config::{fs_permissions::PathPermission, FsPermissions}; use foundry_test_utils::Filter; /// Executes reverting fork test #[tokio::test(flavor = "multi_thread")] async fn test_cheats_fork_revert() { - let mut runner = runner().await; - let suite_result = runner - .test( - &Filter::new( - "testNonExistingContractRevert", - ".*", - &format!(".*cheats{RE_PATH_SEPARATOR}Fork"), - ), - None, - test_opts(), - ) - .await; + let filter = Filter::new( + "testNonExistingContractRevert", + ".*", + &format!(".*cheats{RE_PATH_SEPARATOR}Fork"), + ); + let mut runner = TEST_DATA_DEFAULT.runner(); + let suite_result = runner.test_collect(&filter); assert_eq!(suite_result.len(), 1); for (_, SuiteResult { test_results, .. }) in suite_result { @@ -38,41 +33,41 @@ async fn test_cheats_fork_revert() { /// Executes all non-reverting fork cheatcodes #[tokio::test(flavor = "multi_thread")] async fn test_cheats_fork() { - let mut config = Config::with_root(PROJECT.root()); + let mut config = TEST_DATA_DEFAULT.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - let runner = runner_with_config(config); + let runner = TEST_DATA_DEFAULT.runner_with_config(config); let filter = Filter::new(".*", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) .exclude_tests(".*Revert"); - TestConfig::with_filter(runner.await, filter).run().await; + TestConfig::with_filter(runner, filter).run().await; } /// Executes eth_getLogs cheatcode #[tokio::test(flavor = "multi_thread")] async fn test_get_logs_fork() { - let mut config = Config::with_root(PROJECT.root()); + let mut config = TEST_DATA_DEFAULT.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - let runner = runner_with_config(config); + let runner = TEST_DATA_DEFAULT.runner_with_config(config); let filter = Filter::new("testEthGetLogs", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) .exclude_tests(".*Revert"); - TestConfig::with_filter(runner.await, filter).run().await; + TestConfig::with_filter(runner, filter).run().await; } /// Executes rpc cheatcode #[tokio::test(flavor = "multi_thread")] async fn test_rpc_fork() { - let mut config = Config::with_root(PROJECT.root()); + let mut config = TEST_DATA_DEFAULT.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - let runner = runner_with_config(config); + let runner = TEST_DATA_DEFAULT.runner_with_config(config); let filter = Filter::new("testRpc", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) .exclude_tests(".*Revert"); - TestConfig::with_filter(runner.await, filter).run().await; + TestConfig::with_filter(runner, filter).run().await; } /// Tests that we can launch in forking mode #[tokio::test(flavor = "multi_thread")] async fn test_launch_fork() { - let rpc_url = foundry_common::rpc::next_http_archive_rpc_endpoint(); - let runner = forked_runner(&rpc_url).await; + let rpc_url = foundry_test_utils::rpc::next_http_archive_rpc_endpoint(); + let runner = TEST_DATA_DEFAULT.forked_runner(&rpc_url).await; let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Launch")); TestConfig::with_filter(runner, filter).run().await; } @@ -80,8 +75,8 @@ async fn test_launch_fork() { /// Smoke test that forking workings with websockets #[tokio::test(flavor = "multi_thread")] async fn test_launch_fork_ws() { - let rpc_url = foundry_common::rpc::next_ws_archive_rpc_endpoint(); - let runner = forked_runner(&rpc_url).await; + let rpc_url = foundry_test_utils::rpc::next_ws_archive_rpc_endpoint(); + let runner = TEST_DATA_DEFAULT.forked_runner(&rpc_url).await; let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Launch")); TestConfig::with_filter(runner, filter).run().await; } @@ -89,13 +84,15 @@ async fn test_launch_fork_ws() { /// Tests that we can transact transactions in forking mode #[tokio::test(flavor = "multi_thread")] async fn test_transact_fork() { + let runner = TEST_DATA_DEFAULT.runner(); let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Transact")); - TestConfig::filter(filter).await.run().await; + TestConfig::with_filter(runner, filter).run().await; } /// Tests that we can create the same fork (provider,block) concurretnly in different tests #[tokio::test(flavor = "multi_thread")] async fn test_create_same_fork() { + let runner = TEST_DATA_DEFAULT.runner(); let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}ForkSame")); - TestConfig::filter(filter).await.run().await; + TestConfig::with_filter(runner, filter).run().await; } diff --git a/crates/forge/tests/it/fs.rs b/crates/forge/tests/it/fs.rs index 9ab7711eda3c7..5bb0b59fb24b3 100644 --- a/crates/forge/tests/it/fs.rs +++ b/crates/forge/tests/it/fs.rs @@ -1,23 +1,23 @@ -//! Tests for reproducing issues +//! Filesystem tests. -use crate::{config::*, test_helpers::PROJECT}; -use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use foundry_config::{fs_permissions::PathPermission, FsPermissions}; use foundry_test_utils::Filter; #[tokio::test(flavor = "multi_thread")] async fn test_fs_disabled() { - let mut config = Config::with_root(PROJECT.root()); + let mut config = TEST_DATA_DEFAULT.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::none("./")]); - let runner = runner_with_config(config).await; + let runner = TEST_DATA_DEFAULT.runner_with_config(config); let filter = Filter::new(".*", ".*", ".*fs/Disabled"); TestConfig::with_filter(runner, filter).run().await; } #[tokio::test(flavor = "multi_thread")] async fn test_fs_default() { - let mut config = Config::with_root(PROJECT.root()); + let mut config = TEST_DATA_DEFAULT.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - let runner = runner_with_config(config); + let runner = TEST_DATA_DEFAULT.runner_with_config(config); let filter = Filter::new(".*", ".*", ".*fs/Default"); - TestConfig::with_filter(runner.await, filter).run().await; + TestConfig::with_filter(runner, filter).run().await; } diff --git a/crates/forge/tests/it/fuzz.rs b/crates/forge/tests/it/fuzz.rs index 5654cb070a811..c6369e896615f 100644 --- a/crates/forge/tests/it/fuzz.rs +++ b/crates/forge/tests/it/fuzz.rs @@ -1,24 +1,21 @@ -//! Tests for invariants +//! Fuzz tests. -use crate::config::*; -use alloy_primitives::U256; -use forge::result::{SuiteResult, TestStatus}; +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use alloy_primitives::{Bytes, U256}; +use forge::{ + fuzz::CounterExample, + result::{SuiteResult, TestStatus}, +}; use foundry_test_utils::Filter; use std::collections::BTreeMap; #[tokio::test(flavor = "multi_thread")] async fn test_fuzz() { - let mut runner = runner().await; - - let suite_result = runner - .test( - &Filter::new(".*", ".*", ".*fuzz/") - .exclude_tests(r"invariantCounter|testIncrement\(address\)|testNeedle\(uint256\)") - .exclude_paths("invariant"), - None, - test_opts(), - ) - .await; + let filter = Filter::new(".*", ".*", ".*fuzz/") + .exclude_tests(r"invariantCounter|testIncrement\(address\)|testNeedle\(uint256\)|testSuccessChecker\(uint256\)|testSuccessChecker2\(int256\)|testSuccessChecker3\(uint32\)") + .exclude_paths("invariant"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let suite_result = runner.test_collect(&filter); assert!(!suite_result.is_empty()); @@ -28,15 +25,17 @@ async fn test_fuzz() { "testPositive(uint256)" | "testPositive(int256)" | "testSuccessfulFuzz(uint128,uint128)" | - "testToStringFuzz(bytes32)" => assert!( - result.status == TestStatus::Success, + "testToStringFuzz(bytes32)" => assert_eq!( + result.status, + TestStatus::Success, "Test {} did not pass as expected.\nReason: {:?}\nLogs:\n{}", test_name, result.reason, result.decoded_logs.join("\n") ), - _ => assert!( - result.status == TestStatus::Failure, + _ => assert_eq!( + result.status, + TestStatus::Failure, "Test {} did not fail as expected.\nReason: {:?}\nLogs:\n{}", test_name, result.reason, @@ -47,27 +46,52 @@ async fn test_fuzz() { } } +#[tokio::test(flavor = "multi_thread")] +async fn test_successful_fuzz_cases() { + let filter = Filter::new(".*", ".*", ".*fuzz/FuzzPositive") + .exclude_tests(r"invariantCounter|testIncrement\(address\)|testNeedle\(uint256\)") + .exclude_paths("invariant"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let suite_result = runner.test_collect(&filter); + + assert!(!suite_result.is_empty()); + + for (_, SuiteResult { test_results, .. }) in suite_result { + for (test_name, result) in test_results { + match test_name.as_str() { + "testSuccessChecker(uint256)" | + "testSuccessChecker2(int256)" | + "testSuccessChecker3(uint32)" => assert_eq!( + result.status, + TestStatus::Success, + "Test {} did not pass as expected.\nReason: {:?}\nLogs:\n{}", + test_name, + result.reason, + result.decoded_logs.join("\n") + ), + _ => {} + } + } + } +} + /// Test that showcases PUSH collection on normal fuzzing. Ignored until we collect them in a /// smarter way. #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_fuzz_collection() { - let mut runner = runner().await; - - let mut opts = test_opts(); - opts.invariant.depth = 100; - opts.invariant.runs = 1000; - opts.fuzz.runs = 1000; - opts.fuzz.seed = Some(U256::from(6u32)); - runner.test_options = opts.clone(); - - let results = - runner.test(&Filter::new(".*", ".*", ".*fuzz/FuzzCollection.t.sol"), None, opts).await; + let filter = Filter::new(".*", ".*", ".*fuzz/FuzzCollection.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.depth = 100; + runner.test_options.invariant.runs = 1000; + runner.test_options.fuzz.runs = 1000; + runner.test_options.fuzz.seed = Some(U256::from(6u32)); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([( - "fuzz/FuzzCollection.t.sol:SampleContractTest", + "default/fuzz/FuzzCollection.t.sol:SampleContractTest", vec![ ("invariantCounter", false, Some("broken counter.".into()), None, None), ( @@ -82,3 +106,50 @@ async fn test_fuzz_collection() { )]), ); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_persist_fuzz_failure() { + let filter = Filter::new(".*", ".*", ".*fuzz/FuzzFailurePersist.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.fuzz.runs = 1000; + + macro_rules! get_failure_result { + () => { + runner + .test_collect(&filter) + .get("default/fuzz/FuzzFailurePersist.t.sol:FuzzFailurePersistTest") + .unwrap() + .test_results + .get("test_persist_fuzzed_failure(uint256,int256,address,bool,string,(address,uint256),address[])") + .unwrap() + .counterexample + .clone() + }; + } + + // record initial counterexample calldata + let initial_counterexample = get_failure_result!(); + let initial_calldata = match initial_counterexample { + Some(CounterExample::Single(counterexample)) => counterexample.calldata, + _ => Bytes::new(), + }; + + // run several times and compare counterexamples calldata + for i in 0..10 { + let new_calldata = match get_failure_result!() { + Some(CounterExample::Single(counterexample)) => counterexample.calldata, + _ => Bytes::new(), + }; + // calldata should be the same with the initial one + assert_eq!(initial_calldata, new_calldata, "run {i}"); + } + + // write new failure in different file + runner.test_options.fuzz.failure_persist_file = Some("failure1".to_string()); + let new_calldata = match get_failure_result!() { + Some(CounterExample::Single(counterexample)) => counterexample.calldata, + _ => Bytes::new(), + }; + // empty file is used to load failure so new calldata is generated + assert_ne!(initial_calldata, new_calldata); +} diff --git a/crates/forge/tests/it/inline.rs b/crates/forge/tests/it/inline.rs index 7fc2957a2a955..09d4fb3230339 100644 --- a/crates/forge/tests/it/inline.rs +++ b/crates/forge/tests/it/inline.rs @@ -1,109 +1,72 @@ -use crate::{ - config::runner, - test_helpers::{COMPILED, PROJECT}, -}; -use forge::{ - result::{SuiteResult, TestKind, TestResult}, - TestOptions, TestOptionsBuilder, -}; +//! Inline configuration tests. + +use crate::test_helpers::TEST_DATA_DEFAULT; +use forge::{result::TestKind, TestOptionsBuilder}; use foundry_config::{FuzzConfig, InvariantConfig}; use foundry_test_utils::Filter; #[tokio::test(flavor = "multi_thread")] async fn inline_config_run_fuzz() { - let opts = default_test_options(); - let filter = Filter::new(".*", ".*", ".*inline/FuzzInlineConf.t.sol"); - - let mut runner = runner().await; - runner.test_options = opts.clone(); - - let result = runner.test(&filter, None, opts).await; - let suite_result: &SuiteResult = - result.get("inline/FuzzInlineConf.t.sol:FuzzInlineConf").unwrap(); - let test_result: &TestResult = - suite_result.test_results.get("testInlineConfFuzz(uint8)").unwrap(); - match &test_result.kind { - TestKind::Fuzz { runs, .. } => { - assert_eq!(runs, &1024); - } - _ => { - unreachable!() - } + let mut runner = TEST_DATA_DEFAULT.runner(); + let result = runner.test_collect(&filter); + let suite_result = result.get("default/inline/FuzzInlineConf.t.sol:FuzzInlineConf").unwrap(); + let test_result = suite_result.test_results.get("testInlineConfFuzz(uint8)").unwrap(); + match test_result.kind { + TestKind::Fuzz { runs, .. } => assert_eq!(runs, 1024), + _ => unreachable!(), } } #[tokio::test(flavor = "multi_thread")] async fn inline_config_run_invariant() { - const ROOT: &str = "inline/InvariantInlineConf.t.sol"; + const ROOT: &str = "default/inline/InvariantInlineConf.t.sol"; - let opts = default_test_options(); let filter = Filter::new(".*", ".*", ".*inline/InvariantInlineConf.t.sol"); - let mut runner = runner().await; - runner.test_options = opts.clone(); - - let result = runner.test(&filter, None, opts).await; + let mut runner = TEST_DATA_DEFAULT.runner(); + let result = runner.test_collect(&filter); let suite_result_1 = result.get(&format!("{ROOT}:InvariantInlineConf")).expect("Result exists"); let suite_result_2 = result.get(&format!("{ROOT}:InvariantInlineConf2")).expect("Result exists"); let test_result_1 = suite_result_1.test_results.get("invariant_neverFalse()").unwrap(); - let test_result_2 = suite_result_2.test_results.get("invariant_neverFalse()").unwrap(); - - match &test_result_1.kind { - TestKind::Invariant { runs, .. } => { - assert_eq!(runs, &333); - } - _ => { - unreachable!() - } + match test_result_1.kind { + TestKind::Invariant { runs, .. } => assert_eq!(runs, 333), + _ => unreachable!(), } - match &test_result_2.kind { - TestKind::Invariant { runs, .. } => { - assert_eq!(runs, &42); - } - _ => { - unreachable!() - } + let test_result_2 = suite_result_2.test_results.get("invariant_neverFalse()").unwrap(); + match test_result_2.kind { + TestKind::Invariant { runs, .. } => assert_eq!(runs, 42), + _ => unreachable!(), } } #[test] fn build_test_options() { - let root = &PROJECT.paths.root; + let root = &TEST_DATA_DEFAULT.project.paths.root; let profiles = vec!["default".to_string(), "ci".to_string()]; let build_result = TestOptionsBuilder::default() .fuzz(FuzzConfig::default()) .invariant(InvariantConfig::default()) .profiles(profiles) - .build(&COMPILED, root); + .build(&TEST_DATA_DEFAULT.output, root); assert!(build_result.is_ok()); } #[test] fn build_test_options_just_one_valid_profile() { - let root = &PROJECT.paths.root; + let root = &TEST_DATA_DEFAULT.project.root(); let valid_profiles = vec!["profile-sheldon-cooper".to_string()]; let build_result = TestOptionsBuilder::default() .fuzz(FuzzConfig::default()) .invariant(InvariantConfig::default()) .profiles(valid_profiles) - .build(&COMPILED, root); + .build(&TEST_DATA_DEFAULT.output, root); // We expect an error, since COMPILED contains in-line // per-test configs for "default" and "ci" profiles assert!(build_result.is_err()); } - -/// Returns the [TestOptions] for the testing [PROJECT]. -pub fn default_test_options() -> TestOptions { - let root = &PROJECT.paths.root; - TestOptionsBuilder::default() - .fuzz(FuzzConfig::default()) - .invariant(InvariantConfig::default()) - .build(&COMPILED, root) - .expect("Config loaded") -} diff --git a/crates/forge/tests/it/invariant.rs b/crates/forge/tests/it/invariant.rs index bfd28d1435b96..e906c83569b94 100644 --- a/crates/forge/tests/it/invariant.rs +++ b/crates/forge/tests/it/invariant.rs @@ -1,32 +1,26 @@ -//! Tests for invariants +//! Invariant tests. -use crate::config::*; +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; use alloy_primitives::U256; -use forge::fuzz::CounterExample; +use forge::{fuzz::CounterExample, result::TestStatus, TestOptions}; use foundry_test_utils::Filter; use std::collections::BTreeMap; #[tokio::test(flavor = "multi_thread")] async fn test_invariant() { - let mut runner = runner().await; - - let results = runner - .test( - &Filter::new(".*", ".*", ".*fuzz/invariant/(target|targetAbi|common)"), - None, - test_opts(), - ) - .await; + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/(target|targetAbi|common)"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([ ( - "fuzz/invariant/common/InvariantHandlerFailure.t.sol:InvariantHandlerFailure", + "default/fuzz/invariant/common/InvariantHandlerFailure.t.sol:InvariantHandlerFailure", vec![("statefulFuzz_BrokenInvariant()", true, None, None, None)], ), ( - "fuzz/invariant/common/InvariantInnerContract.t.sol:InvariantInnerContract", + "default/fuzz/invariant/common/InvariantInnerContract.t.sol:InvariantInnerContract", vec![( "invariantHideJesus()", false, @@ -36,11 +30,11 @@ async fn test_invariant() { )], ), ( - "fuzz/invariant/common/InvariantReentrancy.t.sol:InvariantReentrancy", + "default/fuzz/invariant/common/InvariantReentrancy.t.sol:InvariantReentrancy", vec![("invariantNotStolen()", true, None, None, None)], ), ( - "fuzz/invariant/common/InvariantTest1.t.sol:InvariantTest", + "default/fuzz/invariant/common/InvariantTest1.t.sol:InvariantTest", vec![ ("invariant_neverFalse()", false, Some("revert: false".into()), None, None), ( @@ -53,15 +47,15 @@ async fn test_invariant() { ], ), ( - "fuzz/invariant/target/ExcludeContracts.t.sol:ExcludeContracts", + "default/fuzz/invariant/target/ExcludeContracts.t.sol:ExcludeContracts", vec![("invariantTrueWorld()", true, None, None, None)], ), ( - "fuzz/invariant/target/TargetContracts.t.sol:TargetContracts", + "default/fuzz/invariant/target/TargetContracts.t.sol:TargetContracts", vec![("invariantTrueWorld()", true, None, None, None)], ), ( - "fuzz/invariant/target/TargetSenders.t.sol:TargetSenders", + "default/fuzz/invariant/target/TargetSenders.t.sol:TargetSenders", vec![( "invariantTrueWorld()", false, @@ -71,7 +65,7 @@ async fn test_invariant() { )], ), ( - "fuzz/invariant/target/TargetInterfaces.t.sol:TargetWorldInterfaces", + "default/fuzz/invariant/target/TargetInterfaces.t.sol:TargetWorldInterfaces", vec![( "invariantTrueWorld()", false, @@ -81,19 +75,19 @@ async fn test_invariant() { )], ), ( - "fuzz/invariant/target/ExcludeSenders.t.sol:ExcludeSenders", + "default/fuzz/invariant/target/ExcludeSenders.t.sol:ExcludeSenders", vec![("invariantTrueWorld()", true, None, None, None)], ), ( - "fuzz/invariant/target/TargetSelectors.t.sol:TargetSelectors", + "default/fuzz/invariant/target/TargetSelectors.t.sol:TargetSelectors", vec![("invariantTrueWorld()", true, None, None, None)], ), ( - "fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol:ExcludeArtifacts", + "default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol:ExcludeArtifacts", vec![("invariantShouldPass()", true, None, None, None)], ), ( - "fuzz/invariant/targetAbi/TargetArtifacts.t.sol:TargetArtifacts", + "default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol:TargetArtifacts", vec![ ("invariantShouldPass()", true, None, None, None), ( @@ -106,11 +100,11 @@ async fn test_invariant() { ], ), ( - "fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol:TargetArtifactSelectors", + "default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol:TargetArtifactSelectors", vec![("invariantShouldPass()", true, None, None, None)], ), ( - "fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:TargetArtifactSelectors2", + "default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:TargetArtifactSelectors2", vec![( "invariantShouldFail()", false, @@ -119,30 +113,90 @@ async fn test_invariant() { None, )], ), + ( + "default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol:InvariantShrinkWithAssert", + vec![( + "invariant_with_assert()", + false, + Some("".into()), + None, + None, + )], + ), + ( + "default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol:InvariantShrinkWithRequire", + vec![( + "invariant_with_require()", + false, + Some("revert: wrong counter".into()), + None, + None, + )], + ), + ( + "default/fuzz/invariant/common/InvariantPreserveState.t.sol:InvariantPreserveState", + vec![("invariant_preserve_state()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol:InvariantCalldataDictionary", + vec![( + "invariant_owner_never_changes()", + false, + Some("".into()), + None, + None, + )], + ), + ( + "default/fuzz/invariant/common/InvariantAssume.t.sol:InvariantAssume", + vec![("invariant_dummy()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/common/InvariantCustomError.t.sol:InvariantCustomError", + vec![("invariant_decode_error()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/target/FuzzedTargetContracts.t.sol:ExplicitTargetContract", + vec![("invariant_explicit_target()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/target/FuzzedTargetContracts.t.sol:DynamicTargetContract", + vec![("invariant_dynamic_targets()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/common/InvariantFixtures.t.sol:InvariantFixtures", + vec![( + "invariant_target_not_compromised()", + false, + Some("".into()), + None, + None, + )], + ), + ( + "default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol:ShrinkBigSequenceTest", + vec![("invariant_shrink_big_sequence()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol:ShrinkFailOnRevertTest", + vec![("invariant_shrink_fail_on_revert()", true, None, None, None)], + ), ]), ); } #[tokio::test(flavor = "multi_thread")] async fn test_invariant_override() { - let mut runner = runner().await; - - let mut opts = test_opts(); - opts.invariant.call_override = true; - runner.test_options = opts.clone(); - - let results = runner - .test( - &Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantReentrancy.t.sol"), - None, - opts, - ) - .await; + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantReentrancy.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.fail_on_revert = false; + runner.test_options.invariant.call_override = true; + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([( - "fuzz/invariant/common/InvariantReentrancy.t.sol:InvariantReentrancy", + "default/fuzz/invariant/common/InvariantReentrancy.t.sol:InvariantReentrancy", vec![("invariantNotStolen()", false, Some("revert: stolen".into()), None, None)], )]), ); @@ -150,26 +204,17 @@ async fn test_invariant_override() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_fail_on_revert() { - let mut runner = runner().await; - - let mut opts = test_opts(); - opts.invariant.fail_on_revert = true; - opts.invariant.runs = 1; - opts.invariant.depth = 10; - runner.test_options = opts.clone(); - - let results = runner - .test( - &Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantHandlerFailure.t.sol"), - None, - opts, - ) - .await; + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantHandlerFailure.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.fail_on_revert = true; + runner.test_options.invariant.runs = 1; + runner.test_options.invariant.depth = 10; + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([( - "fuzz/invariant/common/InvariantHandlerFailure.t.sol:InvariantHandlerFailure", + "default/fuzz/invariant/common/InvariantHandlerFailure.t.sol:InvariantHandlerFailure", vec![( "statefulFuzz_BrokenInvariant()", false, @@ -184,25 +229,16 @@ async fn test_invariant_fail_on_revert() { #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_invariant_storage() { - let mut runner = runner().await; - - let mut opts = test_opts(); - opts.invariant.depth = 100 + (50 * cfg!(windows) as u32); - opts.fuzz.seed = Some(U256::from(6u32)); - runner.test_options = opts.clone(); - - let results = runner - .test( - &Filter::new(".*", ".*", ".*fuzz/invariant/storage/InvariantStorageTest.t.sol"), - None, - opts, - ) - .await; + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/storage/InvariantStorageTest.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.depth = 100 + (50 * cfg!(windows) as u32); + runner.test_options.fuzz.seed = Some(U256::from(6u32)); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([( - "fuzz/invariant/storage/InvariantStorageTest.t.sol:InvariantStorageTest", + "default/fuzz/invariant/storage/InvariantStorageTest.t.sol:InvariantStorageTest", vec![ ("invariantChangeAddress()", false, Some("changedAddr".to_string()), None, None), ("invariantChangeString()", false, Some("changedString".to_string()), None, None), @@ -216,19 +252,10 @@ async fn test_invariant_storage() { #[tokio::test(flavor = "multi_thread")] #[cfg_attr(windows, ignore = "for some reason there's different rng")] async fn test_invariant_shrink() { - let mut runner = runner().await; - - let mut opts = test_opts(); - opts.fuzz.seed = Some(U256::from(102u32)); - runner.test_options = opts.clone(); - - let results = runner - .test( - &Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantInnerContract.t.sol"), - None, - opts, - ) - .await; + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantInnerContract.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.fuzz.seed = Some(U256::from(119u32)); + let results = runner.test_collect(&filter); let results = results.values().last().expect("`InvariantInnerContract.t.sol` should be testable."); @@ -243,10 +270,312 @@ async fn test_invariant_shrink() { match counter { CounterExample::Single(_) => panic!("CounterExample should be a sequence."), - // `fuzz_seed` at 100 makes this sequence shrinkable from 4 to 2. + // `fuzz_seed` at 119 makes this sequence shrinkable from 4 to 2. + CounterExample::Sequence(sequence) => { + assert!(sequence.len() <= 3); + + if sequence.len() == 2 { + // call order should always be preserved + let create_fren_sequence = sequence[0].clone(); + assert_eq!( + create_fren_sequence.contract_name.unwrap(), + "default/fuzz/invariant/common/InvariantInnerContract.t.sol:Jesus" + ); + assert_eq!(create_fren_sequence.signature.unwrap(), "create_fren()"); + + let betray_sequence = sequence[1].clone(); + assert_eq!( + betray_sequence.contract_name.unwrap(), + "default/fuzz/invariant/common/InvariantInnerContract.t.sol:Judas" + ); + assert_eq!(betray_sequence.signature.unwrap(), "betray()"); + } + } + }; +} + +#[tokio::test(flavor = "multi_thread")] +#[cfg_attr(windows, ignore = "for some reason there's different rng")] +async fn test_invariant_assert_shrink() { + let mut opts = TEST_DATA_DEFAULT.test_opts.clone(); + opts.fuzz.seed = Some(U256::from(119u32)); + + // ensure assert and require shrinks to same sequence of 3 or less + test_shrink(opts.clone(), "InvariantShrinkWithAssert").await; + test_shrink(opts.clone(), "InvariantShrinkWithRequire").await; +} + +async fn test_shrink(opts: TestOptions, contract_pattern: &str) { + let filter = Filter::new( + ".*", + contract_pattern, + ".*fuzz/invariant/common/InvariantShrinkWithAssert.t.sol", + ); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options = opts.clone(); + let results = runner.test_collect(&filter); + let results = results.values().last().expect("`InvariantShrinkWithAssert` should be testable."); + + let result = results + .test_results + .values() + .last() + .expect("`InvariantShrinkWithAssert` should be testable."); + + assert_eq!(result.status, TestStatus::Failure); + + let counter = result + .counterexample + .as_ref() + .expect("`InvariantShrinkWithAssert` should have failed with a counterexample."); + + match counter { + CounterExample::Single(_) => panic!("CounterExample should be a sequence."), + CounterExample::Sequence(sequence) => { + assert!(sequence.len() <= 3); + } + }; +} + +#[tokio::test(flavor = "multi_thread")] +#[cfg_attr(windows, ignore = "for some reason there's different rng")] +async fn test_shrink_big_sequence() { + let mut opts = TEST_DATA_DEFAULT.test_opts.clone(); + opts.fuzz.seed = Some(U256::from(119u32)); + + let filter = + Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantShrinkBigSequence.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options = opts.clone(); + runner.test_options.invariant.runs = 1; + runner.test_options.invariant.depth = 500; + let results = runner.test_collect(&filter); + let results = + results.values().last().expect("`InvariantShrinkBigSequence` should be testable."); + + let result = results + .test_results + .values() + .last() + .expect("`InvariantShrinkBigSequence` should be testable."); + + assert_eq!(result.status, TestStatus::Failure); + + let counter = result + .counterexample + .as_ref() + .expect("`InvariantShrinkBigSequence` should have failed with a counterexample."); + + match counter { + CounterExample::Single(_) => panic!("CounterExample should be a sequence."), + CounterExample::Sequence(sequence) => { + // ensure shrinks to same sequence of 77 + assert_eq!(sequence.len(), 77); + } + }; +} + +#[tokio::test(flavor = "multi_thread")] +#[cfg_attr(windows, ignore = "for some reason there's different rng")] +async fn test_shrink_fail_on_revert() { + let mut opts = TEST_DATA_DEFAULT.test_opts.clone(); + opts.fuzz.seed = Some(U256::from(119u32)); + + let filter = + Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options = opts.clone(); + runner.test_options.invariant.fail_on_revert = true; + runner.test_options.invariant.runs = 1; + runner.test_options.invariant.depth = 100; + let results = runner.test_collect(&filter); + let results = + results.values().last().expect("`InvariantShrinkFailOnRevert` should be testable."); + + let result = results + .test_results + .values() + .last() + .expect("`InvariantShrinkFailOnRevert` should be testable."); + + assert_eq!(result.status, TestStatus::Failure); + + let counter = result + .counterexample + .as_ref() + .expect("`InvariantShrinkFailOnRevert` should have failed with a counterexample."); + + match counter { + CounterExample::Single(_) => panic!("CounterExample should be a sequence."), CounterExample::Sequence(sequence) => { - // there some diff across platforms for some reason, either 3 or 2 - assert!(sequence.len() <= 3) + // ensure shrinks to sequence of 10 + assert_eq!(sequence.len(), 10); } }; } + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_preserve_state() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantPreserveState.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + // Should not fail with default options. + runner.test_options.invariant.fail_on_revert = true; + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantPreserveState.t.sol:InvariantPreserveState", + vec![("invariant_preserve_state()", true, None, None, None)], + )]), + ); + + // same test should revert when preserve state enabled + runner.test_options.invariant.fail_on_revert = true; + runner.test_options.invariant.preserve_state = true; + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantPreserveState.t.sol:InvariantPreserveState", + vec![( + "invariant_preserve_state()", + false, + Some("EvmError: Revert".into()), + None, + None, + )], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_with_address_fixture() { + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&Filter::new( + ".*", + ".*", + ".*fuzz/invariant/common/InvariantCalldataDictionary.t.sol", + )); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol:InvariantCalldataDictionary", + vec![( + "invariant_owner_never_changes()", + false, + Some("".into()), + None, + None, + )], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_assume_does_not_revert() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantAssume.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + // Should not treat vm.assume as revert. + runner.test_options.invariant.fail_on_revert = true; + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantAssume.t.sol:InvariantAssume", + vec![("invariant_dummy()", true, None, None, None)], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_assume_respects_restrictions() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantAssume.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.runs = 1; + runner.test_options.invariant.depth = 10; + runner.test_options.invariant.max_assume_rejects = 1; + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantAssume.t.sol:InvariantAssume", + vec![( + "invariant_dummy()", + false, + Some("The `vm.assume` cheatcode rejected too many inputs (1 allowed)".into()), + None, + None, + )], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_decode_custom_error() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantCustomError.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.fail_on_revert = true; + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantCustomError.t.sol:InvariantCustomError", + vec![( + "invariant_decode_error()", + false, + Some("InvariantCustomError(111, \"custom\")".into()), + None, + None, + )], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_fuzzed_selected_targets() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/target/FuzzedTargetContracts.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.fail_on_revert = true; + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([ + ( + "default/fuzz/invariant/target/FuzzedTargetContracts.t.sol:ExplicitTargetContract", + vec![("invariant_explicit_target()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/target/FuzzedTargetContracts.t.sol:DynamicTargetContract", + vec![( + "invariant_dynamic_targets()", + false, + Some("revert: wrong target selector called".into()), + None, + None, + )], + ), + ]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_fixtures() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantFixtures.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.runs = 1; + runner.test_options.invariant.depth = 100; + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantFixtures.t.sol:InvariantFixtures", + vec![( + "invariant_target_not_compromised()", + false, + Some("".into()), + None, + None, + )], + )]), + ); +} diff --git a/crates/forge/tests/it/repros.rs b/crates/forge/tests/it/repros.rs index a472123a615ae..6f95767653857 100644 --- a/crates/forge/tests/it/repros.rs +++ b/crates/forge/tests/it/repros.rs @@ -1,10 +1,20 @@ //! Regression tests for previous issues. -use crate::{config::*, test_helpers::PROJECT}; -use alloy_primitives::{address, Address}; -use ethers_core::abi::{Event, EventParam, Log, LogParam, ParamType, RawLog, Token}; +use std::sync::Arc; + +use crate::{ + config::*, + test_helpers::{ForgeTestData, TEST_DATA_DEFAULT}, +}; +use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt}; +use alloy_json_abi::Event; +use alloy_primitives::{address, Address, U256}; use forge::result::TestStatus; use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; +use foundry_evm::{ + constants::HARDHAT_CONSOLE_ADDRESS, + traces::{CallKind, CallTraceDecoder, DecodedCallData, TraceKind}, +}; use foundry_test_utils::Filter; /// Creates a test that runs `testdata/repros/Issue{issue}.t.sol`. @@ -19,7 +29,7 @@ macro_rules! test_repro { paste::paste! { #[tokio::test(flavor = "multi_thread")] async fn [< issue_ $issue_number >]() { - repro_config($issue_number, $should_fail, $sender.into()).await.run().await; + repro_config($issue_number, $should_fail, $sender.into(), &*TEST_DATA_DEFAULT).await.run().await; } } }; @@ -27,23 +37,40 @@ macro_rules! test_repro { paste::paste! { #[tokio::test(flavor = "multi_thread")] async fn [< issue_ $issue_number >]() { - let mut $res = repro_config($issue_number, $should_fail, $sender.into()).await.test().await; + let mut $res = repro_config($issue_number, $should_fail, $sender.into(), &*TEST_DATA_DEFAULT).await.test(); $e } } }; + ($issue_number:literal; |$config:ident| $e:expr $(,)?) => { + paste::paste! { + #[tokio::test(flavor = "multi_thread")] + async fn [< issue_ $issue_number >]() { + let mut $config = repro_config($issue_number, false, None, &*TEST_DATA_DEFAULT).await; + $e + $config.run().await; + } + } + }; } -async fn repro_config(issue: usize, should_fail: bool, sender: Option
) -> TestConfig { +async fn repro_config( + issue: usize, + should_fail: bool, + sender: Option
, + test_data: &ForgeTestData, +) -> TestConfig { + foundry_test_utils::init_tracing(); let filter = Filter::path(&format!(".*repros/Issue{issue}.t.sol")); - let mut config = Config::with_root(PROJECT.root()); - config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); + let mut config = test_data.config.clone(); + config.fs_permissions = + FsPermissions::new(vec![PathPermission::read("./fixtures"), PathPermission::read("out")]); if let Some(sender) = sender { config.sender = sender; } - let runner = runner_with_config(config).await; + let runner = TEST_DATA_DEFAULT.runner_with_config(config); TestConfig::with_filter(runner, filter).set_should_fail(should_fail) } @@ -97,26 +124,18 @@ test_repro!(3223, false, address!("F0959944122fb1ed4CfaBA645eA06EED30427BAA")); // https://github.com/foundry-rs/foundry/issues/3347 test_repro!(3347, false, None, |res| { - let mut res = res.remove("repros/Issue3347.t.sol:Issue3347Test").unwrap(); + let mut res = res.remove("default/repros/Issue3347.t.sol:Issue3347Test").unwrap(); let test = res.test_results.remove("test()").unwrap(); assert_eq!(test.logs.len(), 1); - let event = Event { - name: "log2".to_string(), - inputs: vec![ - EventParam { name: "x".to_string(), kind: ParamType::Uint(256), indexed: false }, - EventParam { name: "y".to_string(), kind: ParamType::Uint(256), indexed: false }, - ], - anonymous: false, - }; - let raw_log = - RawLog { topics: test.logs[0].topics.clone(), data: test.logs[0].data.clone().to_vec() }; - let log = event.parse_log(raw_log).unwrap(); + let event = Event::parse("event log2(uint256, uint256)").unwrap(); + let decoded = event.decode_log(&test.logs[0].data, false).unwrap(); assert_eq!( - log, - Log { - params: vec![ - LogParam { name: "x".to_string(), value: Token::Uint(1u64.into()) }, - LogParam { name: "y".to_string(), value: Token::Uint(2u64.into()) } + decoded, + DecodedEvent { + indexed: vec![], + body: vec![ + DynSolValue::Uint(U256::from(1), 256), + DynSolValue::Uint(U256::from(2), 256) ] } ); @@ -157,6 +176,9 @@ test_repro!(3753); // https://github.com/foundry-rs/foundry/issues/3792 test_repro!(3792); +// https://github.com/foundry-rs/foundry/issues/4402 +test_repro!(4402); + // https://github.com/foundry-rs/foundry/issues/4586 test_repro!(4586); @@ -176,6 +198,9 @@ test_repro!(5038); // https://github.com/foundry-rs/foundry/issues/5808 test_repro!(5808); +// +test_repro!(5929); + // test_repro!(5935); @@ -185,6 +210,9 @@ test_repro!(5948); // https://github.com/foundry-rs/foundry/issues/6006 test_repro!(6006); +// https://github.com/foundry-rs/foundry/issues/6032 +test_repro!(6032); + // https://github.com/foundry-rs/foundry/issues/6070 test_repro!(6070); @@ -193,18 +221,21 @@ test_repro!(6115); // https://github.com/foundry-rs/foundry/issues/6170 test_repro!(6170, false, None, |res| { - let mut res = res.remove("repros/Issue6170.t.sol:Issue6170Test").unwrap(); + let mut res = res.remove("default/repros/Issue6170.t.sol:Issue6170Test").unwrap(); let test = res.test_results.remove("test()").unwrap(); assert_eq!(test.status, TestStatus::Failure); assert_eq!(test.reason, Some("log != expected log".to_string())); }); +// +test_repro!(6293); + // https://github.com/foundry-rs/foundry/issues/6180 test_repro!(6180); // https://github.com/foundry-rs/foundry/issues/6355 test_repro!(6355, false, None, |res| { - let mut res = res.remove("repros/Issue6355.t.sol:Issue6355Test").unwrap(); + let mut res = res.remove("default/repros/Issue6355.t.sol:Issue6355Test").unwrap(); let test = res.test_results.remove("test_shouldFail()").unwrap(); assert_eq!(test.status, TestStatus::Failure); @@ -214,3 +245,84 @@ test_repro!(6355, false, None, |res| { // https://github.com/foundry-rs/foundry/issues/6437 test_repro!(6437); + +// Test we decode Hardhat console logs AND traces correctly. +// https://github.com/foundry-rs/foundry/issues/6501 +test_repro!(6501, false, None, |res| { + let mut res = res.remove("default/repros/Issue6501.t.sol:Issue6501Test").unwrap(); + let test = res.test_results.remove("test_hhLogs()").unwrap(); + assert_eq!(test.status, TestStatus::Success); + assert_eq!(test.decoded_logs, ["a".to_string(), "1".to_string(), "b 2".to_string()]); + + let (kind, traces) = test.traces[1].clone(); + let nodes = traces.into_nodes(); + assert_eq!(kind, TraceKind::Execution); + + let test_call = nodes.first().unwrap(); + assert_eq!(test_call.idx, 0); + assert_eq!(test_call.children, [1, 2, 3]); + assert_eq!(test_call.trace.depth, 0); + assert!(test_call.trace.success); + + let expected = [ + ("log(string)", vec!["\"a\""]), + ("log(uint256)", vec!["1"]), + ("log(string,uint256)", vec!["\"b\"", "2"]), + ]; + for (node, expected) in nodes[1..=3].iter().zip(expected) { + let trace = &node.trace; + let decoded = CallTraceDecoder::new().decode_function(trace).await; + assert_eq!(trace.kind, CallKind::StaticCall); + assert_eq!(trace.address, HARDHAT_CONSOLE_ADDRESS); + assert_eq!(decoded.label, Some("console".into())); + assert_eq!(trace.depth, 1); + assert!(trace.success); + assert_eq!( + decoded.func, + Some(DecodedCallData { + signature: expected.0.into(), + args: expected.1.into_iter().map(ToOwned::to_owned).collect(), + }) + ); + } +}); + +// https://github.com/foundry-rs/foundry/issues/6538 +test_repro!(6538); + +// https://github.com/foundry-rs/foundry/issues/6554 +test_repro!(6554; |config| { + let path = config.runner.config.__root.0.join("out/default/Issue6554.t.sol"); + + let mut prj_config = Config::clone(&config.runner.config); + prj_config.fs_permissions.add(PathPermission::read_write(path)); + config.runner.config = Arc::new(prj_config); + +}); + +// https://github.com/foundry-rs/foundry/issues/6759 +test_repro!(6759); + +// https://github.com/foundry-rs/foundry/issues/6966 +test_repro!(6966); + +// https://github.com/foundry-rs/foundry/issues/6616 +test_repro!(6616); + +// https://github.com/foundry-rs/foundry/issues/5529 +test_repro!(5529; |config| { + let mut prj_config = Config::clone(&config.runner.config); + prj_config.always_use_create_2_factory = true; + config.runner.evm_opts.always_use_create_2_factory = true; + config.runner.config = Arc::new(prj_config); +}); + +// https://github.com/foundry-rs/foundry/issues/6634 +test_repro!(6634; |config| { + let mut prj_config = Config::clone(&config.runner.config); + prj_config.always_use_create_2_factory = true; + config.runner.evm_opts.always_use_create_2_factory = true; + config.runner.config = Arc::new(prj_config); +}); + +test_repro!(7481); diff --git a/crates/forge/tests/it/spec.rs b/crates/forge/tests/it/spec.rs index 16ed249833096..db98a15d1af24 100644 --- a/crates/forge/tests/it/spec.rs +++ b/crates/forge/tests/it/spec.rs @@ -1,9 +1,14 @@ -use crate::config::*; +//! Integration tests for EVM specifications. + +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; use foundry_evm::revm::primitives::SpecId; use foundry_test_utils::Filter; #[tokio::test(flavor = "multi_thread")] async fn test_shanghai_compat() { let filter = Filter::new("", "ShanghaiCompat", ".*spec"); - TestConfig::filter(filter).await.evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(TEST_DATA_DEFAULT.runner(), filter) + .evm_spec(SpecId::SHANGHAI) + .run() + .await; } diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index e0bac51b4ea69..27f5749904ffd 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -1,48 +1,275 @@ +//! Test helpers for Forge integration tests. + use alloy_primitives::U256; +use forge::{ + revm::primitives::SpecId, MultiContractRunner, MultiContractRunnerBuilder, TestOptions, + TestOptionsBuilder, +}; use foundry_compilers::{ artifacts::{Libraries, Settings}, - Project, ProjectCompileOutput, ProjectPathsConfig, SolcConfig, + EvmVersion, Project, ProjectCompileOutput, SolcConfig, +}; +use foundry_config::{ + fs_permissions::PathPermission, Config, FsPermissions, FuzzConfig, FuzzDictionaryConfig, + InvariantConfig, RpcEndpoint, RpcEndpoints, }; -use foundry_config::Config; use foundry_evm::{ constants::CALLER, - executors::{Executor, FuzzedExecutor}, opts::{Env, EvmOpts}, - revm::db::DatabaseRef, }; -use foundry_test_utils::fd_lock; +use foundry_test_utils::{fd_lock, init_tracing}; use once_cell::sync::Lazy; -use std::{env, io::Write}; +use std::{ + env, fmt, + io::Write, + path::{Path, PathBuf}, + sync::Arc, +}; pub const RE_PATH_SEPARATOR: &str = "/"; - const TESTDATA: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata"); -pub static PROJECT: Lazy = Lazy::new(|| { - let paths = ProjectPathsConfig::builder().root(TESTDATA).sources(TESTDATA).build().unwrap(); +/// Profile for the tests group. Used to configure separate configurations for test runs. +pub enum ForgeTestProfile { + Default, + Cancun, + MultiVersion, +} + +impl fmt::Display for ForgeTestProfile { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ForgeTestProfile::Default => write!(f, "default"), + ForgeTestProfile::Cancun => write!(f, "cancun"), + ForgeTestProfile::MultiVersion => write!(f, "multi-version"), + } + } +} + +impl ForgeTestProfile { + /// Returns true if the profile is Cancun. + pub fn is_cancun(&self) -> bool { + matches!(self, Self::Cancun) + } + + pub fn root(&self) -> PathBuf { + PathBuf::from(TESTDATA) + } + + /// Configures the solc settings for the test profile. + pub fn solc_config(&self) -> SolcConfig { + let libs = + ["fork/Fork.t.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4".to_string()]; - let libs = - ["fork/Fork.t.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4".to_string()]; - let settings = Settings { libraries: Libraries::parse(&libs).unwrap(), ..Default::default() }; - let solc_config = SolcConfig::builder().settings(settings).build(); + let mut settings = + Settings { libraries: Libraries::parse(&libs).unwrap(), ..Default::default() }; - Project::builder().paths(paths).solc_config(solc_config).build().unwrap() -}); + if matches!(self, Self::Cancun) { + settings.evm_version = Some(EvmVersion::Cancun); + } -pub static COMPILED: Lazy = Lazy::new(|| { - const LOCK: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata/.lock"); + SolcConfig::builder().settings(settings).build() + } - let project = &*PROJECT; - assert!(project.cached); + pub fn project(&self) -> Project { + self.config().project().expect("Failed to build project") + } + + pub fn test_opts(&self, output: &ProjectCompileOutput) -> TestOptions { + TestOptionsBuilder::default() + .fuzz(FuzzConfig { + runs: 256, + max_test_rejects: 65536, + seed: None, + dictionary: FuzzDictionaryConfig { + include_storage: true, + include_push_bytes: true, + dictionary_weight: 40, + max_fuzz_dictionary_addresses: 10_000, + max_fuzz_dictionary_values: 10_000, + }, + gas_report_samples: 256, + failure_persist_dir: Some(tempfile::tempdir().unwrap().into_path()), + failure_persist_file: Some("testfailure".to_string()), + }) + .invariant(InvariantConfig { + runs: 256, + depth: 15, + fail_on_revert: false, + call_override: false, + dictionary: FuzzDictionaryConfig { + dictionary_weight: 80, + include_storage: true, + include_push_bytes: true, + max_fuzz_dictionary_addresses: 10_000, + max_fuzz_dictionary_values: 10_000, + }, + shrink_sequence: true, + shrink_run_limit: 2usize.pow(18u32), + preserve_state: false, + max_assume_rejects: 65536, + gas_report_samples: 256, + }) + .build(output, Path::new(self.project().root())) + .expect("Config loaded") + } + + pub fn evm_opts(&self) -> EvmOpts { + EvmOpts { + env: Env { + gas_limit: u64::MAX, + chain_id: None, + tx_origin: CALLER, + block_number: 1, + block_timestamp: 1, + ..Default::default() + }, + sender: CALLER, + initial_balance: U256::MAX, + ffi: true, + verbosity: 3, + memory_limit: 1 << 26, + ..Default::default() + } + } + + /// Build [Config] for test profile. + /// + /// Project source files are read from testdata/{profile_name} + /// Project output files are written to testdata/out/{profile_name} + /// Cache is written to testdata/cache/{profile_name} + /// + /// AST output is enabled by default to support inline configs. + pub fn config(&self) -> Config { + let mut config = Config::with_root(self.root()); + + config.ast = true; + config.src = self.root().join(self.to_string()); + config.out = self.root().join("out").join(self.to_string()); + config.cache_path = self.root().join("cache").join(self.to_string()); + config.libraries = vec![ + "fork/Fork.t.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4".to_string(), + ]; + + if self.is_cancun() { + config.evm_version = EvmVersion::Cancun; + } + + config + } +} + +/// Container for test data for a specific test profile. +pub struct ForgeTestData { + pub project: Project, + pub output: ProjectCompileOutput, + pub test_opts: TestOptions, + pub evm_opts: EvmOpts, + pub config: Config, + pub profile: ForgeTestProfile, +} + +impl ForgeTestData { + /// Builds [ForgeTestData] for the given [ForgeTestProfile]. + /// + /// Uses [get_compiled] to lazily compile the project. + pub fn new(profile: ForgeTestProfile) -> Self { + let project = profile.project(); + let output = get_compiled(&project); + let test_opts = profile.test_opts(&output); + let config = profile.config(); + let evm_opts = profile.evm_opts(); + + Self { project, output, test_opts, evm_opts, config, profile } + } + /// Builds a base runner + pub fn base_runner(&self) -> MultiContractRunnerBuilder { + init_tracing(); + let mut runner = MultiContractRunnerBuilder::new(Arc::new(self.config.clone())) + .sender(self.evm_opts.sender) + .with_test_options(self.test_opts.clone()); + if self.profile.is_cancun() { + runner = runner.evm_spec(SpecId::CANCUN); + } + + runner + } + + /// Builds a non-tracing runner + pub fn runner(&self) -> MultiContractRunner { + let mut config = self.config.clone(); + config.fs_permissions = + FsPermissions::new(vec![PathPermission::read_write(manifest_root())]); + self.runner_with_config(config) + } + + /// Builds a non-tracing runner + pub fn runner_with_config(&self, mut config: Config) -> MultiContractRunner { + config.rpc_endpoints = rpc_endpoints(); + config.allow_paths.push(manifest_root().to_path_buf()); + + // no prompt testing + config.prompt_timeout = 0; + + let root = self.project.root(); + let mut opts = self.evm_opts.clone(); + + if config.isolate { + opts.isolate = true; + } + + let env = opts.local_evm_env(); + let output = self.output.clone(); + + let sender = config.sender; + + let mut builder = self.base_runner(); + builder.config = Arc::new(config); + builder + .enable_isolation(opts.isolate) + .sender(sender) + .with_test_options(self.test_opts.clone()) + .build(root, output, env, opts.clone()) + .unwrap() + } + + /// Builds a tracing runner + pub fn tracing_runner(&self) -> MultiContractRunner { + let mut opts = self.evm_opts.clone(); + opts.verbosity = 5; + self.base_runner() + .build(self.project.root(), self.output.clone(), opts.local_evm_env(), opts) + .unwrap() + } + + /// Builds a runner that runs against forked state + pub async fn forked_runner(&self, rpc: &str) -> MultiContractRunner { + let mut opts = self.evm_opts.clone(); + + opts.env.chain_id = None; // clear chain id so the correct one gets fetched from the RPC + opts.fork_url = Some(rpc.to_string()); + + let env = opts.evm_env().await.expect("Could not instantiate fork environment"); + let fork = opts.get_fork(&Default::default(), env.clone()); + + self.base_runner() + .with_fork(fork) + .build(self.project.root(), self.output.clone(), env, opts) + .unwrap() + } +} + +pub fn get_compiled(project: &Project) -> ProjectCompileOutput { + let lock_file_path = project.sources_path().join(".lock"); // Compile only once per test run. // We need to use a file lock because `cargo-nextest` runs tests in different processes. // This is similar to [`foundry_test_utils::util::initialize`], see its comments for more // details. - let mut lock = fd_lock::new_lock(LOCK); + let mut lock = fd_lock::new_lock(&lock_file_path); let read = lock.read().unwrap(); let out; - if project.cache_path().exists() && std::fs::read(LOCK).unwrap() == b"1" { + if project.cache_path().exists() && std::fs::read(&lock_file_path).unwrap() == b"1" { out = project.compile(); drop(read); } else { @@ -51,39 +278,52 @@ pub static COMPILED: Lazy = Lazy::new(|| { write.write_all(b"1").unwrap(); out = project.compile(); drop(write); - }; + } let out = out.unwrap(); if out.has_compiler_errors() { panic!("Compiled with errors:\n{out}"); } out -}); - -pub static EVM_OPTS: Lazy = Lazy::new(|| EvmOpts { - env: Env { - gas_limit: u64::MAX, - chain_id: None, - tx_origin: Config::DEFAULT_SENDER, - block_number: 1, - block_timestamp: 1, - ..Default::default() - }, - sender: Config::DEFAULT_SENDER, - initial_balance: U256::MAX, - ffi: true, - verbosity: 3, - memory_limit: 1 << 26, - ..Default::default() -}); - -pub fn fuzz_executor(executor: Executor) -> FuzzedExecutor { - let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; - - FuzzedExecutor::new( - executor, - proptest::test_runner::TestRunner::new(cfg), - CALLER, - crate::config::test_opts().fuzz, - ) +} + +/// Default data for the tests group. +pub static TEST_DATA_DEFAULT: Lazy = + Lazy::new(|| ForgeTestData::new(ForgeTestProfile::Default)); + +/// Data for tests requiring Cancun support on Solc and EVM level. +pub static TEST_DATA_CANCUN: Lazy = + Lazy::new(|| ForgeTestData::new(ForgeTestProfile::Cancun)); + +/// Data for tests requiring Cancun support on Solc and EVM level. +pub static TEST_DATA_MULTI_VERSION: Lazy = + Lazy::new(|| ForgeTestData::new(ForgeTestProfile::MultiVersion)); + +pub fn manifest_root() -> &'static Path { + let mut root = Path::new(env!("CARGO_MANIFEST_DIR")); + // need to check here where we're executing the test from, if in `forge` we need to also allow + // `testdata` + if root.ends_with("forge") { + root = root.parent().unwrap(); + } + root +} + +/// the RPC endpoints used during tests +pub fn rpc_endpoints() -> RpcEndpoints { + RpcEndpoints::new([ + ( + "rpcAlias", + RpcEndpoint::Url( + "https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf".to_string(), + ), + ), + ( + "rpcAliasSepolia", + RpcEndpoint::Url( + "https://eth-sepolia.g.alchemy.com/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf".to_string(), + ), + ), + ("rpcEnvAlias", RpcEndpoint::Env("${RPC_ENV_ALIAS}".to_string())), + ]) } diff --git a/crates/linking/Cargo.toml b/crates/linking/Cargo.toml new file mode 100644 index 0000000000000..31edf1c5d1b3f --- /dev/null +++ b/crates/linking/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "foundry-linking" +description = "Smart contract linking tools" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +foundry-compilers = { workspace = true, features = ["full"] } +semver = "1" +alloy-primitives = { workspace = true, features = ["rlp"] } +thiserror = "1" \ No newline at end of file diff --git a/crates/linking/src/lib.rs b/crates/linking/src/lib.rs new file mode 100644 index 0000000000000..869a18e64b34d --- /dev/null +++ b/crates/linking/src/lib.rs @@ -0,0 +1,520 @@ +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +use alloy_primitives::{Address, Bytes}; +use foundry_compilers::{ + artifacts::{CompactContractBytecode, CompactContractBytecodeCow, Libraries}, + contracts::ArtifactContracts, + Artifact, ArtifactId, +}; +use semver::Version; +use std::{ + collections::{BTreeMap, BTreeSet}, + path::{Path, PathBuf}, + str::FromStr, +}; + +/// Errors that can occur during linking. +#[derive(Debug, thiserror::Error)] +pub enum LinkerError { + #[error("wasn't able to find artifact for library {name} at {file}")] + MissingLibraryArtifact { file: String, name: String }, + #[error("target artifact is not present in provided artifacts set")] + MissingTargetArtifact, + #[error(transparent)] + InvalidAddress(
::Err), +} + +pub struct Linker<'a> { + /// Root of the project, used to determine whether artifact/library path can be stripped. + pub root: PathBuf, + /// Compilation artifacts. + pub contracts: ArtifactContracts>, +} + +/// Output of the `link_with_nonce_or_address` +pub struct LinkOutput { + /// Resolved library addresses. Contains both user-provided and newly deployed libraries. + /// It will always contain library paths with stripped path prefixes. + pub libraries: Libraries, + /// Vector of libraries that need to be deployed from sender address. + /// The order in which they appear in the vector is the order in which they should be deployed. + pub libs_to_deploy: Vec, +} + +impl<'a> Linker<'a> { + pub fn new( + root: impl Into, + contracts: ArtifactContracts>, + ) -> Linker<'a> { + Linker { root: root.into(), contracts } + } + + /// Helper method to convert [ArtifactId] to the format in which libraries are stored in + /// [Libraries] object. + /// + /// Strips project root path from source file path. + fn convert_artifact_id_to_lib_path(&self, id: &ArtifactId) -> (PathBuf, String) { + let path = id.source.strip_prefix(self.root.as_path()).unwrap_or(&id.source); + // name is either {LibName} or {LibName}.{version} + let name = id.name.split('.').next().unwrap(); + + (path.to_path_buf(), name.to_owned()) + } + + /// Finds an [ArtifactId] object in the given [ArtifactContracts] keys which corresponds to the + /// library path in the form of "./path/to/Lib.sol:Lib" + /// + /// Optionally accepts solc version, and if present, only compares artifacts with given version. + fn find_artifact_id_by_library_path( + &'a self, + file: &str, + name: &str, + version: Option<&Version>, + ) -> Option<&'a ArtifactId> { + for id in self.contracts.keys() { + if let Some(version) = version { + if id.version != *version { + continue; + } + } + let (artifact_path, artifact_name) = self.convert_artifact_id_to_lib_path(id); + + if artifact_name == *name && artifact_path == Path::new(file) { + return Some(id); + } + } + + None + } + + /// Performs DFS on the graph of link references, and populates `deps` with all found libraries. + fn collect_dependencies( + &'a self, + target: &'a ArtifactId, + deps: &mut BTreeSet<&'a ArtifactId>, + ) -> Result<(), LinkerError> { + let contract = self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?; + + let mut references = BTreeMap::new(); + if let Some(bytecode) = &contract.bytecode { + references.extend(bytecode.link_references.clone()); + } + if let Some(deployed_bytecode) = &contract.deployed_bytecode { + if let Some(bytecode) = &deployed_bytecode.bytecode { + references.extend(bytecode.link_references.clone()); + } + } + + for (file, libs) in &references { + for contract in libs.keys() { + let id = self + .find_artifact_id_by_library_path(file, contract, Some(&target.version)) + .ok_or_else(|| LinkerError::MissingLibraryArtifact { + file: file.to_string(), + name: contract.to_string(), + })?; + if deps.insert(id) { + self.collect_dependencies(id, deps)?; + } + } + } + + Ok(()) + } + + /// Links given artifact with either given library addresses or address computed from sender and + /// nonce. + /// + /// Each key in `libraries` should either be a global path or relative to project root. All + /// remappings should be resolved. + /// + /// When calling for `target` being an external library itself, you should check that `target` + /// does not appear in `libs_to_deploy` to avoid deploying it twice. It may happen in cases + /// when there is a dependency cycle including `target`. + pub fn link_with_nonce_or_address( + &'a self, + libraries: Libraries, + sender: Address, + mut nonce: u64, + target: &'a ArtifactId, + ) -> Result { + // Library paths in `link_references` keys are always stripped, so we have to strip + // user-provided paths to be able to match them correctly. + let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path()); + + let mut needed_libraries = BTreeSet::new(); + self.collect_dependencies(target, &mut needed_libraries)?; + + let mut libs_to_deploy = Vec::new(); + + // If `libraries` does not contain needed dependency, compute its address and add to + // `libs_to_deploy`. + for id in needed_libraries { + let (lib_path, lib_name) = self.convert_artifact_id_to_lib_path(id); + + libraries.libs.entry(lib_path).or_default().entry(lib_name).or_insert_with(|| { + let address = sender.create(nonce); + libs_to_deploy.push((id, address)); + nonce += 1; + + address.to_checksum(None) + }); + } + + // Link and collect bytecodes for `libs_to_deploy`. + let libs_to_deploy = libs_to_deploy + .into_iter() + .map(|(id, _)| { + Ok(self.link(id, &libraries)?.get_bytecode_bytes().unwrap().into_owned()) + }) + .collect::, LinkerError>>()?; + + Ok(LinkOutput { libraries, libs_to_deploy }) + } + + /// Links given artifact with given libraries. + pub fn link( + &self, + target: &ArtifactId, + libraries: &Libraries, + ) -> Result { + let mut contract = + self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?.clone(); + for (file, libs) in &libraries.libs { + for (name, address) in libs { + let address = Address::from_str(address).map_err(LinkerError::InvalidAddress)?; + if let Some(bytecode) = contract.bytecode.as_mut() { + bytecode.to_mut().link(file.to_string_lossy(), name, address); + } + if let Some(deployed_bytecode) = + contract.deployed_bytecode.as_mut().and_then(|b| b.to_mut().bytecode.as_mut()) + { + deployed_bytecode.link(file.to_string_lossy(), name, address); + } + } + } + + Ok(CompactContractBytecode { + abi: contract.abi.map(|a| a.into_owned()), + bytecode: contract.bytecode.map(|b| b.into_owned()), + deployed_bytecode: contract.deployed_bytecode.map(|b| b.into_owned()), + }) + } + + pub fn get_linked_artifacts( + &self, + libraries: &Libraries, + ) -> Result { + self.contracts.keys().map(|id| Ok((id.clone(), self.link(id, libraries)?))).collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use foundry_compilers::{Project, ProjectCompileOutput, ProjectPathsConfig}; + use std::collections::HashMap; + + struct LinkerTest { + project: Project, + output: ProjectCompileOutput, + dependency_assertions: HashMap>, + } + + impl LinkerTest { + fn new(path: impl Into, strip_prefixes: bool) -> Self { + let path = path.into(); + let paths = ProjectPathsConfig::builder() + .root("../../testdata/linking") + .lib("../../testdata/lib") + .sources(path.clone()) + .tests(path) + .build() + .unwrap(); + + let project = + Project::builder().paths(paths).ephemeral().no_artifacts().build().unwrap(); + + let mut output = project.compile().unwrap(); + + if strip_prefixes { + output = output.with_stripped_file_prefixes(project.root()); + } + + Self { project, output, dependency_assertions: HashMap::new() } + } + + fn assert_dependencies( + mut self, + artifact_id: String, + deps: Vec<(String, Address)>, + ) -> Self { + self.dependency_assertions.insert(artifact_id, deps); + self + } + + fn test_with_sender_and_nonce(self, sender: Address, initial_nonce: u64) { + let linker = Linker::new(self.project.root(), self.output.artifact_ids().collect()); + for id in linker.contracts.keys() { + // If we didn't strip paths, artifacts will have absolute paths. + // That's expected and we want to ensure that only `libraries` object has relative + // paths, artifacts should be kept as is. + let source = id + .source + .strip_prefix(self.project.root()) + .unwrap_or(&id.source) + .to_string_lossy(); + let identifier = format!("{source}:{}", id.name); + + // Skip ds-test as it always has no dependencies etc. (and the path is outside root + // so is not sanitized) + if identifier.contains("DSTest") { + continue; + } + + let LinkOutput { libs_to_deploy, libraries, .. } = linker + .link_with_nonce_or_address(Default::default(), sender, initial_nonce, id) + .expect("Linking failed"); + + let assertions = self + .dependency_assertions + .get(&identifier) + .unwrap_or_else(|| panic!("Unexpected artifact: {identifier}")); + + assert_eq!( + libs_to_deploy.len(), + assertions.len(), + "artifact {identifier} has more/less dependencies than expected ({} vs {}): {:#?}", + libs_to_deploy.len(), + assertions.len(), + libs_to_deploy + ); + + for (dep_identifier, address) in assertions { + let (file, name) = dep_identifier.split_once(':').unwrap(); + if let Some(lib_address) = + libraries.libs.get(&PathBuf::from(file)).and_then(|libs| libs.get(name)) + { + assert_eq!(*lib_address, address.to_string(), "incorrect library address for dependency {dep_identifier} of {identifier}"); + } else { + panic!("Library not found") + } + } + } + } + } + + fn link_test(path: impl Into, test_fn: impl Fn(LinkerTest)) { + let path = path.into(); + test_fn(LinkerTest::new(path.clone(), true)); + test_fn(LinkerTest::new(path, false)); + } + + #[test] + fn link_simple() { + link_test("../../testdata/linking/simple", |linker| { + linker + .assert_dependencies("simple/Simple.t.sol:Lib".to_string(), vec![]) + .assert_dependencies( + "simple/Simple.t.sol:LibraryConsumer".to_string(), + vec![( + "simple/Simple.t.sol:Lib".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), + )], + ) + .assert_dependencies( + "simple/Simple.t.sol:SimpleLibraryLinkingTest".to_string(), + vec![( + "simple/Simple.t.sol:Lib".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), + )], + ) + .test_with_sender_and_nonce(Address::default(), 1); + }); + } + + #[test] + fn link_nested() { + link_test("../../testdata/linking/nested", |linker| { + linker + .assert_dependencies("nested/Nested.t.sol:Lib".to_string(), vec![]) + .assert_dependencies( + "nested/Nested.t.sol:NestedLib".to_string(), + vec![( + "nested/Nested.t.sol:Lib".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), + )], + ) + .assert_dependencies( + "nested/Nested.t.sol:LibraryConsumer".to_string(), + vec![ + // Lib shows up here twice, because the linker sees it twice, but it should + // have the same address and nonce. + ( + "nested/Nested.t.sol:Lib".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") + .unwrap(), + ), + ( + "nested/Nested.t.sol:NestedLib".to_string(), + Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D") + .unwrap(), + ), + ], + ) + .assert_dependencies( + "nested/Nested.t.sol:NestedLibraryLinkingTest".to_string(), + vec![ + ( + "nested/Nested.t.sol:Lib".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") + .unwrap(), + ), + ( + "nested/Nested.t.sol:NestedLib".to_string(), + Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") + .unwrap(), + ), + ], + ) + .test_with_sender_and_nonce(Address::default(), 1); + }); + } + + #[test] + fn link_duplicate() { + link_test("../../testdata/linking/duplicate", |linker| { + linker + .assert_dependencies("duplicate/Duplicate.t.sol:A".to_string(), vec![]) + .assert_dependencies("duplicate/Duplicate.t.sol:B".to_string(), vec![]) + .assert_dependencies( + "duplicate/Duplicate.t.sol:C".to_string(), + vec![( + "duplicate/Duplicate.t.sol:A".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), + )], + ) + .assert_dependencies( + "duplicate/Duplicate.t.sol:D".to_string(), + vec![( + "duplicate/Duplicate.t.sol:B".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), + )], + ) + .assert_dependencies( + "duplicate/Duplicate.t.sol:E".to_string(), + vec![ + ( + "duplicate/Duplicate.t.sol:A".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") + .unwrap(), + ), + ( + "duplicate/Duplicate.t.sol:C".to_string(), + Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") + .unwrap(), + ), + ], + ) + .assert_dependencies( + "duplicate/Duplicate.t.sol:LibraryConsumer".to_string(), + vec![ + ( + "duplicate/Duplicate.t.sol:A".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") + .unwrap(), + ), + ( + "duplicate/Duplicate.t.sol:B".to_string(), + Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") + .unwrap(), + ), + ( + "duplicate/Duplicate.t.sol:C".to_string(), + Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca") + .unwrap(), + ), + ( + "duplicate/Duplicate.t.sol:D".to_string(), + Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782") + .unwrap(), + ), + ( + "duplicate/Duplicate.t.sol:E".to_string(), + Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f") + .unwrap(), + ), + ], + ) + .assert_dependencies( + "duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest".to_string(), + vec![ + ( + "duplicate/Duplicate.t.sol:A".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") + .unwrap(), + ), + ( + "duplicate/Duplicate.t.sol:B".to_string(), + Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") + .unwrap(), + ), + ( + "duplicate/Duplicate.t.sol:C".to_string(), + Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca") + .unwrap(), + ), + ( + "duplicate/Duplicate.t.sol:D".to_string(), + Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782") + .unwrap(), + ), + ( + "duplicate/Duplicate.t.sol:E".to_string(), + Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f") + .unwrap(), + ), + ], + ) + .test_with_sender_and_nonce(Address::default(), 1); + }); + } + + #[test] + fn link_cycle() { + link_test("../../testdata/linking/cycle", |linker| { + linker + .assert_dependencies( + "cycle/Cycle.t.sol:Foo".to_string(), + vec![ + ( + "cycle/Cycle.t.sol:Foo".to_string(), + Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D") + .unwrap(), + ), + ( + "cycle/Cycle.t.sol:Bar".to_string(), + Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3") + .unwrap(), + ), + ], + ) + .assert_dependencies( + "cycle/Cycle.t.sol:Bar".to_string(), + vec![ + ( + "cycle/Cycle.t.sol:Foo".to_string(), + Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D") + .unwrap(), + ), + ( + "cycle/Cycle.t.sol:Bar".to_string(), + Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3") + .unwrap(), + ), + ], + ) + .test_with_sender_and_nonce(Address::default(), 1); + }); + } +} diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml new file mode 100644 index 0000000000000..3d41ac7954786 --- /dev/null +++ b/crates/script/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "forge-script" +description = "Solidity scripting" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +forge-verify.workspace = true +foundry-cli.workspace = true +foundry-config.workspace = true +foundry-common.workspace = true +foundry-evm.workspace = true +foundry-debugger.workspace = true +foundry-cheatcodes.workspace = true +foundry-wallets.workspace = true +foundry-linking.workspace = true + +hex.workspace = true +serde.workspace = true +eyre.workspace = true +serde_json.workspace = true +dunce = "1" +foundry-compilers = { workspace = true, features = ["full"] } +tracing.workspace = true +clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } +semver = "1" +futures = "0.3" +async-recursion = "1.0.5" + +itertools.workspace = true +parking_lot = "0.12" +yansi.workspace = true +revm-inspectors.workspace = true +alloy-rpc-types.workspace = true +alloy-json-abi.workspace = true +dialoguer = { version = "0.11", default-features = false } +indicatif = "0.17" + +alloy-signer.workspace = true +alloy-network.workspace = true +alloy-provider.workspace = true +alloy-chains.workspace = true +alloy-dyn-abi.workspace = true +alloy-primitives.workspace = true +alloy-eips.workspace = true +alloy-transport.workspace = true + +[dev-dependencies] +tempfile.workspace = true diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs new file mode 100644 index 0000000000000..a278e44d75917 --- /dev/null +++ b/crates/script/src/broadcast.rs @@ -0,0 +1,417 @@ +use super::receipts; +use crate::{ + build::LinkedBuildData, sequence::ScriptSequenceKind, verify::BroadcastedState, ScriptArgs, + ScriptConfig, +}; +use alloy_chains::Chain; +use alloy_eips::eip2718::Encodable2718; +use alloy_network::{AnyNetwork, EthereumSigner, TransactionBuilder}; +use alloy_primitives::{utils::format_units, Address, TxHash}; +use alloy_provider::{utils::Eip1559Estimation, Provider}; +use alloy_rpc_types::{BlockId, TransactionRequest, WithOtherFields}; +use alloy_transport::Transport; +use eyre::{bail, Context, Result}; +use forge_verify::provider::VerificationProviderType; +use foundry_cheatcodes::ScriptWallets; +use foundry_cli::{ + init_progress, update_progress, + utils::{has_batch_support, has_different_gas_calc}, +}; +use foundry_common::{ + provider::{get_http_provider, try_get_http_provider, RetryProvider}, + shell, +}; +use foundry_config::Config; +use futures::{future::join_all, StreamExt}; +use itertools::Itertools; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; + +pub async fn estimate_gas( + tx: &mut WithOtherFields, + provider: &P, + estimate_multiplier: u64, +) -> Result<()> +where + P: Provider, + T: Transport + Clone, +{ + // if already set, some RPC endpoints might simply return the gas value that is already + // set in the request and omit the estimate altogether, so we remove it here + tx.gas = None; + + tx.set_gas_limit( + provider + .estimate_gas(tx, BlockId::latest()) + .await + .wrap_err("Failed to estimate gas for tx")? * + estimate_multiplier as u128 / + 100, + ); + Ok(()) +} + +pub async fn next_nonce(caller: Address, provider_url: &str) -> eyre::Result { + let provider = try_get_http_provider(provider_url) + .wrap_err_with(|| format!("bad fork_url provider: {provider_url}"))?; + Ok(provider.get_transaction_count(caller, BlockId::latest()).await?) +} + +pub async fn send_transaction( + provider: Arc, + mut tx: WithOtherFields, + kind: SendTransactionKind<'_>, + sequential_broadcast: bool, + is_fixed_gas_limit: bool, + estimate_via_rpc: bool, + estimate_multiplier: u64, +) -> Result { + let from = tx.from.expect("no sender"); + + if sequential_broadcast { + let nonce = provider.get_transaction_count(from, BlockId::latest()).await?; + + let tx_nonce = tx.nonce.expect("no nonce"); + if nonce != tx_nonce { + bail!("EOA nonce changed unexpectedly while sending transactions. Expected {tx_nonce} got {nonce} from provider.") + } + } + + // Chains which use `eth_estimateGas` are being sent sequentially and require their + // gas to be re-estimated right before broadcasting. + if !is_fixed_gas_limit && estimate_via_rpc { + estimate_gas(&mut tx, &provider, estimate_multiplier).await?; + } + + let pending = match kind { + SendTransactionKind::Unlocked(addr) => { + debug!("sending transaction from unlocked account {:?}: {:?}", addr, tx); + + // Submit the transaction + provider.send_transaction(tx).await? + } + SendTransactionKind::Raw(signer) => { + debug!("sending transaction: {:?}", tx); + + let signed = tx.build(signer).await?; + + // Submit the raw transaction + provider.send_raw_transaction(signed.encoded_2718().as_ref()).await? + } + }; + + Ok(*pending.tx_hash()) +} + +/// How to send a single transaction +#[derive(Clone)] +pub enum SendTransactionKind<'a> { + Unlocked(Address), + Raw(&'a EthereumSigner), +} + +/// Represents how to send _all_ transactions +pub enum SendTransactionsKind { + /// Send via `eth_sendTransaction` and rely on the `from` address being unlocked. + Unlocked(HashSet
), + /// Send a signed transaction via `eth_sendRawTransaction` + Raw(HashMap), +} + +impl SendTransactionsKind { + /// Returns the [`SendTransactionKind`] for the given address + /// + /// Returns an error if no matching signer is found or the address is not unlocked + pub fn for_sender(&self, addr: &Address) -> Result> { + match self { + SendTransactionsKind::Unlocked(unlocked) => { + if !unlocked.contains(addr) { + bail!("Sender address {:?} is not unlocked", addr) + } + Ok(SendTransactionKind::Unlocked(*addr)) + } + SendTransactionsKind::Raw(wallets) => { + if let Some(wallet) = wallets.get(addr) { + Ok(SendTransactionKind::Raw(wallet)) + } else { + bail!("No matching signer for {:?} found", addr) + } + } + } + } + + /// How many signers are set + pub fn signers_count(&self) -> usize { + match self { + SendTransactionsKind::Unlocked(addr) => addr.len(), + SendTransactionsKind::Raw(signers) => signers.len(), + } + } +} + +/// State after we have bundled all [TransactionWithMetadata] objects into a single +/// [ScriptSequenceKind] object containing one or more script sequences. +pub struct BundledState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: ScriptWallets, + pub build_data: LinkedBuildData, + pub sequence: ScriptSequenceKind, +} + +impl BundledState { + pub async fn wait_for_pending(mut self) -> Result { + let futs = self + .sequence + .sequences_mut() + .iter_mut() + .map(|sequence| async move { + let rpc_url = sequence.rpc_url(); + let provider = Arc::new(get_http_provider(rpc_url)); + receipts::wait_for_pending(provider, sequence).await + }) + .collect::>(); + + let errors = join_all(futs).await.into_iter().filter_map(Result::err).collect::>(); + + self.sequence.save(true, false)?; + + if !errors.is_empty() { + return Err(eyre::eyre!("{}", errors.iter().format("\n"))); + } + + Ok(self) + } + + /// Broadcasts transactions from all sequences. + pub async fn broadcast(mut self) -> Result { + let required_addresses = self + .sequence + .sequences() + .iter() + .flat_map(|sequence| { + sequence + .transactions() + .map(|tx| (tx.from().expect("No sender for onchain transaction!"))) + }) + .collect::>(); + + if required_addresses.contains(&Config::DEFAULT_SENDER) { + eyre::bail!( + "You seem to be using Foundry's default sender. Be sure to set your own --sender." + ); + } + + let send_kind = if self.args.unlocked { + SendTransactionsKind::Unlocked(required_addresses) + } else { + let signers = self.script_wallets.into_multi_wallet().into_signers()?; + let mut missing_addresses = Vec::new(); + + for addr in &required_addresses { + if !signers.contains_key(addr) { + missing_addresses.push(addr); + } + } + + if !missing_addresses.is_empty() { + eyre::bail!( + "No associated wallet for addresses: {:?}. Unlocked wallets: {:?}", + missing_addresses, + signers.keys().collect::>() + ); + } + + let signers = signers + .into_iter() + .map(|(addr, signer)| (addr, EthereumSigner::new(signer))) + .collect(); + + SendTransactionsKind::Raw(signers) + }; + + for i in 0..self.sequence.sequences().len() { + let mut sequence = self.sequence.sequences_mut().get_mut(i).unwrap(); + + let provider = Arc::new(try_get_http_provider(sequence.rpc_url())?); + let already_broadcasted = sequence.receipts.len(); + + if already_broadcasted < sequence.transactions.len() { + let is_legacy = Chain::from(sequence.chain).is_legacy() || self.args.legacy; + // Make a one-time gas price estimation + let (gas_price, eip1559_fees) = match ( + is_legacy, + self.args.with_gas_price, + self.args.priority_gas_price, + ) { + (true, Some(gas_price), _) => (Some(gas_price.to()), None), + (true, None, _) => (Some(provider.get_gas_price().await?), None), + (false, Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => ( + None, + Some(Eip1559Estimation { + max_fee_per_gas: max_fee_per_gas.to(), + max_priority_fee_per_gas: max_priority_fee_per_gas.to(), + }), + ), + (false, _, _) => { + let mut fees = provider.estimate_eip1559_fees(None).await.wrap_err("Failed to estimate EIP1559 fees. This chain might not support EIP1559, try adding --legacy to your command.")?; + + if let Some(gas_price) = self.args.with_gas_price { + fees.max_fee_per_gas = gas_price.to(); + } + + if let Some(priority_gas_price) = self.args.priority_gas_price { + fees.max_priority_fee_per_gas = priority_gas_price.to(); + } + + (None, Some(fees)) + } + }; + + // Iterate through transactions, matching the `from` field with the associated + // wallet. Then send the transaction. Panics if we find a unknown `from` + let transactions = sequence + .transactions + .iter() + .skip(already_broadcasted) + .map(|tx_with_metadata| { + let tx = tx_with_metadata.tx(); + let from = tx.from().expect("No sender for onchain transaction!"); + + let kind = send_kind.for_sender(&from)?; + let is_fixed_gas_limit = tx_with_metadata.is_fixed_gas_limit; + + let mut tx = tx.clone(); + tx.set_chain_id(sequence.chain); + + // Set TxKind::Create explicityly to satify `check_reqd_fields` in alloy + if tx.to().is_none() { + tx.set_create(); + } + + if let Some(gas_price) = gas_price { + tx.set_gas_price(gas_price); + } else { + let eip1559_fees = eip1559_fees.expect("was set above"); + tx.set_max_priority_fee_per_gas(eip1559_fees.max_priority_fee_per_gas); + tx.set_max_fee_per_gas(eip1559_fees.max_fee_per_gas); + } + + Ok((tx, kind, is_fixed_gas_limit)) + }) + .collect::>>()?; + + let estimate_via_rpc = + has_different_gas_calc(sequence.chain) || self.args.skip_simulation; + + // We only wait for a transaction receipt before sending the next transaction, if + // there is more than one signer. There would be no way of assuring + // their order otherwise. + // Or if the chain does not support batched transactions (eg. Arbitrum). + // Or if we need to invoke eth_estimateGas before sending transactions. + let sequential_broadcast = estimate_via_rpc || + self.args.slow || + send_kind.signers_count() != 1 || + !has_batch_support(sequence.chain); + + let pb = init_progress!(transactions, "txes"); + + // We send transactions and wait for receipts in batches. + let batch_size = if sequential_broadcast { 1 } else { self.args.batch_size }; + let mut index = already_broadcasted; + + for (batch_number, batch) in transactions.chunks(batch_size).enumerate() { + let mut pending_transactions = vec![]; + + shell::println(format!( + "##\nSending transactions [{} - {}].", + batch_number * batch_size, + batch_number * batch_size + std::cmp::min(batch_size, batch.len()) - 1 + ))?; + for (tx, kind, is_fixed_gas_limit) in batch { + let fut = send_transaction( + provider.clone(), + tx.clone(), + kind.clone(), + sequential_broadcast, + *is_fixed_gas_limit, + estimate_via_rpc, + self.args.gas_estimate_multiplier, + ); + pending_transactions.push(fut); + } + + if !pending_transactions.is_empty() { + let mut buffer = futures::stream::iter(pending_transactions).buffered(7); + + while let Some(tx_hash) = buffer.next().await { + let tx_hash = tx_hash.wrap_err("Failed to send transaction")?; + sequence.add_pending(index, tx_hash); + + // Checkpoint save + self.sequence.save(true, false)?; + sequence = self.sequence.sequences_mut().get_mut(i).unwrap(); + + update_progress!(pb, index - already_broadcasted); + index += 1; + } + + // Checkpoint save + self.sequence.save(true, false)?; + sequence = self.sequence.sequences_mut().get_mut(i).unwrap(); + + shell::println("##\nWaiting for receipts.")?; + receipts::clear_pendings(provider.clone(), sequence, None).await?; + } + // Checkpoint save + self.sequence.save(true, false)?; + sequence = self.sequence.sequences_mut().get_mut(i).unwrap(); + } + } + + shell::println("\n\n==========================")?; + shell::println("\nONCHAIN EXECUTION COMPLETE & SUCCESSFUL.")?; + + let (total_gas, total_gas_price, total_paid) = + sequence.receipts.iter().fold((0, 0, 0), |acc, receipt| { + let gas_used = receipt.gas_used; + let gas_price = receipt.effective_gas_price; + (acc.0 + gas_used, acc.1 + gas_price, acc.2 + gas_used * gas_price) + }); + let paid = format_units(total_paid, 18).unwrap_or_else(|_| "N/A".to_string()); + let avg_gas_price = format_units(total_gas_price / sequence.receipts.len() as u128, 9) + .unwrap_or_else(|_| "N/A".to_string()); + + shell::println(format!( + "Total Paid: {} ETH ({} gas * avg {} gwei)", + paid.trim_end_matches('0'), + total_gas, + avg_gas_price.trim_end_matches('0').trim_end_matches('.') + ))?; + } + + Ok(BroadcastedState { + args: self.args, + script_config: self.script_config, + build_data: self.build_data, + sequence: self.sequence, + }) + } + + pub fn verify_preflight_check(&self) -> Result<()> { + for sequence in self.sequence.sequences() { + if self.args.verifier.verifier == VerificationProviderType::Etherscan && + self.script_config + .config + .get_etherscan_api_key(Some(sequence.chain.into())) + .is_none() + { + eyre::bail!("Missing etherscan key for chain {}", sequence.chain); + } + } + + Ok(()) + } +} diff --git a/crates/script/src/build.rs b/crates/script/src/build.rs new file mode 100644 index 0000000000000..06d76af74ab9e --- /dev/null +++ b/crates/script/src/build.rs @@ -0,0 +1,323 @@ +use crate::{ + broadcast::BundledState, + execute::LinkedState, + multi_sequence::MultiChainSequence, + sequence::{ScriptSequence, ScriptSequenceKind}, + ScriptArgs, ScriptConfig, +}; + +use alloy_primitives::{Address, Bytes}; +use alloy_provider::Provider; +use eyre::{OptionExt, Result}; +use foundry_cheatcodes::ScriptWallets; +use foundry_common::{ + compile::{ContractSources, ProjectCompiler}, + provider::try_get_http_provider, + ContractData, ContractsByArtifact, +}; +use foundry_compilers::{ + artifacts::{BytecodeObject, Libraries}, + info::ContractInfo, + utils::source_files_iter, + ArtifactId, ProjectCompileOutput, +}; +use foundry_linking::{LinkOutput, Linker}; +use std::{path::PathBuf, str::FromStr, sync::Arc}; + +/// Container for the compiled contracts. +pub struct BuildData { + /// Root of the project + pub project_root: PathBuf, + /// Linker which can be used to link contracts, owns [ArtifactContracts] map. + pub output: ProjectCompileOutput, + /// Id of target contract artifact. + pub target: ArtifactId, +} + +impl BuildData { + pub fn get_linker(&self) -> Linker { + Linker::new(self.project_root.clone(), self.output.artifact_ids().collect()) + } + + /// Links the build data with given libraries, using sender and nonce to compute addresses of + /// missing libraries. + pub fn link( + self, + known_libraries: Libraries, + sender: Address, + nonce: u64, + ) -> Result { + let link_output = self.get_linker().link_with_nonce_or_address( + known_libraries, + sender, + nonce, + &self.target, + )?; + + LinkedBuildData::new(link_output, self) + } + + /// Links the build data with the given libraries. Expects supplied libraries set being enough + /// to fully link target contract. + pub fn link_with_libraries(self, libraries: Libraries) -> Result { + let link_output = self.get_linker().link_with_nonce_or_address( + libraries, + Address::ZERO, + 0, + &self.target, + )?; + + if !link_output.libs_to_deploy.is_empty() { + eyre::bail!("incomplete libraries set"); + } + + LinkedBuildData::new(link_output, self) + } +} + +/// Container for the linked contracts and their dependencies +pub struct LinkedBuildData { + /// Original build data, might be used to relink this object with different libraries. + pub build_data: BuildData, + /// Known fully linked contracts. + pub known_contracts: ContractsByArtifact, + /// Libraries used to link the contracts. + pub libraries: Libraries, + /// Libraries that need to be deployed by sender before script execution. + pub predeploy_libraries: Vec, + /// Source files of the contracts. Used by debugger. + pub sources: ContractSources, +} + +impl LinkedBuildData { + pub fn new(link_output: LinkOutput, build_data: BuildData) -> Result { + let sources = ContractSources::from_project_output( + &build_data.output, + &build_data.project_root, + &link_output.libraries, + )?; + + let known_contracts = ContractsByArtifact::new( + build_data.get_linker().get_linked_artifacts(&link_output.libraries)?, + ); + + Ok(Self { + build_data, + known_contracts, + libraries: link_output.libraries, + predeploy_libraries: link_output.libs_to_deploy, + sources, + }) + } + + /// Fetches target bytecode from linked contracts. + pub fn get_target_contract(&self) -> Result { + self.known_contracts + .get(&self.build_data.target) + .cloned() + .ok_or_eyre("target not found in linked artifacts") + } +} + +/// First state basically containing only inputs of the user. +pub struct PreprocessedState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: ScriptWallets, +} + +impl PreprocessedState { + /// Parses user input and compiles the contracts depending on script target. + /// After compilation, finds exact [ArtifactId] of the target contract. + pub fn compile(self) -> Result { + let Self { args, script_config, script_wallets } = self; + let project = script_config.config.project()?; + + let mut target_name = args.target_contract.clone(); + + // If we've received correct path, use it as target_path + // Otherwise, parse input as : and use the path from the contract info, if + // present. + let target_path = if let Ok(path) = dunce::canonicalize(&args.path) { + path + } else { + let contract = ContractInfo::from_str(&args.path)?; + target_name = Some(contract.name.clone()); + if let Some(path) = contract.path { + dunce::canonicalize(path)? + } else { + project.find_contract_path(contract.name.as_str())? + } + }; + + let sources_to_compile = + source_files_iter(project.paths.sources.as_path()).chain([target_path.to_path_buf()]); + + let output = ProjectCompiler::new() + .quiet_if(args.opts.silent) + .files(sources_to_compile) + .compile(&project)?; + + let mut target_id: Option = None; + + // Find target artfifact id by name and path in compilation artifacts. + for (id, contract) in output.artifact_ids().filter(|(id, _)| id.source == target_path) { + if let Some(name) = &target_name { + if id.name != *name { + continue; + } + } else if contract.abi.as_ref().map_or(true, |abi| abi.is_empty()) || + contract.bytecode.as_ref().map_or(true, |b| match &b.object { + BytecodeObject::Bytecode(b) => b.is_empty(), + BytecodeObject::Unlinked(_) => false, + }) + { + // Ignore contracts with empty abi or linked bytecode of length 0 which are + // interfaces/abstract contracts/libraries. + continue; + } + + if let Some(target) = target_id { + // We might have multiple artifacts for the same contract but with different + // solc versions. Their names will have form of {name}.0.X.Y, so we are + // stripping versions off before comparing them. + let target_name = target.name.split('.').next().unwrap(); + let id_name = id.name.split('.').next().unwrap(); + if target_name != id_name { + eyre::bail!("Multiple contracts in the target path. Please specify the contract name with `--tc ContractName`") + } + } + target_id = Some(id); + } + + let target = target_id.ok_or_eyre("Could not find target contract")?; + + Ok(CompiledState { + args, + script_config, + script_wallets, + build_data: BuildData { output, target, project_root: project.root().clone() }, + }) + } +} + +/// State after we have determined and compiled target contract to be executed. +pub struct CompiledState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: ScriptWallets, + pub build_data: BuildData, +} + +impl CompiledState { + /// Uses provided sender address to compute library addresses and link contracts with them. + pub fn link(self) -> Result { + let Self { args, script_config, script_wallets, build_data } = self; + + let sender = script_config.evm_opts.sender; + let nonce = script_config.sender_nonce; + let known_libraries = script_config.config.libraries_with_remappings()?; + let build_data = build_data.link(known_libraries, sender, nonce)?; + + Ok(LinkedState { args, script_config, script_wallets, build_data }) + } + + /// Tries loading the resumed state from the cache files, skipping simulation stage. + pub async fn resume(self) -> Result { + let chain = if self.args.multi { + None + } else { + let fork_url = self.script_config.evm_opts.fork_url.clone().ok_or_eyre("Missing --fork-url field, if you were trying to broadcast a multi-chain sequence, please use --multi flag")?; + let provider = Arc::new(try_get_http_provider(fork_url)?); + Some(provider.get_chain_id().await?) + }; + + let sequence = match self.try_load_sequence(chain, false) { + Ok(sequence) => sequence, + Err(_) => { + // If the script was simulated, but there was no attempt to broadcast yet, + // try to read the script sequence from the `dry-run/` folder + let mut sequence = self.try_load_sequence(chain, true)?; + + // If sequence was in /dry-run, Update its paths so it is not saved into /dry-run + // this time as we are about to broadcast it. + sequence.update_paths_to_broadcasted( + &self.script_config.config, + &self.args.sig, + &self.build_data.target, + )?; + + sequence.save(true, true)?; + sequence + } + }; + + let (args, build_data, script_wallets, script_config) = if !self.args.unlocked { + let mut froms = sequence.sequences().iter().flat_map(|s| { + s.transactions + .iter() + .skip(s.receipts.len()) + .map(|t| t.transaction.from.expect("from is missing in script artifact")) + }); + + let available_signers = self + .script_wallets + .signers() + .map_err(|e| eyre::eyre!("Failed to get available signers: {}", e))?; + + if !froms.all(|from| available_signers.contains(&from)) { + // IF we are missing required signers, execute script as we might need to collect + // private keys from the execution. + let executed = self.link()?.prepare_execution().await?.execute().await?; + ( + executed.args, + executed.build_data.build_data, + executed.script_wallets, + executed.script_config, + ) + } else { + (self.args, self.build_data, self.script_wallets, self.script_config) + } + } else { + (self.args, self.build_data, self.script_wallets, self.script_config) + }; + + // Collect libraries from sequence and link contracts with them. + let libraries = match sequence { + ScriptSequenceKind::Single(ref seq) => Libraries::parse(&seq.libraries)?, + // Library linking is not supported for multi-chain sequences + ScriptSequenceKind::Multi(_) => Libraries::default(), + }; + + let linked_build_data = build_data.link_with_libraries(libraries)?; + + Ok(BundledState { + args, + script_config, + script_wallets, + build_data: linked_build_data, + sequence, + }) + } + + fn try_load_sequence(&self, chain: Option, dry_run: bool) -> Result { + if let Some(chain) = chain { + let sequence = ScriptSequence::load( + &self.script_config.config, + &self.args.sig, + &self.build_data.target, + chain, + dry_run, + )?; + Ok(ScriptSequenceKind::Single(sequence)) + } else { + let sequence = MultiChainSequence::load( + &self.script_config.config, + &self.args.sig, + &self.build_data.target, + dry_run, + )?; + Ok(ScriptSequenceKind::Multi(sequence)) + } + } +} diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs new file mode 100644 index 0000000000000..ea307de5bd045 --- /dev/null +++ b/crates/script/src/execute.rs @@ -0,0 +1,518 @@ +use crate::{ + build::{CompiledState, LinkedBuildData}, + simulate::PreSimulationState, + ScriptArgs, ScriptConfig, +}; + +use super::{runner::ScriptRunner, JsonResult, NestedValue, ScriptResult}; +use alloy_dyn_abi::FunctionExt; +use alloy_json_abi::{Function, InternalType, JsonAbi}; +use alloy_primitives::{Address, Bytes}; +use alloy_provider::Provider; +use alloy_rpc_types::request::TransactionRequest; +use async_recursion::async_recursion; +use eyre::{OptionExt, Result}; +use foundry_cheatcodes::ScriptWallets; +use foundry_cli::utils::{ensure_clean_constructor, needs_setup}; +use foundry_common::{ + fmt::{format_token, format_token_raw}, + provider::get_http_provider, + shell, ContractData, ContractsByArtifact, +}; +use foundry_config::{Config, NamedChain}; +use foundry_debugger::Debugger; +use foundry_evm::{ + decode::decode_console_logs, + inspectors::cheatcodes::{BroadcastableTransaction, BroadcastableTransactions}, + traces::{ + identifier::{SignaturesIdentifier, TraceIdentifiers}, + render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, + }, +}; +use futures::future::join_all; +use itertools::Itertools; +use std::collections::{HashMap, HashSet}; +use yansi::Paint; + +/// State after linking, contains the linked build data along with library addresses and optional +/// array of libraries that need to be predeployed. +pub struct LinkedState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: ScriptWallets, + pub build_data: LinkedBuildData, +} + +/// Container for data we need for execution which can only be obtained after linking stage. +pub struct ExecutionData { + /// Function to call. + pub func: Function, + /// Calldata to pass to the target contract. + pub calldata: Bytes, + /// Bytecode of the target contract. + pub bytecode: Bytes, + /// ABI of the target contract. + pub abi: JsonAbi, +} + +impl LinkedState { + /// Given linked and compiled artifacts, prepares data we need for execution. + /// This includes the function to call and the calldata to pass to it. + pub async fn prepare_execution(self) -> Result { + let Self { args, script_config, script_wallets, build_data } = self; + + let ContractData { abi, bytecode, .. } = build_data.get_target_contract()?; + + let bytecode = bytecode.ok_or_eyre("target contract has no bytecode")?; + + let (func, calldata) = args.get_method_and_calldata(&abi)?; + + ensure_clean_constructor(&abi)?; + + Ok(PreExecutionState { + args, + script_config, + script_wallets, + build_data, + execution_data: ExecutionData { func, calldata, bytecode, abi }, + }) + } +} + +/// Same as [LinkedState], but also contains [ExecutionData]. +pub struct PreExecutionState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: ScriptWallets, + pub build_data: LinkedBuildData, + pub execution_data: ExecutionData, +} + +impl PreExecutionState { + /// Executes the script and returns the state after execution. + /// Might require executing script twice in cases when we determine sender from execution. + #[async_recursion] + pub async fn execute(mut self) -> Result { + let mut runner = self + .script_config + .get_runner_with_cheatcodes( + self.build_data.known_contracts.clone(), + self.script_wallets.clone(), + self.args.debug, + self.build_data.build_data.target.clone(), + ) + .await?; + let mut result = self.execute_with_runner(&mut runner).await?; + + // If we have a new sender from execution, we need to use it to deploy libraries and relink + // contracts. + if let Some(new_sender) = self.maybe_new_sender(result.transactions.as_ref())? { + self.script_config.update_sender(new_sender).await?; + + // Rollback to rerun linking with the new sender. + let state = CompiledState { + args: self.args, + script_config: self.script_config, + script_wallets: self.script_wallets, + build_data: self.build_data.build_data, + }; + + return state.link()?.prepare_execution().await?.execute().await; + } + + // Add library deployment transactions to broadcastable transactions list. + if let Some(txs) = result.transactions.take() { + result.transactions = Some( + self.build_data + .predeploy_libraries + .iter() + .enumerate() + .map(|(i, bytes)| BroadcastableTransaction { + rpc: self.script_config.evm_opts.fork_url.clone(), + transaction: TransactionRequest { + from: Some(self.script_config.evm_opts.sender), + input: Some(bytes.clone()).into(), + nonce: Some(self.script_config.sender_nonce + i as u64), + ..Default::default() + }, + }) + .chain(txs) + .collect(), + ); + } + + Ok(ExecutedState { + args: self.args, + script_config: self.script_config, + script_wallets: self.script_wallets, + build_data: self.build_data, + execution_data: self.execution_data, + execution_result: result, + }) + } + + /// Executes the script using the provided runner and returns the [ScriptResult]. + pub async fn execute_with_runner(&self, runner: &mut ScriptRunner) -> Result { + let (address, mut setup_result) = runner.setup( + &self.build_data.predeploy_libraries, + self.execution_data.bytecode.clone(), + needs_setup(&self.execution_data.abi), + self.script_config.sender_nonce, + self.args.broadcast, + self.script_config.evm_opts.fork_url.is_none(), + )?; + + if setup_result.success { + let script_result = runner.script(address, self.execution_data.calldata.clone())?; + + setup_result.success &= script_result.success; + setup_result.gas_used = script_result.gas_used; + setup_result.logs.extend(script_result.logs); + setup_result.traces.extend(script_result.traces); + setup_result.debug = script_result.debug; + setup_result.labeled_addresses.extend(script_result.labeled_addresses); + setup_result.returned = script_result.returned; + setup_result.breakpoints = script_result.breakpoints; + + match (&mut setup_result.transactions, script_result.transactions) { + (Some(txs), Some(new_txs)) => { + txs.extend(new_txs); + } + (None, Some(new_txs)) => { + setup_result.transactions = Some(new_txs); + } + _ => {} + } + } + + Ok(setup_result) + } + + /// It finds the deployer from the running script and uses it to predeploy libraries. + /// + /// If there are multiple candidate addresses, it skips everything and lets `--sender` deploy + /// them instead. + fn maybe_new_sender( + &self, + transactions: Option<&BroadcastableTransactions>, + ) -> Result> { + let mut new_sender = None; + + if let Some(txs) = transactions { + // If the user passed a `--sender` don't check anything. + if !self.build_data.predeploy_libraries.is_empty() && + self.args.evm_opts.sender.is_none() + { + for tx in txs.iter() { + if tx.transaction.to.is_none() { + let sender = tx.transaction.from.expect("no sender"); + if let Some(ns) = new_sender { + if sender != ns { + shell::println("You have more than one deployer who could predeploy libraries. Using `--sender` instead.")?; + return Ok(None); + } + } else if sender != self.script_config.evm_opts.sender { + new_sender = Some(sender); + } + } + } + } + } + Ok(new_sender) + } +} + +/// Container for information about RPC-endpoints used during script execution. +pub struct RpcData { + /// Unique list of rpc urls present. + pub total_rpcs: HashSet, + /// If true, one of the transactions did not have a rpc. + pub missing_rpc: bool, +} + +impl RpcData { + /// Iterates over script transactions and collects RPC urls. + fn from_transactions(txs: &BroadcastableTransactions) -> Self { + let missing_rpc = txs.iter().any(|tx| tx.rpc.is_none()); + let total_rpcs = + txs.iter().filter_map(|tx| tx.rpc.as_ref().cloned()).collect::>(); + + Self { total_rpcs, missing_rpc } + } + + /// Returns true if script might be multi-chain. + /// Returns false positive in case when missing rpc is the same as the only rpc present. + pub fn is_multi_chain(&self) -> bool { + self.total_rpcs.len() > 1 || (self.missing_rpc && !self.total_rpcs.is_empty()) + } + + /// Checks if all RPCs support EIP-3855. Prints a warning if not. + async fn check_shanghai_support(&self) -> Result<()> { + let chain_ids = self.total_rpcs.iter().map(|rpc| async move { + let provider = get_http_provider(rpc); + let id = provider.get_chain_id().await.ok()?; + NamedChain::try_from(id).ok() + }); + + let chains = join_all(chain_ids).await; + let iter = chains.iter().flatten().map(|c| (c.supports_shanghai(), c)); + if iter.clone().any(|(s, _)| !s) { + let msg = format!( + "\ +EIP-3855 is not supported in one or more of the RPCs used. +Unsupported Chain IDs: {}. +Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly. +For more information, please see https://eips.ethereum.org/EIPS/eip-3855", + iter.filter(|(supported, _)| !supported) + .map(|(_, chain)| *chain as u64) + .format(", ") + ); + shell::println(msg.yellow())?; + } + Ok(()) + } +} + +/// Container for data being collected after execution. +pub struct ExecutionArtifacts { + /// Trace decoder used to decode traces. + pub decoder: CallTraceDecoder, + /// Return values from the execution result. + pub returns: HashMap, + /// Information about RPC endpoints used during script execution. + pub rpc_data: RpcData, +} + +/// State after the script has been executed. +pub struct ExecutedState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: ScriptWallets, + pub build_data: LinkedBuildData, + pub execution_data: ExecutionData, + pub execution_result: ScriptResult, +} + +impl ExecutedState { + /// Collects the data we need for simulation and various post-execution tasks. + pub async fn prepare_simulation(self) -> Result { + let returns = self.get_returns()?; + + let decoder = self.build_trace_decoder(&self.build_data.known_contracts)?; + + let txs = self.execution_result.transactions.clone().unwrap_or_default(); + let rpc_data = RpcData::from_transactions(&txs); + + if rpc_data.is_multi_chain() { + shell::eprintln(format!( + "{}", + "Multi chain deployment is still under development. Use with caution.".yellow() + ))?; + if !self.build_data.libraries.is_empty() { + eyre::bail!( + "Multi chain deployment does not support library linking at the moment." + ) + } + } + rpc_data.check_shanghai_support().await?; + + Ok(PreSimulationState { + args: self.args, + script_config: self.script_config, + script_wallets: self.script_wallets, + build_data: self.build_data, + execution_data: self.execution_data, + execution_result: self.execution_result, + execution_artifacts: ExecutionArtifacts { decoder, returns, rpc_data }, + }) + } + + /// Builds [CallTraceDecoder] from the execution result and known contracts. + fn build_trace_decoder( + &self, + known_contracts: &ContractsByArtifact, + ) -> Result { + let mut decoder = CallTraceDecoderBuilder::new() + .with_labels(self.execution_result.labeled_addresses.clone()) + .with_verbosity(self.script_config.evm_opts.verbosity) + .with_known_contracts(known_contracts) + .with_signature_identifier(SignaturesIdentifier::new( + Config::foundry_cache_dir(), + self.script_config.config.offline, + )?) + .build(); + + let mut identifier = TraceIdentifiers::new().with_local(known_contracts).with_etherscan( + &self.script_config.config, + self.script_config.evm_opts.get_remote_chain_id(), + )?; + + // Decoding traces using etherscan is costly as we run into rate limits, + // causing scripts to run for a very long time unnecessarily. + // Therefore, we only try and use etherscan if the user has provided an API key. + let should_use_etherscan_traces = self.script_config.config.etherscan_api_key.is_some(); + if !should_use_etherscan_traces { + identifier.etherscan = None; + } + + for (_, trace) in &self.execution_result.traces { + decoder.identify(trace, &mut identifier); + } + + Ok(decoder) + } + + /// Collects the return values from the execution result. + fn get_returns(&self) -> Result> { + let mut returns = HashMap::new(); + let returned = &self.execution_result.returned; + let func = &self.execution_data.func; + + match func.abi_decode_output(returned, false) { + Ok(decoded) => { + for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() { + let internal_type = + output.internal_type.clone().unwrap_or(InternalType::Other { + contract: None, + ty: "unknown".to_string(), + }); + + let label = if !output.name.is_empty() { + output.name.to_string() + } else { + index.to_string() + }; + + returns.insert( + label, + NestedValue { + internal_type: internal_type.to_string(), + value: format_token_raw(token), + }, + ); + } + } + Err(_) => { + shell::println(format!("{returned:?}"))?; + } + } + + Ok(returns) + } +} + +impl PreSimulationState { + pub fn show_json(&self) -> Result<()> { + let result = &self.execution_result; + + let console_logs = decode_console_logs(&result.logs); + let output = JsonResult { + logs: console_logs, + gas_used: result.gas_used, + returns: self.execution_artifacts.returns.clone(), + }; + let j = serde_json::to_string(&output)?; + shell::println(j)?; + + if !self.execution_result.success { + return Err(eyre::eyre!( + "script failed: {}", + &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None) + )); + } + + Ok(()) + } + + pub async fn show_traces(&self) -> Result<()> { + let verbosity = self.script_config.evm_opts.verbosity; + let func = &self.execution_data.func; + let result = &self.execution_result; + let decoder = &self.execution_artifacts.decoder; + + if !result.success || verbosity > 3 { + if result.traces.is_empty() { + warn!(verbosity, "no traces"); + } + + shell::println("Traces:")?; + for (kind, trace) in &result.traces { + let should_include = match kind { + TraceKind::Setup => verbosity >= 5, + TraceKind::Execution => verbosity > 3, + _ => false, + } || !result.success; + + if should_include { + shell::println(render_trace_arena(trace, decoder).await?)?; + } + } + shell::println(String::new())?; + } + + if result.success { + shell::println(format!("{}", "Script ran successfully.".green()))?; + } + + if self.script_config.evm_opts.fork_url.is_none() { + shell::println(format!("Gas used: {}", result.gas_used))?; + } + + if result.success && !result.returned.is_empty() { + shell::println("\n== Return ==")?; + match func.abi_decode_output(&result.returned, false) { + Ok(decoded) => { + for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() { + let internal_type = + output.internal_type.clone().unwrap_or(InternalType::Other { + contract: None, + ty: "unknown".to_string(), + }); + + let label = if !output.name.is_empty() { + output.name.to_string() + } else { + index.to_string() + }; + shell::println(format!( + "{}: {internal_type} {}", + label.trim_end(), + format_token(token) + ))?; + } + } + Err(_) => { + shell::println(format!("{:x?}", (&result.returned)))?; + } + } + } + + let console_logs = decode_console_logs(&result.logs); + if !console_logs.is_empty() { + shell::println("\n== Logs ==")?; + for log in console_logs { + shell::println(format!(" {log}"))?; + } + } + + if !result.success { + return Err(eyre::eyre!( + "script failed: {}", + &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None) + )); + } + + Ok(()) + } + + pub fn run_debugger(&self) -> Result<()> { + let mut debugger = Debugger::builder() + .debug_arenas(self.execution_result.debug.as_deref().unwrap_or_default()) + .decoder(&self.execution_artifacts.decoder) + .sources(self.build_data.sources.clone()) + .breakpoints(self.execution_result.breakpoints.clone()) + .build(); + debugger.try_run()?; + Ok(()) + } +} diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs new file mode 100644 index 0000000000000..6445b845044c5 --- /dev/null +++ b/crates/script/src/lib.rs @@ -0,0 +1,870 @@ +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +#[macro_use] +extern crate tracing; + +use self::transaction::AdditionalContract; +use crate::runner::ScriptRunner; +use alloy_json_abi::{Function, JsonAbi}; +use alloy_primitives::{Address, Bytes, Log, TxKind, U256}; +use alloy_signer::Signer; +use broadcast::next_nonce; +use build::PreprocessedState; +use clap::{Parser, ValueHint}; +use dialoguer::Confirm; +use eyre::{ContextCompat, Result}; +use forge_verify::RetryArgs; +use foundry_cli::{opts::CoreBuildArgs, utils::LoadConfig}; +use foundry_common::{ + abi::{encode_function_args, get_func}, + compile::SkipBuildFilter, + evm::{Breakpoints, EvmArgs}, + shell, ContractsByArtifact, CONTRACT_MAX_SIZE, SELECTOR_LEN, +}; +use foundry_compilers::ArtifactId; +use foundry_config::{ + figment, + figment::{ + value::{Dict, Map}, + Metadata, Profile, Provider, + }, + Config, +}; +use foundry_evm::{ + backend::Backend, + constants::DEFAULT_CREATE2_DEPLOYER, + debug::DebugArena, + executors::ExecutorBuilder, + inspectors::{ + cheatcodes::{BroadcastableTransactions, ScriptWallets}, + CheatsConfig, + }, + opts::EvmOpts, + traces::Traces, +}; +use foundry_wallets::MultiWalletOpts; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, sync::Arc}; +use yansi::Paint; + +mod broadcast; +mod build; +mod execute; +mod multi_sequence; +mod providers; +mod receipts; +mod runner; +mod sequence; +mod simulate; +mod transaction; +mod verify; + +// Loads project's figment and merges the build cli arguments into it +foundry_config::merge_impl_figment_convert!(ScriptArgs, opts, evm_opts); + +/// CLI arguments for `forge script`. +#[derive(Clone, Debug, Default, Parser)] +pub struct ScriptArgs { + /// The contract you want to run. Either the file path or contract name. + /// + /// If multiple contracts exist in the same file you must specify the target contract with + /// --target-contract. + #[arg(value_hint = ValueHint::FilePath)] + pub path: String, + + /// Arguments to pass to the script function. + pub args: Vec, + + /// The name of the contract you want to run. + #[arg(long, visible_alias = "tc", value_name = "CONTRACT_NAME")] + pub target_contract: Option, + + /// The signature of the function you want to call in the contract, or raw calldata. + #[arg(long, short, default_value = "run()")] + pub sig: String, + + /// Max priority fee per gas for EIP1559 transactions. + #[arg( + long, + env = "ETH_PRIORITY_GAS_PRICE", + value_parser = foundry_cli::utils::parse_ether_value, + value_name = "PRICE" + )] + pub priority_gas_price: Option, + + /// Use legacy transactions instead of EIP1559 ones. + /// + /// This is auto-enabled for common networks without EIP1559. + #[arg(long)] + pub legacy: bool, + + /// Broadcasts the transactions. + #[arg(long)] + pub broadcast: bool, + + /// Batch size of transactions. + /// + /// This is ignored and set to 1 if batching is not available or `--slow` is enabled. + #[arg(long, default_value = "100")] + pub batch_size: usize, + + /// Skips on-chain simulation. + #[arg(long)] + pub skip_simulation: bool, + + /// Relative percentage to multiply gas estimates by. + #[arg(long, short, default_value = "130")] + pub gas_estimate_multiplier: u64, + + /// Send via `eth_sendTransaction` using the `--from` argument or `$ETH_FROM` as sender + #[arg( + long, + conflicts_with_all = &["private_key", "private_keys", "froms", "ledger", "trezor", "aws"], + )] + pub unlocked: bool, + + /// Resumes submitting transactions that failed or timed-out previously. + /// + /// It DOES NOT simulate the script again and it expects nonces to have remained the same. + /// + /// Example: If transaction N has a nonce of 22, then the account should have a nonce of 22, + /// otherwise it fails. + #[arg(long)] + pub resume: bool, + + /// If present, --resume or --verify will be assumed to be a multi chain deployment. + #[arg(long)] + pub multi: bool, + + /// Open the script in the debugger. + /// + /// Takes precedence over broadcast. + #[arg(long)] + pub debug: bool, + + /// Makes sure a transaction is sent, + /// only after its previous one has been confirmed and succeeded. + #[arg(long)] + pub slow: bool, + + /// Disables interactive prompts that might appear when deploying big contracts. + /// + /// For more info on the contract size limit, see EIP-170: + #[arg(long)] + pub non_interactive: bool, + + /// The Etherscan (or equivalent) API key + #[arg(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")] + pub etherscan_api_key: Option, + + /// Verifies all the contracts found in the receipts of a script, if any. + #[arg(long)] + pub verify: bool, + + /// Output results in JSON format. + #[arg(long)] + pub json: bool, + + /// Gas price for legacy transactions, or max fee per gas for EIP1559 transactions. + #[arg( + long, + env = "ETH_GAS_PRICE", + value_parser = foundry_cli::utils::parse_ether_value, + value_name = "PRICE", + )] + pub with_gas_price: Option, + + /// Skip building files whose names contain the given filter. + /// + /// `test` and `script` are aliases for `.t.sol` and `.s.sol`. + #[arg(long, num_args(1..))] + pub skip: Option>, + + #[command(flatten)] + pub opts: CoreBuildArgs, + + #[command(flatten)] + pub wallets: MultiWalletOpts, + + #[command(flatten)] + pub evm_opts: EvmArgs, + + #[command(flatten)] + pub verifier: forge_verify::VerifierArgs, + + #[command(flatten)] + pub retry: RetryArgs, +} + +// === impl ScriptArgs === + +impl ScriptArgs { + async fn preprocess(self) -> Result { + let script_wallets = + ScriptWallets::new(self.wallets.get_multi_wallet().await?, self.evm_opts.sender); + + let (config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; + + if let Some(sender) = self.maybe_load_private_key()? { + evm_opts.sender = sender; + } + + let script_config = ScriptConfig::new(config, evm_opts).await?; + + Ok(PreprocessedState { args: self, script_config, script_wallets }) + } + + /// Executes the script + pub async fn run_script(self) -> Result<()> { + trace!(target: "script", "executing script command"); + + let compiled = self.preprocess().await?.compile()?; + + // Move from `CompiledState` to `BundledState` either by resuming or executing and + // simulating script. + let bundled = if compiled.args.resume || (compiled.args.verify && !compiled.args.broadcast) + { + compiled.resume().await? + } else { + // Drive state machine to point at which we have everything needed for simulation. + let pre_simulation = compiled + .link()? + .prepare_execution() + .await? + .execute() + .await? + .prepare_simulation() + .await?; + + if pre_simulation.args.debug { + pre_simulation.run_debugger()?; + } + + if pre_simulation.args.json { + pre_simulation.show_json()?; + } else { + pre_simulation.show_traces().await?; + } + + // Ensure that we have transactions to simulate/broadcast, otherwise exit early to avoid + // hard error. + if pre_simulation + .execution_result + .transactions + .as_ref() + .map_or(true, |txs| txs.is_empty()) + { + return Ok(()); + } + + // Check if there are any missing RPCs and exit early to avoid hard error. + if pre_simulation.execution_artifacts.rpc_data.missing_rpc { + shell::println("\nIf you wish to simulate on-chain transactions pass a RPC URL.")?; + return Ok(()); + } + + pre_simulation.args.check_contract_sizes( + &pre_simulation.execution_result, + &pre_simulation.build_data.known_contracts, + )?; + + pre_simulation.fill_metadata().await?.bundle().await? + }; + + // Exit early in case user didn't provide any broadcast/verify related flags. + if !bundled.args.broadcast && !bundled.args.resume && !bundled.args.verify { + shell::println("\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.")?; + return Ok(()); + } + + // Exit early if something is wrong with verification options. + if bundled.args.verify { + bundled.verify_preflight_check()?; + } + + // Wait for pending txes and broadcast others. + let broadcasted = bundled.wait_for_pending().await?.broadcast().await?; + + if broadcasted.args.verify { + broadcasted.verify().await?; + } + + Ok(()) + } + + /// In case the user has loaded *only* one private-key, we can assume that he's using it as the + /// `--sender` + fn maybe_load_private_key(&self) -> Result> { + let maybe_sender = self + .wallets + .private_keys()? + .filter(|pks| pks.len() == 1) + .map(|pks| pks.first().unwrap().address()); + Ok(maybe_sender) + } + + /// Returns the Function and calldata based on the signature + /// + /// If the `sig` is a valid human-readable function we find the corresponding function in the + /// `abi` If the `sig` is valid hex, we assume it's calldata and try to find the + /// corresponding function by matching the selector, first 4 bytes in the calldata. + /// + /// Note: We assume that the `sig` is already stripped of its prefix, See [`ScriptArgs`] + fn get_method_and_calldata(&self, abi: &JsonAbi) -> Result<(Function, Bytes)> { + if let Ok(decoded) = hex::decode(&self.sig) { + let selector = &decoded[..SELECTOR_LEN]; + let func = + abi.functions().find(|func| selector == &func.selector()[..]).ok_or_else(|| { + eyre::eyre!( + "Function selector `{}` not found in the ABI", + hex::encode(selector) + ) + })?; + return Ok((func.clone(), decoded.into())); + } + + let func = if self.sig.contains('(') { + let func = get_func(&self.sig)?; + abi.functions() + .find(|&abi_func| abi_func.selector() == func.selector()) + .wrap_err(format!("Function `{}` is not implemented in your script.", self.sig))? + } else { + let matching_functions = + abi.functions().filter(|func| func.name == self.sig).collect::>(); + match matching_functions.len() { + 0 => eyre::bail!("Function `{}` not found in the ABI", self.sig), + 1 => matching_functions[0], + 2.. => eyre::bail!( + "Multiple functions with the same name `{}` found in the ABI", + self.sig + ), + } + }; + let data = encode_function_args(func, &self.args)?; + + Ok((func.clone(), data.into())) + } + + /// Checks if the transaction is a deployment with either a size above the `CONTRACT_MAX_SIZE` + /// or specified `code_size_limit`. + /// + /// If `self.broadcast` is enabled, it asks confirmation of the user. Otherwise, it just warns + /// the user. + fn check_contract_sizes( + &self, + result: &ScriptResult, + known_contracts: &ContractsByArtifact, + ) -> Result<()> { + // (name, &init, &deployed)[] + let mut bytecodes: Vec<(String, &[u8], &[u8])> = vec![]; + + // From artifacts + for (artifact, contract) in known_contracts.iter() { + let Some(bytecode) = &contract.bytecode else { continue }; + let Some(deployed_bytecode) = &contract.deployed_bytecode else { continue }; + bytecodes.push((artifact.name.clone(), bytecode, deployed_bytecode)); + } + + // From traces + let create_nodes = result.traces.iter().flat_map(|(_, traces)| { + traces.nodes().iter().filter(|node| node.trace.kind.is_any_create()) + }); + let mut unknown_c = 0usize; + for node in create_nodes { + let init_code = &node.trace.data; + let deployed_code = &node.trace.output; + if !bytecodes.iter().any(|(_, b, _)| *b == init_code.as_ref()) { + bytecodes.push((format!("Unknown{unknown_c}"), init_code, deployed_code)); + unknown_c += 1; + } + continue; + } + + let mut prompt_user = false; + let max_size = match self.evm_opts.env.code_size_limit { + Some(size) => size, + None => CONTRACT_MAX_SIZE, + }; + + for (data, to) in result.transactions.iter().flat_map(|txes| { + txes.iter().filter_map(|tx| { + tx.transaction + .input + .clone() + .into_input() + .filter(|data| data.len() > max_size) + .map(|data| (data, tx.transaction.to)) + }) + }) { + let mut offset = 0; + + // Find if it's a CREATE or CREATE2. Otherwise, skip transaction. + if let Some(TxKind::Call(to)) = to { + if to == DEFAULT_CREATE2_DEPLOYER { + // Size of the salt prefix. + offset = 32; + } else { + continue; + } + } else if let Some(TxKind::Create) = to { + // Pass + } + + // Find artifact with a deployment code same as the data. + if let Some((name, _, deployed_code)) = + bytecodes.iter().find(|(_, init_code, _)| *init_code == &data[offset..]) + { + let deployment_size = deployed_code.len(); + + if deployment_size > max_size { + prompt_user = self.broadcast; + shell::println(format!( + "{}", + format!( + "`{name}` is above the contract size limit ({deployment_size} > {max_size})." + ).red() + ))?; + } + } + } + + // Only prompt if we're broadcasting and we've not disabled interactivity. + if prompt_user && + !self.non_interactive && + !Confirm::new().with_prompt("Do you wish to continue?".to_string()).interact()? + { + eyre::bail!("User canceled the script."); + } + + Ok(()) + } +} + +impl Provider for ScriptArgs { + fn metadata(&self) -> Metadata { + Metadata::named("Script Args Provider") + } + + fn data(&self) -> Result, figment::Error> { + let mut dict = Dict::default(); + if let Some(ref etherscan_api_key) = + self.etherscan_api_key.as_ref().filter(|s| !s.trim().is_empty()) + { + dict.insert( + "etherscan_api_key".to_string(), + figment::value::Value::from(etherscan_api_key.to_string()), + ); + } + Ok(Map::from([(Config::selected_profile(), dict)])) + } +} + +#[derive(Default)] +pub struct ScriptResult { + pub success: bool, + pub logs: Vec, + pub traces: Traces, + pub debug: Option>, + pub gas_used: u64, + pub labeled_addresses: HashMap, + pub transactions: Option, + pub returned: Bytes, + pub address: Option
, + pub breakpoints: Breakpoints, +} + +impl ScriptResult { + pub fn get_created_contracts(&self) -> Vec { + self.traces + .iter() + .flat_map(|(_, traces)| { + traces.nodes().iter().filter_map(|node| { + if node.trace.kind.is_any_create() { + return Some(AdditionalContract { + opcode: node.trace.kind, + address: node.trace.address, + init_code: node.trace.data.clone(), + }); + } + None + }) + }) + .collect() + } +} + +#[derive(Serialize, Deserialize)] +struct JsonResult { + logs: Vec, + gas_used: u64, + returns: HashMap, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct NestedValue { + pub internal_type: String, + pub value: String, +} + +#[derive(Clone, Debug)] +pub struct ScriptConfig { + pub config: Config, + pub evm_opts: EvmOpts, + pub sender_nonce: u64, + /// Maps a rpc url to a backend + pub backends: HashMap, +} + +impl ScriptConfig { + pub async fn new(config: Config, evm_opts: EvmOpts) -> Result { + let sender_nonce = if let Some(fork_url) = evm_opts.fork_url.as_ref() { + next_nonce(evm_opts.sender, fork_url).await? + } else { + // dapptools compatibility + 1 + }; + Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::new() }) + } + + pub async fn update_sender(&mut self, sender: Address) -> Result<()> { + self.sender_nonce = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() { + next_nonce(sender, fork_url).await? + } else { + // dapptools compatibility + 1 + }; + self.evm_opts.sender = sender; + Ok(()) + } + + async fn get_runner(&mut self) -> Result { + self._get_runner(None, false).await + } + + async fn get_runner_with_cheatcodes( + &mut self, + known_contracts: ContractsByArtifact, + script_wallets: ScriptWallets, + debug: bool, + target: ArtifactId, + ) -> Result { + self._get_runner(Some((known_contracts, script_wallets, target)), debug).await + } + + async fn _get_runner( + &mut self, + cheats_data: Option<(ContractsByArtifact, ScriptWallets, ArtifactId)>, + debug: bool, + ) -> Result { + trace!("preparing script runner"); + let env = self.evm_opts.evm_env().await?; + + let db = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() { + match self.backends.get(fork_url) { + Some(db) => db.clone(), + None => { + let fork = self.evm_opts.get_fork(&self.config, env.clone()); + let backend = Backend::spawn(fork); + self.backends.insert(fork_url.clone(), backend.clone()); + backend + } + } + } else { + // It's only really `None`, when we don't pass any `--fork-url`. And if so, there is + // no need to cache it, since there won't be any onchain simulation that we'd need + // to cache the backend for. + Backend::spawn(None) + }; + + // We need to enable tracing to decode contract names: local or external. + let mut builder = ExecutorBuilder::new() + .inspectors(|stack| stack.trace(true)) + .spec(self.config.evm_spec_id()) + .gas_limit(self.evm_opts.gas_limit()); + + if let Some((known_contracts, script_wallets, target)) = cheats_data { + builder = builder.inspectors(|stack| { + stack + .debug(debug) + .cheatcodes( + CheatsConfig::new( + &self.config, + self.evm_opts.clone(), + Some(Arc::new(known_contracts)), + Some(script_wallets), + Some(target.version), + ) + .into(), + ) + .enable_isolation(self.evm_opts.isolate) + }); + } + + Ok(ScriptRunner::new( + builder.build(env, db), + self.evm_opts.initial_balance, + self.evm_opts.sender, + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use foundry_config::{NamedChain, UnresolvedEnvVarError}; + use std::fs; + use tempfile::tempdir; + + #[test] + fn can_parse_sig() { + let sig = "0x522bb704000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfFFb92266"; + let args = ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--sig", sig]); + assert_eq!(args.sig, sig); + } + + #[test] + fn can_parse_unlocked() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "Contract.sol", + "--sender", + "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "--unlocked", + ]); + assert!(args.unlocked); + + let key = U256::ZERO; + let args = ScriptArgs::try_parse_from([ + "foundry-cli", + "Contract.sol", + "--sender", + "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "--unlocked", + "--private-key", + key.to_string().as_str(), + ]); + assert!(args.is_err()); + } + + #[test] + fn can_merge_script_config() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "Contract.sol", + "--etherscan-api-key", + "goerli", + ]); + let config = args.load_config(); + assert_eq!(config.etherscan_api_key, Some("goerli".to_string())); + } + + #[test] + fn can_parse_verifier_url() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "script", + "script/Test.s.sol:TestScript", + "--fork-url", + "http://localhost:8545", + "--verifier-url", + "http://localhost:3000/api/verify", + "--etherscan-api-key", + "blacksmith", + "--broadcast", + "--verify", + "-vvvvv", + ]); + assert_eq!( + args.verifier.verifier_url, + Some("http://localhost:3000/api/verify".to_string()) + ); + } + + #[test] + fn can_extract_code_size_limit() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "script", + "script/Test.s.sol:TestScript", + "--fork-url", + "http://localhost:8545", + "--broadcast", + "--code-size-limit", + "50000", + ]); + assert_eq!(args.evm_opts.env.code_size_limit, Some(50000)); + } + + #[test] + fn can_extract_script_etherscan_key() { + let temp = tempdir().unwrap(); + let root = temp.path(); + + let config = r#" + [profile.default] + etherscan_api_key = "mumbai" + + [etherscan] + mumbai = { key = "https://etherscan-mumbai.com/" } + "#; + + let toml_file = root.join(Config::FILE_NAME); + fs::write(toml_file, config).unwrap(); + let args = ScriptArgs::parse_from([ + "foundry-cli", + "Contract.sol", + "--etherscan-api-key", + "mumbai", + "--root", + root.as_os_str().to_str().unwrap(), + ]); + + let config = args.load_config(); + let mumbai = config.get_etherscan_api_key(Some(NamedChain::PolygonMumbai.into())); + assert_eq!(mumbai, Some("https://etherscan-mumbai.com/".to_string())); + } + + #[test] + fn can_extract_script_rpc_alias() { + let temp = tempdir().unwrap(); + let root = temp.path(); + + let config = r#" + [profile.default] + + [rpc_endpoints] + polygonMumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_CAN_EXTRACT_RPC_ALIAS}" + "#; + + let toml_file = root.join(Config::FILE_NAME); + fs::write(toml_file, config).unwrap(); + let args = ScriptArgs::parse_from([ + "foundry-cli", + "DeployV1", + "--rpc-url", + "polygonMumbai", + "--root", + root.as_os_str().to_str().unwrap(), + ]); + + let err = args.load_config_and_evm_opts().unwrap_err(); + + assert!(err.downcast::().is_ok()); + + std::env::set_var("_CAN_EXTRACT_RPC_ALIAS", "123456"); + let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); + assert_eq!(config.eth_rpc_url, Some("polygonMumbai".to_string())); + assert_eq!( + evm_opts.fork_url, + Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string()) + ); + } + + #[test] + fn can_extract_script_rpc_and_etherscan_alias() { + let temp = tempdir().unwrap(); + let root = temp.path(); + + let config = r#" + [profile.default] + + [rpc_endpoints] + mumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_EXTRACT_RPC_ALIAS}" + + [etherscan] + mumbai = { key = "${_POLYSCAN_API_KEY}", chain = 80001, url = "https://api-testnet.polygonscan.com/" } + "#; + + let toml_file = root.join(Config::FILE_NAME); + fs::write(toml_file, config).unwrap(); + let args = ScriptArgs::parse_from([ + "foundry-cli", + "DeployV1", + "--rpc-url", + "mumbai", + "--etherscan-api-key", + "mumbai", + "--root", + root.as_os_str().to_str().unwrap(), + ]); + let err = args.load_config_and_evm_opts().unwrap_err(); + + assert!(err.downcast::().is_ok()); + + std::env::set_var("_EXTRACT_RPC_ALIAS", "123456"); + std::env::set_var("_POLYSCAN_API_KEY", "polygonkey"); + let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); + assert_eq!(config.eth_rpc_url, Some("mumbai".to_string())); + assert_eq!( + evm_opts.fork_url, + Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string()) + ); + let etherscan = config.get_etherscan_api_key(Some(80001u64.into())); + assert_eq!(etherscan, Some("polygonkey".to_string())); + let etherscan = config.get_etherscan_api_key(None); + assert_eq!(etherscan, Some("polygonkey".to_string())); + } + + #[test] + fn can_extract_script_rpc_and_sole_etherscan_alias() { + let temp = tempdir().unwrap(); + let root = temp.path(); + + let config = r#" + [profile.default] + + [rpc_endpoints] + mumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_SOLE_EXTRACT_RPC_ALIAS}" + + [etherscan] + mumbai = { key = "${_SOLE_POLYSCAN_API_KEY}" } + "#; + + let toml_file = root.join(Config::FILE_NAME); + fs::write(toml_file, config).unwrap(); + let args = ScriptArgs::parse_from([ + "foundry-cli", + "DeployV1", + "--rpc-url", + "mumbai", + "--root", + root.as_os_str().to_str().unwrap(), + ]); + let err = args.load_config_and_evm_opts().unwrap_err(); + + assert!(err.downcast::().is_ok()); + + std::env::set_var("_SOLE_EXTRACT_RPC_ALIAS", "123456"); + std::env::set_var("_SOLE_POLYSCAN_API_KEY", "polygonkey"); + let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); + assert_eq!( + evm_opts.fork_url, + Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string()) + ); + let etherscan = config.get_etherscan_api_key(Some(80001u64.into())); + assert_eq!(etherscan, Some("polygonkey".to_string())); + let etherscan = config.get_etherscan_api_key(None); + assert_eq!(etherscan, Some("polygonkey".to_string())); + } + + // + #[test] + fn test_5923() { + let args = + ScriptArgs::parse_from(["foundry-cli", "DeployV1", "--priority-gas-price", "100"]); + assert!(args.priority_gas_price.is_some()); + } + + // + #[test] + fn test_5910() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "--broadcast", + "--with-gas-price", + "0", + "SolveTutorial", + ]); + assert!(args.with_gas_price.unwrap().is_zero()); + } +} diff --git a/crates/script/src/multi_sequence.rs b/crates/script/src/multi_sequence.rs new file mode 100644 index 0000000000000..ea6dfd0d4c5fa --- /dev/null +++ b/crates/script/src/multi_sequence.rs @@ -0,0 +1,154 @@ +use super::sequence::{sig_to_file_name, ScriptSequence, SensitiveScriptSequence, DRY_RUN_DIR}; +use eyre::{ContextCompat, Result, WrapErr}; +use foundry_cli::utils::now; +use foundry_common::fs; +use foundry_compilers::ArtifactId; +use foundry_config::Config; +use serde::{Deserialize, Serialize}; +use std::{ + io::{BufWriter, Write}, + path::PathBuf, +}; + +/// Holds the sequences of multiple chain deployments. +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct MultiChainSequence { + pub deployments: Vec, + #[serde(skip)] + pub path: PathBuf, + #[serde(skip)] + pub sensitive_path: PathBuf, + pub timestamp: u64, +} + +/// Sensitive values from script sequences. +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct SensitiveMultiChainSequence { + pub deployments: Vec, +} + +impl SensitiveMultiChainSequence { + fn from_multi_sequence(sequence: MultiChainSequence) -> SensitiveMultiChainSequence { + SensitiveMultiChainSequence { + deployments: sequence.deployments.into_iter().map(|sequence| sequence.into()).collect(), + } + } +} + +impl MultiChainSequence { + pub fn new( + deployments: Vec, + sig: &str, + target: &ArtifactId, + config: &Config, + dry_run: bool, + ) -> Result { + let (path, sensitive_path) = MultiChainSequence::get_paths(config, sig, target, dry_run)?; + + Ok(MultiChainSequence { deployments, path, sensitive_path, timestamp: now().as_secs() }) + } + + /// Gets paths in the formats + /// ./broadcast/multi/contract_filename[-timestamp]/sig.json and + /// ./cache/multi/contract_filename[-timestamp]/sig.json + pub fn get_paths( + config: &Config, + sig: &str, + target: &ArtifactId, + dry_run: bool, + ) -> Result<(PathBuf, PathBuf)> { + let mut broadcast = config.broadcast.to_path_buf(); + let mut cache = config.cache_path.to_path_buf(); + let mut common = PathBuf::new(); + + common.push("multi"); + + if dry_run { + common.push(DRY_RUN_DIR); + } + + let target_fname = target + .source + .file_name() + .wrap_err_with(|| format!("No filename for {:?}", target.source))? + .to_string_lossy(); + + common.push(format!("{target_fname}-latest")); + + broadcast.push(common.clone()); + cache.push(common); + + fs::create_dir_all(&broadcast)?; + fs::create_dir_all(&cache)?; + + let filename = format!("{}.json", sig_to_file_name(sig)); + + broadcast.push(filename.clone()); + cache.push(filename); + + Ok((broadcast, cache)) + } + + /// Loads the sequences for the multi chain deployment. + pub fn load(config: &Config, sig: &str, target: &ArtifactId, dry_run: bool) -> Result { + let (path, sensitive_path) = MultiChainSequence::get_paths(config, sig, target, dry_run)?; + let mut sequence: MultiChainSequence = foundry_compilers::utils::read_json_file(&path) + .wrap_err("Multi-chain deployment not found.")?; + let sensitive_sequence: SensitiveMultiChainSequence = + foundry_compilers::utils::read_json_file(&sensitive_path) + .wrap_err("Multi-chain deployment sensitive details not found.")?; + + sequence.deployments.iter_mut().enumerate().for_each(|(i, sequence)| { + sequence.fill_sensitive(&sensitive_sequence.deployments[i]); + }); + + sequence.path = path; + sequence.sensitive_path = sensitive_path; + + Ok(sequence) + } + + /// Saves the transactions as file if it's a standalone deployment. + pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { + self.deployments.iter_mut().for_each(|sequence| sequence.sort_receipts()); + + self.timestamp = now().as_secs(); + + let sensitive_sequence = SensitiveMultiChainSequence::from_multi_sequence(self.clone()); + + // broadcast writes + //../Contract-latest/run.json + let mut writer = BufWriter::new(fs::create_file(&self.path)?); + serde_json::to_writer_pretty(&mut writer, &self)?; + writer.flush()?; + + if save_ts { + //../Contract-[timestamp]/run.json + let path = self.path.to_string_lossy(); + let file = PathBuf::from(&path.replace("-latest", &format!("-{}", self.timestamp))); + fs::create_dir_all(file.parent().unwrap())?; + fs::copy(&self.path, &file)?; + } + + // cache writes + //../Contract-latest/run.json + let mut writer = BufWriter::new(fs::create_file(&self.sensitive_path)?); + serde_json::to_writer_pretty(&mut writer, &sensitive_sequence)?; + writer.flush()?; + + if save_ts { + //../Contract-[timestamp]/run.json + let path = self.sensitive_path.to_string_lossy(); + let file = PathBuf::from(&path.replace("-latest", &format!("-{}", self.timestamp))); + fs::create_dir_all(file.parent().unwrap())?; + fs::copy(&self.sensitive_path, &file)?; + } + + if !silent { + println!("\nTransactions saved to: {}\n", self.path.display()); + println!("Sensitive details saved to: {}\n", self.sensitive_path.display()); + } + + Ok(()) + } +} diff --git a/crates/forge/bin/cmd/script/providers.rs b/crates/script/src/providers.rs similarity index 65% rename from crates/forge/bin/cmd/script/providers.rs rename to crates/script/src/providers.rs index 92638becaac4f..aeb94fddf89ba 100644 --- a/crates/forge/bin/cmd/script/providers.rs +++ b/crates/script/src/providers.rs @@ -1,7 +1,6 @@ -use alloy_primitives::U256; -use ethers_providers::{Middleware, Provider}; +use alloy_provider::{utils::Eip1559Estimation, Provider}; use eyre::{Result, WrapErr}; -use foundry_common::{get_http_provider, runtime_client::RuntimeClient, types::ToAlloy, RpcUrl}; +use foundry_common::provider::{get_http_provider, RetryProvider}; use foundry_config::Chain; use std::{ collections::{hash_map::Entry, HashMap}, @@ -12,7 +11,7 @@ use std::{ /// Contains a map of RPC urls to single instances of [`ProviderInfo`]. #[derive(Default)] pub struct ProvidersManager { - pub inner: HashMap, + pub inner: HashMap, } impl ProvidersManager { @@ -33,7 +32,7 @@ impl ProvidersManager { } impl Deref for ProvidersManager { - type Target = HashMap; + type Target = HashMap; fn deref(&self) -> &Self::Target { &self.inner @@ -43,23 +42,22 @@ impl Deref for ProvidersManager { /// Holds related metadata to each provider RPC. #[derive(Debug)] pub struct ProviderInfo { - pub provider: Arc>, + pub provider: Arc, pub chain: u64, pub gas_price: GasPrice, - pub is_legacy: bool, } /// Represents the outcome of a gas price request #[derive(Debug)] pub enum GasPrice { - Legacy(Result), - EIP1559(Result<(U256, U256)>), + Legacy(Result), + EIP1559(Result), } impl ProviderInfo { pub async fn new(rpc: &str, mut is_legacy: bool) -> Result { let provider = Arc::new(get_http_provider(rpc)); - let chain = provider.get_chainid().await?.as_u64(); + let chain = provider.get_chain_id().await?; if let Some(chain) = Chain::from(chain).named() { is_legacy |= chain.is_legacy(); @@ -67,30 +65,22 @@ impl ProviderInfo { let gas_price = if is_legacy { GasPrice::Legacy( - provider - .get_gas_price() - .await - .wrap_err("Failed to get legacy gas price") - .map(|p| p.to_alloy()), + provider.get_gas_price().await.wrap_err("Failed to get legacy gas price"), ) } else { GasPrice::EIP1559( - provider - .estimate_eip1559_fees(None) - .await - .wrap_err("Failed to get EIP-1559 fees") - .map(|p| (p.0.to_alloy(), p.1.to_alloy())), + provider.estimate_eip1559_fees(None).await.wrap_err("Failed to get EIP-1559 fees"), ) }; - Ok(ProviderInfo { provider, chain, gas_price, is_legacy }) + Ok(ProviderInfo { provider, chain, gas_price }) } /// Returns the gas price to use - pub fn gas_price(&self) -> Result { + pub fn gas_price(&self) -> Result { let res = match &self.gas_price { GasPrice::Legacy(res) => res.as_ref(), - GasPrice::EIP1559(res) => res.as_ref().map(|res| &res.0), + GasPrice::EIP1559(res) => res.as_ref().map(|res| &res.max_fee_per_gas), }; match res { Ok(val) => Ok(*val), diff --git a/crates/forge/bin/cmd/script/receipts.rs b/crates/script/src/receipts.rs similarity index 66% rename from crates/forge/bin/cmd/script/receipts.rs rename to crates/script/src/receipts.rs index fb8640903c950..5686f18f68d74 100644 --- a/crates/forge/bin/cmd/script/receipts.rs +++ b/crates/script/src/receipts.rs @@ -1,27 +1,24 @@ use super::sequence::ScriptSequence; -use alloy_primitives::TxHash; -use ethers_core::types::TransactionReceipt; -use ethers_providers::{Middleware, PendingTransaction}; +use alloy_chains::Chain; +use alloy_primitives::{utils::format_units, TxHash, U256}; +use alloy_provider::{PendingTransactionBuilder, Provider}; +use alloy_rpc_types::AnyTransactionReceipt; use eyre::Result; -use foundry_cli::{init_progress, update_progress, utils::print_receipt}; -use foundry_common::{ - types::{ToAlloy, ToEthers}, - RetryProvider, -}; +use foundry_cli::{init_progress, update_progress}; +use foundry_common::provider::RetryProvider; use futures::StreamExt; use std::sync::Arc; /// Convenience enum for internal signalling of transaction status enum TxStatus { Dropped, - Success(TransactionReceipt), - Revert(TransactionReceipt), + Success(AnyTransactionReceipt), + Revert(AnyTransactionReceipt), } -impl From for TxStatus { - fn from(receipt: TransactionReceipt) -> Self { - let status = receipt.status.expect("receipt is from an ancient, pre-EIP658 block"); - if status.is_zero() { +impl From for TxStatus { + fn from(receipt: AnyTransactionReceipt) -> Self { + if !receipt.inner.inner.inner.receipt.status { TxStatus::Revert(receipt) } else { TxStatus::Success(receipt) @@ -36,7 +33,7 @@ pub async fn wait_for_pending( deployment_sequence: &mut ScriptSequence, ) -> Result<()> { if deployment_sequence.pending.is_empty() { - return Ok(()) + return Ok(()); } println!("##\nChecking previously pending transactions."); clear_pendings(provider, deployment_sequence, None).await @@ -68,7 +65,7 @@ pub async fn clear_pendings( let mut tasks = futures::stream::iter(futs).buffer_unordered(10); let mut errors: Vec = vec![]; - let mut receipts = Vec::::with_capacity(count); + let mut receipts = Vec::::with_capacity(count); // set up progress bar let mut pos = 0; @@ -87,7 +84,7 @@ pub async fn clear_pendings( } Ok(TxStatus::Success(receipt)) => { trace!(tx_hash=?tx_hash, "received tx receipt"); - deployment_sequence.remove_pending(receipt.transaction_hash.to_alloy()); + deployment_sequence.remove_pending(receipt.transaction_hash); receipts.push(receipt); } Ok(TxStatus::Revert(receipt)) => { @@ -95,7 +92,7 @@ pub async fn clear_pendings( // if this is not removed from pending, then the script becomes // un-resumable. Is this desirable on reverts? warn!(tx_hash=?tx_hash, "Transaction Failure"); - deployment_sequence.remove_pending(receipt.transaction_hash.to_alloy()); + deployment_sequence.remove_pending(receipt.transaction_hash); errors.push(format!("Transaction Failure: {:?}", receipt.transaction_hash)); } } @@ -105,7 +102,7 @@ pub async fn clear_pendings( } // sort receipts by blocks asc and index - receipts.sort_unstable(); + receipts.sort_by_key(|r| (r.block_number, r.transaction_index)); // print all receipts for receipt in receipts { @@ -136,20 +133,53 @@ async fn check_tx_status( // still neatly return the tuple let result = async move { // First check if there's a receipt - let receipt_opt = provider.get_transaction_receipt(hash.to_ethers()).await?; + let receipt_opt = provider.get_transaction_receipt(hash).await?; if let Some(receipt) = receipt_opt { - return Ok(receipt.into()) + return Ok(receipt.into()); } // If the tx is present in the mempool, run the pending tx future, and // assume the next drop is really really real - let pending_res = PendingTransaction::new(hash.to_ethers(), provider).await?; - match pending_res { - Some(receipt) => Ok(receipt.into()), - None => Ok(TxStatus::Dropped), - } + Ok(PendingTransactionBuilder::new(provider, hash) + .get_receipt() + .await + .map_or(TxStatus::Dropped, |r| r.into())) } .await; (hash, result) } + +/// Prints parts of the receipt to stdout +pub fn print_receipt(chain: Chain, receipt: &AnyTransactionReceipt) { + let gas_used = receipt.gas_used; + let gas_price = receipt.effective_gas_price; + foundry_common::shell::println(format!( + "\n##### {chain}\n{status}Hash: {tx_hash:?}{caddr}\nBlock: {bn}\n{gas}\n", + status = if !receipt.inner.inner.inner.receipt.status { + "❌ [Failed]" + } else { + "✅ [Success]" + }, + tx_hash = receipt.transaction_hash, + caddr = if let Some(addr) = &receipt.contract_address { + format!("\nContract Address: {}", addr.to_checksum(None)) + } else { + String::new() + }, + bn = receipt.block_number.unwrap_or_default(), + gas = if gas_price == 0 { + format!("Gas Used: {gas_used}") + } else { + let paid = format_units(gas_used.saturating_mul(gas_price), 18) + .unwrap_or_else(|_| "N/A".into()); + let gas_price = format_units(U256::from(gas_price), 9).unwrap_or_else(|_| "N/A".into()); + format!( + "Paid: {} ETH ({gas_used} gas * {} gwei)", + paid.trim_end_matches('0'), + gas_price.trim_end_matches('0').trim_end_matches('.') + ) + }, + )) + .expect("could not print receipt"); +} diff --git a/crates/forge/bin/cmd/script/runner.rs b/crates/script/src/runner.rs similarity index 78% rename from crates/forge/bin/cmd/script/runner.rs rename to crates/script/src/runner.rs index 8678848d74868..ff77b8e61cb81 100644 --- a/crates/forge/bin/cmd/script/runner.rs +++ b/crates/script/src/runner.rs @@ -1,19 +1,14 @@ -use super::*; +use super::ScriptResult; use alloy_primitives::{Address, Bytes, U256}; -use ethers_core::types::NameOrAddress; use eyre::Result; -use forge::{ +use foundry_config::Config; +use foundry_evm::{ constants::CALLER, - executors::{CallResult, DeployResult, EvmError, ExecutionErr, Executor, RawCallResult}, + executors::{DeployResult, EvmError, ExecutionErr, Executor, RawCallResult}, revm::interpreter::{return_ok, InstructionResult}, traces::{TraceKind, Traces}, }; - -/// Represents which simulation stage is the script execution at. -pub enum SimulationStage { - Local, - OnChain, -} +use yansi::Paint; /// Drives script execution #[derive(Debug)] @@ -60,46 +55,40 @@ impl ScriptRunner { let mut traces: Traces = libraries .iter() .filter_map(|code| { - let DeployResult { traces, .. } = self - .executor + self.executor .deploy(self.sender, code.clone(), U256::ZERO, None) - .expect("couldn't deploy library"); - - traces + .expect("couldn't deploy library") + .raw + .traces }) .map(|traces| (TraceKind::Deployment, traces)) .collect(); + let address = CALLER.create(self.executor.get_nonce(CALLER)?); + + // Set the contracts initial balance before deployment, so it is available during the + // construction + self.executor.set_balance(address, self.initial_balance)?; + // Deploy an instance of the contract let DeployResult { address, - mut logs, - traces: constructor_traces, - debug: constructor_debug, - .. + raw: + RawCallResult { mut logs, traces: constructor_traces, debug: constructor_debug, .. }, } = self .executor .deploy(CALLER, code, U256::ZERO, None) .map_err(|err| eyre::eyre!("Failed to deploy script:\n{}", err))?; traces.extend(constructor_traces.map(|traces| (TraceKind::Deployment, traces))); - self.executor.set_balance(address, self.initial_balance)?; // Optionally call the `setUp` function - let (success, gas_used, labeled_addresses, transactions, debug, script_wallets) = if !setup - { + let (success, gas_used, labeled_addresses, transactions, debug) = if !setup { self.executor.backend.set_test_contract(address); - ( - true, - 0, - Default::default(), - None, - vec![constructor_debug].into_iter().collect(), - vec![], - ) + (true, 0, Default::default(), None, vec![constructor_debug].into_iter().collect()) } else { - match self.executor.setup(Some(self.sender), address) { - Ok(CallResult { + match self.executor.setup(Some(self.sender), address, None) { + Ok(RawCallResult { reverted, traces: setup_traces, labels, @@ -107,7 +96,6 @@ impl ScriptRunner { debug, gas_used, transactions, - script_wallets, .. }) => { traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces))); @@ -121,11 +109,10 @@ impl ScriptRunner { labels, transactions, vec![constructor_debug, debug].into_iter().collect(), - script_wallets, ) } Err(EvmError::Execution(err)) => { - let ExecutionErr { + let RawCallResult { reverted, traces: setup_traces, labels, @@ -133,9 +120,8 @@ impl ScriptRunner { debug, gas_used, transactions, - script_wallets, .. - } = *err; + } = err.raw; traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces))); logs.extend_from_slice(&setup_logs); @@ -147,7 +133,6 @@ impl ScriptRunner { labels, transactions, vec![constructor_debug, debug].into_iter().collect(), - script_wallets, ) } Err(e) => return Err(e.into()), @@ -160,16 +145,12 @@ impl ScriptRunner { returned: Bytes::new(), success, gas_used, - labeled_addresses: labeled_addresses - .into_iter() - .map(|l| (l.0, l.1)) - .collect::>(), + labeled_addresses, transactions, logs, traces, debug, address: None, - script_wallets, ..Default::default() }, )) @@ -202,33 +183,25 @@ impl ScriptRunner { pub fn simulate( &mut self, from: Address, - to: Option, + to: Option
, calldata: Option, value: Option, ) -> Result { - if let Some(NameOrAddress::Address(to)) = to { - self.call( - from, - to.to_alloy(), - calldata.unwrap_or_default(), - value.unwrap_or(U256::ZERO), - true, - ) + if let Some(to) = to { + self.call(from, to, calldata.unwrap_or_default(), value.unwrap_or(U256::ZERO), true) } else if to.is_none() { - let (address, gas_used, logs, traces, debug) = match self.executor.deploy( + let res = self.executor.deploy( from, calldata.expect("No data for create transaction"), value.unwrap_or(U256::ZERO), None, - ) { - Ok(DeployResult { address, gas_used, logs, traces, debug, .. }) => { - (address, gas_used, logs, traces, debug) - } + ); + let (address, RawCallResult { gas_used, logs, traces, debug, .. }) = match res { + Ok(DeployResult { address, raw }) => (address, raw), Err(EvmError::Execution(err)) => { - let ExecutionErr { reason, traces, gas_used, logs, debug, .. } = *err; - println!("{}", Paint::red(format!("\nFailed with `{reason}`:\n"))); - - (Address::ZERO, gas_used, logs, traces, debug) + let ExecutionErr { raw, reason } = *err; + println!("{}", format!("\nFailed with `{reason}`:\n").red()); + (Address::ZERO, raw) } Err(e) => eyre::bail!("Failed deploying contract: {e:?}"), }; @@ -238,14 +211,11 @@ impl ScriptRunner { success: address != Address::ZERO, gas_used, logs, + // Manually adjust gas for the trace to add back the stipend/real used gas traces: traces - .map(|mut traces| { - // Manually adjust gas for the trace to add back the stipend/real used gas - traces.arena[0].trace.gas_cost = gas_used; - vec![(TraceKind::Execution, traces)] - }) + .map(|traces| vec![(TraceKind::Execution, traces)]) .unwrap_or_default(), - debug: vec![debug].into_iter().collect(), + debug: debug.map(|debug| vec![debug]), address: Some(address), ..Default::default() }) @@ -281,17 +251,7 @@ impl ScriptRunner { res = self.executor.call_raw_committing(from, to, calldata, value)?; } - let RawCallResult { - result, - reverted, - logs, - traces, - labels, - debug, - transactions, - script_wallets, - .. - } = res; + let RawCallResult { result, reverted, logs, traces, labels, debug, transactions, .. } = res; let breakpoints = res.cheatcodes.map(|cheats| cheats.breakpoints).unwrap_or_default(); Ok(ScriptResult { @@ -300,9 +260,9 @@ impl ScriptRunner { gas_used, logs, traces: traces - .map(|mut traces| { + .map(|traces| { // Manually adjust gas for the trace to add back the stipend/real used gas - traces.arena[0].trace.gas_cost = gas_used; + vec![(TraceKind::Execution, traces)] }) .unwrap_or_default(), @@ -310,7 +270,6 @@ impl ScriptRunner { labeled_addresses: labels, transactions, address: None, - script_wallets, breakpoints, }) } @@ -344,7 +303,7 @@ impl ScriptRunner { match res.exit_reason { InstructionResult::Revert | InstructionResult::OutOfGas | - InstructionResult::OutOfFund => { + InstructionResult::OutOfFunds => { lowest_gas_limit = mid_gas_limit; } _ => { @@ -358,7 +317,7 @@ impl ScriptRunner { { // update the gas gas_used = highest_gas_limit; - break + break; } last_highest_gas_limit = highest_gas_limit; } diff --git a/crates/forge/bin/cmd/script/sequence.rs b/crates/script/src/sequence.rs similarity index 64% rename from crates/forge/bin/cmd/script/sequence.rs rename to crates/script/src/sequence.rs index 62068aaf4fbd0..f98326564ffb6 100644 --- a/crates/forge/bin/cmd/script/sequence.rs +++ b/crates/script/src/sequence.rs @@ -1,22 +1,15 @@ -use super::NestedValue; -use crate::cmd::{ - init::get_commit_hash, - script::{ - transaction::{wrapper, AdditionalContract, TransactionWithMetadata}, - verify::VerifyBundle, - }, - verify::provider::VerificationProviderType, +use super::{multi_sequence::MultiChainSequence, NestedValue}; +use crate::{ + transaction::{AdditionalContract, TransactionWithMetadata}, + verify::VerifyBundle, }; use alloy_primitives::{Address, TxHash}; -use ethers_core::types::{transaction::eip2718::TypedTransaction, TransactionReceipt}; +use alloy_rpc_types::{AnyTransactionReceipt, TransactionRequest, WithOtherFields}; use eyre::{ContextCompat, Result, WrapErr}; -use foundry_cli::utils::now; -use foundry_common::{ - fs, shell, - types::{ToAlloy, ToEthers}, - SELECTOR_LEN, -}; -use foundry_compilers::{artifacts::Libraries, ArtifactId}; +use forge_verify::provider::VerificationProviderType; +use foundry_cli::utils::{now, Git}; +use foundry_common::{fs, shell, SELECTOR_LEN}; +use foundry_compilers::ArtifactId; use foundry_config::Config; use serde::{Deserialize, Serialize}; use std::{ @@ -26,43 +19,101 @@ use std::{ }; use yansi::Paint; +/// Returns the commit hash of the project if it exists +pub fn get_commit_hash(root: &Path) -> Option { + Git::new(root).commit_hash(true, "HEAD").ok() +} + +pub enum ScriptSequenceKind { + Single(ScriptSequence), + Multi(MultiChainSequence), +} + +impl ScriptSequenceKind { + pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { + match self { + ScriptSequenceKind::Single(sequence) => sequence.save(silent, save_ts), + ScriptSequenceKind::Multi(sequence) => sequence.save(silent, save_ts), + } + } + + pub fn sequences(&self) -> &[ScriptSequence] { + match self { + ScriptSequenceKind::Single(sequence) => std::slice::from_ref(sequence), + ScriptSequenceKind::Multi(sequence) => &sequence.deployments, + } + } + + pub fn sequences_mut(&mut self) -> &mut [ScriptSequence] { + match self { + ScriptSequenceKind::Single(sequence) => std::slice::from_mut(sequence), + ScriptSequenceKind::Multi(sequence) => &mut sequence.deployments, + } + } + /// Updates underlying sequence paths to not be under /dry-run directory. + pub fn update_paths_to_broadcasted( + &mut self, + config: &Config, + sig: &str, + target: &ArtifactId, + ) -> Result<()> { + match self { + ScriptSequenceKind::Single(sequence) => { + sequence.paths = + Some(ScriptSequence::get_paths(config, sig, target, sequence.chain, false)?); + } + ScriptSequenceKind::Multi(sequence) => { + (sequence.path, sequence.sensitive_path) = + MultiChainSequence::get_paths(config, sig, target, false)?; + } + }; + + Ok(()) + } +} + +impl Drop for ScriptSequenceKind { + fn drop(&mut self) { + if let Err(err) = self.save(false, true) { + error!(?err, "could not save deployment sequence"); + } + } +} + pub const DRY_RUN_DIR: &str = "dry-run"; /// Helper that saves the transactions sequence and its state on which transactions have been /// broadcasted -#[derive(Deserialize, Serialize, Clone, Default)] +#[derive(Clone, Default, Serialize, Deserialize)] pub struct ScriptSequence { pub transactions: VecDeque, - #[serde(serialize_with = "wrapper::serialize_receipts")] - pub receipts: Vec, + pub receipts: Vec, pub libraries: Vec, pub pending: Vec, #[serde(skip)] - pub path: PathBuf, - #[serde(skip)] - pub sensitive_path: PathBuf, + /// Contains paths to the sequence files + /// None if sequence should not be saved to disk (e.g. part of a multi-chain sequence) + pub paths: Option<(PathBuf, PathBuf)>, pub returns: HashMap, pub timestamp: u64, pub chain: u64, - /// If `True`, the sequence belongs to a `MultiChainSequence` and won't save to disk as usual. - pub multi: bool, pub commit: Option, } /// Sensitive values from the transactions in a script sequence -#[derive(Deserialize, Serialize, Clone, Default)] +#[derive(Clone, Default, Serialize, Deserialize)] pub struct SensitiveTransactionMetadata { - pub rpc: Option, + pub rpc: String, } /// Sensitive info from the script sequence which is saved into the cache folder -#[derive(Deserialize, Serialize, Clone, Default)] +#[derive(Clone, Default, Serialize, Deserialize)] pub struct SensitiveScriptSequence { pub transactions: VecDeque, } -impl From<&mut ScriptSequence> for SensitiveScriptSequence { - fn from(sequence: &mut ScriptSequence) -> Self { +impl From for SensitiveScriptSequence { + fn from(sequence: ScriptSequence) -> Self { SensitiveScriptSequence { transactions: sequence .transactions @@ -74,59 +125,16 @@ impl From<&mut ScriptSequence> for SensitiveScriptSequence { } impl ScriptSequence { - pub fn new( - transactions: VecDeque, - returns: HashMap, - sig: &str, - target: &ArtifactId, - config: &Config, - broadcasted: bool, - is_multi: bool, - ) -> Result { - let chain = config.chain.unwrap_or_default().id(); - - let (path, sensitive_path) = ScriptSequence::get_paths( - &config.broadcast, - &config.cache_path, - sig, - target, - chain, - broadcasted && !is_multi, - )?; - - let commit = get_commit_hash(&config.__root.0); - - Ok(ScriptSequence { - transactions, - returns, - receipts: vec![], - pending: vec![], - path, - sensitive_path, - timestamp: now().as_secs(), - libraries: vec![], - chain, - multi: is_multi, - commit, - }) - } - /// Loads The sequence for the corresponding json file pub fn load( config: &Config, sig: &str, target: &ArtifactId, chain_id: u64, - broadcasted: bool, + dry_run: bool, ) -> Result { - let (path, sensitive_path) = ScriptSequence::get_paths( - &config.broadcast, - &config.cache_path, - sig, - target, - chain_id, - broadcasted, - )?; + let (path, sensitive_path) = + ScriptSequence::get_paths(config, sig, target, chain_id, dry_run)?; let mut script_sequence: Self = foundry_compilers::utils::read_json_file(&path) .wrap_err(format!("Deployment not found for chain `{chain_id}`."))?; @@ -136,58 +144,65 @@ impl ScriptSequence { "Deployment's sensitive details not found for chain `{chain_id}`." ))?; - script_sequence - .transactions - .iter_mut() - .enumerate() - .for_each(|(i, tx)| tx.rpc = sensitive_script_sequence.transactions[i].rpc.clone()); + script_sequence.fill_sensitive(&sensitive_script_sequence); - script_sequence.path = path; - script_sequence.sensitive_path = sensitive_path; + script_sequence.paths = Some((path, sensitive_path)); Ok(script_sequence) } /// Saves the transactions as file if it's a standalone deployment. - pub fn save(&mut self) -> Result<()> { - if self.multi || self.transactions.is_empty() { + /// `save_ts` should be set to true for checkpoint updates, which might happen many times and + /// could result in us saving many identical files. + pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { + self.sort_receipts(); + + if self.transactions.is_empty() { return Ok(()) } + let Some((path, sensitive_path)) = self.paths.clone() else { return Ok(()) }; + self.timestamp = now().as_secs(); let ts_name = format!("run-{}.json", self.timestamp); - let sensitive_script_sequence: SensitiveScriptSequence = self.into(); + let sensitive_script_sequence: SensitiveScriptSequence = self.clone().into(); // broadcast folder writes //../run-latest.json - let mut writer = BufWriter::new(fs::create_file(&self.path)?); + let mut writer = BufWriter::new(fs::create_file(&path)?); serde_json::to_writer_pretty(&mut writer, &self)?; writer.flush()?; - //../run-[timestamp].json - fs::copy(&self.path, self.path.with_file_name(&ts_name))?; + if save_ts { + //../run-[timestamp].json + fs::copy(&path, path.with_file_name(&ts_name))?; + } // cache folder writes //../run-latest.json - let mut writer = BufWriter::new(fs::create_file(&self.sensitive_path)?); + let mut writer = BufWriter::new(fs::create_file(&sensitive_path)?); serde_json::to_writer_pretty(&mut writer, &sensitive_script_sequence)?; writer.flush()?; - //../run-[timestamp].json - fs::copy(&self.sensitive_path, self.sensitive_path.with_file_name(&ts_name))?; + if save_ts { + //../run-[timestamp].json + fs::copy(&sensitive_path, sensitive_path.with_file_name(&ts_name))?; + } - shell::println(format!("\nTransactions saved to: {}\n", self.path.display()))?; - shell::println(format!("Sensitive values saved to: {}\n", self.sensitive_path.display()))?; + if !silent { + shell::println(format!("\nTransactions saved to: {}\n", path.display()))?; + shell::println(format!("Sensitive values saved to: {}\n", sensitive_path.display()))?; + } Ok(()) } - pub fn add_receipt(&mut self, receipt: TransactionReceipt) { + pub fn add_receipt(&mut self, receipt: AnyTransactionReceipt) { self.receipts.push(receipt); } /// Sorts all receipts with ascending transaction index pub fn sort_receipts(&mut self) { - self.receipts.sort_unstable() + self.receipts.sort_by_key(|r| (r.block_number, r.transaction_index)); } pub fn add_pending(&mut self, index: usize, tx_hash: TxHash) { @@ -201,36 +216,24 @@ impl ScriptSequence { self.pending.retain(|element| element != &tx_hash); } - pub fn add_libraries(&mut self, libraries: Libraries) { - self.libraries = libraries - .libs - .iter() - .flat_map(|(file, libs)| { - libs.iter() - .map(|(name, address)| format!("{}:{name}:{address}", file.to_string_lossy())) - }) - .collect(); - } - /// Gets paths in the formats /// ./broadcast/[contract_filename]/[chain_id]/[sig]-[timestamp].json and /// ./cache/[contract_filename]/[chain_id]/[sig]-[timestamp].json pub fn get_paths( - broadcast: &Path, - cache: &Path, + config: &Config, sig: &str, target: &ArtifactId, chain_id: u64, - broadcasted: bool, + dry_run: bool, ) -> Result<(PathBuf, PathBuf)> { - let mut broadcast = broadcast.to_path_buf(); - let mut cache = cache.to_path_buf(); + let mut broadcast = config.broadcast.to_path_buf(); + let mut cache = config.cache_path.to_path_buf(); let mut common = PathBuf::new(); let target_fname = target.source.file_name().wrap_err("No filename.")?; common.push(target_fname); common.push(chain_id.to_string()); - if !broadcasted { + if dry_run { common.push(DRY_RUN_DIR); } @@ -249,20 +252,6 @@ impl ScriptSequence { Ok((broadcast, cache)) } - /// Checks that there is an Etherscan key for the chain id of this sequence. - pub fn verify_preflight_check(&self, config: &Config, verify: &VerifyBundle) -> Result<()> { - if config.get_etherscan_api_key(Some(self.chain.into())).is_none() && - verify.verifier.verifier == VerificationProviderType::Etherscan - { - eyre::bail!( - "Etherscan API key wasn't found for chain id {}. On-chain execution aborted", - self.chain - ) - } - - Ok(()) - } - /// Given the broadcast log, it matches transactions with receipts, and tries to verify any /// created contract on etherscan. pub async fn verify_contracts( @@ -274,7 +263,7 @@ impl ScriptSequence { verify.set_chain(config, self.chain.into()); - if verify.etherscan.key.is_some() || + if verify.etherscan.has_key() || verify.verifier.verifier != VerificationProviderType::Etherscan { trace!(target: "script", "prepare future verifications"); @@ -290,13 +279,13 @@ impl ScriptSequence { let mut offset = 0; if tx.is_create2() { - receipt.contract_address = tx.contract_address.map(|a| a.to_ethers()); + receipt.contract_address = tx.contract_address; offset = 32; } // Verify contract created directly from the transaction if let (Some(address), Some(data)) = - (receipt.contract_address.map(|h| h.to_alloy()), tx.typed_tx().data()) + (receipt.contract_address, tx.tx().input.input()) { match verify.get_verify_args(address, offset, &data.0, &self.libraries) { Some(verify) => future_verifications.push(verify.run()), @@ -306,7 +295,7 @@ impl ScriptSequence { // Verify potential contracts created during the transaction execution for AdditionalContract { address, init_code, .. } in &tx.additional_contracts { - match verify.get_verify_args(*address, 0, init_code, &self.libraries) { + match verify.get_verify_args(*address, 0, init_code.as_ref(), &self.libraries) { Some(verify) => future_verifications.push(verify.run()), None => unverifiable_contracts.push(*address), }; @@ -335,11 +324,12 @@ impl ScriptSequence { if !unverifiable_contracts.is_empty() { println!( "\n{}", - Paint::yellow(format!( + format!( "We haven't found any matching bytecode for the following contracts: {:?}.\n\n{}", unverifiable_contracts, "This may occur when resuming a verification, but the underlying source code or compiler version has changed." - )) + ) + .yellow() .bold(), ); @@ -357,21 +347,21 @@ impl ScriptSequence { } } + /// Returns the first RPC URL of this sequence. + pub fn rpc_url(&self) -> &str { + self.transactions.front().expect("empty sequence").rpc.as_str() + } + /// Returns the list of the transactions without the metadata. - pub fn typed_transactions(&self) -> Vec<(String, &TypedTransaction)> { - self.transactions - .iter() - .map(|tx| { - (tx.rpc.clone().expect("to have been filled with a proper rpc"), tx.typed_tx()) - }) - .collect() + pub fn transactions(&self) -> impl Iterator> { + self.transactions.iter().map(|tx| tx.tx()) } -} -impl Drop for ScriptSequence { - fn drop(&mut self) { - self.sort_receipts(); - self.save().expect("not able to save deployment sequence"); + pub fn fill_sensitive(&mut self, sensitive: &SensitiveScriptSequence) { + self.transactions + .iter_mut() + .enumerate() + .for_each(|(i, tx)| tx.rpc.clone_from(&sensitive.transactions[i].rpc)); } } @@ -379,7 +369,7 @@ impl Drop for ScriptSequence { /// /// This accepts either the signature of the function or the raw calldata -fn sig_to_file_name(sig: &str) -> String { +pub fn sig_to_file_name(sig: &str) -> String { if let Some((name, _)) = sig.split_once('(') { // strip until call argument parenthesis return name.to_string() diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs new file mode 100644 index 0000000000000..a27506810ed22 --- /dev/null +++ b/crates/script/src/simulate.rs @@ -0,0 +1,430 @@ +use super::{ + multi_sequence::MultiChainSequence, + providers::ProvidersManager, + runner::ScriptRunner, + sequence::{ScriptSequence, ScriptSequenceKind}, + transaction::TransactionWithMetadata, +}; +use crate::{ + broadcast::{estimate_gas, BundledState}, + build::LinkedBuildData, + execute::{ExecutionArtifacts, ExecutionData}, + sequence::get_commit_hash, + ScriptArgs, ScriptConfig, ScriptResult, +}; +use alloy_network::TransactionBuilder; +use alloy_primitives::{utils::format_units, Address, TxKind, U256}; +use eyre::{Context, Result}; +use foundry_cheatcodes::{BroadcastableTransactions, ScriptWallets}; +use foundry_cli::utils::{has_different_gas_calc, now}; +use foundry_common::{get_contract_name, shell, ContractData}; +use foundry_evm::traces::render_trace_arena; +use futures::future::{join_all, try_join_all}; +use parking_lot::RwLock; +use std::{ + collections::{BTreeMap, HashMap, VecDeque}, + sync::Arc, +}; + +/// Same as [ExecutedState], but also contains [ExecutionArtifacts] which are obtained from +/// [ScriptResult]. +/// +/// Can be either converted directly to [BundledState] via [PreSimulationState::resume] or driven to +/// it through [FilledTransactionsState]. +pub struct PreSimulationState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: ScriptWallets, + pub build_data: LinkedBuildData, + pub execution_data: ExecutionData, + pub execution_result: ScriptResult, + pub execution_artifacts: ExecutionArtifacts, +} + +impl PreSimulationState { + /// If simulation is enabled, simulates transactions against fork and fills gas estimation and + /// metadata. Otherwise, metadata (e.g. additional contracts, created contract names) is + /// left empty. + /// + /// Both modes will panic if any of the transactions have None for the `rpc` field. + pub async fn fill_metadata(self) -> Result { + let transactions = if let Some(txs) = self.execution_result.transactions.as_ref() { + if self.args.skip_simulation { + shell::println("\nSKIPPING ON CHAIN SIMULATION.")?; + self.no_simulation(txs.clone())? + } else { + self.onchain_simulation(txs.clone()).await? + } + } else { + VecDeque::new() + }; + + Ok(FilledTransactionsState { + args: self.args, + script_config: self.script_config, + script_wallets: self.script_wallets, + build_data: self.build_data, + execution_artifacts: self.execution_artifacts, + transactions, + }) + } + + /// Builds separate runners and environments for each RPC used in script and executes all + /// transactions in those environments. + /// + /// Collects gas usage and metadata for each transaction. + pub async fn onchain_simulation( + &self, + transactions: BroadcastableTransactions, + ) -> Result> { + trace!(target: "script", "executing onchain simulation"); + + let runners = Arc::new( + self.build_runners() + .await? + .into_iter() + .map(|(rpc, runner)| (rpc, Arc::new(RwLock::new(runner)))) + .collect::>(), + ); + + let address_to_abi = self.build_address_to_abi_map(); + + let mut final_txs = VecDeque::new(); + + // Executes all transactions from the different forks concurrently. + let futs = transactions + .into_iter() + .map(|transaction| async { + let rpc = transaction.rpc.expect("missing broadcastable tx rpc url"); + let mut runner = runners.get(&rpc).expect("invalid rpc url").write(); + + let mut tx = transaction.transaction; + let to = if let Some(TxKind::Call(to)) = tx.to { Some(to) } else { None }; + let result = runner + .simulate( + tx.from + .expect("transaction doesn't have a `from` address at execution time"), + to, + tx.input.clone().into_input(), + tx.value, + ) + .wrap_err("Internal EVM error during simulation")?; + + if !result.success { + return Ok((None, result.traces)); + } + + let created_contracts = result.get_created_contracts(); + + // Simulate mining the transaction if the user passes `--slow`. + if self.args.slow { + runner.executor.env.block.number += U256::from(1); + } + + let is_fixed_gas_limit = tx.gas.is_some(); + match tx.gas { + // If tx.gas is already set that means it was specified in script + Some(gas) => { + println!("Gas limit was set in script to {gas}"); + } + // We inflate the gas used by the user specified percentage + None => { + let gas = result.gas_used * self.args.gas_estimate_multiplier / 100; + tx.gas = Some(gas as u128); + } + } + let tx = TransactionWithMetadata::new( + tx, + rpc, + &result, + &address_to_abi, + &self.execution_artifacts.decoder, + created_contracts, + is_fixed_gas_limit, + )?; + + eyre::Ok((Some(tx), result.traces)) + }) + .collect::>(); + + if self.script_config.evm_opts.verbosity > 3 { + println!("=========================="); + println!("Simulated On-chain Traces:\n"); + } + + let mut abort = false; + for res in join_all(futs).await { + let (tx, traces) = res?; + + // Transaction will be `None`, if execution didn't pass. + if tx.is_none() || self.script_config.evm_opts.verbosity > 3 { + for (_, trace) in &traces { + println!( + "{}", + render_trace_arena(trace, &self.execution_artifacts.decoder).await? + ); + } + } + + if let Some(tx) = tx { + final_txs.push_back(tx); + } else { + abort = true; + } + } + + if abort { + eyre::bail!("Simulated execution failed.") + } + + Ok(final_txs) + } + + /// Build mapping from contract address to its ABI, code and contract name. + fn build_address_to_abi_map(&self) -> BTreeMap { + self.execution_artifacts + .decoder + .contracts + .iter() + .filter_map(move |(addr, contract_id)| { + let contract_name = get_contract_name(contract_id); + if let Ok(Some((_, data))) = + self.build_data.known_contracts.find_by_name_or_identifier(contract_name) + { + return Some((*addr, data)); + } + None + }) + .collect() + } + + /// Build [ScriptRunner] forking given RPC for each RPC used in the script. + async fn build_runners(&self) -> Result> { + let rpcs = self.execution_artifacts.rpc_data.total_rpcs.clone(); + if !shell::verbosity().is_silent() { + let n = rpcs.len(); + let s = if n != 1 { "s" } else { "" }; + println!("\n## Setting up {n} EVM{s}."); + } + + let futs = rpcs.into_iter().map(|rpc| async move { + let mut script_config = self.script_config.clone(); + script_config.evm_opts.fork_url = Some(rpc.clone()); + let runner = script_config.get_runner().await?; + Ok((rpc.clone(), runner)) + }); + try_join_all(futs).await + } + + /// If simulation is disabled, converts transactions into [TransactionWithMetadata] type + /// skipping metadata filling. + fn no_simulation( + &self, + transactions: BroadcastableTransactions, + ) -> Result> { + Ok(transactions + .into_iter() + .map(|btx| { + let mut tx = TransactionWithMetadata::from_tx_request(btx.transaction); + tx.rpc = btx.rpc.expect("missing broadcastable tx rpc url"); + tx + }) + .collect()) + } +} + +/// At this point we have converted transactions collected during script execution to +/// [TransactionWithMetadata] objects which contain additional metadata needed for broadcasting and +/// verification. +pub struct FilledTransactionsState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: ScriptWallets, + pub build_data: LinkedBuildData, + pub execution_artifacts: ExecutionArtifacts, + pub transactions: VecDeque, +} + +impl FilledTransactionsState { + /// Bundles all transactions of the [`TransactionWithMetadata`] type in a list of + /// [`ScriptSequence`]. List length will be higher than 1, if we're dealing with a multi + /// chain deployment. + /// + /// Each transaction will be added with the correct transaction type and gas estimation. + pub async fn bundle(self) -> Result { + let is_multi_deployment = self.execution_artifacts.rpc_data.total_rpcs.len() > 1; + + if is_multi_deployment && !self.build_data.libraries.is_empty() { + eyre::bail!("Multi-chain deployment is not supported with libraries."); + } + + let mut total_gas_per_rpc: HashMap = HashMap::new(); + + // Batches sequence of transactions from different rpcs. + let mut new_sequence = VecDeque::new(); + let mut manager = ProvidersManager::default(); + let mut sequences = vec![]; + + // Peeking is used to check if the next rpc url is different. If so, it creates a + // [`ScriptSequence`] from all the collected transactions up to this point. + let mut txes_iter = self.transactions.clone().into_iter().peekable(); + + while let Some(mut tx) = txes_iter.next() { + let tx_rpc = tx.rpc.clone(); + let provider_info = manager.get_or_init_provider(&tx.rpc, self.args.legacy).await?; + + // Handles chain specific requirements. + tx.transaction.set_chain_id(provider_info.chain); + + if !self.args.skip_simulation { + let tx = tx.tx_mut(); + + if has_different_gas_calc(provider_info.chain) { + trace!("estimating with different gas calculation"); + let gas = tx.gas.expect("gas is set by simulation."); + + // We are trying to show the user an estimation of the total gas usage. + // + // However, some transactions might depend on previous ones. For + // example, tx1 might deploy a contract that tx2 uses. That + // will result in the following `estimate_gas` call to fail, + // since tx1 hasn't been broadcasted yet. + // + // Not exiting here will not be a problem when actually broadcasting, because + // for chains where `has_different_gas_calc` returns true, + // we await each transaction before broadcasting the next + // one. + if let Err(err) = + estimate_gas(tx, &provider_info.provider, self.args.gas_estimate_multiplier) + .await + { + trace!("gas estimation failed: {err}"); + + // Restore gas value, since `estimate_gas` will remove it. + tx.set_gas_limit(gas); + } + } + + let total_gas = total_gas_per_rpc.entry(tx_rpc.clone()).or_insert(0); + *total_gas += tx.gas.expect("gas is set"); + } + + new_sequence.push_back(tx); + // We only create a [`ScriptSequence`] object when we collect all the rpc related + // transactions. + if let Some(next_tx) = txes_iter.peek() { + if next_tx.rpc == tx_rpc { + continue; + } + } + + let sequence = + self.create_sequence(is_multi_deployment, provider_info.chain, new_sequence)?; + + sequences.push(sequence); + + new_sequence = VecDeque::new(); + } + + if !self.args.skip_simulation { + // Present gas information on a per RPC basis. + for (rpc, total_gas) in total_gas_per_rpc { + let provider_info = manager.get(&rpc).expect("provider is set."); + + // We don't store it in the transactions, since we want the most updated value. + // Right before broadcasting. + let per_gas = if let Some(gas_price) = self.args.with_gas_price { + gas_price.to() + } else { + provider_info.gas_price()? + }; + + shell::println("\n==========================")?; + shell::println(format!("\nChain {}", provider_info.chain))?; + + shell::println(format!( + "\nEstimated gas price: {} gwei", + format_units(per_gas, 9) + .unwrap_or_else(|_| "[Could not calculate]".to_string()) + .trim_end_matches('0') + .trim_end_matches('.') + ))?; + shell::println(format!("\nEstimated total gas used for script: {total_gas}"))?; + shell::println(format!( + "\nEstimated amount required: {} ETH", + format_units(total_gas.saturating_mul(per_gas), 18) + .unwrap_or_else(|_| "[Could not calculate]".to_string()) + .trim_end_matches('0') + ))?; + shell::println("\n==========================")?; + } + } + + let sequence = if sequences.len() == 1 { + ScriptSequenceKind::Single(sequences.pop().expect("empty sequences")) + } else { + ScriptSequenceKind::Multi(MultiChainSequence::new( + sequences, + &self.args.sig, + &self.build_data.build_data.target, + &self.script_config.config, + !self.args.broadcast, + )?) + }; + + Ok(BundledState { + args: self.args, + script_config: self.script_config, + script_wallets: self.script_wallets, + build_data: self.build_data, + sequence, + }) + } + + /// Creates a [ScriptSequence] object from the given transactions. + fn create_sequence( + &self, + multi: bool, + chain: u64, + transactions: VecDeque, + ) -> Result { + // Paths are set to None for multi-chain sequences parts, because they don't need to be + // saved to a separate file. + let paths = if multi { + None + } else { + Some(ScriptSequence::get_paths( + &self.script_config.config, + &self.args.sig, + &self.build_data.build_data.target, + chain, + !self.args.broadcast, + )?) + }; + + let commit = get_commit_hash(&self.script_config.config.__root.0); + + let libraries = self + .build_data + .libraries + .libs + .iter() + .flat_map(|(file, libs)| { + libs.iter() + .map(|(name, address)| format!("{}:{name}:{address}", file.to_string_lossy())) + }) + .collect(); + + Ok(ScriptSequence { + transactions, + returns: self.execution_artifacts.returns.clone(), + receipts: vec![], + pending: vec![], + paths, + timestamp: now().as_secs(), + libraries, + chain, + commit, + }) + } +} diff --git a/crates/script/src/transaction.rs b/crates/script/src/transaction.rs new file mode 100644 index 0000000000000..c46301f9f3985 --- /dev/null +++ b/crates/script/src/transaction.rs @@ -0,0 +1,223 @@ +use super::ScriptResult; +use alloy_dyn_abi::JsonAbiExt; +use alloy_primitives::{Address, Bytes, TxKind, B256}; +use alloy_rpc_types::{request::TransactionRequest, WithOtherFields}; +use eyre::{ContextCompat, Result, WrapErr}; +use foundry_common::{fmt::format_token_raw, ContractData, SELECTOR_LEN}; +use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, traces::CallTraceDecoder}; +use itertools::Itertools; +use revm_inspectors::tracing::types::CallKind; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AdditionalContract { + #[serde(rename = "transactionType")] + pub opcode: CallKind, + pub address: Address, + pub init_code: Bytes, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionWithMetadata { + pub hash: Option, + #[serde(rename = "transactionType")] + pub opcode: CallKind, + #[serde(default = "default_string")] + pub contract_name: Option, + #[serde(default = "default_address")] + pub contract_address: Option
, + #[serde(default = "default_string")] + pub function: Option, + #[serde(default = "default_vec_of_strings")] + pub arguments: Option>, + #[serde(skip)] + pub rpc: String, + pub transaction: WithOtherFields, + pub additional_contracts: Vec, + pub is_fixed_gas_limit: bool, +} + +fn default_string() -> Option { + Some("".to_string()) +} + +fn default_address() -> Option
{ + Some(Address::ZERO) +} + +fn default_vec_of_strings() -> Option> { + Some(vec![]) +} + +impl TransactionWithMetadata { + pub fn from_tx_request(transaction: TransactionRequest) -> Self { + Self { transaction: WithOtherFields::new(transaction), ..Default::default() } + } + + pub fn new( + transaction: TransactionRequest, + rpc: String, + result: &ScriptResult, + local_contracts: &BTreeMap, + decoder: &CallTraceDecoder, + additional_contracts: Vec, + is_fixed_gas_limit: bool, + ) -> Result { + let mut metadata = Self::from_tx_request(transaction); + metadata.rpc = rpc; + metadata.is_fixed_gas_limit = is_fixed_gas_limit; + + // Specify if any contract was directly created with this transaction + if let Some(TxKind::Call(to)) = metadata.transaction.to { + if to == DEFAULT_CREATE2_DEPLOYER { + metadata.set_create( + true, + Address::from_slice(&result.returned), + local_contracts, + )?; + } else { + metadata + .set_call(to, local_contracts, decoder) + .wrap_err("Could not decode transaction type.")?; + } + } else { + metadata.set_create( + false, + result.address.wrap_err("There should be a contract address from CREATE.")?, + local_contracts, + )?; + } + + // Add the additional contracts created in this transaction, so we can verify them later. + if let Some(tx_address) = metadata.contract_address { + metadata.additional_contracts = additional_contracts + .into_iter() + .filter_map(|contract| { + // Filter out the transaction contract repeated init_code. + if contract.address != tx_address { + Some(contract) + } else { + None + } + }) + .collect(); + } + + Ok(metadata) + } + + /// Populate the transaction as CREATE tx + /// + /// If this is a CREATE2 transaction this attempt to decode the arguments from the CREATE2 + /// deployer's function + fn set_create( + &mut self, + is_create2: bool, + address: Address, + contracts: &BTreeMap, + ) -> Result<()> { + if is_create2 { + self.opcode = CallKind::Create2; + } else { + self.opcode = CallKind::Create; + } + + let info = contracts.get(&address); + self.contract_name = info.map(|info| info.name.clone()); + self.contract_address = Some(address); + + let Some(data) = self.transaction.input.input() else { return Ok(()) }; + let Some(info) = info else { return Ok(()) }; + let Some(bytecode) = info.bytecode.as_ref() else { return Ok(()) }; + + // `create2` transactions are prefixed by a 32 byte salt. + let creation_code = if is_create2 { + if data.len() < 32 { + return Ok(()) + } + &data[32..] + } else { + data + }; + + // The constructor args start after bytecode. + let contains_constructor_args = creation_code.len() > bytecode.len(); + if !contains_constructor_args { + return Ok(()); + } + let constructor_args = &creation_code[bytecode.len()..]; + + let Some(constructor) = info.abi.constructor() else { return Ok(()) }; + let values = constructor.abi_decode_input(constructor_args, false).map_err(|e| { + error!( + contract=?self.contract_name, + signature=%format!("constructor({})", constructor.inputs.iter().map(|p| &p.ty).format(",")), + is_create2, + constructor_args=%hex::encode(constructor_args), + "Failed to decode constructor arguments", + ); + debug!(full_data=%hex::encode(data), bytecode=%hex::encode(creation_code)); + e + })?; + self.arguments = Some(values.iter().map(format_token_raw).collect()); + + Ok(()) + } + + /// Populate the transaction as CALL tx + fn set_call( + &mut self, + target: Address, + local_contracts: &BTreeMap, + decoder: &CallTraceDecoder, + ) -> Result<()> { + self.opcode = CallKind::Call; + self.contract_address = Some(target); + + let Some(data) = self.transaction.input.input() else { return Ok(()) }; + if data.len() < SELECTOR_LEN { + return Ok(()); + } + let (selector, data) = data.split_at(SELECTOR_LEN); + + let function = if let Some(info) = local_contracts.get(&target) { + // This CALL is made to a local contract. + self.contract_name = Some(info.name.clone()); + info.abi.functions().find(|function| function.selector() == selector) + } else { + // This CALL is made to an external contract; try to decode it from the given decoder. + decoder.functions.get(selector).and_then(|v| v.first()) + }; + if let Some(function) = function { + self.function = Some(function.signature()); + + let values = function.abi_decode_input(data, false).map_err(|e| { + error!( + contract=?self.contract_name, + signature=?function, + data=hex::encode(data), + "Failed to decode function arguments", + ); + e + })?; + self.arguments = Some(values.iter().map(format_token_raw).collect()); + } + + Ok(()) + } + + pub fn tx(&self) -> &WithOtherFields { + &self.transaction + } + + pub fn tx_mut(&mut self) -> &mut WithOtherFields { + &mut self.transaction + } + + pub fn is_create2(&self) -> bool { + self.opcode == CallKind::Create2 + } +} diff --git a/crates/forge/bin/cmd/script/verify.rs b/crates/script/src/verify.rs similarity index 73% rename from crates/forge/bin/cmd/script/verify.rs rename to crates/script/src/verify.rs index 4c0205fcfc34a..f6545c30194cc 100644 --- a/crates/forge/bin/cmd/script/verify.rs +++ b/crates/script/src/verify.rs @@ -1,14 +1,44 @@ -use crate::cmd::{ - retry::RetryArgs, - verify::{VerifierArgs, VerifyArgs}, -}; +use crate::{build::LinkedBuildData, sequence::ScriptSequenceKind, ScriptArgs, ScriptConfig}; + use alloy_primitives::Address; +use eyre::Result; +use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs}; use foundry_cli::opts::{EtherscanOpts, ProjectPathsArgs}; use foundry_common::ContractsByArtifact; use foundry_compilers::{info::ContractInfo, Project}; use foundry_config::{Chain, Config}; use semver::Version; +/// State after we have broadcasted the script. +/// It is assumed that at this point [BroadcastedState::sequence] contains receipts for all +/// broadcasted transactions. +pub struct BroadcastedState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub build_data: LinkedBuildData, + pub sequence: ScriptSequenceKind, +} + +impl BroadcastedState { + pub async fn verify(self) -> Result<()> { + let Self { args, script_config, build_data, mut sequence, .. } = self; + + let verify = VerifyBundle::new( + &script_config.config.project()?, + &script_config.config, + build_data.known_contracts, + args.retry, + args.verifier, + ); + + for sequence in sequence.sequences_mut() { + sequence.verify_contracts(&script_config.config, verify.clone()).await?; + } + + Ok(()) + } +} + /// Data struct to help `ScriptSequence` verify contracts on `etherscan`. #[derive(Clone)] pub struct VerifyBundle { @@ -18,6 +48,7 @@ pub struct VerifyBundle { pub etherscan: EtherscanOpts, pub retry: RetryArgs, pub verifier: VerifierArgs, + pub via_ir: bool, } impl VerifyBundle { @@ -44,6 +75,8 @@ impl VerifyBundle { config_path: if config_path.exists() { Some(config_path) } else { None }, }; + let via_ir = config.via_ir; + VerifyBundle { num_of_optimizations, known_contracts, @@ -51,6 +84,7 @@ impl VerifyBundle { project_paths, retry, verifier, + via_ir, } } @@ -71,7 +105,8 @@ impl VerifyBundle { data: &[u8], libraries: &[String], ) -> Option { - for (artifact, (_contract, bytecode)) in self.known_contracts.iter() { + for (artifact, contract) in self.known_contracts.iter() { + let Some(bytecode) = contract.bytecode.as_ref() else { continue }; // If it's a CREATE2, the tx.data comes with a 32-byte salt in the beginning // of the transaction if data.split_at(create2_offset).1.starts_with(bytecode) { @@ -101,15 +136,19 @@ impl VerifyBundle { constructor_args_path: None, num_of_optimizations: self.num_of_optimizations, etherscan: self.etherscan.clone(), + rpc: Default::default(), flatten: false, force: false, - skip_is_verified_check: false, + skip_is_verified_check: true, watch: true, retry: self.retry, libraries: libraries.to_vec(), root: None, verifier: self.verifier.clone(), + via_ir: self.via_ir, + evm_version: None, show_standard_json_input: false, + guess_constructor_args: false, }; return Some(verify) diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 8e2b75931734b..a73c53a394c27 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -17,9 +17,8 @@ foundry-compilers = { workspace = true, features = ["project-util"] } foundry-config.workspace = true alloy-primitives.workspace = true - -ethers-core.workspace = true -ethers-providers.workspace = true +alloy-provider.workspace = true +alloy-rpc-types.workspace = true eyre.workspace = true fd-lock = "4.0.0" @@ -31,6 +30,7 @@ serde_json.workspace = true tracing = "0.1" tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } walkdir = "2" +rand.workspace = true [features] # feature for integration tests that test external projects diff --git a/crates/test-utils/src/fd_lock.rs b/crates/test-utils/src/fd_lock.rs index 222ac3cfbd88a..1c5a479cd5efd 100644 --- a/crates/test-utils/src/fd_lock.rs +++ b/crates/test-utils/src/fd_lock.rs @@ -13,7 +13,7 @@ pub fn new_lock(lock_path: impl AsRef) -> RwLock { fn new_lock(lock_path: &Path) -> RwLock { let lock_file = pretty_err( lock_path, - OpenOptions::new().read(true).write(true).create(true).open(lock_path), + OpenOptions::new().read(true).write(true).create(true).truncate(false).open(lock_path), ); RwLock::new(lock_file) } diff --git a/crates/test-utils/src/filter.rs b/crates/test-utils/src/filter.rs index c04eb2a39a6fd..e24f87c17523c 100644 --- a/crates/test-utils/src/filter.rs +++ b/crates/test-utils/src/filter.rs @@ -1,11 +1,13 @@ use foundry_common::TestFilter; use regex::Regex; +use std::path::Path; pub struct Filter { test_regex: Regex, contract_regex: Regex, path_regex: Regex, exclude_tests: Option, + exclude_contracts: Option, exclude_paths: Option, } @@ -20,6 +22,7 @@ impl Filter { path_regex: Regex::new(path_pattern) .unwrap_or_else(|_| panic!("Failed to parse path pattern: `{path_pattern}`")), exclude_tests: None, + exclude_contracts: None, exclude_paths: None, } } @@ -40,6 +43,14 @@ impl Filter { self } + /// All contracts to also exclude + /// + /// This is a workaround since regex does not support negative look aheads + pub fn exclude_contracts(mut self, pattern: &str) -> Self { + self.exclude_contracts = Some(Regex::new(pattern).unwrap()); + self + } + /// All paths to also exclude /// /// This is a workaround since regex does not support negative look aheads @@ -54,31 +65,40 @@ impl Filter { contract_regex: Regex::new(".*").unwrap(), path_regex: Regex::new(".*").unwrap(), exclude_tests: None, + exclude_contracts: None, exclude_paths: None, } } } impl TestFilter for Filter { - fn matches_test(&self, test_name: impl AsRef) -> bool { - let test_name = test_name.as_ref(); - if let Some(ref exclude) = self.exclude_tests { + fn matches_test(&self, test_name: &str) -> bool { + if let Some(exclude) = &self.exclude_tests { if exclude.is_match(test_name) { - return false + return false; } } self.test_regex.is_match(test_name) } - fn matches_contract(&self, contract_name: impl AsRef) -> bool { - self.contract_regex.is_match(contract_name.as_ref()) + fn matches_contract(&self, contract_name: &str) -> bool { + if let Some(exclude) = &self.exclude_contracts { + if exclude.is_match(contract_name) { + return false; + } + } + + self.contract_regex.is_match(contract_name) } - fn matches_path(&self, path: impl AsRef) -> bool { - let path = path.as_ref(); - if let Some(ref exclude) = self.exclude_paths { + fn matches_path(&self, path: &Path) -> bool { + let Some(path) = path.to_str() else { + return false; + }; + + if let Some(exclude) = &self.exclude_paths { if exclude.is_match(path) { - return false + return false; } } self.path_regex.is_match(path) diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index 042ff4aba1cb1..a4b70f493c5ca 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -6,6 +6,8 @@ extern crate tracing; // Macros useful for testing. mod macros; +pub mod rpc; + pub mod fd_lock; mod filter; diff --git a/crates/test-utils/src/macros.rs b/crates/test-utils/src/macros.rs index d5cb88e5a8ba1..f05c8e36b8e42 100644 --- a/crates/test-utils/src/macros.rs +++ b/crates/test-utils/src/macros.rs @@ -92,82 +92,3 @@ macro_rules! forgetest_init { } }; } - -/// Clones an external repository and makes sure the tests pass. -/// Can optionally enable fork mode as well if a fork block is passed. -/// The fork block needs to be greater than 0. -#[macro_export] -macro_rules! forgetest_external { - // forgetest_external!(test_name, "owner/repo"); - ($(#[$attr:meta])* $test:ident, $repo:literal) => { - $crate::forgetest_external!($(#[$attr])* $test, $repo, 0, Vec::::new()); - }; - // forgetest_external!(test_name, "owner/repo", 1234); - ($(#[$attr:meta])* $test:ident, $repo:literal, $fork_block:literal) => { - $crate::forgetest_external!( - $(#[$attr])* - $test, - $repo, - $crate::foundry_compilers::PathStyle::Dapptools, - $fork_block, - Vec::::new() - ); - }; - // forgetest_external!(test_name, "owner/repo", &["--extra-opt", "val"]); - ($(#[$attr:meta])* $test:ident, $repo:literal, $forge_opts:expr) => { - $crate::forgetest_external!($(#[$attr])* $test, $repo, 0, $forge_opts); - }; - // forgetest_external!(test_name, "owner/repo", 1234, &["--extra-opt", "val"]); - ($(#[$attr:meta])* $test:ident, $repo:literal, $fork_block:literal, $forge_opts:expr) => { - $crate::forgetest_external!( - $(#[$attr])* - $test, - $repo, - $crate::foundry_compilers::PathStyle::Dapptools, - $fork_block, - $forge_opts - ); - }; - // forgetest_external!(test_name, "owner/repo", PathStyle::Dapptools, 123); - ($(#[$attr:meta])* $test:ident, $repo:literal, $style:expr, $fork_block:literal, $forge_opts:expr) => { - #[test] - $(#[$attr])* - fn $test() { - use std::process::{Command, Stdio}; - - // Skip fork tests if the RPC url is not set. - if $fork_block > 0 && std::env::var("ETH_RPC_URL").is_err() { - eprintln!("Skipping test {}. ETH_RPC_URL is not set.", $repo); - return - }; - - let (prj, mut cmd) = $crate::util::setup_forge(stringify!($test), $style); - - // Wipe the default structure - prj.wipe(); - - // Clone the external repository - $crate::util::clone_remote(concat!("https://github.com/", $repo), prj.root().to_str().unwrap()); - - // Run common installation commands - $crate::util::run_install_commands(prj.root()); - - // Run the tests - cmd.arg("test").args($forge_opts).args([ - "--optimize", - "--optimizer-runs=20000", - "--fuzz-runs=256", - "--ffi", - "-vvvvv", - ]); - cmd.set_env("FOUNDRY_FUZZ_RUNS", "1"); - - let next_eth_rpc_url = foundry_common::rpc::next_http_archive_rpc_endpoint(); - if $fork_block > 0 { - cmd.set_env("FOUNDRY_ETH_RPC_URL", next_eth_rpc_url); - cmd.set_env("FOUNDRY_FORK_BLOCK_NUMBER", stringify!($fork_block)); - } - cmd.assert_non_empty_stdout(); - } - }; -} diff --git a/crates/common/src/rpc.rs b/crates/test-utils/src/rpc.rs similarity index 85% rename from crates/common/src/rpc.rs rename to crates/test-utils/src/rpc.rs index eb53213353056..63a701a4f82fc 100644 --- a/crates/common/src/rpc.rs +++ b/crates/test-utils/src/rpc.rs @@ -51,6 +51,22 @@ static ALCHEMY_MAINNET_KEYS: Lazy> = Lazy::new(|| { keys }); +// List of etherscan keys for mainnet +static ETHERSCAN_MAINNET_KEYS: Lazy> = Lazy::new(|| { + let mut keys = vec![ + "MCAUM7WPE9XP5UQMZPCKIBUJHPM1C24FP6", + "JW6RWCG2C5QF8TANH4KC7AYIF1CX7RB5D1", + "ZSMDY6BI2H55MBE3G9CUUQT4XYUDBB6ZSK", + "4FYHTY429IXYMJNS4TITKDMUKW5QRYDX61", + "QYKNT5RHASZ7PGQE68FNQWH99IXVTVVD2I", + "VXMQ117UN58Y4RHWUB8K1UGCEA7UQEWK55", + ]; + + keys.shuffle(&mut rand::thread_rng()); + + keys +}); + /// counts how many times a rpc endpoint was requested for _mainnet_ static NEXT_RPC_ENDPOINT: AtomicUsize = AtomicUsize::new(0); @@ -111,6 +127,12 @@ pub fn next_ws_archive_rpc_endpoint() -> String { format!("wss://eth-mainnet.alchemyapi.io/v2/{}", ALCHEMY_MAINNET_KEYS[idx]) } +/// Returns the next etherscan api key +pub fn next_etherscan_api_key() -> String { + let idx = next() % ETHERSCAN_MAINNET_KEYS.len(); + ETHERSCAN_MAINNET_KEYS[idx].to_string() +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index a2f47ed4d7e42..b14ba2a54f358 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -1,13 +1,9 @@ use crate::{init_tracing, TestCommand}; -use alloy_primitives::{Address, U256}; -use ethers_core::types::NameOrAddress; -use ethers_providers::Middleware; +use alloy_primitives::Address; +use alloy_provider::Provider; +use alloy_rpc_types::BlockId; use eyre::Result; -use foundry_common::{ - get_http_provider, - types::{ToAlloy, ToEthers}, - RetryProvider, -}; +use foundry_common::provider::{get_http_provider, RetryProvider}; use std::{collections::BTreeMap, fs, path::Path, str::FromStr}; const BROADCAST_TEST_PATH: &str = "src/Broadcast.t.sol"; @@ -18,8 +14,8 @@ pub struct ScriptTester { pub accounts_pub: Vec
, pub accounts_priv: Vec, pub provider: Option, - pub nonces: BTreeMap, - pub address_nonces: BTreeMap, + pub nonces: BTreeMap, + pub address_nonces: BTreeMap, pub cmd: TestCommand, } @@ -39,6 +35,8 @@ impl ScriptTester { "script", "-R", "ds-test/=lib/", + "-R", + "cheats/=cheats/", target_contract, "--root", project_root.to_str().unwrap(), @@ -76,7 +74,7 @@ impl ScriptTester { // copy the broadcast test fs::copy( - Self::testdata_path().join("cheats/Broadcast.t.sol"), + Self::testdata_path().join("default/cheats/Broadcast.t.sol"), project_root.join(BROADCAST_TEST_PATH), ) .expect("Failed to initialize broadcast contract"); @@ -91,8 +89,11 @@ impl ScriptTester { // copy the broadcast test let testdata = Self::testdata_path(); - fs::copy(testdata.join("cheats/Broadcast.t.sol"), project_root.join(BROADCAST_TEST_PATH)) - .expect("Failed to initialize broadcast contract"); + fs::copy( + testdata.join("default/cheats/Broadcast.t.sol"), + project_root.join(BROADCAST_TEST_PATH), + ) + .expect("Failed to initialize broadcast contract"); Self::new(cmd, None, project_root, &target_contract) } @@ -105,7 +106,8 @@ impl ScriptTester { /// Initialises the test contracts by copying them into the workspace fn copy_testdata(current_dir: &Path) -> Result<()> { let testdata = Self::testdata_path(); - fs::copy(testdata.join("cheats/Vm.sol"), current_dir.join("src/Vm.sol"))?; + fs::create_dir_all(current_dir.join("cheats"))?; + fs::copy(testdata.join("cheats/Vm.sol"), current_dir.join("cheats/Vm.sol"))?; fs::copy(testdata.join("lib/ds-test/src/test.sol"), current_dir.join("lib/test.sol"))?; Ok(()) } @@ -116,13 +118,10 @@ impl ScriptTester { if let Some(provider) = &self.provider { let nonce = provider - .get_transaction_count( - NameOrAddress::Address(self.accounts_pub[index as usize].to_ethers()), - None, - ) + .get_transaction_count(self.accounts_pub[index as usize], BlockId::latest()) .await .unwrap(); - self.nonces.insert(index, nonce.to_alloy()); + self.nonces.insert(index, nonce); } } self @@ -134,10 +133,10 @@ impl ScriptTester { .provider .as_ref() .unwrap() - .get_transaction_count(NameOrAddress::Address(address.to_ethers()), None) + .get_transaction_count(address, BlockId::latest()) .await .unwrap(); - self.address_nonces.insert(address, nonce.to_alloy()); + self.address_nonces.insert(address, nonce); } self } @@ -180,14 +179,14 @@ impl ScriptTester { .provider .as_ref() .unwrap() - .get_transaction_count(NameOrAddress::Address(addr.to_ethers()), None) + .get_transaction_count(addr, BlockId::latest()) .await .unwrap(); let prev_nonce = self.nonces.get(&private_key_slot).unwrap(); assert_eq!( nonce, - (prev_nonce + U256::from(expected_increment)).to_ethers(), + (*prev_nonce + expected_increment as u64), "nonce not incremented correctly for {addr}: \ {prev_nonce} + {expected_increment} != {nonce}" ); @@ -205,12 +204,12 @@ impl ScriptTester { .provider .as_ref() .unwrap() - .get_transaction_count(NameOrAddress::Address(address.to_ethers()), None) + .get_transaction_count(*address, BlockId::latest()) .await .unwrap(); let prev_nonce = self.address_nonces.get(address).unwrap(); - assert_eq!(nonce, (prev_nonce + U256::from(*expected_increment)).to_ethers()); + assert_eq!(nonce, *prev_nonce + *expected_increment as u64); } self } @@ -259,6 +258,7 @@ pub enum ScriptOutcome { ScriptFailed, UnsupportedLibraries, ErrorSelectForkOnBroadcast, + OkRun, } impl ScriptOutcome { @@ -274,6 +274,7 @@ impl ScriptOutcome { Self::ScriptFailed => "script failed: ", Self::UnsupportedLibraries => "Multi chain deployment does not support library linking at the moment.", Self::ErrorSelectForkOnBroadcast => "cannot select forks during a broadcast", + Self::OkRun => "Script ran successfully", } } @@ -282,7 +283,8 @@ impl ScriptOutcome { ScriptOutcome::OkNoEndpoint | ScriptOutcome::OkSimulation | ScriptOutcome::OkBroadcast | - ScriptOutcome::WarnSpecifyDeployer => false, + ScriptOutcome::WarnSpecifyDeployer | + ScriptOutcome::OkRun => false, ScriptOutcome::MissingSender | ScriptOutcome::MissingWallet | ScriptOutcome::StaticCallNotAllowed | diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 6a879378acdf7..9d9905d1ce789 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -13,10 +13,8 @@ use regex::Regex; use std::{ env, ffi::OsStr, - fmt::Display, - fs, - fs::File, - io::{BufWriter, IsTerminal, Write}, + fs::{self, File}, + io::{BufWriter, IsTerminal, Read, Seek, Write}, path::{Path, PathBuf}, process::{ChildStdin, Command, Output, Stdio}, sync::{ @@ -50,6 +48,160 @@ pub const SOLC_VERSION: &str = "0.8.23"; /// versions. pub const OTHER_SOLC_VERSION: &str = "0.8.22"; +/// External test builder +#[derive(Clone, Debug)] +#[must_use = "ExtTester does nothing unless you `run` it"] +pub struct ExtTester { + pub org: &'static str, + pub name: &'static str, + pub rev: &'static str, + pub style: PathStyle, + pub fork_block: Option, + pub args: Vec, + pub envs: Vec<(String, String)>, + pub install_commands: Vec>, +} + +impl ExtTester { + /// Creates a new external test builder. + pub fn new(org: &'static str, name: &'static str, rev: &'static str) -> Self { + Self { + org, + name, + rev, + style: PathStyle::Dapptools, + fork_block: None, + args: vec![], + envs: vec![], + install_commands: vec![], + } + } + + /// Sets the path style. + pub fn style(mut self, style: PathStyle) -> Self { + self.style = style; + self + } + + /// Sets the fork block. + pub fn fork_block(mut self, fork_block: u64) -> Self { + self.fork_block = Some(fork_block); + self + } + + /// Adds an argument to the forge command. + pub fn arg(mut self, arg: impl Into) -> Self { + self.args.push(arg.into()); + self + } + + /// Adds multiple arguments to the forge command. + pub fn args(mut self, args: I) -> Self + where + I: IntoIterator, + A: Into, + { + self.args.extend(args.into_iter().map(Into::into)); + self + } + + /// Adds an environment variable to the forge command. + pub fn env(mut self, key: impl Into, value: impl Into) -> Self { + self.envs.push((key.into(), value.into())); + self + } + + /// Adds multiple environment variables to the forge command. + pub fn envs(mut self, envs: I) -> Self + where + I: IntoIterator, + K: Into, + V: Into, + { + self.envs.extend(envs.into_iter().map(|(k, v)| (k.into(), v.into()))); + self + } + + /// Adds a command to run after the project is cloned. + /// + /// Note that the command is run in the project's root directory, and it won't fail the test if + /// it fails. + pub fn install_command(mut self, command: &[&str]) -> Self { + self.install_commands.push(command.iter().map(|s| s.to_string()).collect()); + self + } + + /// Runs the test. + pub fn run(&self) { + // Skip fork tests if the RPC url is not set. + if self.fork_block.is_some() && std::env::var_os("ETH_RPC_URL").is_none() { + eprintln!("ETH_RPC_URL is not set; skipping"); + return; + } + + let (prj, mut test_cmd) = setup_forge(self.name, self.style.clone()); + + // Wipe the default structure. + prj.wipe(); + + // Clone the external repository. + let repo_url = format!("https://github.com/{}/{}.git", self.org, self.name); + let root = prj.root().to_str().unwrap(); + clone_remote(&repo_url, root); + + // Checkout the revision. + if self.rev.is_empty() { + let mut git = Command::new("git"); + git.current_dir(root).args(["log", "-n", "1"]); + eprintln!("$ {git:?}"); + let output = git.output().unwrap(); + if !output.status.success() { + panic!("git log failed: {output:?}"); + } + let stdout = String::from_utf8(output.stdout).unwrap(); + let commit = stdout.lines().next().unwrap().split_whitespace().nth(1).unwrap(); + panic!("pin to latest commit: {commit}"); + } else { + let mut git = Command::new("git"); + git.current_dir(root).args(["checkout", self.rev]); + eprintln!("$ {git:?}"); + let status = git.status().unwrap(); + if !status.success() { + panic!("git checkout failed: {status}"); + } + } + + // Run installation command. + for install_command in &self.install_commands { + let mut install_cmd = Command::new(&install_command[0]); + install_cmd.args(&install_command[1..]).current_dir(root); + eprintln!("cd {root}; {install_cmd:?}"); + match install_cmd.status() { + Ok(s) => { + eprintln!("\n\n{install_cmd:?}: {s}"); + if s.success() { + break; + } + } + Err(e) => eprintln!("\n\n{install_cmd:?}: {e}"), + } + } + + // Run the tests. + test_cmd.arg("test"); + test_cmd.args(&self.args); + test_cmd.args(["--fuzz-runs=32", "--ffi", "-vvvvv"]); + + test_cmd.envs(self.envs.iter().map(|(k, v)| (k, v))); + if let Some(fork_block) = self.fork_block { + test_cmd.env("FOUNDRY_ETH_RPC_URL", crate::rpc::next_http_archive_rpc_endpoint()); + test_cmd.env("FOUNDRY_FORK_BLOCK_NUMBER", fork_block.to_string()); + } + + test_cmd.assert_non_empty_stdout(); + } +} + /// Initializes a project with `forge init` at the given path. /// /// This should be called after an empty project is created like in @@ -83,21 +235,31 @@ pub fn initialize(target: &Path) { // Release the read lock and acquire a write lock, initializing the lock file. _read = None; + let mut write = lock.write().unwrap(); - write.write_all(b"1").unwrap(); - // Initialize and build. - let (prj, mut cmd) = setup_forge("template", foundry_compilers::PathStyle::Dapptools); - eprintln!("- initializing template dir in {}", prj.root().display()); + let mut data = String::new(); + write.read_to_string(&mut data).unwrap(); + + if data != "1" { + // Initialize and build. + let (prj, mut cmd) = setup_forge("template", foundry_compilers::PathStyle::Dapptools); + eprintln!("- initializing template dir in {}", prj.root().display()); + + cmd.args(["init", "--force"]).assert_success(); + cmd.forge_fuse().args(["build", "--use", SOLC_VERSION]).assert_success(); - cmd.args(["init", "--force"]).assert_success(); - cmd.forge_fuse().args(["build", "--use", SOLC_VERSION]).assert_success(); + // Remove the existing template, if any. + let _ = fs::remove_dir_all(tpath); - // Remove the existing template, if any. - let _ = fs::remove_dir_all(tpath); + // Copy the template to the global template path. + pretty_err(tpath, copy_dir(prj.root(), tpath)); - // Copy the template to the global template path. - pretty_err(tpath, copy_dir(prj.root(), tpath)); + // Update lockfile to mark that template is initialized. + write.set_len(0).unwrap(); + write.seek(std::io::SeekFrom::Start(0)).unwrap(); + write.write_all(b"1").unwrap(); + } // Release the write lock and acquire a new read lock. drop(write); @@ -112,46 +274,16 @@ pub fn initialize(target: &Path) { /// Clones a remote repository into the specified directory. Panics if the command fails. pub fn clone_remote(repo_url: &str, target_dir: &str) { let mut cmd = Command::new("git"); - cmd.args(["clone", "--depth=1", "--recursive", "--shallow-submodules", repo_url, target_dir]); + cmd.args(["clone", "--no-tags", "--recursive", "--shallow-submodules"]); + cmd.args([repo_url, target_dir]); eprintln!("{cmd:?}"); let status = cmd.status().unwrap(); if !status.success() { - panic!("git clone failed: {status:?}"); + panic!("git clone failed: {status}"); } eprintln!(); } -/// Runs common installation commands, such as `make` and `npm`. Continues if any command fails. -pub fn run_install_commands(root: &Path) { - let root_files = - std::fs::read_dir(root).unwrap().flatten().map(|x| x.path()).collect::>(); - let contains = |path: &str| root_files.iter().any(|p| p.to_str().unwrap().contains(path)); - let run = |args: &[&str]| { - let mut cmd = Command::new(args[0]); - cmd.args(&args[1..]).current_dir(root); - eprintln!("cd {}; {cmd:?}", root.display()); - #[cfg(windows)] - let st = cmd.status(); - #[cfg(not(windows))] - let st = cmd.status().unwrap(); - eprintln!("\n\n{cmd:?}: {st:?}"); - }; - let maybe_run = |path: &str, args: &[&str]| { - let c = contains(path); - if c { - run(args); - } - c - }; - - maybe_run("Makefile", &["make", "install"]); - let pnpm = maybe_run("pnpm-lock.yaml", &["pnpm", "install", "--prefer-offline"]); - let yarn = maybe_run("yarn.lock", &["yarn", "install", "--prefer-offline"]); - if !pnpm && !yarn && contains("package.json") { - run(&["npm", "install"]); - } -} - /// Setup an empty test project and return a command pointing to the forge /// executable whose CWD is set to the project's root. /// @@ -168,7 +300,7 @@ pub fn setup_forge_project(test: TestProject) -> (TestProject, TestCommand) { } /// How to initialize a remote git project -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct RemoteProject { id: String, run_build: bool, @@ -320,6 +452,12 @@ impl TestProject { &self.paths().artifacts } + /// Removes the project's cache and artifacts directory. + pub fn clear(&self) { + self.clear_cache(); + self.clear_artifacts(); + } + /// Removes this project's cache file. pub fn clear_cache(&self) { let _ = fs::remove_file(self.cache()); @@ -438,7 +576,7 @@ impl TestProject { /// Adds `console.sol` as a source under "console.sol" pub fn insert_console(&self) -> PathBuf { - let s = include_str!("../../../testdata/logs/console.sol"); + let s = include_str!("../../../testdata/default/logs/console.sol"); self.add_source("console.sol", s).unwrap() } @@ -490,6 +628,7 @@ impl TestProject { /// Returns the path to the forge executable. pub fn forge_bin(&self) -> Command { let forge = self.exe_root.join(format!("../forge{}", env::consts::EXE_SUFFIX)); + let forge = forge.canonicalize().unwrap_or_else(|_| forge.clone()); let mut cmd = Command::new(forge); cmd.current_dir(self.inner.root()); // disable color output for comparisons @@ -500,6 +639,7 @@ impl TestProject { /// Returns the path to the cast executable. pub fn cast_bin(&self) -> Command { let cast = self.exe_root.join(format!("../cast{}", env::consts::EXE_SUFFIX)); + let cast = cast.canonicalize().unwrap_or_else(|_| cast.clone()); let mut cmd = Command::new(cast); // disable color output for comparisons cmd.env("NO_COLOR", "1"); @@ -640,8 +780,18 @@ impl TestCommand { } /// Set the environment variable `k` to value `v` for the command. - pub fn set_env(&mut self, k: impl AsRef, v: impl Display) { - self.cmd.env(k, v.to_string()); + pub fn env(&mut self, k: impl AsRef, v: impl AsRef) { + self.cmd.env(k, v); + } + + /// Set the environment variable `k` to value `v` for the command. + pub fn envs(&mut self, envs: I) + where + I: IntoIterator, + K: AsRef, + V: AsRef, + { + self.cmd.envs(envs); } /// Unsets the environment variable `k` for the command. @@ -680,6 +830,31 @@ impl TestCommand { output } + /// Returns a new [Command] that is inside the current project dir + pub fn cmd_in_current_dir(&self, program: &str) -> Command { + let mut cmd = Command::new(program); + cmd.current_dir(self.project.root()); + cmd + } + + /// Runs `git add .` inside the project's dir + #[track_caller] + pub fn git_add(&self) -> Result<()> { + let mut cmd = self.cmd_in_current_dir("git"); + cmd.arg("add").arg("."); + let output = cmd.output()?; + self.ensure_success(&output) + } + + /// Runs `git commit .` inside the project's dir + #[track_caller] + pub fn git_commit(&self, msg: &str) -> Result<()> { + let mut cmd = self.cmd_in_current_dir("git"); + cmd.arg("commit").arg("-m").arg(msg); + let output = cmd.output()?; + self.ensure_success(&output) + } + /// Executes the command and returns the `(stdout, stderr)` of the output as lossy `String`s. /// /// Expects the command to be successful. @@ -872,6 +1047,9 @@ stderr: /// fixture. Since `forge` commands may emit colorized output depending on whether the current /// terminal is tty, the path argument can be wrapped in [tty_fixture_path()] pub trait OutputExt { + /// Ensure the command wrote the expected data to `stdout`. + fn stdout_matches_content(&self, expected: &str); + /// Ensure the command wrote the expected data to `stdout`. fn stdout_matches_path(&self, expected_path: impl AsRef); @@ -890,7 +1068,7 @@ static IGNORE_IN_FIXTURES: Lazy = Lazy::new(|| { // solc runs r"runs: \d+, μ: \d+, ~: \d+", // elapsed time - "finished in .*?s", + r"(?:finished)? ?in .*?s(?: \(.*?s CPU time\))?", // file paths r"-->.*\.sol", r"Location(.|\n)*\.rs(.|\n)*Backtrace", @@ -906,11 +1084,16 @@ fn normalize_output(s: &str) -> String { } impl OutputExt for Output { + #[track_caller] + fn stdout_matches_content(&self, expected: &str) { + let out = lossy_string(&self.stdout); + pretty_assertions::assert_eq!(normalize_output(&out), normalize_output(expected)); + } + #[track_caller] fn stdout_matches_path(&self, expected_path: impl AsRef) { let expected = fs::read_to_string(expected_path).unwrap(); - let out = lossy_string(&self.stdout); - pretty_assertions::assert_eq!(normalize_output(&out), normalize_output(&expected)); + self.stdout_matches_content(&expected); } #[track_caller] diff --git a/crates/verify/Cargo.toml b/crates/verify/Cargo.toml new file mode 100644 index 0000000000000..21eb659423c32 --- /dev/null +++ b/crates/verify/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "forge-verify" +description = "Contract verification tools" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +foundry-config.workspace = true +foundry-cli.workspace = true +foundry-common.workspace = true +foundry-evm.workspace = true +serde_json.workspace = true +hex.workspace = true +alloy-json-abi.workspace = true +alloy-primitives.workspace = true +alloy-rpc-types.workspace = true +revm-primitives.workspace = true +serde.workspace = true +eyre.workspace = true +alloy-provider.workspace = true +tracing.workspace = true +foundry-compilers = { workspace = true, features = ["full"] } +foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } + +clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } +reqwest = { workspace = true, features = ["json"] } +async-trait = "0.1" +futures = "0.3" +semver = "1" +regex = { version = "1", default-features = false } +once_cell = "1" +yansi.workspace = true + +[dev-dependencies] +tokio = { version = "1", features = ["macros"] } +foundry-test-utils.workspace = true +tempfile.workspace = true diff --git a/crates/verify/src/bytecode.rs b/crates/verify/src/bytecode.rs new file mode 100644 index 0000000000000..317a65941f94e --- /dev/null +++ b/crates/verify/src/bytecode.rs @@ -0,0 +1,654 @@ +use alloy_primitives::{Address, Bytes, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockId, BlockNumberOrTag}; +use clap::{Parser, ValueHint}; +use eyre::{OptionExt, Result}; +use foundry_block_explorers::{contract::Metadata, Client}; +use foundry_cli::{ + opts::EtherscanOpts, + utils::{self, read_constructor_args_file, LoadConfig}, +}; +use foundry_common::{ + compile::{ProjectCompiler, SkipBuildFilter, SkipBuildFilters}, + provider::ProviderBuilder, +}; +use foundry_compilers::{ + artifacts::{BytecodeHash, BytecodeObject, CompactContractBytecode}, + info::ContractInfo, + Artifact, EvmVersion, +}; +use foundry_config::{figment, impl_figment_convert, Chain, Config}; +use foundry_evm::{ + constants::DEFAULT_CREATE2_DEPLOYER, executors::TracingExecutor, utils::configure_tx_env, +}; +use revm_primitives::{db::Database, EnvWithHandlerCfg, HandlerCfg, SpecId}; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::{fmt, path::PathBuf, str::FromStr}; +use yansi::Paint; + +impl_figment_convert!(VerifyBytecodeArgs); + +/// CLI arguments for `forge verify-bytecode`. +#[derive(Clone, Debug, Parser)] +pub struct VerifyBytecodeArgs { + /// The address of the contract to verify. + pub address: Address, + + /// The contract identifier in the form `:`. + pub contract: ContractInfo, + + /// The block at which the bytecode should be verified. + #[clap(long, value_name = "BLOCK")] + pub block: Option, + + /// The constructor args to generate the creation code. + #[clap( + long, + conflicts_with = "constructor_args_path", + value_name = "ARGS", + visible_alias = "encoded-constructor-args" + )] + pub constructor_args: Option, + + /// The path to a file containing the constructor arguments. + #[clap(long, value_hint = ValueHint::FilePath, value_name = "PATH")] + pub constructor_args_path: Option, + + /// The rpc url to use for verification. + #[clap(short = 'r', long, value_name = "RPC_URL", env = "ETH_RPC_URL")] + pub rpc_url: Option, + + /// Verfication Type: `full` or `partial`. Ref: https://docs.sourcify.dev/docs/full-vs-partial-match/ + #[clap(long, default_value = "full", value_name = "TYPE")] + pub verification_type: VerificationType, + + #[clap(flatten)] + pub etherscan_opts: EtherscanOpts, + + /// Skip building files whose names contain the given filter. + /// + /// `test` and `script` are aliases for `.t.sol` and `.s.sol`. + #[arg(long, num_args(1..))] + pub skip: Option>, + + /// The path to the project's root directory. + pub root: Option, + + /// Suppress logs and emit json results to stdout + #[clap(long, default_value = "false")] + pub json: bool, +} + +impl figment::Provider for VerifyBytecodeArgs { + fn metadata(&self) -> figment::Metadata { + figment::Metadata::named("Verify Bytecode Provider") + } + + fn data( + &self, + ) -> Result, figment::Error> { + let mut dict = figment::value::Dict::new(); + if let Some(block) = &self.block { + dict.insert("block".into(), figment::value::Value::serialize(block)?); + } + if let Some(rpc_url) = &self.rpc_url { + dict.insert("eth_rpc_url".into(), rpc_url.to_string().into()); + } + dict.insert("verification_type".into(), self.verification_type.to_string().into()); + + Ok(figment::value::Map::from([(Config::selected_profile(), dict)])) + } +} + +impl VerifyBytecodeArgs { + /// Run the `verify-bytecode` command to verify the bytecode onchain against the locally built + /// bytecode. + pub async fn run(mut self) -> Result<()> { + // Setup + let config = self.load_config_emit_warnings(); + let provider = ProviderBuilder::new(&config.get_rpc_url_or_localhost_http()?).build()?; + + let code = provider.get_code_at(self.address, BlockId::latest()).await?; + if code.is_empty() { + eyre::bail!("No bytecode found at address {}", self.address); + } + + if !self.json { + println!( + "Verifying bytecode for contract {} at address {}", + self.contract.name.clone().green(), + self.address.green() + ); + } + + // If chain is not set, we try to get it from the RPC + // If RPC is not set, the default chain is used + let chain = if config.get_rpc_url().is_some() { + let chain_id = provider.get_chain_id().await?; + Chain::from(chain_id) + } else { + config.chain.unwrap_or_default() + }; + + // Set Etherscan options + self.etherscan_opts.chain = Some(chain); + self.etherscan_opts.key = + config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key); + + // If etherscan key is not set, we can't proceed with etherscan verification + let Some(key) = self.etherscan_opts.key.clone() else { + eyre::bail!("Etherscan API key is required for verification"); + }; + let etherscan = Client::new(chain, key)?; + + // Get the constructor args using `source_code` endpoint + let source_code = etherscan.contract_source_code(self.address).await?; + + // Check if the contract name matches + let name = source_code.items.first().map(|item| item.contract_name.to_owned()); + if name.as_ref() != Some(&self.contract.name) { + eyre::bail!("Contract name mismatch"); + } + + // Get the constructor args from etherscan + let constructor_args = if let Some(args) = source_code.items.first() { + args.constructor_arguments.clone() + } else { + eyre::bail!("No constructor arguments found for contract at address {}", self.address); + }; + + // Get user provided constructor args + let provided_constructor_args = if let Some(args) = self.constructor_args.to_owned() { + args + } else if let Some(path) = self.constructor_args_path.to_owned() { + // Read from file + let res = read_constructor_args_file(path)?; + // Convert res to Bytes + res.join("") + } else { + constructor_args.to_string() + }; + + // Constructor args mismatch + if provided_constructor_args != constructor_args.to_string() && !self.json { + println!( + "{}", + "The provided constructor args do not match the constructor args from etherscan. This will result in a mismatch - Using the args from etherscan".red().bold(), + ); + } + + // Get creation tx hash + let creation_data = etherscan.contract_creation_data(self.address).await?; + + let mut transaction = provider + .get_transaction_by_hash(creation_data.transaction_hash) + .await + .or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))?; + let receipt = provider + .get_transaction_receipt(creation_data.transaction_hash) + .await + .or_else(|e| eyre::bail!("Couldn't fetch transacrion receipt from RPC: {:?}", e))?; + + let receipt = if let Some(receipt) = receipt { + receipt + } else { + eyre::bail!( + "Receipt not found for transaction hash {}", + creation_data.transaction_hash + ); + }; + // Extract creation code + let maybe_creation_code = if receipt.contract_address == Some(self.address) { + &transaction.input + } else if transaction.to == Some(DEFAULT_CREATE2_DEPLOYER) { + &transaction.input[32..] + } else { + eyre::bail!( + "Could not extract the creation code for contract at address {}", + self.address + ); + }; + + // If bytecode_hash is disabled then its always partial verification + let (verification_type, has_metadata) = + match (&self.verification_type, config.bytecode_hash) { + (VerificationType::Full, BytecodeHash::None) => (VerificationType::Partial, false), + (VerificationType::Partial, BytecodeHash::None) => { + (VerificationType::Partial, false) + } + (VerificationType::Full, _) => (VerificationType::Full, true), + (VerificationType::Partial, _) => (VerificationType::Partial, true), + }; + + // Etherscan compilation metadata + let etherscan_metadata = source_code.items.first().unwrap(); + + let local_bytecode = + if let Some(local_bytecode) = self.build_using_cache(etherscan_metadata, &config) { + local_bytecode + } else { + self.build_project(&config)? + }; + + // Append constructor args to the local_bytecode + let mut local_bytecode_vec = local_bytecode.to_vec(); + local_bytecode_vec.extend_from_slice(&constructor_args); + + // Cmp creation code with locally built bytecode and maybe_creation_code + let (did_match, with_status) = try_match( + local_bytecode_vec.as_slice(), + maybe_creation_code, + &constructor_args, + &verification_type, + false, + has_metadata, + )?; + + let mut json_results: Vec = vec![]; + self.print_result( + (did_match, with_status), + BytecodeType::Creation, + &mut json_results, + etherscan_metadata, + &config, + ); + + // Get contract creation block + let simulation_block = match self.block { + Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block, + Some(_) => eyre::bail!("Invalid block number"), + None => { + let provider = utils::get_provider(&config)?; + provider + .get_transaction_by_hash(creation_data.transaction_hash) + .await.or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))? + .block_number.ok_or_else(|| { + eyre::eyre!("Failed to get block number of the contract creation tx, specify using the --block flag") + })? + } + }; + + // Fork the chain at `simulation_block` + + let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?; + fork_config.fork_block_number = Some(simulation_block - 1); + fork_config.evm_version = + etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()); + let (mut env, fork, _chain) = + TracingExecutor::get_fork_material(&fork_config, evm_opts).await?; + + let mut executor = + TracingExecutor::new(env.clone(), fork, Some(fork_config.evm_version), false); + env.block.number = U256::from(simulation_block); + let block = provider.get_block(simulation_block.into(), true).await?; + + // Workaround for the NonceTooHigh issue as we're not simulating prior txs of the same + // block. + let prev_block_id = BlockId::Number(BlockNumberOrTag::Number(simulation_block - 1)); + let prev_block_nonce = + provider.get_transaction_count(creation_data.contract_creator, prev_block_id).await?; + transaction.nonce = prev_block_nonce; + + if let Some(ref block) = block { + env.block.timestamp = U256::from(block.header.timestamp); + env.block.coinbase = block.header.miner; + env.block.difficulty = block.header.difficulty; + env.block.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); + env.block.basefee = U256::from(block.header.base_fee_per_gas.unwrap_or_default()); + env.block.gas_limit = U256::from(block.header.gas_limit); + } + + configure_tx_env(&mut env, &transaction); + + let env_with_handler = + EnvWithHandlerCfg::new(Box::new(env.clone()), HandlerCfg::new(SpecId::LATEST)); + + let contract_address = if let Some(to) = transaction.to { + if to != DEFAULT_CREATE2_DEPLOYER { + eyre::bail!("Transaction `to` address is not the default create2 deployer i.e the tx is not a contract creation tx."); + } + let result = executor.commit_tx_with_env(env_with_handler.to_owned())?; + + if result.result.len() > 20 { + eyre::bail!("Failed to deploy contract using commit_tx_with_env on fork at block {} | Err: Call result is greater than 20 bytes, cannot be converted to Address", simulation_block); + } + + Address::from_slice(&result.result) + } else { + let deploy_result = executor.deploy_with_env(env_with_handler, None)?; + deploy_result.address + }; + + // State commited using deploy_with_env, now get the runtime bytecode from the db. + let fork_runtime_code = executor + .backend + .basic(contract_address)? + .ok_or_else(|| { + eyre::eyre!( + "Failed to get runtime code for contract deployed on fork at address {}", + contract_address + ) + })? + .code + .ok_or_else(|| { + eyre::eyre!( + "Bytecode does not exist for contract deployed on fork at address {}", + contract_address + ) + })?; + + let onchain_runtime_code = provider + .get_code_at(self.address, BlockId::Number(BlockNumberOrTag::Number(simulation_block))) + .await?; + + // Compare the runtime bytecode with the locally built bytecode + let (did_match, with_status) = try_match( + &fork_runtime_code.bytecode, + &onchain_runtime_code, + &constructor_args, + &verification_type, + true, + has_metadata, + )?; + + self.print_result( + (did_match, with_status), + BytecodeType::Runtime, + &mut json_results, + etherscan_metadata, + &config, + ); + + if self.json { + println!("{}", serde_json::to_string(&json_results)?); + } + Ok(()) + } + + fn build_project(&self, config: &Config) -> Result { + let project = config.project()?; + let mut compiler = ProjectCompiler::new(); + + if let Some(skip) = &self.skip { + if !skip.is_empty() { + compiler = compiler.filter(Box::new(SkipBuildFilters::new( + skip.to_owned(), + project.root().to_path_buf(), + )?)); + } + } + let output = compiler.compile(&project)?; + + let artifact = output + .find_contract(&self.contract) + .ok_or_eyre("Build Error: Contract artifact not found locally")?; + + let local_bytecode = artifact + .get_bytecode_object() + .ok_or_eyre("Contract artifact does not have bytecode")?; + + let local_bytecode = match local_bytecode.as_ref() { + BytecodeObject::Bytecode(bytes) => bytes, + BytecodeObject::Unlinked(_) => { + eyre::bail!("Unlinked bytecode is not supported for verification") + } + }; + + Ok(local_bytecode.to_owned()) + } + + fn build_using_cache(&self, etherscan_settings: &Metadata, config: &Config) -> Option { + let project = config.project().ok()?; + let cache = project.read_cache_file().ok()?; + let cached_artifacts = cache.read_artifacts::().ok()?; + + for (key, value) in cached_artifacts { + let name = self.contract.name.to_owned() + ".sol"; + let version = etherscan_settings.compiler_version.to_owned(); + if version.starts_with("vyper:") { + return None; + } + // Parse etherscan version string + let version = + version.split('+').next().unwrap_or("").trim_start_matches('v').to_string(); + if key.ends_with(name.as_str()) { + if let Some(artifact) = value.into_iter().next() { + if let Ok(version) = Version::parse(&version) { + if let Some(artifact) = artifact.1.iter().find(|a| { + a.version.major == version.major && + a.version.minor == version.minor && + a.version.patch == version.patch + }) { + return artifact + .artifact + .bytecode + .as_ref() + .and_then(|bytes| bytes.bytes().to_owned()) + .cloned(); + } + } + let artifact = artifact.1.first().unwrap(); // Get the first artifact + let local_bytecode = if let Some(local_bytecode) = &artifact.artifact.bytecode { + local_bytecode.bytes() + } else { + None + }; + + return local_bytecode.map(|bytes| bytes.to_owned()); + } + } + } + + None + } + + fn print_result( + &self, + res: (bool, Option), + bytecode_type: BytecodeType, + json_results: &mut Vec, + etherscan_config: &Metadata, + config: &Config, + ) { + if res.0 { + if !self.json { + println!( + "{} with status {}", + format!("{:?} code matched", bytecode_type).green().bold(), + res.1.unwrap().green().bold() + ); + } else { + let json_res = JsonResult { + bytecode_type, + matched: true, + verification_type: res.1.unwrap(), + message: None, + }; + json_results.push(json_res); + } + } else if !res.0 && !self.json { + println!( + "{}", + format!( + "{:?} code did not match - this may be due to varying compiler settings", + bytecode_type + ) + .red() + .bold() + ); + let mismatches = find_mismatch_in_settings(etherscan_config, config); + for mismatch in mismatches { + println!("{}", mismatch.red().bold()); + } + } else if !res.0 && self.json { + let json_res = JsonResult { + bytecode_type, + matched: false, + verification_type: self.verification_type, + message: Some(format!( + "{:?} code did not match - this may be due to varying compiler settings", + bytecode_type + )), + }; + json_results.push(json_res); + } + } +} + +/// Enum to represent the type of verification: `full` or `partial`. Ref: https://docs.sourcify.dev/docs/full-vs-partial-match/ +#[derive(Debug, Clone, clap::ValueEnum, Default, PartialEq, Eq, Serialize, Deserialize, Copy)] +pub enum VerificationType { + #[default] + #[serde(rename = "full")] + Full, + #[serde(rename = "partial")] + Partial, +} + +impl FromStr for VerificationType { + type Err = eyre::Error; + + fn from_str(s: &str) -> Result { + match s { + "full" => Ok(VerificationType::Full), + "partial" => Ok(VerificationType::Partial), + _ => eyre::bail!("Invalid verification type"), + } + } +} + +impl From for String { + fn from(v: VerificationType) -> Self { + match v { + VerificationType::Full => "full".to_string(), + VerificationType::Partial => "partial".to_string(), + } + } +} + +impl fmt::Display for VerificationType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + VerificationType::Full => write!(f, "full"), + VerificationType::Partial => write!(f, "partial"), + } + } +} + +/// Enum to represent the type of bytecode being verified +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +pub enum BytecodeType { + #[serde(rename = "creation")] + Creation, + #[serde(rename = "runtime")] + Runtime, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct JsonResult { + pub bytecode_type: BytecodeType, + pub matched: bool, + pub verification_type: VerificationType, + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, +} + +fn try_match( + local_bytecode: &[u8], + bytecode: &[u8], + constructor_args: &[u8], + match_type: &VerificationType, + is_runtime: bool, + has_metadata: bool, +) -> Result<(bool, Option)> { + // 1. Try full match + if *match_type == VerificationType::Full && local_bytecode.starts_with(bytecode) { + Ok((true, Some(VerificationType::Full))) + } else { + try_partial_match(local_bytecode, bytecode, constructor_args, is_runtime, has_metadata) + .map(|matched| (matched, matched.then_some(VerificationType::Partial))) + } +} + +fn try_partial_match( + mut local_bytecode: &[u8], + mut bytecode: &[u8], + constructor_args: &[u8], + is_runtime: bool, + has_metadata: bool, +) -> Result { + // 1. Check length of constructor args + if constructor_args.is_empty() { + // Assume metadata is at the end of the bytecode + if has_metadata { + local_bytecode = extract_metadata_hash(local_bytecode)?; + bytecode = extract_metadata_hash(bytecode)?; + } + + // Now compare the creation code and bytecode + return Ok(local_bytecode.starts_with(bytecode)); + } + + if is_runtime { + if has_metadata { + local_bytecode = extract_metadata_hash(local_bytecode)?; + bytecode = extract_metadata_hash(bytecode)?; + } + + // Now compare the local code and bytecode + return Ok(local_bytecode.starts_with(bytecode)); + } + + // If not runtime, extract constructor args from the end of the bytecode + bytecode = &bytecode[..bytecode.len() - constructor_args.len()]; + local_bytecode = &local_bytecode[..local_bytecode.len() - constructor_args.len()]; + + if has_metadata { + local_bytecode = extract_metadata_hash(local_bytecode)?; + bytecode = extract_metadata_hash(bytecode)?; + } + + Ok(local_bytecode.starts_with(bytecode)) +} + +/// @dev This assumes that the metadata is at the end of the bytecode +fn extract_metadata_hash(bytecode: &[u8]) -> Result<&[u8]> { + // Get the last two bytes of the bytecode to find the length of CBOR metadata + let metadata_len = &bytecode[bytecode.len() - 2..]; + let metadata_len = u16::from_be_bytes([metadata_len[0], metadata_len[1]]); + + // Now discard the metadata from the bytecode + Ok(&bytecode[..bytecode.len() - 2 - metadata_len as usize]) +} + +fn find_mismatch_in_settings( + etherscan_settings: &Metadata, + local_settings: &Config, +) -> Vec { + let mut mismatches: Vec = vec![]; + if etherscan_settings.evm_version != local_settings.evm_version.to_string().to_lowercase() { + let str = format!( + "EVM version mismatch: local={}, onchain={}", + local_settings.evm_version, etherscan_settings.evm_version + ); + mismatches.push(str); + } + let local_optimizer: u64 = if local_settings.optimizer { 1 } else { 0 }; + if etherscan_settings.optimization_used != local_optimizer { + let str = format!( + "Optimizer mismatch: local={}, onchain={}", + local_settings.optimizer, etherscan_settings.optimization_used + ); + mismatches.push(str); + } + if etherscan_settings.runs != local_settings.optimizer_runs as u64 { + let str = format!( + "Optimizer runs mismatch: local={}, onchain={}", + local_settings.optimizer_runs, etherscan_settings.runs + ); + mismatches.push(str); + } + + mismatches +} diff --git a/crates/forge/bin/cmd/verify/etherscan/flatten.rs b/crates/verify/src/etherscan/flatten.rs similarity index 95% rename from crates/forge/bin/cmd/verify/etherscan/flatten.rs rename to crates/verify/src/etherscan/flatten.rs index a9ed47bc23709..62e534a66785e 100644 --- a/crates/forge/bin/cmd/verify/etherscan/flatten.rs +++ b/crates/verify/src/etherscan/flatten.rs @@ -80,16 +80,16 @@ impl EtherscanFlattenedSource { if out.has_error() { let mut o = AggregatedCompilerOutput::default(); o.extend(version, out); - eprintln!("{}", o.diagnostics(&[], Default::default())); + let diags = o.diagnostics(&[], &[], Default::default()); - eprintln!( - r#"Failed to compile the flattened code locally. + eyre::bail!( + "\ +Failed to compile the flattened code locally. This could be a bug, please inspect the output of `forge flatten {}` and report an issue. To skip this solc dry, pass `--force`. -"#, +Diagnostics: {diags}", contract_path.display() ); - std::process::exit(1) } Ok(()) diff --git a/crates/forge/bin/cmd/verify/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs similarity index 73% rename from crates/forge/bin/cmd/verify/etherscan/mod.rs rename to crates/verify/src/etherscan/mod.rs index 64471ed3fe02c..ec218e714e90d 100644 --- a/crates/forge/bin/cmd/verify/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -1,22 +1,30 @@ use super::{provider::VerificationProvider, VerifyArgs, VerifyCheckArgs}; -use crate::cmd::retry::RETRY_CHECK_ON_VERIFY; +use crate::retry::RETRY_CHECK_ON_VERIFY; use alloy_json_abi::Function; -use eyre::{eyre, Context, Result}; +use alloy_provider::Provider; +use eyre::{eyre, Context, OptionExt, Result}; use foundry_block_explorers::{ errors::EtherscanError, utils::lookup_compiler_version, verify::{CodeFormat, VerifyContract}, Client, }; -use foundry_cli::utils::{get_cached_entry_by_name, read_constructor_args_file, LoadConfig}; +use foundry_cli::utils::{self, get_cached_entry_by_name, read_constructor_args_file, LoadConfig}; use foundry_common::{abi::encode_function_args, retry::Retry}; -use foundry_compilers::{artifacts::CompactContract, cache::CacheEntry, Project, Solc}; +use foundry_compilers::{ + artifacts::{BytecodeObject, CompactContract}, + cache::CacheEntry, + info::ContractInfo, + Artifact, Project, Solc, +}; use foundry_config::{Chain, Config, SolcReq}; +use foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER; use futures::FutureExt; use once_cell::sync::Lazy; use regex::Regex; use semver::{BuildMetadata, Version}; use std::{ + collections::HashSet, fmt::Debug, path::{Path, PathBuf}, }; @@ -27,7 +35,7 @@ mod standard_json; pub static RE_BUILD_COMMIT: Lazy = Lazy::new(|| Regex::new(r"(?Pcommit\.[0-9,a-f]{8})").unwrap()); -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] #[non_exhaustive] pub struct EtherscanVerificationProvider { /// Memoized cached entry of the target contract @@ -105,9 +113,9 @@ impl VerificationProvider for EtherscanVerificationProvider { warn!("Failed verify submission: {:?}", resp); eprintln!( - "Encountered an error verifying this contract:\nResponse: `{}`\nDetails: `{}`", - resp.message, resp.result - ); + "Encountered an error verifying this contract:\nResponse: `{}`\nDetails: `{}`", + resp.message, resp.result + ); std::process::exit(1); } @@ -117,8 +125,7 @@ impl VerificationProvider for EtherscanVerificationProvider { if let Some(resp) = resp { println!( - "Submitted contract for verification:\n\tResponse: `{}`\n\tGUID: `{}`\n\tURL: - {}", + "Submitted contract for verification:\n\tResponse: `{}`\n\tGUID: `{}`\n\tURL: {}", resp.message, resp.result, etherscan.address_url(args.address) @@ -147,7 +154,7 @@ impl VerificationProvider for EtherscanVerificationProvider { let etherscan = self.client( args.etherscan.chain.unwrap_or_default(), args.verifier.verifier_url.as_deref(), - args.etherscan.key.as_deref(), + args.etherscan.key().as_deref(), &config, )?; let retry: Retry = args.retry.into(); @@ -212,15 +219,28 @@ impl EtherscanVerificationProvider { fn cache_entry( &mut self, project: &Project, - contract_name: &str, + contract: &ContractInfo, ) -> Result<&(PathBuf, CacheEntry, CompactContract)> { if let Some(ref entry) = self.cached_entry { return Ok(entry) } let cache = project.read_cache_file()?; - let (path, entry) = get_cached_entry_by_name(&cache, contract_name)?; - let contract: CompactContract = cache.read_artifact(path.clone(), contract_name)?; + let (path, entry) = if let Some(path) = contract.path.as_ref() { + let path = project.root().join(path); + ( + path.clone(), + cache + .entry(&path) + .ok_or_else(|| { + eyre::eyre!(format!("Cache entry not found for {}", path.display())) + })? + .to_owned(), + ) + } else { + get_cached_entry_by_name(&cache, &contract.name)? + }; + let contract: CompactContract = cache.read_artifact(path.clone(), &contract.name)?; Ok(self.cached_entry.insert((path, entry, contract))) } @@ -230,7 +250,7 @@ impl EtherscanVerificationProvider { let etherscan = self.client( args.etherscan.chain.unwrap_or_default(), args.verifier.verifier_url.as_deref(), - args.etherscan.key.as_deref(), + args.etherscan.key().as_deref(), &config, )?; let verify_args = self.create_verify_request(args, Some(config)).await?; @@ -268,8 +288,7 @@ impl EtherscanVerificationProvider { let etherscan_api_url = verifier_url .or_else(|| etherscan_config.as_ref().map(|c| c.api_url.as_str())) - .map(str::to_owned) - .map(|url| if url.ends_with('?') { url } else { url + "?" }); + .map(str::to_owned); let api_url = etherscan_api_url.as_deref(); let base_url = etherscan_config @@ -283,7 +302,12 @@ impl EtherscanVerificationProvider { let mut builder = Client::builder(); builder = if let Some(api_url) = api_url { - builder.with_api_url(api_url)?.with_url(base_url.unwrap_or(api_url))? + // we don't want any trailing slashes because this can cause cloudflare issues: + let api_url = api_url.trim_end_matches('/'); + builder + .with_chain_id(chain) + .with_api_url(api_url)? + .with_url(base_url.unwrap_or(api_url))? } else { builder.chain(chain)? }; @@ -316,12 +340,20 @@ impl EtherscanVerificationProvider { self.source_provider(args).source(args, &project, &contract_path, &compiler_version)?; let compiler_version = format!("v{}", ensure_solc_build_metadata(compiler_version).await?); - let constructor_args = self.constructor_args(args, &project)?; + let constructor_args = self.constructor_args(args, &project, &config).await?; let mut verify_args = VerifyContract::new(args.address, contract_name, source, compiler_version) .constructor_arguments(constructor_args) .code_format(code_format); + if args.via_ir { + // we explicitly set this __undocumented__ argument to true if provided by the user, + // though this info is also available in the compiler settings of the standard json + // object if standard json is used + // unclear how etherscan interprets this field in standard-json mode + verify_args = verify_args.via_ir(true); + } + if code_format == CodeFormat::SingleFile { verify_args = if let Some(optimizations) = args.num_of_optimizations { verify_args.optimized().runs(optimizations as u32) @@ -338,14 +370,13 @@ impl EtherscanVerificationProvider { /// Get the target contract path. If it wasn't provided, attempt a lookup /// in cache. Validate the path indeed exists on disk. fn contract_path(&mut self, args: &VerifyArgs, project: &Project) -> Result { - let path = match args.contract.path.as_ref() { - Some(path) => project.root().join(path), - None => { - let (path, _, _) = self.cache_entry(project, &args.contract.name).wrap_err( - "If cache is disabled, contract info must be provided in the format :", - )?; - path.to_owned() - } + let path = if let Some(path) = args.contract.path.as_ref() { + project.root().join(path) + } else { + let (path, _, _) = self.cache_entry(project, &args.contract).wrap_err( + "If cache is disabled, contract info must be provided in the format :", + )?; + path.to_owned() }; // check that the provided contract is part of the source dir @@ -382,35 +413,44 @@ impl EtherscanVerificationProvider { } } - let (_, entry, _) = self.cache_entry(project, &args.contract.name).wrap_err( + let (_, entry, _) = self.cache_entry(project, &args.contract).wrap_err( "If cache is disabled, compiler version must be either provided with `--compiler-version` option or set in foundry.toml" )?; let artifacts = entry.artifacts_versions().collect::>(); - if artifacts.len() == 1 { - let mut version = artifacts[0].0.to_owned(); - version.build = match RE_BUILD_COMMIT.captures(version.build.as_str()) { - Some(cap) => BuildMetadata::new(cap.name("commit").unwrap().as_str())?, - _ => BuildMetadata::EMPTY, - }; - return Ok(version) - } if artifacts.is_empty() { - warn!("No artifacts detected") - } else { - let versions = artifacts.iter().map(|a| a.0.to_string()).collect::>(); + eyre::bail!("No matching artifact found for {}", args.contract.name); + } + + // ensure we have a single version + let unique_versions = artifacts.iter().map(|a| a.0.to_string()).collect::>(); + if unique_versions.len() > 1 { + let versions = unique_versions.into_iter().collect::>(); warn!("Ambiguous compiler versions found in cache: {}", versions.join(", ")); + eyre::bail!("Compiler version has to be set in `foundry.toml`. If the project was not deployed with foundry, specify the version through `--compiler-version` flag.") } - eyre::bail!("Compiler version has to be set in `foundry.toml`. If the project was not deployed with foundry, specify the version through `--compiler-version` flag.") + // we have a unique version + let mut version = artifacts[0].0.clone(); + version.build = match RE_BUILD_COMMIT.captures(version.build.as_str()) { + Some(cap) => BuildMetadata::new(cap.name("commit").unwrap().as_str())?, + _ => BuildMetadata::EMPTY, + }; + + Ok(version) } /// Return the optional encoded constructor arguments. If the path to /// constructor arguments was provided, read them and encode. Otherwise, /// return whatever was set in the [VerifyArgs] args. - fn constructor_args(&mut self, args: &VerifyArgs, project: &Project) -> Result> { + async fn constructor_args( + &mut self, + args: &VerifyArgs, + project: &Project, + config: &Config, + ) -> Result> { if let Some(ref constructor_args_path) = args.constructor_args_path { - let (_, _, contract) = self.cache_entry(project, &args.contract.name).wrap_err( + let (_, _, contract) = self.cache_entry(project, &args.contract).wrap_err( "Cache must be enabled in order to use the `--constructor-args-path` option", )?; let abi = @@ -432,9 +472,71 @@ impl EtherscanVerificationProvider { let encoded_args = hex::encode(encoded_args); return Ok(Some(encoded_args[8..].into())) } + if args.guess_constructor_args { + return Ok(Some(self.guess_constructor_args(args, project, config).await?)) + } Ok(args.constructor_args.clone()) } + + /// Uses Etherscan API to fetch contract creation transaction. + /// If transaction is a create transaction or a invocation of default CREATE2 deployer, tries to + /// match provided creation code with local bytecode of the target contract. + /// If bytecode match, returns latest bytes of on-chain creation code as constructor arguments. + async fn guess_constructor_args( + &mut self, + args: &VerifyArgs, + project: &Project, + config: &Config, + ) -> Result { + let provider = utils::get_provider(config)?; + let client = self.client( + args.etherscan.chain.unwrap_or_default(), + args.verifier.verifier_url.as_deref(), + args.etherscan.key.as_deref(), + config, + )?; + + let creation_data = client.contract_creation_data(args.address).await?; + let transaction = provider.get_transaction_by_hash(creation_data.transaction_hash).await?; + let receipt = provider + .get_transaction_receipt(creation_data.transaction_hash) + .await? + .ok_or_eyre("Couldn't fetch transaction receipt from RPC")?; + + let maybe_creation_code: &[u8]; + + if receipt.contract_address == Some(args.address) { + maybe_creation_code = &transaction.input; + } else if transaction.to == Some(DEFAULT_CREATE2_DEPLOYER) { + maybe_creation_code = &transaction.input[32..]; + } else { + eyre::bail!("Fetching of constructor arguments is not supported for contracts created by contracts") + } + + let contract_path = self.contract_path(args, project)?.to_string_lossy().into_owned(); + let output = project.compile()?; + let artifact = output + .find(contract_path, &args.contract.name) + .ok_or_eyre("Contract artifact wasn't found locally")?; + let bytecode = artifact + .get_bytecode_object() + .ok_or_eyre("Contract artifact does not contain bytecode")?; + + let bytecode = match bytecode.as_ref() { + BytecodeObject::Bytecode(bytes) => Ok(bytes), + BytecodeObject::Unlinked(_) => { + Err(eyre!("You have to provide correct libraries to use --guess-constructor-args")) + } + }?; + + if maybe_creation_code.starts_with(bytecode) { + let constructor_args = &maybe_creation_code[bytecode.len()..]; + Ok(hex::encode(constructor_args)) + } else { + eyre::bail!("Local bytecode doesn't match on-chain bytecode") + } + } } /// Given any solc [Version] return a [Version] with build metadata @@ -459,8 +561,8 @@ async fn ensure_solc_build_metadata(version: Version) -> Result { mod tests { use super::*; use clap::Parser; - use foundry_cli::utils::LoadConfig; use foundry_common::fs; + use foundry_test_utils::forgetest_async; use tempfile::tempdir; #[test] @@ -495,11 +597,11 @@ mod tests { .client( args.etherscan.chain.unwrap_or_default(), args.verifier.verifier_url.as_deref(), - args.etherscan.key.as_deref(), + args.etherscan.key().as_deref(), &config, ) .unwrap(); - assert_eq!(client.etherscan_api_url().as_str(), "https://api-testnet.polygonscan.com/?/"); + assert_eq!(client.etherscan_api_url().as_str(), "https://api-testnet.polygonscan.com/"); assert!(format!("{client:?}").contains("dummykey")); @@ -522,11 +624,11 @@ mod tests { .client( args.etherscan.chain.unwrap_or_default(), args.verifier.verifier_url.as_deref(), - args.etherscan.key.as_deref(), + args.etherscan.key().as_deref(), &config, ) .unwrap(); - assert_eq!(client.etherscan_api_url().as_str(), "https://verifier-url.com/?/"); + assert_eq!(client.etherscan_api_url().as_str(), "https://verifier-url.com/"); assert!(format!("{client:?}").contains("dummykey")); } @@ -600,4 +702,22 @@ mod tests { "Cache must be enabled in order to use the `--constructor-args-path` option", ); } + + forgetest_async!(respects_path_for_duplicate, |prj, cmd| { + prj.add_source("Counter1", "contract Counter {}").unwrap(); + prj.add_source("Counter2", "contract Counter {}").unwrap(); + + cmd.args(["build", "--force"]).ensure_execute_success().unwrap(); + + let args = VerifyArgs::parse_from([ + "foundry-cli", + "0x0000000000000000000000000000000000000000", + "src/Counter1.sol:Counter", + "--root", + &prj.root().to_string_lossy(), + ]); + + let mut etherscan = EtherscanVerificationProvider::default(); + etherscan.preflight_check(args).await.unwrap(); + }); } diff --git a/crates/forge/bin/cmd/verify/etherscan/standard_json.rs b/crates/verify/src/etherscan/standard_json.rs similarity index 100% rename from crates/forge/bin/cmd/verify/etherscan/standard_json.rs rename to crates/verify/src/etherscan/standard_json.rs diff --git a/crates/forge/bin/cmd/verify/mod.rs b/crates/verify/src/lib.rs similarity index 66% rename from crates/forge/bin/cmd/verify/mod.rs rename to crates/verify/src/lib.rs index 1bcdd4c8baff5..ee6dd13f63fce 100644 --- a/crates/forge/bin/cmd/verify/mod.rs +++ b/crates/verify/src/lib.rs @@ -1,9 +1,17 @@ -use super::retry::RetryArgs; +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +#[macro_use] +extern crate tracing; + use alloy_primitives::Address; use clap::{Parser, ValueHint}; use eyre::Result; -use foundry_cli::{opts::EtherscanOpts, utils::LoadConfig}; -use foundry_compilers::info::ContractInfo; +use foundry_cli::{ + opts::{EtherscanOpts, RpcOpts}, + utils, + utils::LoadConfig, +}; +use foundry_compilers::{info::ContractInfo, EvmVersion}; use foundry_config::{figment, impl_figment_convert, impl_figment_convert_cast, Config}; use provider::VerificationProviderType; use reqwest::Url; @@ -15,17 +23,21 @@ use etherscan::EtherscanVerificationProvider; pub mod provider; use provider::VerificationProvider; +pub mod bytecode; +pub mod retry; mod sourcify; +pub use retry::RetryArgs; + /// Verification provider arguments -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct VerifierArgs { /// The contract verification provider to use. - #[clap(long, help_heading = "Verifier options", default_value = "etherscan", value_enum)] + #[arg(long, help_heading = "Verifier options", default_value = "etherscan", value_enum)] pub verifier: VerificationProviderType, /// The verifier URL, if using a custom provider - #[clap(long, help_heading = "Verifier options", env = "VERIFIER_URL")] + #[arg(long, help_heading = "Verifier options", env = "VERIFIER_URL")] pub verifier_url: Option, } @@ -36,7 +48,7 @@ impl Default for VerifierArgs { } /// CLI arguments for `forge verify`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct VerifyArgs { /// The address of the contract to verify. pub address: Address, @@ -45,62 +57,84 @@ pub struct VerifyArgs { pub contract: ContractInfo, /// The ABI-encoded constructor arguments. - #[clap(long, conflicts_with = "constructor_args_path", value_name = "ARGS")] + #[arg( + long, + conflicts_with = "constructor_args_path", + value_name = "ARGS", + visible_alias = "encoded-constructor-args" + )] pub constructor_args: Option, /// The path to a file containing the constructor arguments. - #[clap(long, value_hint = ValueHint::FilePath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::FilePath, value_name = "PATH")] pub constructor_args_path: Option, + /// Try to extract constructor arguments from on-chain creation code. + #[arg(long)] + pub guess_constructor_args: bool, + /// The `solc` version to use to build the smart contract. - #[clap(long, value_name = "VERSION")] + #[arg(long, value_name = "VERSION")] pub compiler_version: Option, /// The number of optimization runs used to build the smart contract. - #[clap(long, visible_alias = "optimizer-runs", value_name = "NUM")] + #[arg(long, visible_alias = "optimizer-runs", value_name = "NUM")] pub num_of_optimizations: Option, /// Flatten the source code before verifying. - #[clap(long)] + #[arg(long)] pub flatten: bool, /// Do not compile the flattened smart contract before verifying (if --flatten is passed). - #[clap(short, long)] + #[arg(short, long)] pub force: bool, /// Do not check if the contract is already verified before verifying. - #[clap(long)] + #[arg(long)] pub skip_is_verified_check: bool, /// Wait for verification result after submission. - #[clap(long)] + #[arg(long)] pub watch: bool, /// Set pre-linked libraries. - #[clap(long, help_heading = "Linker options", env = "DAPP_LIBRARIES")] + #[arg(long, help_heading = "Linker options", env = "DAPP_LIBRARIES")] pub libraries: Vec, /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] pub root: Option, /// Prints the standard json compiler input. /// /// The standard json compiler input can be used to manually submit contract verification in /// the browser. - #[clap(long, conflicts_with = "flatten")] + #[arg(long, conflicts_with = "flatten")] pub show_standard_json_input: bool, - #[clap(flatten)] + /// Use the Yul intermediate representation compilation pipeline. + #[arg(long)] + pub via_ir: bool, + + /// The EVM version to use. + /// + /// Overrides the version specified in the config. + #[arg(long)] + pub evm_version: Option, + + #[command(flatten)] pub etherscan: EtherscanOpts, - #[clap(flatten)] + #[command(flatten)] + pub rpc: RpcOpts, + + #[command(flatten)] pub retry: RetryArgs, - #[clap(flatten)] + #[command(flatten)] pub verifier: VerifierArgs, } @@ -110,10 +144,13 @@ impl figment::Provider for VerifyArgs { fn metadata(&self) -> figment::Metadata { figment::Metadata::named("Verify Provider") } + fn data( &self, ) -> Result, figment::Error> { let mut dict = self.etherscan.dict(); + dict.extend(self.rpc.dict()); + if let Some(root) = self.root.as_ref() { dict.insert("root".to_string(), figment::value::Value::serialize(root)?); } @@ -124,6 +161,12 @@ impl figment::Provider for VerifyArgs { figment::value::Value::serialize(optimizer_runs)?, ); } + if let Some(evm_version) = self.evm_version { + dict.insert("evm_version".to_string(), figment::value::Value::serialize(evm_version)?); + } + if self.via_ir { + dict.insert("via_ir".to_string(), figment::value::Value::serialize(self.via_ir)?); + } Ok(figment::value::Map::from([(Config::selected_profile(), dict)])) } } @@ -132,7 +175,23 @@ impl VerifyArgs { /// Run the verify command to submit the contract's source code for verification on etherscan pub async fn run(mut self) -> Result<()> { let config = self.load_config_emit_warnings(); - let chain = config.chain.unwrap_or_default(); + + if self.guess_constructor_args && config.get_rpc_url().is_none() { + eyre::bail!( + "You have to provide a valid RPC URL to use --guess-constructor-args feature" + ) + } + + // If chain is not set, we try to get it from the RPC + // If RPC is not set, the default chain is used + let chain = match config.get_rpc_url() { + Some(_) => { + let provider = utils::get_provider(&config)?; + utils::get_chain(config.chain, provider).await? + } + None => config.chain.unwrap_or_default(), + }; + self.etherscan.chain = Some(chain); self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key); @@ -145,7 +204,7 @@ impl VerifyArgs { let verifier_url = self.verifier.verifier_url.clone(); println!("Start verifying contract `{}` deployed on {chain}", self.address); - self.verifier.verifier.client(&self.etherscan.key)?.verify(self).await.map_err(|err| { + self.verifier.verifier.client(&self.etherscan.key())?.verify(self).await.map_err(|err| { if let Some(verifier_url) = verifier_url { match Url::parse(&verifier_url) { Ok(url) => { @@ -169,12 +228,12 @@ impl VerifyArgs { /// Returns the configured verification provider pub fn verification_provider(&self) -> Result> { - self.verifier.verifier.client(&self.etherscan.key) + self.verifier.verifier.client(&self.etherscan.key()) } } /// Check verification status arguments -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct VerifyCheckArgs { /// The verification ID. /// @@ -183,13 +242,13 @@ pub struct VerifyCheckArgs { /// For Sourcify - Contract Address. id: String, - #[clap(flatten)] + #[command(flatten)] retry: RetryArgs, - #[clap(flatten)] + #[command(flatten)] etherscan: EtherscanOpts, - #[clap(flatten)] + #[command(flatten)] verifier: VerifierArgs, } @@ -199,7 +258,7 @@ impl VerifyCheckArgs { /// Run the verify command to submit the contract's source code for verification on etherscan pub async fn run(self) -> Result<()> { println!("Checking verification status on {}", self.etherscan.chain.unwrap_or_default()); - self.verifier.verifier.client(&self.etherscan.key)?.check(self).await + self.verifier.verifier.client(&self.etherscan.key())?.check(self).await } } @@ -233,4 +292,15 @@ mod tests { assert!(is_host_only(&Url::parse("https://blockscout.net/").unwrap())); assert!(is_host_only(&Url::parse("https://blockscout.net").unwrap())); } + + #[test] + fn can_parse_verify_contract() { + let args: VerifyArgs = VerifyArgs::parse_from([ + "foundry-cli", + "0x0000000000000000000000000000000000000000", + "src/Domains.sol:Domains", + "--via-ir", + ]); + assert!(args.via_ir); + } } diff --git a/crates/forge/bin/cmd/verify/provider.rs b/crates/verify/src/provider.rs similarity index 97% rename from crates/forge/bin/cmd/verify/provider.rs rename to crates/verify/src/provider.rs index 08f4a65e2146e..d5721018dce84 100644 --- a/crates/forge/bin/cmd/verify/provider.rs +++ b/crates/verify/src/provider.rs @@ -55,7 +55,7 @@ impl fmt::Display for VerificationProviderType { } } -#[derive(clap::ValueEnum, Debug, Default, Clone, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq, clap::ValueEnum)] pub enum VerificationProviderType { #[default] Etherscan, diff --git a/crates/forge/bin/cmd/retry.rs b/crates/verify/src/retry.rs similarity index 91% rename from crates/forge/bin/cmd/retry.rs rename to crates/verify/src/retry.rs index 4f9d979632361..8ffc61b88dc57 100644 --- a/crates/forge/bin/cmd/retry.rs +++ b/crates/verify/src/retry.rs @@ -9,19 +9,19 @@ pub const RETRY_CHECK_ON_VERIFY: RetryArgs = RetryArgs { retries: 8, delay: 15 } pub const RETRY_VERIFY_ON_CREATE: RetryArgs = RetryArgs { retries: 15, delay: 5 }; /// Retry arguments for contract verification. -#[derive(Debug, Clone, Copy, Parser)] -#[clap(about = "Allows to use retry arguments for contract verification")] // override doc +#[derive(Clone, Copy, Debug, Parser)] +#[command(about = "Allows to use retry arguments for contract verification")] // override doc pub struct RetryArgs { /// Number of attempts for retrying verification. - #[clap( + #[arg( long, - value_parser = RangedU64ValueParser::::new().range(1..=10), + value_parser = RangedU64ValueParser::::new().range(1..), default_value = "5", )] pub retries: u32, /// Optional delay to apply inbetween verification attempts, in seconds. - #[clap( + #[arg( long, value_parser = RangedU64ValueParser::::new().range(0..=30), default_value = "5", diff --git a/crates/forge/bin/cmd/verify/sourcify.rs b/crates/verify/src/sourcify.rs similarity index 75% rename from crates/forge/bin/cmd/verify/sourcify.rs rename to crates/verify/src/sourcify.rs index 3a4593c4e15d6..d30946f8945e6 100644 --- a/crates/forge/bin/cmd/verify/sourcify.rs +++ b/crates/verify/src/sourcify.rs @@ -5,13 +5,14 @@ use foundry_cli::utils::{get_cached_entry_by_name, LoadConfig}; use foundry_common::{fs, retry::Retry}; use foundry_compilers::ConfigurableContractArtifact; use futures::FutureExt; +use reqwest::Url; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, path::PathBuf}; +use std::{collections::HashMap, path::PathBuf, str::FromStr}; pub static SOURCIFY_URL: &str = "https://sourcify.dev/server/"; /// The type that can verify a contract on `sourcify` -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] #[non_exhaustive] pub struct SourcifyVerificationProvider; @@ -48,14 +49,10 @@ impl VerificationProvider for SourcifyVerificationProvider { let status = response.status(); if !status.is_success() { let error: serde_json::Value = response.json().await?; - eprintln!( - "Sourcify verification request for address ({}) failed with status code {}\nDetails: {:#}", - format_args!("{:?}", args.address), - status, - error + eyre::bail!( + "Sourcify verification request for address ({}) failed with status code {status}\nDetails: {error:#}", + args.address, ); - warn!("Failed verify submission: {:?}", error); - std::process::exit(1); } let text = response.text().await?; @@ -65,8 +62,7 @@ impl VerificationProvider for SourcifyVerificationProvider { }) .await?; - self.process_sourcify_response(resp.map(|r| r.result)); - Ok(()) + self.process_sourcify_response(resp.map(|r| r.result)) } async fn check(&self, args: VerifyCheckArgs) -> Result<()> { @@ -74,20 +70,21 @@ impl VerificationProvider for SourcifyVerificationProvider { let resp = retry .run_async(|| { async { - let url = format!( - "{}check-by-addresses?addresses={}&chainIds={}", + let url = Url::from_str( args.verifier.verifier_url.as_deref().unwrap_or(SOURCIFY_URL), + )?; + let query = format!( + "check-by-addresses?addresses={}&chainIds={}", args.id, args.etherscan.chain.unwrap_or_default().id(), ); - + let url = url.join(&query)?; let response = reqwest::get(url).await?; if !response.status().is_success() { - eprintln!( + eyre::bail!( "Failed to request verification status with status code {}", response.status() ); - std::process::exit(1); }; Ok(Some(response.json::>().await?)) @@ -96,8 +93,7 @@ impl VerificationProvider for SourcifyVerificationProvider { }) .await?; - self.process_sourcify_response(resp); - Ok(()) + self.process_sourcify_response(resp) } } @@ -165,26 +161,30 @@ metadata output can be enabled via `extra_output = ["metadata"]` in `foundry.tom Ok(req) } - fn process_sourcify_response(&self, response: Option>) { - let response = response.unwrap().remove(0); - if response.status == "perfect" { - if let Some(ts) = response.storage_timestamp { - println!("Contract source code already verified. Storage Timestamp: {ts}"); - } else { - println!("Contract successfully verified") + fn process_sourcify_response( + &self, + response: Option>, + ) -> Result<()> { + let Some([response, ..]) = response.as_deref() else { return Ok(()) }; + match response.status.as_str() { + "perfect" => { + if let Some(ts) = &response.storage_timestamp { + println!("Contract source code already verified. Storage Timestamp: {ts}"); + } else { + println!("Contract successfully verified"); + } } - } else if response.status == "partial" { - println!("The recompiled contract partially matches the deployed version") - } else if response.status == "false" { - println!("Contract source code is not verified") - } else { - eprintln!("Unknown status from sourcify. Status: {}", response.status); - std::process::exit(1); + "partial" => { + println!("The recompiled contract partially matches the deployed version"); + } + "false" => println!("Contract source code is not verified"), + s => eyre::bail!("Unknown status from sourcify. Status: {s:?}"), } + Ok(()) } } -#[derive(Serialize, Debug)] +#[derive(Debug, Serialize)] pub struct SourcifyVerifyRequest { address: String, chain: String, @@ -193,14 +193,29 @@ pub struct SourcifyVerifyRequest { chosen_contract: Option, } -#[derive(Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct SourcifyVerificationResponse { result: Vec, } -#[derive(Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct SourcifyResponseElement { status: String, #[serde(rename = "storageTimestamp")] storage_timestamp: Option, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_check_addresses_url() { + let url = Url::from_str("https://server-verify.hashscan.io").unwrap(); + let url = url.join("check-by-addresses?addresses=0x1234&chainIds=1").unwrap(); + assert_eq!( + url.as_str(), + "https://server-verify.hashscan.io/check-by-addresses?addresses=0x1234&chainIds=1" + ); + } +} diff --git a/crates/wallets/Cargo.toml b/crates/wallets/Cargo.toml new file mode 100644 index 0000000000000..31ea5607ce964 --- /dev/null +++ b/crates/wallets/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "foundry-wallets" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +alloy-primitives.workspace = true +alloy-signer = { workspace = true, features = ["eip712"] } +alloy-signer-wallet = { workspace = true, features = ["mnemonic", "keystore"] } +alloy-signer-aws.workspace = true +alloy-signer-ledger.workspace = true +alloy-signer-trezor.workspace = true +alloy-network.workspace = true +alloy-consensus.workspace = true +alloy-sol-types.workspace = true +alloy-dyn-abi.workspace = true + +aws-sdk-kms = { version = "1", default-features = false } +aws-config = "1" + +foundry-config.workspace = true +foundry-common.workspace = true + +async-trait = "0.1" +clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } +derive_builder = "0.20.0" +eyre.workspace = true +hex = { workspace = true, features = ["serde"] } +itertools.workspace = true +rpassword = "7" +serde.workspace = true +thiserror = "1" +tracing.workspace = true + +[dev-dependencies] +tokio = { version = "1", features = ["macros"] } + +[features] +default = ["rustls"] +rustls = ["aws-sdk-kms/rustls"] diff --git a/crates/wallets/src/error.rs b/crates/wallets/src/error.rs new file mode 100644 index 0000000000000..b9a5e34f592ab --- /dev/null +++ b/crates/wallets/src/error.rs @@ -0,0 +1,34 @@ +use alloy_signer::k256::ecdsa; +use alloy_signer_aws::AwsSignerError; +use alloy_signer_ledger::LedgerError; +use alloy_signer_trezor::TrezorError; +use alloy_signer_wallet::WalletError; +use hex::FromHexError; + +#[derive(Debug, thiserror::Error)] +pub enum PrivateKeyError { + #[error("Failed to create wallet from private key. Private key is invalid hex: {0}")] + InvalidHex(#[from] FromHexError), + #[error("Failed to create wallet from private key. Invalid private key. But env var {0} exists. Is the `$` anchor missing?")] + ExistsAsEnvVar(String), +} + +#[derive(Debug, thiserror::Error)] +pub enum WalletSignerError { + #[error(transparent)] + Local(#[from] WalletError), + #[error(transparent)] + Ledger(#[from] LedgerError), + #[error(transparent)] + Trezor(#[from] TrezorError), + #[error(transparent)] + Aws(#[from] AwsSignerError), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + InvalidHex(#[from] FromHexError), + #[error(transparent)] + Ecdsa(#[from] ecdsa::Error), + #[error("{0} cannot sign raw hashes")] + CannotSignRawHash(&'static str), +} diff --git a/crates/wallets/src/lib.rs b/crates/wallets/src/lib.rs new file mode 100644 index 0000000000000..38ff5e7fa91a0 --- /dev/null +++ b/crates/wallets/src/lib.rs @@ -0,0 +1,14 @@ +#[macro_use] +extern crate tracing; + +pub mod error; +pub mod multi_wallet; +pub mod raw_wallet; +pub mod utils; +pub mod wallet; +pub mod wallet_signer; + +pub use multi_wallet::MultiWalletOpts; +pub use raw_wallet::RawWalletOpts; +pub use wallet::WalletOpts; +pub use wallet_signer::{PendingSigner, WalletSigner}; diff --git a/crates/wallets/src/multi_wallet.rs b/crates/wallets/src/multi_wallet.rs new file mode 100644 index 0000000000000..c95bb8d0e2ae0 --- /dev/null +++ b/crates/wallets/src/multi_wallet.rs @@ -0,0 +1,474 @@ +use crate::{ + utils, + wallet_signer::{PendingSigner, WalletSigner}, +}; +use alloy_primitives::Address; +use alloy_signer::Signer; +use clap::Parser; +use derive_builder::Builder; +use eyre::Result; +use foundry_config::Config; +use serde::Serialize; +use std::{collections::HashMap, iter::repeat, path::PathBuf}; + +/// Container for multiple wallets. +#[derive(Debug, Default)] +pub struct MultiWallet { + /// Vector of wallets that require an action to be unlocked. + /// Those are lazily unlocked on the first access of the signers. + pending_signers: Vec, + /// Contains unlocked signers. + signers: HashMap, +} + +impl MultiWallet { + pub fn new(pending_signers: Vec, signers: Vec) -> Self { + let signers = signers.into_iter().map(|signer| (signer.address(), signer)).collect(); + Self { pending_signers, signers } + } + + fn maybe_unlock_pending(&mut self) -> Result<()> { + for pending in self.pending_signers.drain(..) { + let signer = pending.unlock()?; + self.signers.insert(signer.address(), signer); + } + Ok(()) + } + + pub fn signers(&mut self) -> Result<&HashMap> { + self.maybe_unlock_pending()?; + Ok(&self.signers) + } + + pub fn into_signers(mut self) -> Result> { + self.maybe_unlock_pending()?; + Ok(self.signers) + } + + pub fn add_signer(&mut self, signer: WalletSigner) { + self.signers.insert(signer.address(), signer); + } +} + +/// A macro that initializes multiple wallets +/// +/// Should be used with a [`MultiWallet`] instance +macro_rules! create_hw_wallets { + ($self:ident, $create_signer:expr, $signers:ident) => { + let mut $signers = vec![]; + + if let Some(hd_paths) = &$self.hd_paths { + for path in hd_paths { + let hw = $create_signer(Some(path), 0).await?; + $signers.push(hw); + } + } + + if let Some(mnemonic_indexes) = &$self.mnemonic_indexes { + for index in mnemonic_indexes { + let hw = $create_signer(None, *index).await?; + $signers.push(hw); + } + } + + if $signers.is_empty() { + let hw = $create_signer(None, 0).await?; + $signers.push(hw); + } + }; +} + +/// The wallet options can either be: +/// 1. Ledger +/// 2. Trezor +/// 3. Mnemonics (via file path) +/// 4. Keystores (via file path) +/// 5. Private Keys (cleartext in CLI) +/// 6. Private Keys (interactively via secure prompt) +/// 7. AWS KMS +#[derive(Builder, Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Wallet options", about = None, long_about = None)] +pub struct MultiWalletOpts { + /// The sender accounts. + #[arg( + long, + short = 'a', + help_heading = "Wallet options - raw", + value_name = "ADDRESSES", + env = "ETH_FROM", + num_args(0..), + )] + #[builder(default = "None")] + pub froms: Option>, + + /// Open an interactive prompt to enter your private key. + /// + /// Takes a value for the number of keys to enter. + #[arg( + long, + short, + help_heading = "Wallet options - raw", + default_value = "0", + value_name = "NUM" + )] + pub interactives: u32, + + /// Use the provided private keys. + #[arg(long, help_heading = "Wallet options - raw", value_name = "RAW_PRIVATE_KEYS")] + #[builder(default = "None")] + pub private_keys: Option>, + + /// Use the provided private key. + #[arg( + long, + help_heading = "Wallet options - raw", + conflicts_with = "private_keys", + value_name = "RAW_PRIVATE_KEY" + )] + #[builder(default = "None")] + pub private_key: Option, + + /// Use the mnemonic phrases of mnemonic files at the specified paths. + #[arg(long, alias = "mnemonic-paths", help_heading = "Wallet options - raw")] + #[builder(default = "None")] + pub mnemonics: Option>, + + /// Use a BIP39 passphrases for the mnemonic. + #[arg(long, help_heading = "Wallet options - raw", value_name = "PASSPHRASE")] + #[builder(default = "None")] + pub mnemonic_passphrases: Option>, + + /// The wallet derivation path. + /// + /// Works with both --mnemonic-path and hardware wallets. + #[arg( + long = "mnemonic-derivation-paths", + alias = "hd-paths", + help_heading = "Wallet options - raw", + value_name = "PATH" + )] + #[builder(default = "None")] + pub hd_paths: Option>, + + /// Use the private key from the given mnemonic index. + /// + /// Can be used with --mnemonics, --ledger, --aws and --trezor. + #[arg( + long, + conflicts_with = "hd_paths", + help_heading = "Wallet options - raw", + default_value = "0", + value_name = "INDEXES" + )] + pub mnemonic_indexes: Option>, + + /// Use the keystore in the given folder or file. + #[arg( + long = "keystore", + visible_alias = "keystores", + help_heading = "Wallet options - keystore", + value_name = "PATHS", + env = "ETH_KEYSTORE" + )] + #[builder(default = "None")] + pub keystore_paths: Option>, + + /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename + #[arg( + long = "account", + visible_alias = "accounts", + help_heading = "Wallet options - keystore", + value_name = "ACCOUNT_NAMES", + env = "ETH_KEYSTORE_ACCOUNT", + conflicts_with = "keystore_paths" + )] + #[builder(default = "None")] + pub keystore_account_names: Option>, + + /// The keystore password. + /// + /// Used with --keystore. + #[arg( + long = "password", + help_heading = "Wallet options - keystore", + requires = "keystore_paths", + value_name = "PASSWORDS" + )] + #[builder(default = "None")] + pub keystore_passwords: Option>, + + /// The keystore password file path. + /// + /// Used with --keystore. + #[arg( + long = "password-file", + help_heading = "Wallet options - keystore", + requires = "keystore_paths", + value_name = "PATHS", + env = "ETH_PASSWORD" + )] + #[builder(default = "None")] + pub keystore_password_files: Option>, + + /// Use a Ledger hardware wallet. + #[arg(long, short, help_heading = "Wallet options - hardware wallet")] + pub ledger: bool, + + /// Use a Trezor hardware wallet. + #[arg(long, short, help_heading = "Wallet options - hardware wallet")] + pub trezor: bool, + + /// Use AWS Key Management Service. + #[arg(long, help_heading = "Wallet options - remote")] + pub aws: bool, +} + +impl MultiWalletOpts { + /// Returns [MultiWallet] container configured with provided options. + pub async fn get_multi_wallet(&self) -> Result { + let mut pending = Vec::new(); + let mut signers: Vec = Vec::new(); + + if let Some(ledgers) = self.ledgers().await? { + signers.extend(ledgers); + } + if let Some(trezors) = self.trezors().await? { + signers.extend(trezors); + } + if let Some(aws_signers) = self.aws_signers().await? { + signers.extend(aws_signers); + } + if let Some((pending_keystores, unlocked)) = self.keystores()? { + pending.extend(pending_keystores); + signers.extend(unlocked); + } + if let Some(pks) = self.private_keys()? { + signers.extend(pks); + } + if let Some(mnemonics) = self.mnemonics()? { + signers.extend(mnemonics); + } + if self.interactives > 0 { + pending.extend(repeat(PendingSigner::Interactive).take(self.interactives as usize)); + } + + Ok(MultiWallet::new(pending, signers)) + } + + pub fn private_keys(&self) -> Result>> { + let mut pks = vec![]; + if let Some(private_key) = &self.private_key { + pks.push(private_key); + } + if let Some(private_keys) = &self.private_keys { + for pk in private_keys { + pks.push(pk); + } + } + if !pks.is_empty() { + let wallets = pks + .into_iter() + .map(|pk| utils::create_private_key_signer(pk)) + .collect::>>()?; + Ok(Some(wallets)) + } else { + Ok(None) + } + } + + fn keystore_paths(&self) -> Result>> { + if let Some(keystore_paths) = &self.keystore_paths { + return Ok(Some(keystore_paths.iter().map(PathBuf::from).collect())); + } + if let Some(keystore_account_names) = &self.keystore_account_names { + let default_keystore_dir = Config::foundry_keystores_dir() + .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; + return Ok(Some( + keystore_account_names + .iter() + .map(|keystore_name| default_keystore_dir.join(keystore_name)) + .collect(), + )); + } + Ok(None) + } + + /// Returns all wallets read from the provided keystores arguments + /// + /// Returns `Ok(None)` if no keystore provided. + pub fn keystores(&self) -> Result, Vec)>> { + if let Some(keystore_paths) = self.keystore_paths()? { + let mut pending = Vec::new(); + let mut signers = Vec::new(); + + let mut passwords_iter = + self.keystore_passwords.clone().unwrap_or_default().into_iter(); + + let mut password_files_iter = + self.keystore_password_files.clone().unwrap_or_default().into_iter(); + + for path in &keystore_paths { + let (maybe_signer, maybe_pending) = utils::create_keystore_signer( + path, + passwords_iter.next().as_deref(), + password_files_iter.next().as_deref(), + )?; + if let Some(pending_signer) = maybe_pending { + pending.push(pending_signer); + } else if let Some(signer) = maybe_signer { + signers.push(signer); + } + } + return Ok(Some((pending, signers))); + } + Ok(None) + } + + pub fn mnemonics(&self) -> Result>> { + if let Some(ref mnemonics) = self.mnemonics { + let mut wallets = vec![]; + + let mut hd_paths_iter = self.hd_paths.clone().unwrap_or_default().into_iter(); + + let mut passphrases_iter = + self.mnemonic_passphrases.clone().unwrap_or_default().into_iter(); + + let mut indexes_iter = self.mnemonic_indexes.clone().unwrap_or_default().into_iter(); + + for mnemonic in mnemonics { + let wallet = utils::create_mnemonic_signer( + mnemonic, + passphrases_iter.next().as_deref(), + hd_paths_iter.next().as_deref(), + indexes_iter.next().unwrap_or(0), + )?; + wallets.push(wallet); + } + return Ok(Some(wallets)); + } + Ok(None) + } + + pub async fn ledgers(&self) -> Result>> { + if self.ledger { + let mut args = self.clone(); + + if let Some(paths) = &args.hd_paths { + if paths.len() > 1 { + eyre::bail!("Ledger only supports one signer."); + } + args.mnemonic_indexes = None; + } + + create_hw_wallets!(args, utils::create_ledger_signer, wallets); + return Ok(Some(wallets)); + } + Ok(None) + } + + pub async fn trezors(&self) -> Result>> { + if self.trezor { + create_hw_wallets!(self, utils::create_trezor_signer, wallets); + return Ok(Some(wallets)); + } + Ok(None) + } + + pub async fn aws_signers(&self) -> Result>> { + if self.aws { + let mut wallets = vec![]; + let aws_keys = std::env::var("AWS_KMS_KEY_IDS") + .or(std::env::var("AWS_KMS_KEY_ID"))? + .split(',') + .map(|k| k.to_string()) + .collect::>(); + + for key in aws_keys { + let aws_signer = WalletSigner::from_aws(key).await?; + wallets.push(aws_signer) + } + + return Ok(Some(wallets)); + } + Ok(None) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{path::Path, str::FromStr}; + + #[test] + fn parse_keystore_args() { + let args: MultiWalletOpts = + MultiWalletOpts::parse_from(["foundry-cli", "--keystores", "my/keystore/path"]); + assert_eq!(args.keystore_paths, Some(vec!["my/keystore/path".to_string()])); + + std::env::set_var("ETH_KEYSTORE", "MY_KEYSTORE"); + let args: MultiWalletOpts = MultiWalletOpts::parse_from(["foundry-cli"]); + assert_eq!(args.keystore_paths, Some(vec!["MY_KEYSTORE".to_string()])); + + std::env::remove_var("ETH_KEYSTORE"); + } + + #[test] + fn parse_keystore_password_file() { + let keystore = + Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../cast/tests/fixtures/keystore")); + let keystore_file = keystore + .join("UTC--2022-12-20T10-30-43.591916000Z--ec554aeafe75601aaab43bd4621a22284db566c2"); + + let keystore_password_file = keystore.join("password-ec554").into_os_string(); + + let args: MultiWalletOpts = MultiWalletOpts::parse_from([ + "foundry-cli", + "--keystores", + keystore_file.to_str().unwrap(), + "--password-file", + keystore_password_file.to_str().unwrap(), + ]); + assert_eq!( + args.keystore_password_files, + Some(vec![keystore_password_file.to_str().unwrap().to_string()]) + ); + + let (_, unlocked) = args.keystores().unwrap().unwrap(); + assert_eq!(unlocked.len(), 1); + assert_eq!( + unlocked[0].address(), + Address::from_str("0xec554aeafe75601aaab43bd4621a22284db566c2").unwrap() + ); + } + + // https://github.com/foundry-rs/foundry/issues/5179 + #[test] + fn should_not_require_the_mnemonics_flag_with_mnemonic_indexes() { + let wallet_options = vec![ + ("ledger", "--mnemonic-indexes", 1), + ("trezor", "--mnemonic-indexes", 2), + ("aws", "--mnemonic-indexes", 10), + ]; + + for test_case in wallet_options { + let args: MultiWalletOpts = MultiWalletOpts::parse_from([ + "foundry-cli", + &format!("--{}", test_case.0), + test_case.1, + &test_case.2.to_string(), + ]); + + match test_case.0 { + "ledger" => assert!(args.ledger), + "trezor" => assert!(args.trezor), + "aws" => assert!(args.aws), + _ => panic!("Should have matched one of the previous wallet options"), + } + + assert_eq!( + args.mnemonic_indexes.expect("--mnemonic-indexes should have been set")[0], + test_case.2 + ) + } + } +} diff --git a/crates/wallets/src/raw_wallet.rs b/crates/wallets/src/raw_wallet.rs new file mode 100644 index 0000000000000..f8a9d447cf9de --- /dev/null +++ b/crates/wallets/src/raw_wallet.rs @@ -0,0 +1,62 @@ +use crate::{utils, PendingSigner, WalletSigner}; +use clap::Parser; +use eyre::Result; +use serde::Serialize; + +/// A wrapper for the raw data options for `Wallet`, extracted to also be used standalone. +/// The raw wallet options can either be: +/// 1. Private Key (cleartext in CLI) +/// 2. Private Key (interactively via secure prompt) +/// 3. Mnemonic (via file path) +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Wallet options - raw", about = None, long_about = None)] +pub struct RawWalletOpts { + /// Open an interactive prompt to enter your private key. + #[arg(long, short)] + pub interactive: bool, + + /// Use the provided private key. + #[arg(long, value_name = "RAW_PRIVATE_KEY")] + pub private_key: Option, + + /// Use the mnemonic phrase of mnemonic file at the specified path. + #[arg(long, alias = "mnemonic-path")] + pub mnemonic: Option, + + /// Use a BIP39 passphrase for the mnemonic. + #[arg(long, value_name = "PASSPHRASE")] + pub mnemonic_passphrase: Option, + + /// The wallet derivation path. + /// + /// Works with both --mnemonic-path and hardware wallets. + #[arg(long = "mnemonic-derivation-path", alias = "hd-path", value_name = "PATH")] + pub hd_path: Option, + + /// Use the private key from the given mnemonic index. + /// + /// Used with --mnemonic-path. + #[arg(long, conflicts_with = "hd_path", default_value_t = 0, value_name = "INDEX")] + pub mnemonic_index: u32, +} + +impl RawWalletOpts { + /// Returns signer configured by provided parameters. + pub fn signer(&self) -> Result> { + if self.interactive { + return Ok(Some(PendingSigner::Interactive.unlock()?)); + } + if let Some(private_key) = &self.private_key { + return Ok(Some(utils::create_private_key_signer(private_key)?)) + } + if let Some(mnemonic) = &self.mnemonic { + return Ok(Some(utils::create_mnemonic_signer( + mnemonic, + self.mnemonic_passphrase.as_deref(), + self.hd_path.as_deref(), + self.mnemonic_index, + )?)) + } + Ok(None) + } +} diff --git a/crates/wallets/src/utils.rs b/crates/wallets/src/utils.rs new file mode 100644 index 0000000000000..08c95242af332 --- /dev/null +++ b/crates/wallets/src/utils.rs @@ -0,0 +1,152 @@ +use crate::{error::PrivateKeyError, PendingSigner, WalletSigner}; +use alloy_primitives::B256; +use alloy_signer_ledger::HDPath as LedgerHDPath; +use alloy_signer_trezor::HDPath as TrezorHDPath; +use alloy_signer_wallet::LocalWallet; +use eyre::{Context, Result}; +use foundry_config::Config; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +fn ensure_pk_not_env(pk: &str) -> Result<()> { + if !pk.starts_with("0x") && std::env::var(pk).is_ok() { + return Err(PrivateKeyError::ExistsAsEnvVar(pk.to_string()).into()); + } + Ok(()) +} + +/// Validates and sanitizes user inputs, returning configured [WalletSigner]. +pub fn create_private_key_signer(private_key: &str) -> Result { + let privk = private_key.trim().strip_prefix("0x").unwrap_or(private_key); + + let Ok(private_key) = hex::decode(privk) else { + ensure_pk_not_env(privk)?; + eyre::bail!("Failed to decode private key") + }; + + match LocalWallet::from_bytes(&B256::from_slice(&private_key)) { + Ok(pk) => Ok(WalletSigner::Local(pk)), + Err(err) => { + ensure_pk_not_env(privk)?; + eyre::bail!("Failed to create wallet from private key: {err}") + } + } +} + +/// Creates [WalletSigner] instance from given mnemonic parameters. +/// +/// Mnemonic can be either a file path or a mnemonic phrase. +pub fn create_mnemonic_signer( + mnemonic: &str, + passphrase: Option<&str>, + hd_path: Option<&str>, + index: u32, +) -> Result { + let mnemonic = if Path::new(mnemonic).is_file() { + fs::read_to_string(mnemonic)?.replace('\n', "") + } else { + mnemonic.to_owned() + }; + + Ok(WalletSigner::from_mnemonic(&mnemonic, passphrase, hd_path, index)?) +} + +/// Creates [WalletSigner] instance from given Ledger parameters. +pub async fn create_ledger_signer( + hd_path: Option<&str>, + mnemonic_index: u32, +) -> Result { + let derivation = if let Some(hd_path) = hd_path { + LedgerHDPath::Other(hd_path.to_owned()) + } else { + LedgerHDPath::LedgerLive(mnemonic_index as usize) + }; + + WalletSigner::from_ledger_path(derivation).await.wrap_err_with(|| { + "\ +Could not connect to Ledger device. +Make sure it's connected and unlocked, with no other desktop wallet apps open." + }) +} + +/// Creates [WalletSigner] instance from given Trezor parameters. +pub async fn create_trezor_signer( + hd_path: Option<&str>, + mnemonic_index: u32, +) -> Result { + let derivation = if let Some(hd_path) = hd_path { + TrezorHDPath::Other(hd_path.to_owned()) + } else { + TrezorHDPath::TrezorLive(mnemonic_index as usize) + }; + + WalletSigner::from_trezor_path(derivation).await.wrap_err_with(|| { + "\ +Could not connect to Trezor device. +Make sure it's connected and unlocked, with no other conflicting desktop wallet apps open." + }) +} + +pub fn maybe_get_keystore_path( + maybe_path: Option<&str>, + maybe_name: Option<&str>, +) -> Result> { + let default_keystore_dir = Config::foundry_keystores_dir() + .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; + Ok(maybe_path + .map(PathBuf::from) + .or_else(|| maybe_name.map(|name| default_keystore_dir.join(name)))) +} + +/// Creates keystore signer from given parameters. +/// +/// If correct password or password file is provided, the keystore is decrypted and a [WalletSigner] +/// is returned. +/// +/// Otherwise, a [PendingSigner] is returned, which can be used to unlock the keystore later, +/// prompting user for password. +pub fn create_keystore_signer( + path: &PathBuf, + maybe_password: Option<&str>, + maybe_password_file: Option<&str>, +) -> Result<(Option, Option)> { + if !path.exists() { + eyre::bail!("Keystore file `{path:?}` does not exist") + } + + if path.is_dir() { + eyre::bail!( + "Keystore path `{path:?}` is a directory. Please specify the keystore file directly." + ) + } + + let password = match (maybe_password, maybe_password_file) { + (Some(password), _) => Ok(Some(password.to_string())), + (_, Some(password_file)) => { + let password_file = Path::new(password_file); + if !password_file.is_file() { + Err(eyre::eyre!("Keystore password file `{password_file:?}` does not exist")) + } else { + Ok(Some( + fs::read_to_string(password_file) + .wrap_err_with(|| { + format!("Failed to read keystore password file at {password_file:?}") + })? + .trim_end() + .to_string(), + )) + } + } + (None, None) => Ok(None), + }?; + + if let Some(password) = password { + let wallet = LocalWallet::decrypt_keystore(path, password) + .wrap_err_with(|| format!("Failed to decrypt keystore {path:?}"))?; + Ok((Some(WalletSigner::Local(wallet)), None)) + } else { + Ok((None, Some(PendingSigner::Keystore(path.clone())))) + } +} diff --git a/crates/wallets/src/wallet.rs b/crates/wallets/src/wallet.rs new file mode 100644 index 0000000000000..5773e57d80b6c --- /dev/null +++ b/crates/wallets/src/wallet.rs @@ -0,0 +1,215 @@ +use crate::{raw_wallet::RawWalletOpts, utils, wallet_signer::WalletSigner}; +use alloy_primitives::Address; +use alloy_signer::Signer; +use clap::Parser; +use eyre::Result; +use serde::Serialize; + +/// The wallet options can either be: +/// 1. Raw (via private key / mnemonic file, see `RawWallet`) +/// 2. Ledger +/// 3. Trezor +/// 4. Keystore (via file path) +/// 5. AWS KMS +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Wallet options", about = None, long_about = None)] +pub struct WalletOpts { + /// The sender account. + #[arg( + long, + short, + value_name = "ADDRESS", + help_heading = "Wallet options - raw", + env = "ETH_FROM" + )] + pub from: Option
, + + #[command(flatten)] + pub raw: RawWalletOpts, + + /// Use the keystore in the given folder or file. + #[arg( + long = "keystore", + help_heading = "Wallet options - keystore", + value_name = "PATH", + env = "ETH_KEYSTORE" + )] + pub keystore_path: Option, + + /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename + #[arg( + long = "account", + help_heading = "Wallet options - keystore", + value_name = "ACCOUNT_NAME", + env = "ETH_KEYSTORE_ACCOUNT", + conflicts_with = "keystore_path" + )] + pub keystore_account_name: Option, + + /// The keystore password. + /// + /// Used with --keystore. + #[arg( + long = "password", + help_heading = "Wallet options - keystore", + requires = "keystore_path", + value_name = "PASSWORD" + )] + pub keystore_password: Option, + + /// The keystore password file path. + /// + /// Used with --keystore. + #[arg( + long = "password-file", + help_heading = "Wallet options - keystore", + requires = "keystore_path", + value_name = "PASSWORD_FILE", + env = "ETH_PASSWORD" + )] + pub keystore_password_file: Option, + + /// Use a Ledger hardware wallet. + #[arg(long, short, help_heading = "Wallet options - hardware wallet")] + pub ledger: bool, + + /// Use a Trezor hardware wallet. + #[arg(long, short, help_heading = "Wallet options - hardware wallet")] + pub trezor: bool, + + /// Use AWS Key Management Service. + #[arg(long, help_heading = "Wallet options - AWS KMS")] + pub aws: bool, +} + +impl WalletOpts { + pub async fn signer(&self) -> Result { + trace!("start finding signer"); + + let signer = if self.ledger { + utils::create_ledger_signer(self.raw.hd_path.as_deref(), self.raw.mnemonic_index) + .await? + } else if self.trezor { + utils::create_trezor_signer(self.raw.hd_path.as_deref(), self.raw.mnemonic_index) + .await? + } else if self.aws { + let key_id = std::env::var("AWS_KMS_KEY_ID")?; + WalletSigner::from_aws(key_id).await? + } else if let Some(raw_wallet) = self.raw.signer()? { + raw_wallet + } else if let Some(path) = utils::maybe_get_keystore_path( + self.keystore_path.as_deref(), + self.keystore_account_name.as_deref(), + )? { + let (maybe_signer, maybe_pending) = utils::create_keystore_signer( + &path, + self.keystore_password.as_deref(), + self.keystore_password_file.as_deref(), + )?; + if let Some(pending) = maybe_pending { + pending.unlock()? + } else if let Some(signer) = maybe_signer { + signer + } else { + unreachable!() + } + } else { + eyre::bail!( + "\ +Error accessing local wallet. Did you set a private key, mnemonic or keystore? +Run `cast send --help` or `forge create --help` and use the corresponding CLI +flag to set your key via: +--private-key, --mnemonic-path, --aws, --interactive, --trezor or --ledger. +Alternatively, if you're using a local node with unlocked accounts, +use the --unlocked flag and either set the `ETH_FROM` environment variable to the address +of the unlocked account you want to use, or provide the --from flag with the address directly." + ) + }; + + Ok(signer) + } + + /// This function prefers the `from` field and may return a different address from the + /// configured signer + /// If from is specified, returns it + /// If from is not specified, but there is a signer configured, returns the signer's address + /// If from is not specified and there is no signer configured, returns zero address + pub async fn sender(&self) -> Address { + if let Some(from) = self.from { + from + } else if let Ok(signer) = self.signer().await { + signer.address() + } else { + Address::ZERO + } + } +} + +impl From for WalletOpts { + fn from(options: RawWalletOpts) -> Self { + Self { raw: options, ..Default::default() } + } +} + +#[cfg(test)] +mod tests { + use std::{path::Path, str::FromStr}; + + use super::*; + + #[tokio::test] + async fn find_keystore() { + let keystore = + Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../cast/tests/fixtures/keystore")); + let keystore_file = keystore + .join("UTC--2022-12-20T10-30-43.591916000Z--ec554aeafe75601aaab43bd4621a22284db566c2"); + let password_file = keystore.join("password-ec554"); + let wallet: WalletOpts = WalletOpts::parse_from([ + "foundry-cli", + "--from", + "560d246fcddc9ea98a8b032c9a2f474efb493c28", + "--keystore", + keystore_file.to_str().unwrap(), + "--password-file", + password_file.to_str().unwrap(), + ]); + let signer = wallet.signer().await.unwrap(); + assert_eq!( + signer.address(), + Address::from_str("ec554aeafe75601aaab43bd4621a22284db566c2").unwrap() + ); + } + + #[tokio::test] + async fn illformed_private_key_generates_user_friendly_error() { + let wallet = WalletOpts { + raw: RawWalletOpts { + interactive: false, + private_key: Some("123".to_string()), + mnemonic: None, + mnemonic_passphrase: None, + hd_path: None, + mnemonic_index: 0, + }, + from: None, + keystore_path: None, + keystore_account_name: None, + keystore_password: None, + keystore_password_file: None, + ledger: false, + trezor: false, + aws: false, + }; + match wallet.signer().await { + Ok(_) => { + panic!("illformed private key shouldn't decode") + } + Err(x) => { + assert!( + x.to_string().contains("Failed to decode private key"), + "Error message is not user-friendly" + ); + } + } + } +} diff --git a/crates/wallets/src/wallet_signer.rs b/crates/wallets/src/wallet_signer.rs new file mode 100644 index 0000000000000..c28126c43b01f --- /dev/null +++ b/crates/wallets/src/wallet_signer.rs @@ -0,0 +1,209 @@ +use crate::error::WalletSignerError; +use alloy_consensus::SignableTransaction; +use alloy_dyn_abi::TypedData; +use alloy_network::TxSigner; +use alloy_primitives::{Address, ChainId, B256}; +use alloy_signer::{Signature, Signer}; +use alloy_signer_aws::AwsSigner; +use alloy_signer_ledger::{HDPath as LedgerHDPath, LedgerSigner}; +use alloy_signer_trezor::{HDPath as TrezorHDPath, TrezorSigner}; +use alloy_signer_wallet::{coins_bip39::English, LocalWallet, MnemonicBuilder}; +use alloy_sol_types::{Eip712Domain, SolStruct}; +use async_trait::async_trait; +use aws_config::BehaviorVersion; +use aws_sdk_kms::Client as AwsClient; +use std::path::PathBuf; + +pub type Result = std::result::Result; + +/// Wrapper enum around different signers. +#[derive(Debug)] +pub enum WalletSigner { + /// Wrapper around local wallet. e.g. private key, mnemonic + Local(LocalWallet), + /// Wrapper around Ledger signer. + Ledger(LedgerSigner), + /// Wrapper around Trezor signer. + Trezor(TrezorSigner), + /// Wrapper around AWS KMS signer. + Aws(AwsSigner), +} + +impl WalletSigner { + pub async fn from_ledger_path(path: LedgerHDPath) -> Result { + let ledger = LedgerSigner::new(path, None).await?; + Ok(Self::Ledger(ledger)) + } + + pub async fn from_trezor_path(path: TrezorHDPath) -> Result { + // cached to ~/.ethers-rs/trezor/cache/trezor.session + let trezor = TrezorSigner::new(path, None).await?; + Ok(Self::Trezor(trezor)) + } + + pub async fn from_aws(key_id: String) -> Result { + let config = aws_config::load_defaults(BehaviorVersion::latest()).await; + let client = AwsClient::new(&config); + + Ok(Self::Aws(AwsSigner::new(client, key_id, None).await?)) + } + + pub fn from_private_key(private_key: impl AsRef<[u8]>) -> Result { + let wallet = LocalWallet::from_bytes(&B256::from_slice(private_key.as_ref()))?; + Ok(Self::Local(wallet)) + } + + /// Returns a list of addresses available to use with current signer + /// + /// - for Ledger and Trezor signers the number of addresses to retrieve is specified as argument + /// - the result for Ledger signers includes addresses available for both LedgerLive and Legacy + /// derivation paths + /// - for Local and AWS signers the result contains a single address + pub async fn available_senders(&self, max: usize) -> Result> { + let mut senders = Vec::new(); + match self { + WalletSigner::Local(local) => { + senders.push(local.address()); + } + WalletSigner::Ledger(ledger) => { + for i in 0..max { + if let Ok(address) = + ledger.get_address_with_path(&LedgerHDPath::LedgerLive(i)).await + { + senders.push(address); + } + } + for i in 0..max { + if let Ok(address) = + ledger.get_address_with_path(&LedgerHDPath::Legacy(i)).await + { + senders.push(address); + } + } + } + WalletSigner::Trezor(trezor) => { + for i in 0..max { + if let Ok(address) = + trezor.get_address_with_path(&TrezorHDPath::TrezorLive(i)).await + { + senders.push(address); + } + } + } + WalletSigner::Aws(aws) => { + senders.push(alloy_signer::Signer::address(aws)); + } + } + Ok(senders) + } + + pub fn from_mnemonic( + mnemonic: &str, + passphrase: Option<&str>, + derivation_path: Option<&str>, + index: u32, + ) -> Result { + let mut builder = MnemonicBuilder::::default().phrase(mnemonic); + + if let Some(passphrase) = passphrase { + builder = builder.password(passphrase) + } + + builder = if let Some(hd_path) = derivation_path { + builder.derivation_path(hd_path)? + } else { + builder.index(index)? + }; + + Ok(Self::Local(builder.build()?)) + } +} + +macro_rules! delegate { + ($s:ident, $inner:ident => $e:expr) => { + match $s { + Self::Local($inner) => $e, + Self::Ledger($inner) => $e, + Self::Trezor($inner) => $e, + Self::Aws($inner) => $e, + } + }; +} + +#[async_trait] +impl Signer for WalletSigner { + /// Signs the given hash. + async fn sign_hash(&self, hash: &B256) -> alloy_signer::Result { + delegate!(self, inner => inner.sign_hash(hash)).await + } + + async fn sign_message(&self, message: &[u8]) -> alloy_signer::Result { + delegate!(self, inner => inner.sign_message(message)).await + } + + fn address(&self) -> Address { + delegate!(self, inner => alloy_signer::Signer::address(inner)) + } + + fn chain_id(&self) -> Option { + delegate!(self, inner => inner.chain_id()) + } + + fn set_chain_id(&mut self, chain_id: Option) { + delegate!(self, inner => inner.set_chain_id(chain_id)) + } + + async fn sign_typed_data( + &self, + payload: &T, + domain: &Eip712Domain, + ) -> alloy_signer::Result + where + Self: Sized, + { + delegate!(self, inner => inner.sign_typed_data(payload, domain)).await + } + + async fn sign_dynamic_typed_data( + &self, + payload: &TypedData, + ) -> alloy_signer::Result { + delegate!(self, inner => inner.sign_dynamic_typed_data(payload)).await + } +} + +#[async_trait] +impl TxSigner for WalletSigner { + fn address(&self) -> Address { + delegate!(self, inner => alloy_signer::Signer::address(inner)) + } + + async fn sign_transaction( + &self, + tx: &mut dyn SignableTransaction, + ) -> alloy_signer::Result { + delegate!(self, inner => inner.sign_transaction(tx)).await + } +} + +/// Signers that require user action to be obtained. +#[derive(Debug, Clone)] +pub enum PendingSigner { + Keystore(PathBuf), + Interactive, +} + +impl PendingSigner { + pub fn unlock(self) -> Result { + match self { + Self::Keystore(path) => { + let password = rpassword::prompt_password("Enter keystore password:")?; + Ok(WalletSigner::Local(LocalWallet::decrypt_keystore(path, password)?)) + } + Self::Interactive => { + let private_key = rpassword::prompt_password("Enter private key:")?; + Ok(WalletSigner::from_private_key(hex::decode(private_key)?)?) + } + } + } +} diff --git a/deny.toml b/deny.toml index 25b3e6a971a68..e3ae679d035c3 100644 --- a/deny.toml +++ b/deny.toml @@ -1,6 +1,6 @@ # Temporarily exclude rusoto and ethers-providers from bans since we've yet to transition to the # Rust AWS SDK. -exclude = ["rusoto_core", "rusoto_kms", "rusoto_credential", "ethers-providers", "tungstenite"] +exclude = ["rusoto_core", "rusoto_kms", "rusoto_credential", "ethers-providers", "tungstenite", "shlex"] # This section is considered when running `cargo deny check advisories` # More documentation for the advisories section can be found here: @@ -10,9 +10,7 @@ vulnerability = "deny" unmaintained = "warn" yanked = "warn" notice = "warn" -ignore = [ - "RUSTSEC-2020-0071", # https://rustsec.org/advisories/RUSTSEC-2020-0071 -] +ignore = [] # This section is considered when running `cargo deny check bans`. # More documentation about the 'bans' section can be found here: @@ -24,9 +22,7 @@ multiple-versions = "warn" wildcards = "allow" highlight = "all" # List of crates to deny -deny = [ - { name = "openssl" }, -] +deny = [{ name = "openssl" }] # Certain crates/versions that will be skipped when doing duplicate detection. skip = [] # Similarly to `skip` allows you to skip certain crates during duplicate @@ -64,15 +60,13 @@ exceptions = [ { allow = ["CC0-1.0"], name = "secp256k1" }, { allow = ["CC0-1.0"], name = "secp256k1-sys" }, { allow = ["CC0-1.0"], name = "tiny-keccak" }, + { allow = ["CC0-1.0"], name = "to_method" }, { allow = ["CC0-1.0"], name = "more-asserts" }, { allow = ["CC0-1.0"], name = "trezor-client" }, { allow = ["CC0-1.0"], name = "notify" }, { allow = ["CC0-1.0"], name = "constant_time_eq" }, { allow = ["CC0-1.0"], name = "dunce" }, { allow = ["CC0-1.0"], name = "aurora-engine-modexp" }, - - { allow = ["GPL-3.0"], name = "fastrlp" }, - { allow = ["GPL-3.0"], name = "fastrlp-derive" }, ] #copyleft = "deny" @@ -81,16 +75,12 @@ exceptions = [ name = "unicode-ident" version = "*" expression = "(MIT OR Apache-2.0) AND Unicode-DFS-2016" -license-files = [ - { path = "LICENSE-UNICODE", hash = 0x3fb01745 } -] +license-files = [{ path = "LICENSE-UNICODE", hash = 0x3fb01745 }] [[licenses.clarify]] name = "ring" version = "*" expression = "OpenSSL" -license-files = [ - { path = "LICENSE", hash = 0xbd0eed23 } -] +license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] # This section is considered when running `cargo deny check sources`. # More documentation about the 'sources' section can be found here: @@ -101,4 +91,4 @@ license-files = [ unknown-registry = "warn" # Lint level for what to happen when a crate from a git repository that is not # in the allow list is encountered -unknown-git = "allow" \ No newline at end of file +unknown-git = "allow" diff --git a/docs/dev/cheatcodes.md b/docs/dev/cheatcodes.md index a7dd39f1a33ff..ca4226be31f48 100644 --- a/docs/dev/cheatcodes.md +++ b/docs/dev/cheatcodes.md @@ -109,11 +109,11 @@ called in the [`Cheatcodes` inspector](#cheatcode-inspector) implementation. Generates the raw Rust bindings for the cheatcodes, as well as lets us specify custom attributes individually for each item, such as functions and structs, or for entire interfaces. -The way bindings are generated and extra information can be found the [`sol!`] documentation. +The way bindings are generated and extra information can be found in the [`sol!`] documentation. We leverage this macro to apply the [`Cheatcode` derive macro](#cheatcode-derive-macro) on the `Vm` interface. -### [`Cheatcode`](../../crates/macros/impl/src/cheatcodes.rs) derive macro +### [`Cheatcode`](../../crates/macros/src/cheatcodes.rs) derive macro This is derived once on the `Vm` interface declaration, which recursively applies it to all of the interface's items, as well as the `sol!`-generated items, such as the `VmCalls` enum. @@ -127,7 +127,7 @@ The latter is what fails compilation when adding a new cheatcode, and is fixed b [`Cheatcode` trait](#cheatcode-trait) to the newly-generated function call struct(s). The `Cheatcode` derive macro also parses the `#[cheatcode(...)]` attributes on functions, which are -used to specify additional properties the JSON interface. +used to specify additional properties of the JSON interface. These are all the attributes that can be specified on cheatcode functions: - `#[cheatcode(group = )]`: The group that the cheatcode belongs to. Required. @@ -165,11 +165,9 @@ update of the files. 3. If a struct, enum, error, or event was added to `Vm`, update [`spec::Cheatcodes::new`] 4. Update the JSON interface by running `cargo cheats` twice. This is expected to fail the first time that this is run after adding a new cheatcode; see [JSON interface](#json-interface) 5. Write an integration test for the cheatcode in [`testdata/cheats/`] -6. Submit a PR to [`forge-std`] updating the Solidity interfaces as necessary. Note that this step won't be necessary once the Solidity interfaces are generated using the JSON interface [`sol!`]: https://docs.rs/alloy-sol-macro/latest/alloy_sol_macro/macro.sol.html [`cheatcodes/spec/src/vm.rs`]: ../../crates/cheatcodes/spec/src/vm.rs [`cheatcodes`]: ../../crates/cheatcodes/ [`spec::Cheatcodes::new`]: ../../crates/cheatcodes/spec/src/lib.rs#L74 [`testdata/cheats/`]: ../../testdata/cheats/ -[`forge-std`]: https://github.com/foundry-rs/forge-std diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000000..9ad80af8be7a5 --- /dev/null +++ b/flake.lock @@ -0,0 +1,109 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1711655175, + "narHash": "sha256-1xiaYhC3ul4y+i3eicYxeERk8ZkrNjLkrFSb/UW36Zw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "64c81edb4b97a51c5bbc54c191763ac71a6517ee", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay", + "solc": "solc" + } + }, + "rust-overlay": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1711678273, + "narHash": "sha256-7lIB0hMRnfzx/9oSIwTnwXmVnbvVGRoadOCW+1HI5zY=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "42a168449605950935f15ea546f6f770e5f7f629", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "solc": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1711538161, + "narHash": "sha256-rETVdEIQ2PyEcNgzXXFSiYAYl0koCeGDIWp9XYBTxoQ=", + "owner": "hellwolf", + "repo": "solc.nix", + "rev": "a995838545a7383a0b37776e969743b1346d5479", + "type": "github" + }, + "original": { + "owner": "hellwolf", + "repo": "solc.nix", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000000..46ddb920c2064 --- /dev/null +++ b/flake.nix @@ -0,0 +1,56 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs = { + nixpkgs.follows = "nixpkgs"; + flake-utils.follows = "flake-utils"; + }; + }; + solc = { + url = "github:hellwolf/solc.nix"; + inputs = { + nixpkgs.follows = "nixpkgs"; + flake-utils.follows = "flake-utils"; + }; + }; + }; + + outputs = { self, nixpkgs, rust-overlay, flake-utils, solc }: + flake-utils.lib.eachDefaultSystem (system: + let + overlays = [ (import rust-overlay) ]; + pkgs = import nixpkgs { + inherit system; + overlays = [ rust-overlay.overlays.default solc.overlay ]; + }; + lib = pkgs.lib; + toolchain = pkgs.rust-bin.stable.latest.default.override { + extensions = [ "rustfmt" "clippy" "rust-src" ]; + }; + in + { + devShells.default = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + pkg-config + libusb1 + ] ++ lib.optionals pkgs.stdenv.isDarwin [ + pkgs.darwin.apple_sdk.frameworks.AppKit + ]; + buildInputs = [ + pkgs.rust-analyzer-unwrapped + toolchain + ]; + packages = with pkgs; [ + solc_0_8_20 + (solc.mkDefault pkgs solc_0_8_20) + ]; + + # Environment variables + RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library"; + LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.libusb1 ]; + }; + }); +} diff --git a/foundryup/foundryup b/foundryup/foundryup index 8143c3e23ac91..f1e40b648f477 100755 --- a/foundryup/foundryup +++ b/foundryup/foundryup @@ -8,7 +8,7 @@ FOUNDRY_MAN_DIR="$FOUNDRY_DIR/share/man/man1" BINS=(forge cast anvil chisel) -export RUSTFLAGS="-C target-cpu=native" +export RUSTFLAGS="${RUSTFLAGS:--C target-cpu=native}" main() { need_cmd git @@ -292,21 +292,21 @@ download() { fi } -# Banner Function for Foundry +# Banner Function for Foundry banner() { printf ' .xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx - + ╔═╗ ╔═╗ ╦ ╦ ╔╗╔ ╔╦╗ ╦═╗ ╦ ╦ Portable and modular toolkit - ╠╣ ║ ║ ║ ║ ║║║ ║║ ╠╦╝ ╚╦╝ for Ethereum Application Development + ╠╣ ║ ║ ║ ║ ║║║ ║║ ╠╦╝ ╚╦╝ for Ethereum Application Development ╚ ╚═╝ ╚═╝ ╝╚╝ ═╩╝ ╩╚═ ╩ written in Rust. .xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx Repo : https://github.com/foundry-rs/ -Book : https://book.getfoundry.sh/ -Chat : https://t.me/foundry_rs/ +Book : https://book.getfoundry.sh/ +Chat : https://t.me/foundry_rs/ Support : https://t.me/foundry_support/ Contribute : https://github.com/orgs/foundry-rs/projects/2/ diff --git a/foundryup/install b/foundryup/install index 0c6f7b1f15bd8..da8156a09fe3f 100755 --- a/foundryup/install +++ b/foundryup/install @@ -1,29 +1,28 @@ #!/usr/bin/env bash -set -e +set -eo pipefail -echo Installing foundryup... +echo "Installing foundryup..." -BASE_DIR=${XDG_CONFIG_HOME:-$HOME} -FOUNDRY_DIR=${FOUNDRY_DIR-"$BASE_DIR/.foundry"} +BASE_DIR="${XDG_CONFIG_HOME:-$HOME}" +FOUNDRY_DIR="${FOUNDRY_DIR-"$BASE_DIR/.foundry"}" FOUNDRY_BIN_DIR="$FOUNDRY_DIR/bin" FOUNDRY_MAN_DIR="$FOUNDRY_DIR/share/man/man1" BIN_URL="https://raw.githubusercontent.com/foundry-rs/foundry/master/foundryup/foundryup" BIN_PATH="$FOUNDRY_BIN_DIR/foundryup" - # Create the .foundry bin directory and foundryup binary if it doesn't exist. -mkdir -p $FOUNDRY_BIN_DIR -curl -# -L $BIN_URL -o $BIN_PATH -chmod +x $BIN_PATH +mkdir -p "$FOUNDRY_BIN_DIR" +curl -sSf -L "$BIN_URL" -o "$BIN_PATH" +chmod +x "$BIN_PATH" # Create the man directory for future man files if it doesn't exist. -mkdir -p $FOUNDRY_MAN_DIR +mkdir -p "$FOUNDRY_MAN_DIR" # Store the correct profile file (i.e. .profile for bash or .zshenv for ZSH). case $SHELL in */zsh) - PROFILE=${ZDOTDIR-"$HOME"}/.zshenv + PROFILE="${ZDOTDIR-"$HOME"}/.zshenv" PREF_SHELL=zsh ;; */bash) @@ -46,7 +45,12 @@ esac # Only add foundryup if it isn't already in PATH. if [[ ":$PATH:" != *":${FOUNDRY_BIN_DIR}:"* ]]; then # Add the foundryup directory to the path and ensure the old PATH variables remain. - echo >> $PROFILE && echo "export PATH=\"\$PATH:$FOUNDRY_BIN_DIR\"" >> $PROFILE + # If the shell is fish, echo fish_add_path instead of export. + if [[ "$PREF_SHELL" == "fish" ]]; then + echo >> "$PROFILE" && echo "fish_add_path -a $FOUNDRY_BIN_DIR" >> "$PROFILE" + else + echo >> "$PROFILE" && echo "export PATH=\"\$PATH:$FOUNDRY_BIN_DIR\"" >> "$PROFILE" + fi fi # Warn MacOS users that they may need to manually install libusb via Homebrew: @@ -54,5 +58,7 @@ if [[ "$OSTYPE" =~ ^darwin ]] && [[ ! -f /usr/local/opt/libusb/lib/libusb-1.0.0. echo && echo "warning: libusb not found. You may need to install it manually on MacOS via Homebrew (brew install libusb)." fi -echo && echo "Detected your preferred shell is ${PREF_SHELL} and added foundryup to PATH. Run 'source ${PROFILE}' or start a new terminal session to use foundryup." +echo +echo "Detected your preferred shell is $PREF_SHELL and added foundryup to PATH." +echo "Run 'source $PROFILE' or start a new terminal session to use foundryup." echo "Then, simply run 'foundryup' to install Foundry." diff --git a/rustfmt.toml b/rustfmt.toml index 7fabf2c1de8f1..68c3c93033d4f 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -9,6 +9,3 @@ trailing_semicolon = false use_field_init_shorthand = true format_code_in_doc_comments = true doc_comment_code_block_width = 100 - -# Ignore automatically-generated code. -ignore = ["crates/abi/src/bindings"] diff --git a/testdata/cancun/cheats/BlobBaseFee.t.sol b/testdata/cancun/cheats/BlobBaseFee.t.sol new file mode 100644 index 0000000000000..54fbc8f7f0616 --- /dev/null +++ b/testdata/cancun/cheats/BlobBaseFee.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.25; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract BlobBaseFeeTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_blob_base_fee() public { + vm.blobBaseFee(6969); + assertEq(vm.getBlobBaseFee(), 6969); + } +} diff --git a/testdata/cancun/cheats/Blobhashes.t.sol b/testdata/cancun/cheats/Blobhashes.t.sol new file mode 100644 index 0000000000000..4a589b45a38ff --- /dev/null +++ b/testdata/cancun/cheats/Blobhashes.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.25; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract BlobhashesTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testSetAndGetBlobhashes() public { + bytes32[] memory blobhashes = new bytes32[](2); + blobhashes[0] = bytes32(0x0000000000000000000000000000000000000000000000000000000000000001); + blobhashes[1] = bytes32(0x0000000000000000000000000000000000000000000000000000000000000002); + vm.blobhashes(blobhashes); + + bytes32[] memory gotBlobhashes = vm.getBlobhashes(); + assertEq(gotBlobhashes[0], blobhashes[0]); + assertEq(gotBlobhashes[1], blobhashes[1]); + } +} diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 4fc5c3ce51b29..db8e45ada043e 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -2,12 +2,13 @@ // This interface is just for internal testing purposes. Use `forge-std` instead. // SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity ^0.8.4; +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; interface Vm { - error CheatcodeError(string message); enum CallerMode { None, Broadcast, RecurrentBroadcast, Prank, RecurrentPrank } - enum AccountAccessKind { Call, DelegateCall, CallCode, StaticCall, Create, SelfDestruct, Resume } + enum AccountAccessKind { Call, DelegateCall, CallCode, StaticCall, Create, SelfDestruct, Resume, Balance, Extcodesize, Extcodehash, Extcodecopy } + enum ForgeContext { TestGroup, Test, Coverage, Snapshot, ScriptGroup, ScriptDryRun, ScriptBroadcast, ScriptResume, Unknown } struct Log { bytes32[] topics; bytes data; address emitter; } struct Rpc { string key; string url; } struct EthGetLogs { address emitter; bytes32[] topics; bytes data; bytes32 blockHash; uint64 blockNumber; bytes32 transactionHash; uint64 transactionIndex; uint256 logIndex; bool removed; } @@ -16,13 +17,135 @@ interface Vm { struct Wallet { address addr; uint256 publicKeyX; uint256 publicKeyY; uint256 privateKey; } struct FfiResult { int32 exitCode; bytes stdout; bytes stderr; } struct ChainInfo { uint256 forkId; uint256 chainId; } - struct AccountAccess { ChainInfo chainInfo; AccountAccessKind kind; address account; address accessor; bool initialized; uint256 oldBalance; uint256 newBalance; bytes deployedCode; uint256 value; bytes data; bool reverted; StorageAccess[] storageAccesses; } + struct AccountAccess { ChainInfo chainInfo; AccountAccessKind kind; address account; address accessor; bool initialized; uint256 oldBalance; uint256 newBalance; bytes deployedCode; uint256 value; bytes data; bool reverted; StorageAccess[] storageAccesses; uint64 depth; } struct StorageAccess { address account; bytes32 slot; bool isWrite; bytes32 previousValue; bytes32 newValue; bool reverted; } + struct Gas { uint64 gasLimit; uint64 gasTotalUsed; uint64 gasMemoryUsed; int64 gasRefunded; uint64 gasRemaining; } + function _expectCheatcodeRevert() external; + function _expectCheatcodeRevert(bytes4 revertData) external; + function _expectCheatcodeRevert(bytes calldata revertData) external; function accesses(address target) external returns (bytes32[] memory readSlots, bytes32[] memory writeSlots); function activeFork() external view returns (uint256 forkId); function addr(uint256 privateKey) external pure returns (address keyAddr); function allowCheatcodes(address account) external; + function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals) external pure; + function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure; + function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals) external pure; + function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure; + function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta) external pure; + function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata error) external pure; + function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta) external pure; + function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata error) external pure; + function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals) external pure; + function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure; + function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals) external pure; + function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure; + function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta) external pure; + function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata error) external pure; + function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta) external pure; + function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata error) external pure; + function assertEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertEqDecimal(int256 left, int256 right, uint256 decimals) external pure; + function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertEq(bool left, bool right) external pure; + function assertEq(bool left, bool right, string calldata error) external pure; + function assertEq(string calldata left, string calldata right) external pure; + function assertEq(string calldata left, string calldata right, string calldata error) external pure; + function assertEq(bytes calldata left, bytes calldata right) external pure; + function assertEq(bytes calldata left, bytes calldata right, string calldata error) external pure; + function assertEq(bool[] calldata left, bool[] calldata right) external pure; + function assertEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; + function assertEq(uint256[] calldata left, uint256[] calldata right) external pure; + function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; + function assertEq(int256[] calldata left, int256[] calldata right) external pure; + function assertEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; + function assertEq(uint256 left, uint256 right) external pure; + function assertEq(address[] calldata left, address[] calldata right) external pure; + function assertEq(address[] calldata left, address[] calldata right, string calldata error) external pure; + function assertEq(bytes32[] calldata left, bytes32[] calldata right) external pure; + function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; + function assertEq(string[] calldata left, string[] calldata right) external pure; + function assertEq(string[] calldata left, string[] calldata right, string calldata error) external pure; + function assertEq(bytes[] calldata left, bytes[] calldata right) external pure; + function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; + function assertEq(uint256 left, uint256 right, string calldata error) external pure; + function assertEq(int256 left, int256 right) external pure; + function assertEq(int256 left, int256 right, string calldata error) external pure; + function assertEq(address left, address right) external pure; + function assertEq(address left, address right, string calldata error) external pure; + function assertEq(bytes32 left, bytes32 right) external pure; + function assertEq(bytes32 left, bytes32 right, string calldata error) external pure; + function assertFalse(bool condition) external pure; + function assertFalse(bool condition, string calldata error) external pure; + function assertGeDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertGeDecimal(int256 left, int256 right, uint256 decimals) external pure; + function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertGe(uint256 left, uint256 right) external pure; + function assertGe(uint256 left, uint256 right, string calldata error) external pure; + function assertGe(int256 left, int256 right) external pure; + function assertGe(int256 left, int256 right, string calldata error) external pure; + function assertGtDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertGtDecimal(int256 left, int256 right, uint256 decimals) external pure; + function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertGt(uint256 left, uint256 right) external pure; + function assertGt(uint256 left, uint256 right, string calldata error) external pure; + function assertGt(int256 left, int256 right) external pure; + function assertGt(int256 left, int256 right, string calldata error) external pure; + function assertLeDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertLeDecimal(int256 left, int256 right, uint256 decimals) external pure; + function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertLe(uint256 left, uint256 right) external pure; + function assertLe(uint256 left, uint256 right, string calldata error) external pure; + function assertLe(int256 left, int256 right) external pure; + function assertLe(int256 left, int256 right, string calldata error) external pure; + function assertLtDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertLtDecimal(int256 left, int256 right, uint256 decimals) external pure; + function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertLt(uint256 left, uint256 right) external pure; + function assertLt(uint256 left, uint256 right, string calldata error) external pure; + function assertLt(int256 left, int256 right) external pure; + function assertLt(int256 left, int256 right, string calldata error) external pure; + function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertNotEqDecimal(int256 left, int256 right, uint256 decimals) external pure; + function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertNotEq(bool left, bool right) external pure; + function assertNotEq(bool left, bool right, string calldata error) external pure; + function assertNotEq(string calldata left, string calldata right) external pure; + function assertNotEq(string calldata left, string calldata right, string calldata error) external pure; + function assertNotEq(bytes calldata left, bytes calldata right) external pure; + function assertNotEq(bytes calldata left, bytes calldata right, string calldata error) external pure; + function assertNotEq(bool[] calldata left, bool[] calldata right) external pure; + function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; + function assertNotEq(uint256[] calldata left, uint256[] calldata right) external pure; + function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; + function assertNotEq(int256[] calldata left, int256[] calldata right) external pure; + function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; + function assertNotEq(uint256 left, uint256 right) external pure; + function assertNotEq(address[] calldata left, address[] calldata right) external pure; + function assertNotEq(address[] calldata left, address[] calldata right, string calldata error) external pure; + function assertNotEq(bytes32[] calldata left, bytes32[] calldata right) external pure; + function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; + function assertNotEq(string[] calldata left, string[] calldata right) external pure; + function assertNotEq(string[] calldata left, string[] calldata right, string calldata error) external pure; + function assertNotEq(bytes[] calldata left, bytes[] calldata right) external pure; + function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; + function assertNotEq(uint256 left, uint256 right, string calldata error) external pure; + function assertNotEq(int256 left, int256 right) external pure; + function assertNotEq(int256 left, int256 right, string calldata error) external pure; + function assertNotEq(address left, address right) external pure; + function assertNotEq(address left, address right, string calldata error) external pure; + function assertNotEq(bytes32 left, bytes32 right) external pure; + function assertNotEq(bytes32 left, bytes32 right, string calldata error) external pure; + function assertTrue(bool condition) external pure; + function assertTrue(bool condition, string calldata error) external pure; function assume(bool condition) external pure; + function blobBaseFee(uint256 newBlobBaseFee) external; + function blobhashes(bytes32[] calldata hashes) external; function breakpoint(string calldata char) external; function breakpoint(string calldata char, bool value) external; function broadcast() external; @@ -48,11 +171,14 @@ interface Vm { function createWallet(uint256 privateKey) external returns (Wallet memory wallet); function createWallet(uint256 privateKey, string calldata walletLabel) external returns (Wallet memory wallet); function deal(address account, uint256 newBalance) external; + function deleteSnapshot(uint256 snapshotId) external returns (bool success); + function deleteSnapshots() external; function deriveKey(string calldata mnemonic, uint32 index) external pure returns (uint256 privateKey); function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index) external pure returns (uint256 privateKey); function deriveKey(string calldata mnemonic, uint32 index, string calldata language) external pure returns (uint256 privateKey); function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index, string calldata language) external pure returns (uint256 privateKey); function difficulty(uint256 newDifficulty) external; + function dumpState(string calldata pathToStateJson) external; function envAddress(string calldata name) external view returns (address value); function envAddress(string calldata name, string calldata delim) external view returns (address[] memory value); function envBool(string calldata name) external view returns (bool value); @@ -61,28 +187,29 @@ interface Vm { function envBytes32(string calldata name, string calldata delim) external view returns (bytes32[] memory value); function envBytes(string calldata name) external view returns (bytes memory value); function envBytes(string calldata name, string calldata delim) external view returns (bytes[] memory value); + function envExists(string calldata name) external view returns (bool result); function envInt(string calldata name) external view returns (int256 value); function envInt(string calldata name, string calldata delim) external view returns (int256[] memory value); - function envOr(string calldata name, bool defaultValue) external returns (bool value); - function envOr(string calldata name, uint256 defaultValue) external returns (uint256 value); - function envOr(string calldata name, string calldata delim, address[] calldata defaultValue) external returns (address[] memory value); - function envOr(string calldata name, string calldata delim, bytes32[] calldata defaultValue) external returns (bytes32[] memory value); - function envOr(string calldata name, string calldata delim, string[] calldata defaultValue) external returns (string[] memory value); - function envOr(string calldata name, string calldata delim, bytes[] calldata defaultValue) external returns (bytes[] memory value); - function envOr(string calldata name, int256 defaultValue) external returns (int256 value); - function envOr(string calldata name, address defaultValue) external returns (address value); - function envOr(string calldata name, bytes32 defaultValue) external returns (bytes32 value); - function envOr(string calldata name, string calldata defaultValue) external returns (string memory value); - function envOr(string calldata name, bytes calldata defaultValue) external returns (bytes memory value); - function envOr(string calldata name, string calldata delim, bool[] calldata defaultValue) external returns (bool[] memory value); - function envOr(string calldata name, string calldata delim, uint256[] calldata defaultValue) external returns (uint256[] memory value); - function envOr(string calldata name, string calldata delim, int256[] calldata defaultValue) external returns (int256[] memory value); + function envOr(string calldata name, bool defaultValue) external view returns (bool value); + function envOr(string calldata name, uint256 defaultValue) external view returns (uint256 value); + function envOr(string calldata name, string calldata delim, address[] calldata defaultValue) external view returns (address[] memory value); + function envOr(string calldata name, string calldata delim, bytes32[] calldata defaultValue) external view returns (bytes32[] memory value); + function envOr(string calldata name, string calldata delim, string[] calldata defaultValue) external view returns (string[] memory value); + function envOr(string calldata name, string calldata delim, bytes[] calldata defaultValue) external view returns (bytes[] memory value); + function envOr(string calldata name, int256 defaultValue) external view returns (int256 value); + function envOr(string calldata name, address defaultValue) external view returns (address value); + function envOr(string calldata name, bytes32 defaultValue) external view returns (bytes32 value); + function envOr(string calldata name, string calldata defaultValue) external view returns (string memory value); + function envOr(string calldata name, bytes calldata defaultValue) external view returns (bytes memory value); + function envOr(string calldata name, string calldata delim, bool[] calldata defaultValue) external view returns (bool[] memory value); + function envOr(string calldata name, string calldata delim, uint256[] calldata defaultValue) external view returns (uint256[] memory value); + function envOr(string calldata name, string calldata delim, int256[] calldata defaultValue) external view returns (int256[] memory value); function envString(string calldata name) external view returns (string memory value); function envString(string calldata name, string calldata delim) external view returns (string[] memory value); function envUint(string calldata name) external view returns (uint256 value); function envUint(string calldata name, string calldata delim) external view returns (uint256[] memory value); function etch(address target, bytes calldata newRuntimeBytecode) external; - function eth_getLogs(uint256 fromBlock, uint256 toBlock, address addr, bytes32[] memory topics) external returns (EthGetLogs[] memory logs); + function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] memory topics) external returns (EthGetLogs[] memory logs); function exists(string calldata path) external returns (bool result); function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) external; function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) external; @@ -104,20 +231,29 @@ interface Vm { function fee(uint256 newBasefee) external; function ffi(string[] calldata commandInput) external returns (bytes memory result); function fsMetadata(string calldata path) external view returns (FsMetadata memory metadata); + function getBlobBaseFee() external view returns (uint256 blobBaseFee); + function getBlobhashes() external view returns (bytes32[] memory hashes); + function getBlockNumber() external view returns (uint256 height); + function getBlockTimestamp() external view returns (uint256 timestamp); function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode); function getDeployedCode(string calldata artifactPath) external view returns (bytes memory runtimeBytecode); - function getLabel(address account) external returns (string memory currentLabel); + function getLabel(address account) external view returns (string memory currentLabel); function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external returns (bool found, bytes32 key, bytes32 parent); function getMappingLength(address target, bytes32 mappingSlot) external returns (uint256 length); function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external returns (bytes32 value); function getNonce(address account) external view returns (uint64 nonce); function getNonce(Wallet calldata wallet) external returns (uint64 nonce); function getRecordedLogs() external returns (Log[] memory logs); + function indexOf(string calldata input, string calldata key) external pure returns (uint256); + function isContext(ForgeContext context) external view returns (bool result); function isDir(string calldata path) external returns (bool result); function isFile(string calldata path) external returns (bool result); function isPersistent(address account) external view returns (bool persistent); function keyExists(string calldata json, string calldata key) external view returns (bool); + function keyExistsJson(string calldata json, string calldata key) external view returns (bool); + function keyExistsToml(string calldata toml, string calldata key) external view returns (bool); function label(address account, string calldata newLabel) external; + function lastCallGas() external view returns (Gas memory gas); function load(address target, bytes32 slot) external view returns (bytes32 data); function loadAllocs(string calldata pathToAllocsJson) external; function makePersistent(address account) external; @@ -150,12 +286,34 @@ interface Vm { function parseJsonUintArray(string calldata json, string calldata key) external pure returns (uint256[] memory); function parseJson(string calldata json) external pure returns (bytes memory abiEncodedData); function parseJson(string calldata json, string calldata key) external pure returns (bytes memory abiEncodedData); + function parseTomlAddress(string calldata toml, string calldata key) external pure returns (address); + function parseTomlAddressArray(string calldata toml, string calldata key) external pure returns (address[] memory); + function parseTomlBool(string calldata toml, string calldata key) external pure returns (bool); + function parseTomlBoolArray(string calldata toml, string calldata key) external pure returns (bool[] memory); + function parseTomlBytes(string calldata toml, string calldata key) external pure returns (bytes memory); + function parseTomlBytes32(string calldata toml, string calldata key) external pure returns (bytes32); + function parseTomlBytes32Array(string calldata toml, string calldata key) external pure returns (bytes32[] memory); + function parseTomlBytesArray(string calldata toml, string calldata key) external pure returns (bytes[] memory); + function parseTomlInt(string calldata toml, string calldata key) external pure returns (int256); + function parseTomlIntArray(string calldata toml, string calldata key) external pure returns (int256[] memory); + function parseTomlKeys(string calldata toml, string calldata key) external pure returns (string[] memory keys); + function parseTomlString(string calldata toml, string calldata key) external pure returns (string memory); + function parseTomlStringArray(string calldata toml, string calldata key) external pure returns (string[] memory); + function parseTomlUint(string calldata toml, string calldata key) external pure returns (uint256); + function parseTomlUintArray(string calldata toml, string calldata key) external pure returns (uint256[] memory); + function parseToml(string calldata toml) external pure returns (bytes memory abiEncodedData); + function parseToml(string calldata toml, string calldata key) external pure returns (bytes memory abiEncodedData); function parseUint(string calldata stringifiedValue) external pure returns (uint256 parsedValue); function pauseGasMetering() external; function prank(address msgSender) external; function prank(address msgSender, address txOrigin) external; function prevrandao(bytes32 newPrevrandao) external; + function prevrandao(uint256 newPrevrandao) external; function projectRoot() external view returns (string memory path); + function prompt(string calldata promptText) external returns (string memory input); + function promptAddress(string calldata promptText) external returns (address); + function promptSecret(string calldata promptText) external returns (string memory input); + function promptUint(string calldata promptText) external returns (uint256); function readCallers() external returns (CallerMode callerMode, address msgSender, address txOrigin); function readDir(string calldata path) external view returns (DirEntry[] memory entries); function readDir(string calldata path, uint64 maxDepth) external view returns (DirEntry[] memory entries); @@ -169,9 +327,11 @@ interface Vm { function rememberKey(uint256 privateKey) external returns (address keyAddr); function removeDir(string calldata path, bool recursive) external; function removeFile(string calldata path) external; + function replace(string calldata input, string calldata from, string calldata to) external pure returns (string memory output); function resetNonce(address account) external; function resumeGasMetering() external; function revertTo(uint256 snapshotId) external returns (bool success); + function revertToAndDelete(uint256 snapshotId) external returns (bool success); function revokePersistent(address account) external; function revokePersistent(address[] calldata accounts) external; function roll(uint256 newHeight) external; @@ -197,16 +357,21 @@ interface Vm { function serializeJson(string calldata objectKey, string calldata value) external returns (string memory json); function serializeString(string calldata objectKey, string calldata valueKey, string calldata value) external returns (string memory json); function serializeString(string calldata objectKey, string calldata valueKey, string[] calldata values) external returns (string memory json); + function serializeUintToHex(string calldata objectKey, string calldata valueKey, uint256 value) external returns (string memory json); function serializeUint(string calldata objectKey, string calldata valueKey, uint256 value) external returns (string memory json); function serializeUint(string calldata objectKey, string calldata valueKey, uint256[] calldata values) external returns (string memory json); function setEnv(string calldata name, string calldata value) external; function setNonce(address account, uint64 newNonce) external; function setNonceUnsafe(address account, uint64 newNonce) external; + function signP256(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 s); function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + function sign(bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s); function skip(bool skipTest) external; function sleep(uint256 duration) external; function snapshot() external returns (uint256 snapshotId); + function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs); function startBroadcast() external; function startBroadcast(address signer) external; function startBroadcast(uint256 privateKey) external; @@ -214,19 +379,27 @@ interface Vm { function startPrank(address msgSender) external; function startPrank(address msgSender, address txOrigin) external; function startStateDiffRecording() external; - function stopAndReturnStateDiff() external returns (AccountAccess[] memory accesses); + function stopAndReturnStateDiff() external returns (AccountAccess[] memory accountAccesses); function stopBroadcast() external; + function stopExpectSafeMemory() external; function stopMappingRecording() external; function stopPrank() external; function store(address target, bytes32 slot, bytes32 value) external; + function toBase64URL(bytes calldata data) external pure returns (string memory); + function toBase64URL(string calldata data) external pure returns (string memory); + function toBase64(bytes calldata data) external pure returns (string memory); + function toBase64(string calldata data) external pure returns (string memory); + function toLowercase(string calldata input) external pure returns (string memory output); function toString(address value) external pure returns (string memory stringifiedValue); function toString(bytes calldata value) external pure returns (string memory stringifiedValue); function toString(bytes32 value) external pure returns (string memory stringifiedValue); function toString(bool value) external pure returns (string memory stringifiedValue); function toString(uint256 value) external pure returns (string memory stringifiedValue); function toString(int256 value) external pure returns (string memory stringifiedValue); + function toUppercase(string calldata input) external pure returns (string memory output); function transact(bytes32 txHash) external; function transact(uint256 forkId, bytes32 txHash) external; + function trim(string calldata input) external pure returns (string memory output); function tryFfi(string[] calldata commandInput) external returns (FfiResult memory result); function txGasPrice(uint256 newGasPrice) external; function unixTime() external returns (uint256 milliseconds); @@ -236,4 +409,6 @@ interface Vm { function writeJson(string calldata json, string calldata path) external; function writeJson(string calldata json, string calldata path, string calldata valueKey) external; function writeLine(string calldata path, string calldata data) external; + function writeToml(string calldata json, string calldata path) external; + function writeToml(string calldata json, string calldata path, string calldata valueKey) external; } diff --git a/testdata/cheats/Addr.t.sol b/testdata/default/cheats/Addr.t.sol similarity index 95% rename from testdata/cheats/Addr.t.sol rename to testdata/default/cheats/Addr.t.sol index 39f14f6aab572..432c52e698c9b 100644 --- a/testdata/cheats/Addr.t.sol +++ b/testdata/default/cheats/Addr.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract AddrTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/Assert.t.sol b/testdata/default/cheats/Assert.t.sol new file mode 100644 index 0000000000000..b33af6292ce31 --- /dev/null +++ b/testdata/default/cheats/Assert.t.sol @@ -0,0 +1,829 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract AssertionsTest is DSTest { + string constant errorMessage = "User provided message"; + uint256 constant maxDecimals = 77; + + Vm constant vm = Vm(HEVM_ADDRESS); + + function _abs(int256 a) internal pure returns (uint256) { + // Required or it will fail when `a = type(int256).min` + if (a == type(int256).min) { + return uint256(type(int256).max) + 1; + } + + return uint256(a > 0 ? a : -a); + } + + function _getDelta(uint256 a, uint256 b) internal pure returns (uint256) { + return a > b ? a - b : b - a; + } + + function _getDelta(int256 a, int256 b) internal pure returns (uint256) { + // a and b are of the same sign + // this works thanks to two's complement, the left-most bit is the sign bit + if ((a ^ b) > -1) { + return _getDelta(_abs(a), _abs(b)); + } + + // a and b are of opposite signs + return _abs(a) + _abs(b); + } + + function _prefixDecWithZeroes(string memory intPart, string memory decimalPart, uint256 decimals) + internal + returns (string memory) + { + while (bytes(decimalPart).length < decimals) { + decimalPart = string.concat("0", decimalPart); + } + + return string.concat(intPart, ".", decimalPart); + } + + function _formatWithDecimals(uint256 value, uint256 decimals) internal returns (string memory) { + string memory intPart = vm.toString(value / (10 ** decimals)); + string memory decimalPart = vm.toString(value % (10 ** decimals)); + + return _prefixDecWithZeroes(intPart, decimalPart, decimals); + } + + function _formatWithDecimals(int256 value, uint256 decimals) internal returns (string memory) { + string memory intPart = vm.toString(value / int256(10 ** decimals)); + int256 mod = value % int256(10 ** decimals); + string memory decimalPart = vm.toString(mod > 0 ? mod : -mod); + + // Add - if we have something like 0.123 + if ((value < 0) && keccak256(abi.encode(intPart)) == keccak256(abi.encode("0"))) { + intPart = string.concat("-", intPart); + } + + return _prefixDecWithZeroes(intPart, decimalPart, decimals); + } + + function testFuzzAssertEqNotEq(uint256 left, uint256 right, uint256 decimals) public { + vm.assume(left != right); + vm.assume(decimals <= maxDecimals); + + vm.assertEq(left, left); + vm.assertEq(right, right); + vm.assertNotEq(left, right); + vm.assertNotEq(right, left); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " != ", vm.toString(right))) + ); + vm.assertEq(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " == ", vm.toString(left))) + ); + vm.assertNotEq(left, left, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " != ", + _formatWithDecimals(right, decimals) + ) + ) + ); + vm.assertEqDecimal(left, right, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " == ", + _formatWithDecimals(left, decimals) + ) + ) + ); + vm.assertNotEqDecimal(left, left, decimals); + } + + function testFuzzAssertEqNotEq(int256 left, int256 right, uint256 decimals) public { + vm.assume(left != right); + vm.assume(decimals <= maxDecimals); + + vm.assertEq(left, left); + vm.assertEq(right, right); + vm.assertNotEq(left, right); + vm.assertNotEq(right, left); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " != ", vm.toString(right))) + ); + vm.assertEq(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " == ", vm.toString(left))) + ); + vm.assertNotEq(left, left, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + errorMessage, + ": ", + _formatWithDecimals(left, decimals), + " != ", + _formatWithDecimals(right, decimals) + ) + ) + ); + vm.assertEqDecimal(left, right, decimals, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + errorMessage, ": ", _formatWithDecimals(left, decimals), " == ", _formatWithDecimals(left, decimals) + ) + ) + ); + vm.assertNotEqDecimal(left, left, decimals, errorMessage); + } + + function testFuzzAssertEqNotEq(bool left, bool right) public { + vm.assume(left != right); + + vm.assertEq(left, left); + vm.assertEq(right, right); + vm.assertNotEq(left, right); + vm.assertNotEq(right, left); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " != ", vm.toString(right))) + ); + vm.assertEq(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " == ", vm.toString(left))) + ); + vm.assertNotEq(left, left, errorMessage); + } + + function testFuzzAssertEqNotEq(address left, address right) public { + vm.assume(left != right); + + vm.assertEq(left, left); + vm.assertEq(right, right); + vm.assertNotEq(left, right); + vm.assertNotEq(right, left); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " != ", vm.toString(right))) + ); + vm.assertEq(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " == ", vm.toString(left))) + ); + vm.assertNotEq(left, left, errorMessage); + } + + function testFuzzAssertEqNotEq(bytes32 left, bytes32 right) public { + vm.assume(left != right); + + vm.assertEq(left, left); + vm.assertEq(right, right); + vm.assertNotEq(left, right); + vm.assertNotEq(right, left); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " != ", vm.toString(right))) + ); + vm.assertEq(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " == ", vm.toString(left))) + ); + vm.assertNotEq(left, left, errorMessage); + } + + function testFuzzAssertEqNotEq(string memory left, string memory right) public { + vm.assume(keccak256(abi.encodePacked(left)) != keccak256(abi.encodePacked(right))); + + vm.assertEq(left, left); + vm.assertEq(right, right); + vm.assertNotEq(left, right); + vm.assertNotEq(right, left); + + vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": ", left, " != ", right))); + vm.assertEq(left, right, errorMessage); + + vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": ", left, " == ", left))); + vm.assertNotEq(left, left, errorMessage); + } + + function testFuzzAssertEqNotEq(bytes memory left, bytes memory right) public { + vm.assume(keccak256(left) != keccak256(right)); + + vm.assertEq(left, left); + vm.assertEq(right, right); + vm.assertNotEq(left, right); + vm.assertNotEq(right, left); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " != ", vm.toString(right))) + ); + vm.assertEq(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " == ", vm.toString(left))) + ); + vm.assertNotEq(left, left, errorMessage); + } + + function testFuzzAssertGtLt(uint256 left, uint256 right, uint256 decimals) public { + vm.assume(left < right); + vm.assume(decimals <= maxDecimals); + + vm.assertGt(right, left); + vm.assertLt(left, right); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " <= ", vm.toString(right))) + ); + vm.assertGt(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(right), " <= ", vm.toString(right))) + ); + vm.assertGt(right, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " >= ", vm.toString(left))) + ); + vm.assertLt(left, left, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(right), " >= ", vm.toString(left))) + ); + vm.assertLt(right, left, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " <= ", + _formatWithDecimals(right, decimals) + ) + ) + ); + vm.assertGtDecimal(left, right, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(right, decimals), + " <= ", + _formatWithDecimals(right, decimals) + ) + ) + ); + vm.assertGtDecimal(right, right, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " >= ", + _formatWithDecimals(left, decimals) + ) + ) + ); + vm.assertLtDecimal(left, left, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(right, decimals), + " >= ", + _formatWithDecimals(left, decimals) + ) + ) + ); + vm.assertLtDecimal(right, left, decimals); + } + + function testFuzzAssertGtLt(int256 left, int256 right, uint256 decimals) public { + vm.assume(left < right); + vm.assume(decimals <= maxDecimals); + + vm.assertGt(right, left); + vm.assertLt(left, right); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " <= ", vm.toString(right))) + ); + vm.assertGt(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(right), " <= ", vm.toString(right))) + ); + vm.assertGt(right, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " >= ", vm.toString(left))) + ); + vm.assertLt(left, left, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(right), " >= ", vm.toString(left))) + ); + vm.assertLt(right, left, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " <= ", + _formatWithDecimals(right, decimals) + ) + ) + ); + vm.assertGtDecimal(left, right, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(right, decimals), + " <= ", + _formatWithDecimals(right, decimals) + ) + ) + ); + vm.assertGtDecimal(right, right, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " >= ", + _formatWithDecimals(left, decimals) + ) + ) + ); + vm.assertLtDecimal(left, left, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(right, decimals), + " >= ", + _formatWithDecimals(left, decimals) + ) + ) + ); + vm.assertLtDecimal(right, left, decimals); + } + + function testFuzzAssertGeLe(uint256 left, uint256 right, uint256 decimals) public { + vm.assume(left < right); + vm.assume(decimals <= maxDecimals); + + vm.assertGe(left, left); + vm.assertLe(left, left); + vm.assertGe(right, right); + vm.assertLe(right, right); + vm.assertGe(right, left); + vm.assertLe(left, right); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " < ", vm.toString(right))) + ); + vm.assertGe(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(right), " > ", vm.toString(left))) + ); + vm.assertLe(right, left, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " < ", + _formatWithDecimals(right, decimals) + ) + ) + ); + vm.assertGeDecimal(left, right, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(right, decimals), + " > ", + _formatWithDecimals(left, decimals) + ) + ) + ); + vm.assertLeDecimal(right, left, decimals); + } + + function testFuzzAssertGeLe(int256 left, int256 right, uint256 decimals) public { + vm.assume(left < right); + vm.assume(decimals <= maxDecimals); + + vm.assertGe(left, left); + vm.assertLe(left, left); + vm.assertGe(right, right); + vm.assertLe(right, right); + vm.assertGe(right, left); + vm.assertLe(left, right); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " < ", vm.toString(right))) + ); + vm.assertGe(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(right), " > ", vm.toString(left))) + ); + vm.assertLe(right, left, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " < ", + _formatWithDecimals(right, decimals) + ) + ) + ); + vm.assertGeDecimal(left, right, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(right, decimals), + " > ", + _formatWithDecimals(left, decimals) + ) + ) + ); + vm.assertLeDecimal(right, left, decimals); + } + + function testFuzzAssertApproxEqAbs(uint256 left, uint256 right, uint256 decimals) public { + uint256 delta = _getDelta(right, left); + vm.assume(decimals <= maxDecimals); + + vm.assertApproxEqAbs(left, right, delta); + + if (delta > 0) { + vm._expectCheatcodeRevert( + bytes( + string.concat( + errorMessage, + ": ", + vm.toString(left), + " !~= ", + vm.toString(right), + " (max delta: ", + vm.toString(delta - 1), + ", real delta: ", + vm.toString(delta), + ")" + ) + ) + ); + vm.assertApproxEqAbs(left, right, delta - 1, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " !~= ", + _formatWithDecimals(right, decimals), + " (max delta: ", + _formatWithDecimals(delta - 1, decimals), + ", real delta: ", + _formatWithDecimals(delta, decimals), + ")" + ) + ) + ); + vm.assertApproxEqAbsDecimal(left, right, delta - 1, decimals); + } + } + + function testFuzzAssertApproxEqAbs(int256 left, int256 right, uint256 decimals) public { + uint256 delta = _getDelta(right, left); + vm.assume(decimals <= maxDecimals); + + vm.assertApproxEqAbs(left, right, delta); + + if (delta > 0) { + vm._expectCheatcodeRevert( + bytes( + string.concat( + errorMessage, + ": ", + vm.toString(left), + " !~= ", + vm.toString(right), + " (max delta: ", + vm.toString(delta - 1), + ", real delta: ", + vm.toString(delta), + ")" + ) + ) + ); + vm.assertApproxEqAbs(left, right, delta - 1, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " !~= ", + _formatWithDecimals(right, decimals), + " (max delta: ", + _formatWithDecimals(delta - 1, decimals), + ", real delta: ", + _formatWithDecimals(delta, decimals), + ")" + ) + ) + ); + vm.assertApproxEqAbsDecimal(left, right, delta - 1, decimals); + } + } + + function testFuzzAssertApproxEqRel(uint256 left, uint256 right, uint256 decimals) public { + vm.assume(right != 0); + uint256 delta = _getDelta(right, left); + vm.assume(delta < type(uint256).max / (10 ** 18)); + vm.assume(decimals <= maxDecimals); + + uint256 percentDelta = delta * (10 ** 18) / right; + + vm.assertApproxEqRel(left, right, percentDelta); + + if (percentDelta > 0) { + vm._expectCheatcodeRevert( + bytes( + string.concat( + errorMessage, + ": ", + vm.toString(left), + " !~= ", + vm.toString(right), + " (max delta: ", + _formatWithDecimals(percentDelta - 1, 16), + "%, real delta: ", + _formatWithDecimals(percentDelta, 16), + "%)" + ) + ) + ); + vm.assertApproxEqRel(left, right, percentDelta - 1, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " !~= ", + _formatWithDecimals(right, decimals), + " (max delta: ", + _formatWithDecimals(percentDelta - 1, 16), + "%, real delta: ", + _formatWithDecimals(percentDelta, 16), + "%)" + ) + ) + ); + vm.assertApproxEqRelDecimal(left, right, percentDelta - 1, decimals); + } + } + + function testFuzzAssertApproxEqRel(int256 left, int256 right, uint256 decimals) public { + vm.assume(left < right); + vm.assume(right != 0); + uint256 delta = _getDelta(right, left); + vm.assume(delta < type(uint256).max / (10 ** 18)); + vm.assume(decimals <= maxDecimals); + + uint256 percentDelta = delta * (10 ** 18) / _abs(right); + + vm.assertApproxEqRel(left, right, percentDelta); + + if (percentDelta > 0) { + vm._expectCheatcodeRevert( + bytes( + string.concat( + errorMessage, + ": ", + vm.toString(left), + " !~= ", + vm.toString(right), + " (max delta: ", + _formatWithDecimals(percentDelta - 1, 16), + "%, real delta: ", + _formatWithDecimals(percentDelta, 16), + "%)" + ) + ) + ); + vm.assertApproxEqRel(left, right, percentDelta - 1, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " !~= ", + _formatWithDecimals(right, decimals), + " (max delta: ", + _formatWithDecimals(percentDelta - 1, 16), + "%, real delta: ", + _formatWithDecimals(percentDelta, 16), + "%)" + ) + ) + ); + vm.assertApproxEqRelDecimal(left, right, percentDelta - 1, decimals); + } + } + + function testAssertEqNotEqArrays() public { + { + uint256[] memory arr1 = new uint256[](1); + arr1[0] = 1; + uint256[] memory arr2 = new uint256[](2); + arr2[0] = 1; + arr2[1] = 2; + + vm.assertEq(arr1, arr1); + vm.assertEq(arr2, arr2); + vm.assertNotEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes("assertion failed: [1] != [1, 2]")); + vm.assertEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes(string.concat("assertion failed: [1, 2] == [1, 2]"))); + vm.assertNotEq(arr2, arr2); + } + { + int256[] memory arr1 = new int256[](2); + int256[] memory arr2 = new int256[](1); + arr1[0] = 5; + arr2[0] = type(int256).max; + + vm.assertEq(arr1, arr1); + vm.assertEq(arr2, arr2); + vm.assertNotEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": [5, 0] != [", vm.toString(arr2[0]), "]"))); + vm.assertEq(arr1, arr2, errorMessage); + + vm._expectCheatcodeRevert(bytes(string.concat("assertion failed: [5, 0] == [5, 0]"))); + vm.assertNotEq(arr1, arr1); + } + { + bool[] memory arr1 = new bool[](2); + bool[] memory arr2 = new bool[](2); + arr1[0] = true; + arr2[1] = true; + + vm.assertEq(arr1, arr1); + vm.assertEq(arr2, arr2); + vm.assertNotEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": [true, false] != [false, true]"))); + vm.assertEq(arr1, arr2, errorMessage); + + vm._expectCheatcodeRevert(bytes(string("assertion failed: [true, false] == [true, false]"))); + vm.assertNotEq(arr1, arr1); + } + { + address[] memory arr1 = new address[](1); + address[] memory arr2 = new address[](0); + + vm.assertEq(arr1, arr1); + vm.assertEq(arr2, arr2); + vm.assertNotEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": [", vm.toString(arr1[0]), "] != []"))); + vm.assertEq(arr1, arr2, errorMessage); + + vm._expectCheatcodeRevert(bytes(string("assertion failed: [] == []"))); + vm.assertNotEq(arr2, arr2); + } + { + bytes32[] memory arr1 = new bytes32[](1); + bytes32[] memory arr2 = new bytes32[](1); + arr1[0] = bytes32(uint256(1)); + + vm.assertEq(arr1, arr1); + vm.assertEq(arr2, arr2); + vm.assertNotEq(arr1, arr2); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": [", vm.toString(arr1[0]), "] != [", vm.toString(arr2[0]), "]")) + ); + vm.assertEq(arr1, arr2, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat("assertion failed: [", vm.toString(arr2[0]), "] == [", vm.toString(arr2[0]), "]")) + ); + vm.assertNotEq(arr2, arr2); + } + { + string[] memory arr1 = new string[](1); + string[] memory arr2 = new string[](3); + + arr1[0] = "foo"; + arr2[2] = "bar"; + + vm.assertEq(arr1, arr1); + vm.assertEq(arr2, arr2); + vm.assertNotEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes("assertion failed: [foo] != [, , bar]")); + vm.assertEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": [foo] == [foo]"))); + vm.assertNotEq(arr1, arr1, errorMessage); + } + { + bytes[] memory arr1 = new bytes[](1); + bytes[] memory arr2 = new bytes[](2); + + arr1[0] = hex"1111"; + arr2[1] = hex"1234"; + + vm.assertEq(arr1, arr1); + vm.assertEq(arr2, arr2); + vm.assertNotEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes("assertion failed: [0x1111] != [0x, 0x1234]")); + vm.assertEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": [0x1111] == [0x1111]"))); + vm.assertNotEq(arr1, arr1, errorMessage); + } + } + + function testAssertBool() public { + vm.assertTrue(true); + vm.assertFalse(false); + + vm._expectCheatcodeRevert(bytes("assertion failed")); + vm.assertTrue(false); + + vm._expectCheatcodeRevert(bytes(errorMessage)); + vm.assertTrue(false, errorMessage); + + vm._expectCheatcodeRevert(bytes("assertion failed")); + vm.assertFalse(true); + + vm._expectCheatcodeRevert(bytes(errorMessage)); + vm.assertFalse(true, errorMessage); + } + + function testAssertApproxEqRel() public { + vm._expectCheatcodeRevert(bytes("assertion failed: overflow in delta calculation")); + vm.assertApproxEqRel(type(int256).min, type(int256).max, 0); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": 1 !~= 0 (max delta: 0.0000000000000000%, real delta: undefined)")) + ); + vm.assertApproxEqRel(int256(1), int256(0), 0, errorMessage); + + vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": overflow in delta calculation"))); + vm.assertApproxEqRel(uint256(0), type(uint256).max, 0, errorMessage); + + vm._expectCheatcodeRevert( + bytes("assertion failed: 1 !~= 0 (max delta: 0.0000000000000000%, real delta: undefined)") + ); + vm.assertApproxEqRel(uint256(1), uint256(0), uint256(0)); + + vm.assertApproxEqRel(uint256(0), uint256(0), uint256(0)); + } +} diff --git a/testdata/cheats/Assume.t.sol b/testdata/default/cheats/Assume.t.sol similarity index 92% rename from testdata/cheats/Assume.t.sol rename to testdata/default/cheats/Assume.t.sol index 7520cfd6d1c2e..de11d6644fe9d 100644 --- a/testdata/cheats/Assume.t.sol +++ b/testdata/default/cheats/Assume.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract AssumeTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Bank.t.sol b/testdata/default/cheats/Bank.t.sol similarity index 95% rename from testdata/cheats/Bank.t.sol rename to testdata/default/cheats/Bank.t.sol index 31feed4988107..a02fe1667e653 100644 --- a/testdata/cheats/Bank.t.sol +++ b/testdata/default/cheats/Bank.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract CoinbaseTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/Base64.t.sol b/testdata/default/cheats/Base64.t.sol new file mode 100644 index 0000000000000..0d2249395aadf --- /dev/null +++ b/testdata/default/cheats/Base64.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../logs/console.sol"; + +contract Base64Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_toBase64() public { + bytes memory input = hex"00112233445566778899aabbccddeeff"; + string memory expected = "ABEiM0RVZneImaq7zN3u/w=="; + string memory actual = vm.toBase64(input); + assertEq(actual, expected); + } + + function test_toBase64URL() public { + bytes memory input = hex"00112233445566778899aabbccddeeff"; + string memory expected = "ABEiM0RVZneImaq7zN3u_w=="; + string memory actual = vm.toBase64URL(input); + assertEq(actual, expected); + } +} diff --git a/testdata/cheats/Broadcast.t.sol b/testdata/default/cheats/Broadcast.t.sol similarity index 91% rename from testdata/cheats/Broadcast.t.sol rename to testdata/default/cheats/Broadcast.t.sol index d542c28c0e6c5..6a099dc6ed54b 100644 --- a/testdata/cheats/Broadcast.t.sol +++ b/testdata/default/cheats/Broadcast.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; library F { function t2() public pure returns (uint256) { @@ -528,3 +528,45 @@ contract ScriptAdditionalContracts is DSTest { new Parent(); } } + +contract SignatureTester { + address public immutable owner; + + constructor() { + owner = msg.sender; + } + + function verifySignature(bytes32 digest, uint8 v, bytes32 r, bytes32 s) public view returns (bool) { + require(ecrecover(digest, v, r, s) == owner, "Invalid signature"); + } +} + +contract ScriptSign is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + bytes32 digest = keccak256("something"); + + function run() external { + vm.startBroadcast(); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(digest); + + vm._expectCheatcodeRevert( + bytes(string.concat("signer with address ", vm.toString(address(this)), " is not available")) + ); + vm.sign(address(this), digest); + + SignatureTester tester = new SignatureTester(); + (, address caller,) = vm.readCallers(); + assertEq(tester.owner(), caller); + tester.verifySignature(digest, v, r, s); + } + + function run(address sender) external { + vm._expectCheatcodeRevert(bytes("could not determine signer")); + vm.sign(digest); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(sender, digest); + address actual = ecrecover(digest, v, r, s); + + assertEq(actual, sender); + } +} diff --git a/testdata/cheats/ChainId.t.sol b/testdata/default/cheats/ChainId.t.sol similarity index 93% rename from testdata/cheats/ChainId.t.sol rename to testdata/default/cheats/ChainId.t.sol index af5312241558c..aa8fa0a131ada 100644 --- a/testdata/cheats/ChainId.t.sol +++ b/testdata/default/cheats/ChainId.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract DealTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Cool.t.sol b/testdata/default/cheats/Cool.t.sol similarity index 95% rename from testdata/cheats/Cool.t.sol rename to testdata/default/cheats/Cool.t.sol index d721a442d0123..82212f1b17fe5 100644 --- a/testdata/cheats/Cool.t.sol +++ b/testdata/default/cheats/Cool.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity 0.8.18; -import "../lib/ds-test/src/test.sol"; -import "./Vm.sol"; +import "lib/ds-test/src/test.sol"; +import "cheats/Vm.sol"; contract CoolTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Deal.t.sol b/testdata/default/cheats/Deal.t.sol similarity index 96% rename from testdata/cheats/Deal.t.sol rename to testdata/default/cheats/Deal.t.sol index 2729ac73e8037..ac47764356869 100644 --- a/testdata/cheats/Deal.t.sol +++ b/testdata/default/cheats/Deal.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract DealTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Derive.t.sol b/testdata/default/cheats/Derive.t.sol similarity index 96% rename from testdata/cheats/Derive.t.sol rename to testdata/default/cheats/Derive.t.sol index e2107e80cd998..fb14433334715 100644 --- a/testdata/cheats/Derive.t.sol +++ b/testdata/default/cheats/Derive.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract DeriveTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Env.t.sol b/testdata/default/cheats/Env.t.sol similarity index 99% rename from testdata/cheats/Env.t.sol rename to testdata/default/cheats/Env.t.sol index ae6c89ec9856c..e325df2fa4429 100644 --- a/testdata/cheats/Env.t.sol +++ b/testdata/default/cheats/Env.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract EnvTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -13,6 +13,14 @@ contract EnvTest is DSTest { vm.setEnv(key, val); } + function testEnvExists() public { + string memory key = "_foundryCheatcodeEnvExistsTestKey"; + string memory val = "_foundryCheatcodeEnvExistsTestVal"; + vm.setEnv(key, val); + require(vm.envExists(key), "envExists failed"); + require(!vm.envExists("nonexistent"), "envExists failed"); + } + uint256 constant numEnvBoolTests = 2; function testEnvBool() public { diff --git a/testdata/cheats/Etch.t.sol b/testdata/default/cheats/Etch.t.sol similarity index 78% rename from testdata/cheats/Etch.t.sol rename to testdata/default/cheats/Etch.t.sol index 81e70d37de4ad..6e58fc13bac28 100644 --- a/testdata/cheats/Etch.t.sol +++ b/testdata/default/cheats/Etch.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract EtchTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -17,7 +17,7 @@ contract EtchTest is DSTest { function testEtchNotAvailableOnPrecompiles() public { address target = address(1); bytes memory code = hex"1010"; - vm.expectRevert(bytes("cannot call `etch` on precompile 0x0000000000000000000000000000000000000001")); + vm._expectCheatcodeRevert(bytes("cannot call `etch` on precompile 0x0000000000000000000000000000000000000001")); vm.etch(target, code); } } diff --git a/testdata/cheats/ExpectCall.t.sol b/testdata/default/cheats/ExpectCall.t.sol similarity index 99% rename from testdata/cheats/ExpectCall.t.sol rename to testdata/default/cheats/ExpectCall.t.sol index 3cc9e6c573c07..86a5290a92603 100644 --- a/testdata/cheats/ExpectCall.t.sol +++ b/testdata/default/cheats/ExpectCall.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Contract { function numberA() public pure returns (uint256) { diff --git a/testdata/cheats/ExpectEmit.t.sol b/testdata/default/cheats/ExpectEmit.t.sol similarity index 99% rename from testdata/cheats/ExpectEmit.t.sol rename to testdata/default/cheats/ExpectEmit.t.sol index b232ab36b3d54..cad184355443e 100644 --- a/testdata/cheats/ExpectEmit.t.sol +++ b/testdata/default/cheats/ExpectEmit.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Emitter { uint256 public thing; diff --git a/testdata/cheats/ExpectRevert.t.sol b/testdata/default/cheats/ExpectRevert.t.sol similarity index 90% rename from testdata/cheats/ExpectRevert.t.sol rename to testdata/default/cheats/ExpectRevert.t.sol index b2179a25db982..0cc6cac59b5fe 100644 --- a/testdata/cheats/ExpectRevert.t.sol +++ b/testdata/default/cheats/ExpectRevert.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Reverter { error CustomError(); @@ -186,4 +186,20 @@ contract ExpectRevertTest is DSTest { function testFailExpectRevertDangling() public { vm.expectRevert("dangling"); } + + function testexpectCheatcodeRevert() public { + vm._expectCheatcodeRevert("JSON value at \".a\" is not an object"); + vm.parseJsonKeys('{"a": "b"}', ".a"); + } + + function testFailexpectCheatcodeRevertForExtCall() public { + Reverter reverter = new Reverter(); + vm._expectCheatcodeRevert(); + reverter.revertWithMessage("revert"); + } + + function testFailexpectCheatcodeRevertForCreate() public { + vm._expectCheatcodeRevert(); + new ConstructorReverter("some message"); + } } diff --git a/testdata/cheats/Fee.t.sol b/testdata/default/cheats/Fee.t.sol similarity index 94% rename from testdata/cheats/Fee.t.sol rename to testdata/default/cheats/Fee.t.sol index 3d6ea72a8bd01..ad93fed6a4ef3 100644 --- a/testdata/cheats/Fee.t.sol +++ b/testdata/default/cheats/Fee.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract FeeTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Ffi.t.sol b/testdata/default/cheats/Ffi.t.sol similarity index 97% rename from testdata/cheats/Ffi.t.sol rename to testdata/default/cheats/Ffi.t.sol index 2aa2175e25117..897783d7ec46f 100644 --- a/testdata/cheats/Ffi.t.sol +++ b/testdata/default/cheats/Ffi.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract FfiTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Fork.t.sol b/testdata/default/cheats/Fork.t.sol similarity index 93% rename from testdata/cheats/Fork.t.sol rename to testdata/default/cheats/Fork.t.sol index 0b64b9eb18699..950865eacca91 100644 --- a/testdata/cheats/Fork.t.sol +++ b/testdata/default/cheats/Fork.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; interface IWETH { function deposit() external payable; @@ -111,4 +111,10 @@ contract ForkTest is DSTest { uint256 expected = block.chainid; assertEq(newChainId, expected); } + + // ensures forks change chain ids automatically + function testCanAutoUpdateChainId() public { + vm.createSelectFork("https://polygon-pokt.nodies.app"); // Polygon mainnet RPC URL + assertEq(block.chainid, 137); + } } diff --git a/testdata/cheats/Fork2.t.sol b/testdata/default/cheats/Fork2.t.sol similarity index 99% rename from testdata/cheats/Fork2.t.sol rename to testdata/default/cheats/Fork2.t.sol index b3c1008b7b73f..4b40533347655 100644 --- a/testdata/cheats/Fork2.t.sol +++ b/testdata/default/cheats/Fork2.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; import "../logs/console.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; struct MyStruct { uint256 value; diff --git a/testdata/cheats/Fs.t.sol b/testdata/default/cheats/Fs.t.sol similarity index 89% rename from testdata/cheats/Fs.t.sol rename to testdata/default/cheats/Fs.t.sol index c1f79c15a621d..c48adefec5a22 100644 --- a/testdata/cheats/Fs.t.sol +++ b/testdata/default/cheats/Fs.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract FsTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -23,10 +23,10 @@ contract FsTest is DSTest { assertEq(vm.readFile(path), "hello readable world\nthis is the second line!"); - vm.expectRevert(FOUNDRY_READ_ERR); + vm._expectCheatcodeRevert(FOUNDRY_READ_ERR); vm.readFile("/etc/hosts"); - vm.expectRevert(FOUNDRY_READ_ERR); + vm._expectCheatcodeRevert(FOUNDRY_READ_ERR); vm.readFileBinary("/etc/hosts"); } @@ -37,7 +37,7 @@ contract FsTest is DSTest { assertEq(vm.readLine(path), "this is the second line!"); assertEq(vm.readLine(path), ""); - vm.expectRevert(FOUNDRY_READ_ERR); + vm._expectCheatcodeRevert(FOUNDRY_READ_ERR); vm.readLine("/etc/hosts"); } @@ -50,9 +50,9 @@ contract FsTest is DSTest { vm.removeFile(path); - vm.expectRevert(FOUNDRY_WRITE_ERR); + vm._expectCheatcodeRevert(FOUNDRY_WRITE_ERR); vm.writeFile("/etc/hosts", "malicious stuff"); - vm.expectRevert(FOUNDRY_WRITE_ERR); + vm._expectCheatcodeRevert(FOUNDRY_WRITE_ERR); vm.writeFileBinary("/etc/hosts", "malicious stuff"); } @@ -78,7 +78,7 @@ contract FsTest is DSTest { vm.removeFile(path); - vm.expectRevert(FOUNDRY_WRITE_ERR); + vm._expectCheatcodeRevert(FOUNDRY_WRITE_ERR); vm.writeLine("/etc/hosts", "malicious stuff"); } @@ -103,7 +103,7 @@ contract FsTest is DSTest { vm.removeFile(path); - vm.expectRevert(FOUNDRY_WRITE_ERR); + vm._expectCheatcodeRevert(FOUNDRY_WRITE_ERR); vm.removeFile("/etc/hosts"); } @@ -111,16 +111,16 @@ contract FsTest is DSTest { string memory root = vm.projectRoot(); string memory foundryToml = string.concat(root, "/", "foundry.toml"); - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.writeLine(foundryToml, "\nffi = true\n"); - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.writeLine("foundry.toml", "\nffi = true\n"); - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.writeLine("./foundry.toml", "\nffi = true\n"); - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.writeLine("./Foundry.toml", "\nffi = true\n"); } @@ -128,16 +128,16 @@ contract FsTest is DSTest { string memory root = vm.projectRoot(); string memory foundryToml = string.concat(root, "/", "foundry.toml"); - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.writeFile(foundryToml, "\nffi = true\n"); - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.writeFile("foundry.toml", "\nffi = true\n"); - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.writeFile("./foundry.toml", "\nffi = true\n"); - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.writeFile("./Foundry.toml", "\nffi = true\n"); } @@ -156,7 +156,7 @@ contract FsTest is DSTest { assertEq(entries[1].path, entries2[1].path); string memory contents = vm.readFile(entries[0].path); - assertEq(contents, unicode"Wow! 😀\n"); + assertEq(contents, unicode"Wow! 😀"); } { @@ -172,7 +172,7 @@ contract FsTest is DSTest { assertEntry(entries[4], 3, false); } - vm.expectRevert(FOUNDRY_READ_DIR_ERR); + vm._expectCheatcodeRevert(FOUNDRY_READ_DIR_ERR); vm.readDir("/etc"); } @@ -184,11 +184,11 @@ contract FsTest is DSTest { assertEq(vm.fsMetadata(path).isDir, true); vm.removeDir(path, false); - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.fsMetadata(path); // reverts because not recursive - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.createDir(child, false); vm.createDir(child, true); @@ -196,9 +196,9 @@ contract FsTest is DSTest { // deleted both, recursively vm.removeDir(path, true); - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.fsMetadata(path); - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.fsMetadata(child); } @@ -226,10 +226,10 @@ contract FsTest is DSTest { // TODO: symlinks are canonicalized away in `ensure_path_allowed` // assertEq(metadata.isSymlink, true); - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.fsMetadata("../not-found"); - vm.expectRevert(FOUNDRY_READ_ERR); + vm._expectCheatcodeRevert(FOUNDRY_READ_ERR); vm.fsMetadata("/etc/hosts"); } diff --git a/testdata/cheats/GasMetering.t.sol b/testdata/default/cheats/GasMetering.t.sol similarity index 98% rename from testdata/cheats/GasMetering.t.sol rename to testdata/default/cheats/GasMetering.t.sol index e5616634fac3d..54d0a7422aece 100644 --- a/testdata/cheats/GasMetering.t.sol +++ b/testdata/default/cheats/GasMetering.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract B { function a() public returns (uint256) { diff --git a/testdata/default/cheats/GetBlockTimestamp.t.sol b/testdata/default/cheats/GetBlockTimestamp.t.sol new file mode 100644 index 0000000000000..383bfa8b0801d --- /dev/null +++ b/testdata/default/cheats/GetBlockTimestamp.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract GetBlockTimestampTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testGetTimestamp() public { + uint256 timestamp = vm.getBlockTimestamp(); + assertEq(timestamp, 1, "timestamp should be 1"); + } + + function testGetTimestampWithWarp() public { + assertEq(vm.getBlockTimestamp(), 1, "timestamp should be 1"); + vm.warp(10); + assertEq(vm.getBlockTimestamp(), 10, "warp failed"); + } + + function testGetTimestampWithWarpFuzzed(uint128 jump) public { + uint256 pre = vm.getBlockTimestamp(); + vm.warp(pre + jump); + assertEq(vm.getBlockTimestamp(), pre + jump, "warp failed"); + } +} diff --git a/testdata/cheats/GetCode.t.sol b/testdata/default/cheats/GetCode.t.sol similarity index 80% rename from testdata/cheats/GetCode.t.sol rename to testdata/default/cheats/GetCode.t.sol index db8841f60dc0b..73980d7b29810 100644 --- a/testdata/cheats/GetCode.t.sol +++ b/testdata/default/cheats/GetCode.t.sol @@ -2,7 +2,11 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; + +contract TestContract {} + +contract TestContractGetCode {} contract GetCodeTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -70,4 +74,22 @@ contract GetCodeTest is DSTest { function testFailGetUnlinked() public { vm.getCode("UnlinkedContract.sol"); } + + function testWithVersion() public { + bytes memory code = vm.getCode("cheats/GetCode.t.sol:TestContract:0.8.18"); + assertEq(type(TestContract).creationCode, code); + + vm._expectCheatcodeRevert("No matching artifact found"); + vm.getCode("cheats/GetCode.t.sol:TestContract:0.8.19"); + } + + function testByName() public { + bytes memory code = vm.getCode("TestContractGetCode"); + assertEq(type(TestContractGetCode).creationCode, code); + } + + function testByNameAndVersion() public { + bytes memory code = vm.getCode("TestContractGetCode:0.8.18"); + assertEq(type(TestContractGetCode).creationCode, code); + } } diff --git a/testdata/cheats/GetDeployedCode.t.sol b/testdata/default/cheats/GetDeployedCode.t.sol similarity index 87% rename from testdata/cheats/GetDeployedCode.t.sol rename to testdata/default/cheats/GetDeployedCode.t.sol index 71020af18448b..8d95b243ce564 100644 --- a/testdata/cheats/GetDeployedCode.t.sol +++ b/testdata/default/cheats/GetDeployedCode.t.sol @@ -2,7 +2,9 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; + +contract TestContract {} contract GetDeployedCodeTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -36,6 +38,16 @@ contract GetDeployedCodeTest is DSTest { emit Payload(address(this), address(0), "hello"); over.emitPayload(address(0), "hello"); } + + function testWithVersion() public { + TestContract test = new TestContract(); + bytes memory code = vm.getDeployedCode("cheats/GetDeployedCode.t.sol:TestContract:0.8.18"); + + assertEq(address(test).code, code); + + vm._expectCheatcodeRevert("No matching artifact found"); + vm.getDeployedCode("cheats/GetDeployedCode.t.sol:TestContract:0.8.19"); + } } interface Override { diff --git a/testdata/cheats/GetLabel.t.sol b/testdata/default/cheats/GetLabel.t.sol similarity index 94% rename from testdata/cheats/GetLabel.t.sol rename to testdata/default/cheats/GetLabel.t.sol index 784a18cea0d70..dcbe0812c8907 100644 --- a/testdata/cheats/GetLabel.t.sol +++ b/testdata/default/cheats/GetLabel.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract GetLabelTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/GetNonce.t.sol b/testdata/default/cheats/GetNonce.t.sol similarity index 94% rename from testdata/cheats/GetNonce.t.sol rename to testdata/default/cheats/GetNonce.t.sol index 8d3a196466b7d..7eb53f205bd19 100644 --- a/testdata/cheats/GetNonce.t.sol +++ b/testdata/default/cheats/GetNonce.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Foo {} diff --git a/testdata/cheats/Json.t.sol b/testdata/default/cheats/Json.t.sol similarity index 75% rename from testdata/cheats/Json.t.sol rename to testdata/default/cheats/Json.t.sol index b4b7059979307..ca53b18012978 100644 --- a/testdata/cheats/Json.t.sol +++ b/testdata/default/cheats/Json.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; import "../logs/console.sol"; contract ParseJsonTest is DSTest { @@ -14,39 +14,25 @@ contract ParseJsonTest is DSTest { json = vm.readFile(path); } - function test_uintArray() public { - bytes memory data = vm.parseJson(json, ".uintArray"); - uint256[] memory decodedData = abi.decode(data, (uint256[])); - assertEq(42, decodedData[0]); - assertEq(43, decodedData[1]); - } - - function test_str() public { - bytes memory data = vm.parseJson(json, ".str"); + function test_basicString() public { + bytes memory data = vm.parseJson(json, ".basicString"); string memory decodedData = abi.decode(data, (string)); assertEq("hai", decodedData); } - function test_strArray() public { - bytes memory data = vm.parseJson(json, ".strArray"); + function test_null() public { + bytes memory data = vm.parseJson(json, ".null"); + bytes memory decodedData = abi.decode(data, (bytes)); + assertEq(new bytes(0), decodedData); + } + + function test_stringArray() public { + bytes memory data = vm.parseJson(json, ".stringArray"); string[] memory decodedData = abi.decode(data, (string[])); assertEq("hai", decodedData[0]); assertEq("there", decodedData[1]); } - function test_bool() public { - bytes memory data = vm.parseJson(json, ".bool"); - bool decodedData = abi.decode(data, (bool)); - assertTrue(decodedData); - } - - function test_boolArray() public { - bytes memory data = vm.parseJson(json, ".boolArray"); - bool[] memory decodedData = abi.decode(data, (bool[])); - assertTrue(decodedData[0]); - assertTrue(!decodedData[1]); - } - function test_address() public { bytes memory data = vm.parseJson(json, ".address"); address decodedData = abi.decode(data, (address)); @@ -65,18 +51,31 @@ contract ParseJsonTest is DSTest { assertEq("0000000000000000000000000000000000001337", data); } - struct Nested { - uint256 number; - string str; + function test_bool() public { + bytes memory data = vm.parseJson(json, ".boolTrue"); + bool decodedData = abi.decode(data, (bool)); + assertTrue(decodedData); + + data = vm.parseJson(json, ".boolFalse"); + decodedData = abi.decode(data, (bool)); + assertTrue(!decodedData); } - function test_nestedObject() public { - bytes memory data = vm.parseJson(json, ".nestedObject"); - Nested memory nested = abi.decode(data, (Nested)); - assertEq(nested.number, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - assertEq(nested.str, "NEST"); + function test_boolArray() public { + bytes memory data = vm.parseJson(json, ".boolArray"); + bool[] memory decodedData = abi.decode(data, (bool[])); + assertTrue(decodedData[0]); + assertTrue(!decodedData[1]); } + function test_uintArray() public { + bytes memory data = vm.parseJson(json, ".uintArray"); + uint256[] memory decodedData = abi.decode(data, (uint256[])); + assertEq(42, decodedData[0]); + assertEq(43, decodedData[1]); + } + + // Object keys are sorted alphabetically, regardless of input. struct Whole { string str; string[] strArray; @@ -85,7 +84,7 @@ contract ParseJsonTest is DSTest { function test_wholeObject() public { // we need to make the path relative to the crate that's running tests for it (forge crate) - string memory path = "fixtures/Json/wholeJson.json"; + string memory path = "fixtures/Json/whole_json.json"; console.log(path); json = vm.readFile(path); bytes memory data = vm.parseJson(json); @@ -98,40 +97,69 @@ contract ParseJsonTest is DSTest { } function test_coercionRevert() public { - vm.expectRevert("values at \".nestedObject\" must not be JSON objects"); - uint256 number = this.parseJsonUint(json, ".nestedObject"); - } - - function parseJsonUint(string memory json, string memory path) public returns (uint256) { - uint256 data = vm.parseJsonUint(json, path); + vm._expectCheatcodeRevert("values at \".nestedObject\" must not be JSON objects"); + vm.parseJsonUint(json, ".nestedObject"); } function test_coercionUint() public { - uint256 number = vm.parseJsonUint(json, ".hexUint"); + uint256 number = vm.parseJsonUint(json, ".uintHex"); assertEq(number, 1231232); - number = vm.parseJsonUint(json, ".stringUint"); + number = vm.parseJsonUint(json, ".uintString"); assertEq(number, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - number = vm.parseJsonUint(json, ".numberUint"); + number = vm.parseJsonUint(json, ".uintNumber"); assertEq(number, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - uint256[] memory numbers = vm.parseJsonUintArray(json, ".arrayUint"); + uint256[] memory numbers = vm.parseJsonUintArray(json, ".uintArray"); + assertEq(numbers[0], 42); + assertEq(numbers[1], 43); + numbers = vm.parseJsonUintArray(json, ".uintStringArray"); assertEq(numbers[0], 1231232); assertEq(numbers[1], 1231232); assertEq(numbers[2], 1231232); } function test_coercionInt() public { - int256 number = vm.parseJsonInt(json, ".hexInt"); + int256 number = vm.parseJsonInt(json, ".intNumber"); assertEq(number, -12); - number = vm.parseJsonInt(json, ".stringInt"); + number = vm.parseJsonInt(json, ".intString"); + assertEq(number, -12); + number = vm.parseJsonInt(json, ".intHex"); assertEq(number, -12); } function test_coercionBool() public { - bool boolean = vm.parseJsonBool(json, ".booleanString"); + bool boolean = vm.parseJsonBool(json, ".boolTrue"); + assertTrue(boolean); + bool boolFalse = vm.parseJsonBool(json, ".boolFalse"); + assertTrue(!boolFalse); + boolean = vm.parseJsonBool(json, ".boolString"); assertEq(boolean, true); - bool[] memory booleans = vm.parseJsonBoolArray(json, ".booleanArray"); - assert(booleans[0]); - assert(!booleans[1]); + bool[] memory booleans = vm.parseJsonBoolArray(json, ".boolArray"); + assertTrue(booleans[0]); + assertTrue(!booleans[1]); + booleans = vm.parseJsonBoolArray(json, ".boolStringArray"); + assertTrue(booleans[0]); + assertTrue(!booleans[1]); + } + + function test_coercionBytes() public { + bytes memory bytes_ = vm.parseJsonBytes(json, ".bytesString"); + assertEq(bytes_, hex"01"); + + bytes[] memory bytesArray = vm.parseJsonBytesArray(json, ".bytesStringArray"); + assertEq(bytesArray[0], hex"01"); + assertEq(bytesArray[1], hex"02"); + } + + struct Nested { + uint256 number; + string str; + } + + function test_nestedObject() public { + bytes memory data = vm.parseJson(json, ".nestedObject"); + Nested memory nested = abi.decode(data, (Nested)); + assertEq(nested.number, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + assertEq(nested.str, "NEST"); } function test_advancedJsonPath() public { @@ -142,7 +170,7 @@ contract ParseJsonTest is DSTest { } function test_canonicalizePath() public { - bytes memory data = vm.parseJson(json, "$.str"); + bytes memory data = vm.parseJson(json, "$.basicString"); string memory decodedData = abi.decode(data, (string)); assertEq("hai", decodedData); } @@ -155,19 +183,27 @@ contract ParseJsonTest is DSTest { function test_parseJsonKeys() public { string memory jsonString = '{"some_key_to_value": "some_value", "some_key_to_array": [1, 2, 3], "some_key_to_object": {"key1": "value1", "key2": 2}}'; + string[] memory keys = vm.parseJsonKeys(jsonString, "$"); - assertEq(abi.encode(keys), abi.encode(["some_key_to_value", "some_key_to_array", "some_key_to_object"])); + string[] memory expected = new string[](3); + expected[0] = "some_key_to_value"; + expected[1] = "some_key_to_array"; + expected[2] = "some_key_to_object"; + assertEq(abi.encode(keys), abi.encode(expected)); keys = vm.parseJsonKeys(jsonString, ".some_key_to_object"); - assertEq(abi.encode(keys), abi.encode(["key1", "key2"])); + expected = new string[](2); + expected[0] = "key1"; + expected[1] = "key2"; + assertEq(abi.encode(keys), abi.encode(expected)); - vm.expectRevert("JSON value at \".some_key_to_array\" is not an object"); + vm._expectCheatcodeRevert("JSON value at \".some_key_to_array\" is not an object"); vm.parseJsonKeys(jsonString, ".some_key_to_array"); - vm.expectRevert("JSON value at \".some_key_to_value\" is not an object"); + vm._expectCheatcodeRevert("JSON value at \".some_key_to_value\" is not an object"); vm.parseJsonKeys(jsonString, ".some_key_to_value"); - vm.expectRevert("JSON value at \".*\" is not an object"); + vm._expectCheatcodeRevert("key \".*\" must return exactly one JSON object"); vm.parseJsonKeys(jsonString, ".*"); } } @@ -188,6 +224,7 @@ contract WriteJsonTest is DSTest { vm.serializeBool(json1, "boolean", true); vm.serializeInt(json2, "key2", -234); vm.serializeUint(json2, "deploy", uint256(254)); + vm.serializeUintToHex(json2, "hexUint", uint256(255)); string memory data = vm.serializeBool(json2, "boolean", true); vm.serializeString(json2, "json1", data); emit log(data); @@ -282,17 +319,25 @@ contract WriteJsonTest is DSTest { assertEq(decodedData.a, 123); } - function test_checkKeyExists() public { + function test_checkKeyExistsJson() public { string memory path = "fixtures/Json/write_complex_test.json"; string memory json = vm.readFile(path); - bool exists = vm.keyExists(json, ".a"); + bool exists = vm.keyExistsJson(json, ".a"); + assertTrue(exists); + + // TODO: issue deprecation warning + exists = vm.keyExists(json, ".a"); assertTrue(exists); } - function test_checkKeyDoesNotExist() public { + function test_checkKeyDoesNotExistJson() public { string memory path = "fixtures/Json/write_complex_test.json"; string memory json = vm.readFile(path); - bool exists = vm.keyExists(json, ".d"); + bool exists = vm.keyExistsJson(json, ".d"); + assertTrue(!exists); + + // TODO: issue deprecation warning + exists = vm.keyExists(json, ".d"); assertTrue(!exists); } diff --git a/testdata/cheats/Label.t.sol b/testdata/default/cheats/Label.t.sol similarity index 91% rename from testdata/cheats/Label.t.sol rename to testdata/default/cheats/Label.t.sol index b8e29d195a05f..d554f637dfbde 100644 --- a/testdata/cheats/Label.t.sol +++ b/testdata/default/cheats/Label.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract LabelTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/LastCallGas.t.sol b/testdata/default/cheats/LastCallGas.t.sol new file mode 100644 index 0000000000000..ec8c6ba0aad5a --- /dev/null +++ b/testdata/default/cheats/LastCallGas.t.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Target { + uint256 public slot0; + + function expandMemory(uint256 n) public pure returns (uint256) { + uint256[] memory arr = new uint256[](n); + + for (uint256 i = 0; i < n; i++) { + arr[i] = i; + } + + return arr.length; + } + + function setValue(uint256 value) public { + slot0 = value; + } + + function resetValue() public { + slot0 = 0; + } + + fallback() external {} +} + +abstract contract LastCallGasFixture is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Target public target; + + struct Gas { + uint64 gasTotalUsed; + uint64 gasMemoryUsed; + int64 gasRefunded; + } + + function testRevertNoCachedLastCallGas() public { + vm.expectRevert(); + vm.lastCallGas(); + } + + function _setup() internal { + // Cannot be set in `setUp` due to `testRevertNoCachedLastCallGas` + // relying on no calls being made before `lastCallGas` is called. + target = new Target(); + } + + function _performCall() internal returns (bool success) { + (success,) = address(target).call(""); + } + + function _performExpandMemory() internal view { + target.expandMemory(1000); + } + + function _performRefund() internal { + target.setValue(1); + target.resetValue(); + } + + function _assertGas(Vm.Gas memory lhs, Gas memory rhs) internal { + assertGt(lhs.gasLimit, 0); + assertGt(lhs.gasRemaining, 0); + assertEq(lhs.gasTotalUsed, rhs.gasTotalUsed); + assertEq(lhs.gasMemoryUsed, rhs.gasMemoryUsed); + assertEq(lhs.gasRefunded, rhs.gasRefunded); + } +} + +contract LastCallGasIsolatedTest is LastCallGasFixture { + function testRecordLastCallGas() public { + _setup(); + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasMemoryUsed: 0, gasRefunded: 0})); + + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasMemoryUsed: 0, gasRefunded: 0})); + + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasMemoryUsed: 0, gasRefunded: 0})); + } + + function testRecordGasMemory() public { + _setup(); + _performExpandMemory(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 186470, gasMemoryUsed: 4994, gasRefunded: 0})); + } + + function testRecordGasRefund() public { + _setup(); + _performRefund(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21380, gasMemoryUsed: 0, gasRefunded: 4800})); + } +} + +// Without isolation mode enabled the gas usage will be incorrect. +contract LastCallGasDefaultTest is LastCallGasFixture { + function testRecordLastCallGas() public { + _setup(); + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasMemoryUsed: 9, gasRefunded: 0})); + + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasMemoryUsed: 9, gasRefunded: 0})); + + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasMemoryUsed: 9, gasRefunded: 0})); + } + + function testRecordGasMemory() public { + _setup(); + _performExpandMemory(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 186470, gasMemoryUsed: 4994, gasRefunded: 0})); + } + + function testRecordGasRefund() public { + _setup(); + _performRefund(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 216, gasMemoryUsed: 9, gasRefunded: 19900})); + } +} diff --git a/testdata/cheats/Load.t.sol b/testdata/default/cheats/Load.t.sol similarity index 97% rename from testdata/cheats/Load.t.sol rename to testdata/default/cheats/Load.t.sol index fa5680d71bed5..37a2c80b298cc 100644 --- a/testdata/cheats/Load.t.sol +++ b/testdata/default/cheats/Load.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Storage { uint256 slot0 = 10; diff --git a/testdata/cheats/Mapping.t.sol b/testdata/default/cheats/Mapping.t.sol similarity index 99% rename from testdata/cheats/Mapping.t.sol rename to testdata/default/cheats/Mapping.t.sol index 4dec4156b7605..6cd141fa85a7e 100644 --- a/testdata/cheats/Mapping.t.sol +++ b/testdata/default/cheats/Mapping.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract RecordMapping { int256 length; diff --git a/testdata/cheats/MemSafety.t.sol b/testdata/default/cheats/MemSafety.t.sol similarity index 91% rename from testdata/cheats/MemSafety.t.sol rename to testdata/default/cheats/MemSafety.t.sol index 0da135d0c2a8a..096d8ac471823 100644 --- a/testdata/cheats/MemSafety.t.sol +++ b/testdata/default/cheats/MemSafety.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract MemSafetyTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -411,6 +411,22 @@ contract MemSafetyTest is DSTest { uint256 b = a + 1; } + /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `MLOAD` opcode. + function testExpectSafeMemory_MLOAD_REVERT() public { + vm.expectSafeMemory(0x80, 0x100); + + vm.expectRevert(); + + // This should revert. Ugly hack to make sure the mload isn't optimized + // out. + uint256 a; + assembly { + a := mload(0x100) + } + uint256 b = a + 1; + } + /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` /// will cause the test to fail while using the `MLOAD` opcode. function testFailExpectSafeMemory_MLOAD() public { @@ -486,6 +502,17 @@ contract MemSafetyTest is DSTest { } } + /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `LOG0` opcode. + function testExpectSafeMemory_LOG0_REVERT() public { + vm.expectSafeMemory(0x80, 0x100); + vm.expectRevert(); + // This should revert. + assembly { + log0(0x100, 0x20) + } + } + //////////////////////////////////////////////////////////////// // CREATE/CREATE2 (Read Expansion) // //////////////////////////////////////////////////////////////// @@ -670,6 +697,65 @@ contract MemSafetyTest is DSTest { } } + //////////////////////////////////////////////////////////////// + // `stopExpectSafeMemory` cheatcode // + //////////////////////////////////////////////////////////////// + + /// @dev Tests that the `stopExpectSafeMemory` cheatcode works as expected. + function testStopExpectSafeMemory() public { + uint64 initPtr; + assembly { + initPtr := mload(0x40) + } + + vm.expectSafeMemory(initPtr, initPtr + 0x20); + assembly { + // write to allowed range + mstore(initPtr, 0x01) + } + + vm.stopExpectSafeMemory(); + + assembly { + // write ouside allowed range, this should be fine + mstore(add(initPtr, 0x20), 0x01) + } + } + + /// @dev Tests that the `stopExpectSafeMemory` cheatcode does not cause violations not being noticed. + function testFailStopExpectSafeMemory() public { + uint64 initPtr; + assembly { + initPtr := mload(0x40) + } + + vm.expectSafeMemory(initPtr, initPtr + 0x20); + assembly { + // write outside of allowed range, this should revert + mstore(add(initPtr, 0x20), 0x01) + } + + vm.stopExpectSafeMemory(); + } + + /// @dev Tests that the `stopExpectSafeMemory` cheatcode can still be called if the free memory pointer was + /// updated to the exclusive upper boundary during execution. + function testStopExpectSafeMemory_freeMemUpdate() public { + uint64 initPtr; + assembly { + initPtr := mload(0x40) + } + + vm.expectSafeMemory(initPtr, initPtr + 0x20); + assembly { + // write outside of allowed range, this should revert + mstore(initPtr, 0x01) + mstore(0x40, add(initPtr, 0x20)) + } + + vm.stopExpectSafeMemory(); + } + //////////////////////////////////////////////////////////////// // HELPERS // //////////////////////////////////////////////////////////////// diff --git a/testdata/cheats/MockCall.t.sol b/testdata/default/cheats/MockCall.t.sol similarity index 99% rename from testdata/cheats/MockCall.t.sol rename to testdata/default/cheats/MockCall.t.sol index fa7d9f31449c8..a70b3572b4a85 100644 --- a/testdata/cheats/MockCall.t.sol +++ b/testdata/default/cheats/MockCall.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Mock { uint256 state = 0; diff --git a/testdata/cheats/Parse.t.sol b/testdata/default/cheats/Parse.t.sol similarity index 99% rename from testdata/cheats/Parse.t.sol rename to testdata/default/cheats/Parse.t.sol index a39d32d084b41..71d49af6f2d0b 100644 --- a/testdata/cheats/Parse.t.sol +++ b/testdata/default/cheats/Parse.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract ParseTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Prank.t.sol b/testdata/default/cheats/Prank.t.sol similarity index 99% rename from testdata/cheats/Prank.t.sol rename to testdata/default/cheats/Prank.t.sol index 0e23ed7b805d9..f7dd9b714f80c 100644 --- a/testdata/cheats/Prank.t.sol +++ b/testdata/default/cheats/Prank.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Victim { function assertCallerAndOrigin( diff --git a/testdata/cheats/Prevrandao.t.sol b/testdata/default/cheats/Prevrandao.t.sol similarity index 85% rename from testdata/cheats/Prevrandao.t.sol rename to testdata/default/cheats/Prevrandao.t.sol index 20bab12c4168b..7011ce3bee9cc 100644 --- a/testdata/cheats/Prevrandao.t.sol +++ b/testdata/default/cheats/Prevrandao.t.sol @@ -2,21 +2,21 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract PrevrandaoTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function testPrevrandao() public { assertEq(block.prevrandao, 0); - vm.prevrandao(bytes32(uint256(10))); + vm.prevrandao(uint256(10)); assertEq(block.prevrandao, 10, "prevrandao cheatcode failed"); } function testPrevrandaoFuzzed(uint256 newPrevrandao) public { vm.assume(newPrevrandao != block.prevrandao); assertEq(block.prevrandao, 0); - vm.prevrandao(bytes32(newPrevrandao)); + vm.prevrandao(newPrevrandao); assertEq(block.prevrandao, newPrevrandao); } @@ -25,7 +25,7 @@ contract PrevrandaoTest is DSTest { uint256 oldPrevrandao = block.prevrandao; uint256 snapshot = vm.snapshot(); - vm.prevrandao(bytes32(newPrevrandao)); + vm.prevrandao(newPrevrandao); assertEq(block.prevrandao, newPrevrandao); assert(vm.revertTo(snapshot)); diff --git a/testdata/cheats/ProjectRoot.t.sol b/testdata/default/cheats/ProjectRoot.t.sol similarity index 97% rename from testdata/cheats/ProjectRoot.t.sol rename to testdata/default/cheats/ProjectRoot.t.sol index 1edfb0e0795f5..31e68e1058cba 100644 --- a/testdata/cheats/ProjectRoot.t.sol +++ b/testdata/default/cheats/ProjectRoot.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract ProjectRootTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/Prompt.t.sol b/testdata/default/cheats/Prompt.t.sol new file mode 100644 index 0000000000000..9e461c2b527e0 --- /dev/null +++ b/testdata/default/cheats/Prompt.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../logs/console.sol"; + +contract PromptTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testPrompt_revertNotATerminal() public { + // should revert in CI and testing environments either with timout or because no terminal is available + vm._expectCheatcodeRevert(); + vm.prompt("test"); + + vm._expectCheatcodeRevert(); + vm.promptSecret("test"); + } + + function testPrompt_Address() public { + vm._expectCheatcodeRevert(); + address test = vm.promptAddress("test"); + } + + function testPrompt_Uint() public { + vm._expectCheatcodeRevert(); + uint256 test = vm.promptUint("test"); + } +} diff --git a/testdata/cheats/ReadCallers.t.sol b/testdata/default/cheats/ReadCallers.t.sol similarity index 99% rename from testdata/cheats/ReadCallers.t.sol rename to testdata/default/cheats/ReadCallers.t.sol index 82210b6c47410..e0da8ed0ddccc 100644 --- a/testdata/cheats/ReadCallers.t.sol +++ b/testdata/default/cheats/ReadCallers.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Target { function consumeNewCaller() external {} diff --git a/testdata/cheats/Record.t.sol b/testdata/default/cheats/Record.t.sol similarity index 98% rename from testdata/cheats/Record.t.sol rename to testdata/default/cheats/Record.t.sol index 6fdfa627d24ae..152a5ccb5d0fc 100644 --- a/testdata/cheats/Record.t.sol +++ b/testdata/default/cheats/Record.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract RecordAccess { function record() public returns (NestedRecordAccess) { diff --git a/testdata/cheats/RecordAccountAccesses.t.sol b/testdata/default/cheats/RecordAccountAccesses.t.sol similarity index 86% rename from testdata/cheats/RecordAccountAccesses.t.sol rename to testdata/default/cheats/RecordAccountAccesses.t.sol index e27c2dd0c5f1c..a0aa2cb5332df 100644 --- a/testdata/cheats/RecordAccountAccesses.t.sol +++ b/testdata/default/cheats/RecordAccountAccesses.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; /// @notice Helper contract with a construction that makes a call to itself then /// optionally reverts if zero-length data is passed @@ -121,6 +121,19 @@ contract NestedRunner { } } +/// Helper contract that uses all three EXT* opcodes on a given address +contract ExtChecker { + function checkExts(address a) external { + assembly { + let x := extcodesize(a) + let y := extcodehash(a) + extcodecopy(a, x, y, 0) + // sstore to check that storage accesses are correctly stored in a new access with a "resume" context + sstore(0, balance(a)) + } + } +} + /// @notice Helper contract that writes to storage in a nested call contract NestedStorer { mapping(bytes32 key => uint256 value) slots; @@ -196,6 +209,7 @@ contract RecordAccountAccessesTest is DSTest { Create2or create2or; StorageAccessor test1; StorageAccessor test2; + ExtChecker extChecker; function setUp() public { runner = new NestedRunner(); @@ -203,6 +217,7 @@ contract RecordAccountAccessesTest is DSTest { create2or = new Create2or(); test1 = new StorageAccessor(); test2 = new StorageAccessor(); + extChecker = new ExtChecker(); } function testStorageAccessDelegateCall() public { @@ -211,7 +226,7 @@ contract RecordAccountAccessesTest is DSTest { cheats.startStateDiffRecording(); address(proxy).call(abi.encodeCall(StorageAccessor.read, bytes32(uint256(1234)))); - Vm.AccountAccess[] memory called = cheats.stopAndReturnStateDiff(); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); assertEq(called.length, 2, "incorrect length"); @@ -246,7 +261,7 @@ contract RecordAccountAccessesTest is DSTest { two.write(bytes32(uint256(5678)), bytes32(uint256(123469))); two.write(bytes32(uint256(5678)), bytes32(uint256(1234))); - Vm.AccountAccess[] memory called = cheats.stopAndReturnStateDiff(); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); assertEq(called.length, 4, "incorrect length"); assertEq(called[0].storageAccesses.length, 1, "incorrect storage length"); @@ -317,7 +332,7 @@ contract RecordAccountAccessesTest is DSTest { // contract calls to self in constructor SelfCaller caller = new SelfCaller{value: 2 ether}("hello2 world2"); - Vm.AccountAccess[] memory called = cheats.stopAndReturnStateDiff(); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); assertEq(called.length, 6); assertEq( called[0], @@ -333,7 +348,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 0 }) ); @@ -351,7 +367,8 @@ contract RecordAccountAccessesTest is DSTest { value: 1 ether, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 }) ); assertEq( @@ -368,7 +385,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: "hello world", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 2 }) ); assertEq( @@ -385,7 +403,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 3 }) ); assertEq( @@ -402,7 +421,8 @@ contract RecordAccountAccessesTest is DSTest { value: 2 ether, data: abi.encodePacked(type(SelfCaller).creationCode, abi.encode("hello2 world2")), reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 3 }) ); assertEq( @@ -419,7 +439,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0.2 ether, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 3 }) ); } @@ -430,7 +451,7 @@ contract RecordAccountAccessesTest is DSTest { uint256 initBalance = address(this).balance; cheats.startStateDiffRecording(); try this.revertingCall{value: 1 ether}(address(1234), "") {} catch {} - Vm.AccountAccess[] memory called = cheats.stopAndReturnStateDiff(); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); assertEq(called.length, 2); assertEq( called[0], @@ -446,7 +467,8 @@ contract RecordAccountAccessesTest is DSTest { value: 1 ether, data: abi.encodeCall(this.revertingCall, (address(1234), "")), reverted: true, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 0 }) ); assertEq( @@ -463,7 +485,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0.1 ether, data: "", reverted: true, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 }) ); } @@ -485,10 +508,10 @@ contract RecordAccountAccessesTest is DSTest { /// @param shouldRevert Whether the first call should revert function runNested(bool shouldRevert, bool expectFirstCall) public { try runner.run{value: 1 ether}(shouldRevert) {} catch {} - Vm.AccountAccess[] memory called = cheats.stopAndReturnStateDiff(); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); assertEq(called.length, 7 + toUint(expectFirstCall), "incorrect length"); - uint256 startingIndex = toUint(expectFirstCall); + uint64 startingIndex = uint64(toUint(expectFirstCall)); if (expectFirstCall) { assertEq( called[0], @@ -504,7 +527,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex }) ); } @@ -536,7 +560,8 @@ contract RecordAccountAccessesTest is DSTest { value: 1 ether, data: abi.encodeCall(NestedRunner.run, (shouldRevert)), reverted: shouldRevert, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex }), false ); @@ -568,7 +593,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0.1 ether, data: abi.encodeCall(Reverter.run, ()), reverted: true, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 1 }), false ); @@ -600,7 +626,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0.01 ether, data: abi.encodeCall(Doer.run, ()), reverted: true, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 2 }), false ); @@ -632,7 +659,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0.001 ether, data: abi.encodeCall(Doer.doStuff, ()), reverted: true, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 3 }), false ); @@ -664,7 +692,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0.1 ether, data: abi.encodeCall(Succeeder.run, ()), reverted: shouldRevert, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 4 }), false ); @@ -696,7 +725,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0.01 ether, data: abi.encodeCall(Doer.run, ()), reverted: shouldRevert, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 5 }), false ); @@ -728,7 +758,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0.001 ether, data: abi.encodeCall(Doer.doStuff, ()), reverted: shouldRevert, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 6 }), false ); @@ -737,7 +768,7 @@ contract RecordAccountAccessesTest is DSTest { function testNestedStorage() public { cheats.startStateDiffRecording(); nestedStorer.run(); - Vm.AccountAccess[] memory called = cheats.stopAndReturnStateDiff(); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); assertEq(called.length, 3, "incorrect account access length"); assertEq(called[0].storageAccesses.length, 2, "incorrect run storage length"); @@ -767,7 +798,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: abi.encodeCall(NestedStorer.run, ()), reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 0 }), false ); @@ -811,7 +843,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: abi.encodeCall(NestedStorer.run2, ()), reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 }), false ); @@ -843,7 +876,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 2 }), false ); @@ -858,7 +892,7 @@ contract RecordAccountAccessesTest is DSTest { bytes memory creationCode = abi.encodePacked(type(ConstructorStorer).creationCode, abi.encode(true)); address hypotheticalStorer = deriveCreate2Address(address(create2or), bytes32(0), keccak256(creationCode)); - Vm.AccountAccess[] memory called = cheats.stopAndReturnStateDiff(); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); assertEq(called.length, 3, "incorrect account access length"); assertEq(toUint(called[0].kind), toUint(Vm.AccountAccessKind.Create), "incorrect kind"); assertEq(toUint(called[1].kind), toUint(Vm.AccountAccessKind.Call), "incorrect kind"); @@ -888,7 +922,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: abi.encodePacked(type(ConstructorStorer).creationCode, abi.encode(false)), reverted: false, - storageAccesses: storageAccesses + storageAccesses: storageAccesses, + depth: 0 }) ); @@ -908,9 +943,10 @@ contract RecordAccountAccessesTest is DSTest { data: abi.encodeCall( Create2or.create2, (bytes32(0), abi.encodePacked(type(ConstructorStorer).creationCode, abi.encode(true))) - ), + ), reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 }) ); @@ -938,7 +974,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: creationCode, reverted: true, - storageAccesses: storageAccesses + storageAccesses: storageAccesses, + depth: 2 }) ); } @@ -967,7 +1004,7 @@ contract RecordAccountAccessesTest is DSTest { try create2or.create2(bytes32(0), creationCode) {} catch {} address hypotheticalAddress = deriveCreate2Address(address(create2or), bytes32(0), keccak256(creationCode)); - Vm.AccountAccess[] memory called = cheats.stopAndReturnStateDiff(); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); assertEq(called.length, 3, "incorrect length"); assertEq( called[1], @@ -983,7 +1020,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: creationCode, reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 }) ); assertEq( @@ -1000,7 +1038,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 2 }) ); } @@ -1013,7 +1052,7 @@ contract RecordAccountAccessesTest is DSTest { this.startRecordingFromLowerDepth(); address a = address(new SelfDestructor{value: 1 ether}(address(this))); address b = address(new SelfDestructor{value: 1 ether}(address(bytes20("doesn't exist yet")))); - Vm.AccountAccess[] memory called = cheats.stopAndReturnStateDiff(); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); assertEq(called.length, 5, "incorrect length"); assertEq( called[1], @@ -1029,7 +1068,8 @@ contract RecordAccountAccessesTest is DSTest { value: 1 ether, data: abi.encodePacked(type(SelfDestructor).creationCode, abi.encode(address(this))), reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 }) ); assertEq( @@ -1046,7 +1086,8 @@ contract RecordAccountAccessesTest is DSTest { value: 1 ether, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 2 }) ); assertEq( @@ -1063,7 +1104,8 @@ contract RecordAccountAccessesTest is DSTest { value: 1 ether, data: abi.encodePacked(type(SelfDestructor).creationCode, abi.encode(address(bytes20("doesn't exist yet")))), reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 3 }) ); assertEq( @@ -1080,11 +1122,72 @@ contract RecordAccountAccessesTest is DSTest { value: 1 ether, data: "", reverted: false, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 4 }) ); } + /// @notice Asserts interaction between broadcast and recording cheatcodes + function testIssue6514() public { + cheats.startStateDiffRecording(); + cheats.startBroadcast(); + + StorageAccessor a = new StorageAccessor(); + + cheats.stopBroadcast(); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + assertEq(called.length, 1, "incorrect length"); + assertEq(toUint(called[0].kind), toUint(Vm.AccountAccessKind.Create)); + assertEq(called[0].account, address(a)); + } + + /// @notice Test that EXT* opcodes are recorded as account accesses + function testExtOpcodes() public { + cheats.startStateDiffRecording(); + extChecker.checkExts(address(1234)); + Vm.AccountAccess[] memory called = cheats.stopAndReturnStateDiff(); + assertEq(called.length, 7, "incorrect length"); + // initial solidity extcodesize check for calling extChecker + assertEq(toUint(called[0].kind), toUint(Vm.AccountAccessKind.Extcodesize)); + // call to extChecker + assertEq(toUint(called[1].kind), toUint(Vm.AccountAccessKind.Call)); + // extChecker checks + assertEq(toUint(called[2].kind), toUint(Vm.AccountAccessKind.Extcodesize)); + assertEq(toUint(called[3].kind), toUint(Vm.AccountAccessKind.Extcodehash)); + assertEq(toUint(called[4].kind), toUint(Vm.AccountAccessKind.Extcodecopy)); + assertEq(toUint(called[5].kind), toUint(Vm.AccountAccessKind.Balance)); + // resume of extChecker to hold SSTORE access + assertEq(toUint(called[6].kind), toUint(Vm.AccountAccessKind.Resume)); + assertEq(called[6].storageAccesses.length, 1, "incorrect length"); + } + + /** + * @notice Filter out extcodesize account accesses for legacy tests written before + * EXT* opcodes were supported. + */ + function filterExtcodesizeForLegacyTests(Vm.AccountAccess[] memory inArr) + internal + pure + returns (Vm.AccountAccess[] memory out) + { + // allocate max length for out array + out = new Vm.AccountAccess[](inArr.length); + // track end size + uint256 size; + for (uint256 i = 0; i < inArr.length; ++i) { + // only append if not extcodesize + if (inArr[i].kind != Vm.AccountAccessKind.Extcodesize) { + out[size] = inArr[i]; + ++size; + } + } + // manually truncate out array + assembly { + mstore(out, size) + } + } + function startRecordingFromLowerDepth() external { cheats.startStateDiffRecording(); assembly { @@ -1115,7 +1218,8 @@ contract RecordAccountAccessesTest is DSTest { value: 0, data: "", reverted: expected.reverted, - storageAccesses: new Vm.StorageAccess[](0) + storageAccesses: new Vm.StorageAccess[](0), + depth: 0 }), false ); diff --git a/testdata/cheats/RecordLogs.t.sol b/testdata/default/cheats/RecordLogs.t.sol similarity index 99% rename from testdata/cheats/RecordLogs.t.sol rename to testdata/default/cheats/RecordLogs.t.sol index 25fbfaeba43f8..728acdb9b0e76 100644 --- a/testdata/cheats/RecordLogs.t.sol +++ b/testdata/default/cheats/RecordLogs.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Emitter { event LogAnonymous(bytes data) anonymous; diff --git a/testdata/cheats/Remember.t.sol b/testdata/default/cheats/Remember.t.sol similarity index 96% rename from testdata/cheats/Remember.t.sol rename to testdata/default/cheats/Remember.t.sol index 5592081ea47e0..b5487c3698597 100644 --- a/testdata/cheats/Remember.t.sol +++ b/testdata/default/cheats/Remember.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract RememberTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/ResetNonce.t.sol b/testdata/default/cheats/ResetNonce.t.sol similarity index 97% rename from testdata/cheats/ResetNonce.t.sol rename to testdata/default/cheats/ResetNonce.t.sol index 914577bdcc060..9014336091d21 100644 --- a/testdata/cheats/ResetNonce.t.sol +++ b/testdata/default/cheats/ResetNonce.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Foo { function f() external view returns (uint256) { diff --git a/testdata/cheats/Roll.t.sol b/testdata/default/cheats/Roll.t.sol similarity index 97% rename from testdata/cheats/Roll.t.sol rename to testdata/default/cheats/Roll.t.sol index 50011fe87e127..820cd9887b6b6 100644 --- a/testdata/cheats/Roll.t.sol +++ b/testdata/default/cheats/Roll.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract RollTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/RpcUrls.t.sol b/testdata/default/cheats/RpcUrls.t.sol similarity index 70% rename from testdata/cheats/RpcUrls.t.sol rename to testdata/default/cheats/RpcUrls.t.sol index c974bf1475369..4e3ceba58115c 100644 --- a/testdata/cheats/RpcUrls.t.sol +++ b/testdata/default/cheats/RpcUrls.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract RpcUrlTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -15,17 +15,17 @@ contract RpcUrlTest is DSTest { // returns an error if env alias does not exist function testRevertsOnMissingEnv() public { - vm.expectRevert("invalid rpc url: rpcUrlEnv"); - string memory url = this.rpcUrl("rpcUrlEnv"); + vm._expectCheatcodeRevert("invalid rpc url: rpcUrlEnv"); + string memory url = vm.rpcUrl("rpcUrlEnv"); } // can set env and return correct url function testCanSetAndGetURLAndAllUrls() public { // this will fail because alias is not set - vm.expectRevert( + vm._expectCheatcodeRevert( "Failed to resolve env var `RPC_ENV_ALIAS` in `${RPC_ENV_ALIAS}`: environment variable not found" ); - string[2][] memory _urls = this.rpcUrls(); + string[2][] memory _urls = vm.rpcUrls(); string memory url = vm.rpcUrl("rpcAlias"); vm.setEnv("RPC_ENV_ALIAS", url); @@ -33,20 +33,15 @@ contract RpcUrlTest is DSTest { assertEq(url, envUrl); string[2][] memory allUrls = vm.rpcUrls(); - assertEq(allUrls.length, 2); + assertEq(allUrls.length, 3); string[2] memory val = allUrls[0]; assertEq(val[0], "rpcAlias"); string[2] memory env = allUrls[1]; - assertEq(env[0], "rpcEnvAlias"); - } - - function rpcUrl(string memory _alias) public returns (string memory) { - return vm.rpcUrl(_alias); - } + assertEq(env[0], "rpcAliasSepolia"); - function rpcUrls() public returns (string[2][] memory) { - return vm.rpcUrls(); + string[2] memory env2 = allUrls[2]; + assertEq(env2[0], "rpcEnvAlias"); } } diff --git a/testdata/cheats/SetNonce.t.sol b/testdata/default/cheats/SetNonce.t.sol similarity index 96% rename from testdata/cheats/SetNonce.t.sol rename to testdata/default/cheats/SetNonce.t.sol index 9285ca7e6293f..7f2e419b946f0 100644 --- a/testdata/cheats/SetNonce.t.sol +++ b/testdata/default/cheats/SetNonce.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Foo { function f() external view returns (uint256) { diff --git a/testdata/cheats/SetNonceUnsafe.t.sol b/testdata/default/cheats/SetNonceUnsafe.t.sol similarity index 97% rename from testdata/cheats/SetNonceUnsafe.t.sol rename to testdata/default/cheats/SetNonceUnsafe.t.sol index 1209a28147b2a..723f66ae2557b 100644 --- a/testdata/cheats/SetNonceUnsafe.t.sol +++ b/testdata/default/cheats/SetNonceUnsafe.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Foo { function f() external view returns (uint256) { diff --git a/testdata/cheats/Setup.t.sol b/testdata/default/cheats/Setup.t.sol similarity index 93% rename from testdata/cheats/Setup.t.sol rename to testdata/default/cheats/Setup.t.sol index 986b232d84410..d694fb2c1c620 100644 --- a/testdata/cheats/Setup.t.sol +++ b/testdata/default/cheats/Setup.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Victim { function assertSender(address sender) external { @@ -21,7 +21,7 @@ contract VmSetupTest is DSTest { vm.chainId(99); vm.roll(100); vm.fee(1000); - vm.prevrandao(bytes32(uint256(10000))); + vm.prevrandao(uint256(10000)); vm.startPrank(address(1337)); } diff --git a/testdata/cheats/Sign.t.sol b/testdata/default/cheats/Sign.t.sol similarity index 96% rename from testdata/cheats/Sign.t.sol rename to testdata/default/cheats/Sign.t.sol index 587d80e5fea7b..e46439b58d16d 100644 --- a/testdata/cheats/Sign.t.sol +++ b/testdata/default/cheats/Sign.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract SignTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/SignP256.t.sol b/testdata/default/cheats/SignP256.t.sol new file mode 100644 index 0000000000000..f1b62fe78d886 --- /dev/null +++ b/testdata/default/cheats/SignP256.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract SignTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testSignP256() public { + bytes32 pk = hex"A8568B74282DCC66FF70F10B4CE5CC7B391282F5381BBB4F4D8DD96974B16E6B"; + bytes32 digest = hex"54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad"; + + (bytes32 r, bytes32 s) = vm.signP256(uint256(pk), digest); + assertEq(r, hex"7C11C3641B19E7822DB644CBF76ED0420A013928C2FD3E36D8EF983B103BDFE1"); + assertEq(s, hex"317D89879868D484810D4E508A96109F8C87617B7BE9337411348D7B786F945F"); + } +} diff --git a/testdata/cheats/Skip.t.sol b/testdata/default/cheats/Skip.t.sol similarity index 96% rename from testdata/cheats/Skip.t.sol rename to testdata/default/cheats/Skip.t.sol index b5f8a019b340f..fb2deadb4f52a 100644 --- a/testdata/cheats/Skip.t.sol +++ b/testdata/default/cheats/Skip.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract SkipTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Sleep.t.sol b/testdata/default/cheats/Sleep.t.sol similarity index 98% rename from testdata/cheats/Sleep.t.sol rename to testdata/default/cheats/Sleep.t.sol index 37be632cc4592..448d34668bed8 100644 --- a/testdata/cheats/Sleep.t.sol +++ b/testdata/default/cheats/Sleep.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract SleepTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Snapshots.t.sol b/testdata/default/cheats/Snapshots.t.sol similarity index 50% rename from testdata/cheats/Snapshots.t.sol rename to testdata/default/cheats/Snapshots.t.sol index 9a174be98c524..bb7b4e0e6008d 100644 --- a/testdata/cheats/Snapshots.t.sol +++ b/testdata/default/cheats/Snapshots.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; struct Storage { uint256 slot0; @@ -32,6 +32,53 @@ contract SnapshotTest is DSTest { assertEq(store.slot1, 20, "snapshot revert for slot 1 unsuccessful"); } + function testSnapshotRevertDelete() public { + uint256 snapshot = vm.snapshot(); + store.slot0 = 300; + store.slot1 = 400; + + assertEq(store.slot0, 300); + assertEq(store.slot1, 400); + + vm.revertToAndDelete(snapshot); + assertEq(store.slot0, 10, "snapshot revert for slot 0 unsuccessful"); + assertEq(store.slot1, 20, "snapshot revert for slot 1 unsuccessful"); + // nothing to revert to anymore + assert(!vm.revertTo(snapshot)); + } + + function testSnapshotDelete() public { + uint256 snapshot = vm.snapshot(); + store.slot0 = 300; + store.slot1 = 400; + + vm.deleteSnapshot(snapshot); + // nothing to revert to anymore + assert(!vm.revertTo(snapshot)); + } + + function testSnapshotDeleteAll() public { + uint256 snapshot = vm.snapshot(); + store.slot0 = 300; + store.slot1 = 400; + + vm.deleteSnapshots(); + // nothing to revert to anymore + assert(!vm.revertTo(snapshot)); + } + + // + function testSnapshotsMany() public { + uint256 preState; + for (uint256 c = 0; c < 10; c++) { + for (uint256 cc = 0; cc < 10; cc++) { + preState = vm.snapshot(); + vm.revertToAndDelete(preState); + assert(!vm.revertTo(preState)); + } + } + } + // tests that snapshots can also revert changes to `block` function testBlockValues() public { uint256 num = block.number; @@ -46,7 +93,7 @@ contract SnapshotTest is DSTest { vm.roll(99); assertEq(block.number, 99); - vm.prevrandao(bytes32(uint256(123))); + vm.prevrandao(uint256(123)); assertEq(block.prevrandao, 123); assert(vm.revertTo(snapshot)); diff --git a/testdata/cheats/Store.t.sol b/testdata/default/cheats/Store.t.sol similarity index 98% rename from testdata/cheats/Store.t.sol rename to testdata/default/cheats/Store.t.sol index 08803b92fde32..059952fca7ff2 100644 --- a/testdata/cheats/Store.t.sol +++ b/testdata/default/cheats/Store.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Storage { uint256 public slot0 = 10; diff --git a/testdata/default/cheats/StringUtils.t.sol b/testdata/default/cheats/StringUtils.t.sol new file mode 100644 index 0000000000000..136164a413d7d --- /dev/null +++ b/testdata/default/cheats/StringUtils.t.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract StringManipulationTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testToLowercase() public { + string memory original = "Hello World"; + string memory lowercased = vm.toLowercase(original); + assertEq("hello world", lowercased); + } + + function testToUppercase() public { + string memory original = "Hello World"; + string memory uppercased = vm.toUppercase(original); + assertEq("HELLO WORLD", uppercased); + } + + function testTrim() public { + string memory original = " Hello World "; + string memory trimmed = vm.trim(original); + assertEq("Hello World", trimmed); + } + + function testReplace() public { + string memory original = "Hello World"; + string memory replaced = vm.replace(original, "World", "Reth"); + assertEq("Hello Reth", replaced); + } + + function testSplit() public { + string memory original = "Hello,World,Reth"; + string[] memory splitResult = vm.split(original, ","); + assertEq(3, splitResult.length); + assertEq("Hello", splitResult[0]); + assertEq("World", splitResult[1]); + assertEq("Reth", splitResult[2]); + } + + function testIndexOf() public { + string memory input = "Hello, World!"; + string memory key1 = "Hello,"; + string memory key2 = "World!"; + string memory key3 = ""; + string memory key4 = "foundry"; + assertEq(vm.indexOf(input, key1), 0); + assertEq(vm.indexOf(input, key2), 7); + assertEq(vm.indexOf(input, key3), 0); + assertEq(vm.indexOf(input, key4), type(uint256).max); + } +} diff --git a/testdata/cheats/ToString.t.sol b/testdata/default/cheats/ToString.t.sol similarity index 98% rename from testdata/cheats/ToString.t.sol rename to testdata/default/cheats/ToString.t.sol index c26fdce4cb2da..835c85242883b 100644 --- a/testdata/cheats/ToString.t.sol +++ b/testdata/default/cheats/ToString.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract ToStringTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/Toml.t.sol b/testdata/default/cheats/Toml.t.sol new file mode 100644 index 0000000000000..40667743f8d65 --- /dev/null +++ b/testdata/default/cheats/Toml.t.sol @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../logs/console.sol"; + +contract ParseTomlTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + string toml; + + function setUp() public { + string memory path = "fixtures/Toml/test.toml"; + toml = vm.readFile(path); + } + + function test_basicString() public { + bytes memory data = vm.parseToml(toml, ".basicString"); + string memory decodedData = abi.decode(data, (string)); + assertEq("hai", decodedData); + } + + function test_nullString() public { + bytes memory data = vm.parseToml(toml, ".nullString"); + string memory decodedData = abi.decode(data, (string)); + assertEq("", decodedData); + } + + function test_stringMultiline() public { + bytes memory data = vm.parseToml(toml, ".multilineString"); + string memory decodedData = abi.decode(data, (string)); + assertEq("hai\nthere\n", decodedData); + } + + function test_stringArray() public { + bytes memory data = vm.parseToml(toml, ".stringArray"); + string[] memory decodedData = abi.decode(data, (string[])); + assertEq("hai", decodedData[0]); + assertEq("there", decodedData[1]); + } + + function test_address() public { + bytes memory data = vm.parseToml(toml, ".address"); + address decodedData = abi.decode(data, (address)); + assertEq(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, decodedData); + } + + function test_addressArray() public { + bytes memory data = vm.parseToml(toml, ".addressArray"); + address[] memory decodedData = abi.decode(data, (address[])); + assertEq(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, decodedData[0]); + assertEq(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D, decodedData[1]); + } + + function test_H160ButNotaddress() public { + string memory data = abi.decode(vm.parseToml(toml, ".H160NotAddress"), (string)); + assertEq("0000000000000000000000000000000000001337", data); + } + + function test_bool() public { + bytes memory data = vm.parseToml(toml, ".boolTrue"); + bool decodedData = abi.decode(data, (bool)); + assertTrue(decodedData); + + data = vm.parseToml(toml, ".boolFalse"); + decodedData = abi.decode(data, (bool)); + assertTrue(!decodedData); + } + + function test_boolArray() public { + bytes memory data = vm.parseToml(toml, ".boolArray"); + bool[] memory decodedData = abi.decode(data, (bool[])); + assertTrue(decodedData[0]); + assertTrue(!decodedData[1]); + } + + function test_dateTime() public { + bytes memory data = vm.parseToml(toml, ".datetime"); + string memory decodedData = abi.decode(data, (string)); + assertEq(decodedData, "2021-08-10T14:48:00Z"); + } + + function test_dateTimeArray() public { + bytes memory data = vm.parseToml(toml, ".datetimeArray"); + string[] memory decodedData = abi.decode(data, (string[])); + assertEq(decodedData[0], "2021-08-10T14:48:00Z"); + assertEq(decodedData[1], "2021-08-10T14:48:00Z"); + } + + function test_uintArray() public { + bytes memory data = vm.parseToml(toml, ".uintArray"); + uint256[] memory decodedData = abi.decode(data, (uint256[])); + assertEq(42, decodedData[0]); + assertEq(43, decodedData[1]); + } + + // Object keys are sorted alphabetically, regardless of input. + struct Whole { + string str; + string[] strArray; + uint256[] uintArray; + } + + function test_wholeToml() public { + // we need to make the path relative to the crate that's running tests for it (forge crate) + string memory path = "fixtures/Toml/whole_toml.toml"; + console.log(path); + toml = vm.readFile(path); + bytes memory data = vm.parseToml(toml); + Whole memory whole = abi.decode(data, (Whole)); + assertEq(whole.str, "hai"); + assertEq(whole.strArray[0], "hai"); + assertEq(whole.strArray[1], "there"); + assertEq(whole.uintArray[0], 42); + assertEq(whole.uintArray[1], 43); + } + + function test_coercionRevert() public { + vm._expectCheatcodeRevert("values at \".nestedObject\" must not be JSON objects"); + vm.parseTomlUint(toml, ".nestedObject"); + } + + function test_coercionUint() public { + uint256 number = vm.parseTomlUint(toml, ".uintNumber"); + assertEq(number, 9223372036854775807); // TOML is limited to 64-bit integers + number = vm.parseTomlUint(toml, ".uintString"); + assertEq(number, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + number = vm.parseTomlUint(toml, ".uintHex"); + assertEq(number, 1231232); + uint256[] memory numbers = vm.parseTomlUintArray(toml, ".uintArray"); + assertEq(numbers[0], 42); + assertEq(numbers[1], 43); + numbers = vm.parseTomlUintArray(toml, ".uintStringArray"); + assertEq(numbers[0], 1231232); + assertEq(numbers[1], 1231232); + assertEq(numbers[2], 1231232); + } + + function test_coercionInt() public { + int256 number = vm.parseTomlInt(toml, ".intNumber"); + assertEq(number, -12); + number = vm.parseTomlInt(toml, ".intString"); + assertEq(number, -12); + number = vm.parseTomlInt(toml, ".intHex"); + assertEq(number, -12); + } + + function test_coercionBool() public { + bool boolean = vm.parseTomlBool(toml, ".boolTrue"); + assertTrue(boolean); + bool boolFalse = vm.parseTomlBool(toml, ".boolFalse"); + assertTrue(!boolFalse); + boolean = vm.parseTomlBool(toml, ".boolString"); + assertEq(boolean, true); + bool[] memory booleans = vm.parseTomlBoolArray(toml, ".boolArray"); + assertTrue(booleans[0]); + assertTrue(!booleans[1]); + booleans = vm.parseTomlBoolArray(toml, ".boolStringArray"); + assertTrue(booleans[0]); + assertTrue(!booleans[1]); + } + + function test_coercionBytes() public { + bytes memory bytes_ = vm.parseTomlBytes(toml, ".bytesString"); + assertEq(bytes_, hex"01"); + + bytes[] memory bytesArray = vm.parseTomlBytesArray(toml, ".bytesStringArray"); + assertEq(bytesArray[0], hex"01"); + assertEq(bytesArray[1], hex"02"); + } + + struct Nested { + uint256 number; + string str; + } + + function test_nestedObject() public { + bytes memory data = vm.parseToml(toml, ".nestedObject"); + Nested memory nested = abi.decode(data, (Nested)); + assertEq(nested.number, 9223372036854775807); // TOML is limited to 64-bit integers + assertEq(nested.str, "NEST"); + } + + function test_advancedJsonPath() public { + bytes memory data = vm.parseToml(toml, ".advancedJsonPath[*].id"); + uint256[] memory numbers = abi.decode(data, (uint256[])); + assertEq(numbers[0], 1); + assertEq(numbers[1], 2); + } + + function test_canonicalizePath() public { + bytes memory data = vm.parseToml(toml, "$.basicString"); + string memory decodedData = abi.decode(data, (string)); + assertEq("hai", decodedData); + } + + function test_nonExistentKey() public { + bytes memory data = vm.parseToml(toml, ".thisKeyDoesNotExist"); + assertEq(0, data.length); + } + + function test_parseTomlKeys() public { + string memory tomlString = + "some_key_to_value = \"some_value\"\n some_key_to_array = [1, 2, 3]\n [some_key_to_object]\n key1 = \"value1\"\n key2 = 2"; + + string[] memory keys = vm.parseTomlKeys(tomlString, "$"); + string[] memory expected = new string[](3); + expected[0] = "some_key_to_value"; + expected[1] = "some_key_to_array"; + expected[2] = "some_key_to_object"; + assertEq(abi.encode(keys), abi.encode(expected)); + + keys = vm.parseTomlKeys(tomlString, ".some_key_to_object"); + expected = new string[](2); + expected[0] = "key1"; + expected[1] = "key2"; + assertEq(abi.encode(keys), abi.encode(expected)); + + vm._expectCheatcodeRevert("JSON value at \".some_key_to_array\" is not an object"); + vm.parseTomlKeys(tomlString, ".some_key_to_array"); + + vm._expectCheatcodeRevert("JSON value at \".some_key_to_value\" is not an object"); + vm.parseTomlKeys(tomlString, ".some_key_to_value"); + + vm._expectCheatcodeRevert("key \".*\" must return exactly one JSON object"); + vm.parseTomlKeys(tomlString, ".*"); + } +} + +contract WriteTomlTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + string json1; + string json2; + + function setUp() public { + json1 = "example"; + json2 = "example2"; + } + + struct simpleJson { + uint256 a; + string b; + } + + struct notSimpleJson { + uint256 a; + string b; + simpleJson c; + } + + function test_serializeNotSimpleToml() public { + string memory json3 = "json3"; + string memory path = "fixtures/Toml/write_complex_test.toml"; + vm.serializeUint(json3, "a", uint256(123)); + string memory semiFinal = vm.serializeString(json3, "b", "test"); + string memory finalJson = vm.serializeString(json3, "c", semiFinal); + console.log(finalJson); + vm.writeToml(finalJson, path); + string memory toml = vm.readFile(path); + bytes memory data = vm.parseToml(toml); + notSimpleJson memory decodedData = abi.decode(data, (notSimpleJson)); + } + + function test_retrieveEntireToml() public { + string memory path = "fixtures/Toml/write_complex_test.toml"; + string memory toml = vm.readFile(path); + bytes memory data = vm.parseToml(toml, "."); + notSimpleJson memory decodedData = abi.decode(data, (notSimpleJson)); + console.log(decodedData.a); + assertEq(decodedData.a, 123); + } + + function test_checkKeyExists() public { + string memory path = "fixtures/Toml/write_complex_test.toml"; + string memory toml = vm.readFile(path); + bool exists = vm.keyExistsToml(toml, ".a"); + assertTrue(exists); + } + + function test_checkKeyDoesNotExist() public { + string memory path = "fixtures/Toml/write_complex_test.toml"; + string memory toml = vm.readFile(path); + bool exists = vm.keyExistsToml(toml, ".d"); + assertTrue(!exists); + } + + function test_writeToml() public { + string memory json3 = "json3"; + string memory path = "fixtures/Toml/write_test.toml"; + vm.serializeUint(json3, "a", uint256(123)); + string memory finalJson = vm.serializeString(json3, "b", "test"); + vm.writeToml(finalJson, path); + + string memory toml = vm.readFile(path); + bytes memory data = vm.parseToml(toml); + simpleJson memory decodedData = abi.decode(data, (simpleJson)); + assertEq(decodedData.a, 123); + assertEq(decodedData.b, "test"); + + // write json3 to key b + vm.writeToml(finalJson, path, ".b"); + // read again + toml = vm.readFile(path); + data = vm.parseToml(toml, ".b"); + decodedData = abi.decode(data, (simpleJson)); + assertEq(decodedData.a, 123); + assertEq(decodedData.b, "test"); + + // replace a single value to key b + address ex = address(0xBEEF); + vm.writeToml(vm.toString(ex), path, ".b"); + toml = vm.readFile(path); + data = vm.parseToml(toml, ".b"); + address decodedAddress = abi.decode(data, (address)); + assertEq(decodedAddress, ex); + } +} diff --git a/testdata/cheats/Travel.t.sol b/testdata/default/cheats/Travel.t.sol similarity index 95% rename from testdata/cheats/Travel.t.sol rename to testdata/default/cheats/Travel.t.sol index 297017852d69e..733559b2926a9 100644 --- a/testdata/cheats/Travel.t.sol +++ b/testdata/default/cheats/Travel.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract ChainIdTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/TryFfi.sol b/testdata/default/cheats/TryFfi.sol similarity index 97% rename from testdata/cheats/TryFfi.sol rename to testdata/default/cheats/TryFfi.sol index 745b65ce09e4b..58d93a48b4fea 100644 --- a/testdata/cheats/TryFfi.sol +++ b/testdata/default/cheats/TryFfi.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract TryFfiTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/UnixTime.t.sol b/testdata/default/cheats/UnixTime.t.sol similarity index 97% rename from testdata/cheats/UnixTime.t.sol rename to testdata/default/cheats/UnixTime.t.sol index 66cbcc395004a..e128dad2463b2 100644 --- a/testdata/cheats/UnixTime.t.sol +++ b/testdata/default/cheats/UnixTime.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract UnixTimeTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Wallet.t.sol b/testdata/default/cheats/Wallet.t.sol similarity index 99% rename from testdata/cheats/Wallet.t.sol rename to testdata/default/cheats/Wallet.t.sol index a8ce5cc020179..8ecb707aec920 100644 --- a/testdata/cheats/Wallet.t.sol +++ b/testdata/default/cheats/Wallet.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Foo {} diff --git a/testdata/cheats/Warp.t.sol b/testdata/default/cheats/Warp.t.sol similarity index 96% rename from testdata/cheats/Warp.t.sol rename to testdata/default/cheats/Warp.t.sol index 0400099f8a3a5..01ebc8e89cbf0 100644 --- a/testdata/cheats/Warp.t.sol +++ b/testdata/default/cheats/Warp.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract WarpTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/dumpState.t.sol b/testdata/default/cheats/dumpState.t.sol new file mode 100644 index 0000000000000..74ebd3071f2ce --- /dev/null +++ b/testdata/default/cheats/dumpState.t.sol @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract SimpleContract { + constructor() { + assembly { + sstore(1, 2) + } + } +} + +contract DumpStateTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testDumpStateCheatAccount() public { + // Path to temporary file that is deleted after the test + string memory path = string.concat(vm.projectRoot(), "/fixtures/Json/test_dump_state_cheat.json"); + + // Define some values to set in the state using cheatcodes + address target = address(1001); + bytes memory bytecode = hex"11223344"; + uint256 balance = 1.2 ether; + uint64 nonce = 45; + + vm.etch(target, bytecode); + vm.deal(target, balance); + vm.setNonce(target, nonce); + vm.store(target, bytes32(uint256(0x20)), bytes32(uint256(0x40))); + vm.store(target, bytes32(uint256(0x40)), bytes32(uint256(0x60))); + + // Write the state to disk + vm.dumpState(path); + + string memory json = vm.readFile(path); + string[] memory keys = vm.parseJsonKeys(json, ""); + assertEq(keys.length, 1); + + string memory key = keys[0]; + assertEq(nonce, vm.parseJsonUint(json, string.concat(".", key, ".nonce"))); + assertEq(balance, vm.parseJsonUint(json, string.concat(".", key, ".balance"))); + assertEq(bytecode, vm.parseJsonBytes(json, string.concat(".", key, ".code"))); + + string[] memory slots = vm.parseJsonKeys(json, string.concat(".", key, ".storage")); + assertEq(slots.length, 2); + + assertEq( + bytes32(uint256(0x40)), + vm.parseJsonBytes32(json, string.concat(".", key, ".storage.", vm.toString(bytes32(uint256(0x20))))) + ); + assertEq( + bytes32(uint256(0x60)), + vm.parseJsonBytes32(json, string.concat(".", key, ".storage.", vm.toString(bytes32(uint256(0x40))))) + ); + + vm.removeFile(path); + } + + function testDumpStateMultipleAccounts() public { + string memory path = string.concat(vm.projectRoot(), "/fixtures/Json/test_dump_state_multiple_accounts.json"); + + vm.setNonce(address(0x100), 1); + vm.deal(address(0x200), 1 ether); + vm.setNonce(address(0x300), 1); + vm.store(address(0x300), bytes32(uint256(1)), bytes32(uint256(2))); + vm.etch(address(0x400), hex"af"); + + vm.dumpState(path); + + string memory json = vm.readFile(path); + string[] memory keys = vm.parseJsonKeys(json, ""); + assertEq(keys.length, 4); + + assertEq(4, vm.parseJsonKeys(json, string.concat(".", vm.toString(address(0x100)))).length); + assertEq(1, vm.parseJsonUint(json, string.concat(".", vm.toString(address(0x100)), ".nonce"))); + assertEq(0, vm.parseJsonUint(json, string.concat(".", vm.toString(address(0x100)), ".balance"))); + assertEq(hex"", vm.parseJsonBytes(json, string.concat(".", vm.toString(address(0x100)), ".code"))); + assertEq(0, vm.parseJsonKeys(json, string.concat(".", vm.toString(address(0x100)), ".storage")).length); + + assertEq(4, vm.parseJsonKeys(json, string.concat(".", vm.toString(address(0x200)))).length); + assertEq(0, vm.parseJsonUint(json, string.concat(".", vm.toString(address(0x200)), ".nonce"))); + assertEq(1 ether, vm.parseJsonUint(json, string.concat(".", vm.toString(address(0x200)), ".balance"))); + assertEq(hex"", vm.parseJsonBytes(json, string.concat(".", vm.toString(address(0x200)), ".code"))); + assertEq(0, vm.parseJsonKeys(json, string.concat(".", vm.toString(address(0x200)), ".storage")).length); + + assertEq(4, vm.parseJsonKeys(json, string.concat(".", vm.toString(address(0x300)))).length); + assertEq(1, vm.parseJsonUint(json, string.concat(".", vm.toString(address(0x300)), ".nonce"))); + assertEq(0, vm.parseJsonUint(json, string.concat(".", vm.toString(address(0x300)), ".balance"))); + assertEq(hex"", vm.parseJsonBytes(json, string.concat(".", vm.toString(address(0x300)), ".code"))); + assertEq(1, vm.parseJsonKeys(json, string.concat(".", vm.toString(address(0x300)), ".storage")).length); + assertEq( + 2, + vm.parseJsonUint( + json, string.concat(".", vm.toString(address(0x300)), ".storage.", vm.toString(bytes32(uint256(1)))) + ) + ); + + assertEq(4, vm.parseJsonKeys(json, string.concat(".", vm.toString(address(0x400)))).length); + assertEq(0, vm.parseJsonUint(json, string.concat(".", vm.toString(address(0x400)), ".nonce"))); + assertEq(0, vm.parseJsonUint(json, string.concat(".", vm.toString(address(0x400)), ".balance"))); + assertEq(hex"af", vm.parseJsonBytes(json, string.concat(".", vm.toString(address(0x400)), ".code"))); + assertEq(0, vm.parseJsonKeys(json, string.concat(".", vm.toString(address(0x400)), ".storage")).length); + + vm.removeFile(path); + } + + function testDumpStateDeployment() public { + string memory path = string.concat(vm.projectRoot(), "/fixtures/Json/test_dump_state_deployment.json"); + + SimpleContract s = new SimpleContract(); + vm.dumpState(path); + + string memory json = vm.readFile(path); + string[] memory keys = vm.parseJsonKeys(json, ""); + assertEq(keys.length, 1); + assertEq(address(s), vm.parseAddress(keys[0])); + assertEq(1, vm.parseJsonKeys(json, string.concat(".", keys[0], ".storage")).length); + assertEq(2, vm.parseJsonUint(json, string.concat(".", keys[0], ".storage.", vm.toString(bytes32(uint256(1)))))); + + vm.removeFile(path); + } + + function testDumpStateEmptyAccount() public { + string memory path = string.concat(vm.projectRoot(), "/fixtures/Json/test_dump_state_empty_account.json"); + + SimpleContract s = new SimpleContract(); + vm.etch(address(s), hex""); + vm.resetNonce(address(s)); + + vm.dumpState(path); + string memory json = vm.readFile(path); + string[] memory keys = vm.parseJsonKeys(json, ""); + assertEq(keys.length, 0); + + vm.removeFile(path); + } +} diff --git a/testdata/default/cheats/getBlockNumber.t.sol b/testdata/default/cheats/getBlockNumber.t.sol new file mode 100644 index 0000000000000..c874e5e2f3899 --- /dev/null +++ b/testdata/default/cheats/getBlockNumber.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract GetBlockNumberTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testGetBlockNumber() public { + uint256 height = vm.getBlockNumber(); + assertEq(height, uint256(block.number), "height should be equal to block.number"); + } + + function testGetBlockNumberWithRoll() public { + vm.roll(10); + assertEq(vm.getBlockNumber(), 10, "could not get correct block height after roll"); + } + + function testGetBlockNumberWithRollFuzzed(uint128 jump) public { + uint256 pre = vm.getBlockNumber(); + vm.roll(pre + jump); + assertEq(vm.getBlockNumber(), pre + jump, "could not get correct block height after roll"); + } +} diff --git a/testdata/cheats/loadAllocs.t.sol b/testdata/default/cheats/loadAllocs.t.sol similarity index 99% rename from testdata/cheats/loadAllocs.t.sol rename to testdata/default/cheats/loadAllocs.t.sol index f219d025e1f94..358608860bd5d 100644 --- a/testdata/cheats/loadAllocs.t.sol +++ b/testdata/default/cheats/loadAllocs.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract LoadAllocsTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/core/Abstract.t.sol b/testdata/default/core/Abstract.t.sol similarity index 100% rename from testdata/core/Abstract.t.sol rename to testdata/default/core/Abstract.t.sol diff --git a/testdata/core/ContractEnvironment.t.sol b/testdata/default/core/ContractEnvironment.t.sol similarity index 100% rename from testdata/core/ContractEnvironment.t.sol rename to testdata/default/core/ContractEnvironment.t.sol diff --git a/testdata/core/DSStyle.t.sol b/testdata/default/core/DSStyle.t.sol similarity index 100% rename from testdata/core/DSStyle.t.sol rename to testdata/default/core/DSStyle.t.sol diff --git a/testdata/core/FailingSetup.t.sol b/testdata/default/core/FailingSetup.t.sol similarity index 100% rename from testdata/core/FailingSetup.t.sol rename to testdata/default/core/FailingSetup.t.sol diff --git a/testdata/core/FailingTestAfterFailedSetup.t.sol b/testdata/default/core/FailingTestAfterFailedSetup.t.sol similarity index 100% rename from testdata/core/FailingTestAfterFailedSetup.t.sol rename to testdata/default/core/FailingTestAfterFailedSetup.t.sol diff --git a/testdata/core/MultipleSetup.t.sol b/testdata/default/core/MultipleSetup.t.sol similarity index 100% rename from testdata/core/MultipleSetup.t.sol rename to testdata/default/core/MultipleSetup.t.sol diff --git a/testdata/core/PaymentFailure.t.sol b/testdata/default/core/PaymentFailure.t.sol similarity index 93% rename from testdata/core/PaymentFailure.t.sol rename to testdata/default/core/PaymentFailure.t.sol index 21558cf9cfa27..d4751b2d52358 100644 --- a/testdata/core/PaymentFailure.t.sol +++ b/testdata/default/core/PaymentFailure.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract Payable { function pay() public payable {} diff --git a/testdata/core/Reverting.t.sol b/testdata/default/core/Reverting.t.sol similarity index 100% rename from testdata/core/Reverting.t.sol rename to testdata/default/core/Reverting.t.sol diff --git a/testdata/core/SetupConsistency.t.sol b/testdata/default/core/SetupConsistency.t.sol similarity index 100% rename from testdata/core/SetupConsistency.t.sol rename to testdata/default/core/SetupConsistency.t.sol diff --git a/testdata/fork/DssExecLib.sol b/testdata/default/fork/DssExecLib.sol similarity index 100% rename from testdata/fork/DssExecLib.sol rename to testdata/default/fork/DssExecLib.sol diff --git a/testdata/fork/ForkSame_1.t.sol b/testdata/default/fork/ForkSame_1.t.sol similarity index 95% rename from testdata/fork/ForkSame_1.t.sol rename to testdata/default/fork/ForkSame_1.t.sol index 01c89e6e2007c..bff9678f65c0b 100644 --- a/testdata/fork/ForkSame_1.t.sol +++ b/testdata/default/fork/ForkSame_1.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract ForkTest is DSTest { address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; diff --git a/testdata/fork/ForkSame_2.t.sol b/testdata/default/fork/ForkSame_2.t.sol similarity index 95% rename from testdata/fork/ForkSame_2.t.sol rename to testdata/default/fork/ForkSame_2.t.sol index 01c89e6e2007c..bff9678f65c0b 100644 --- a/testdata/fork/ForkSame_2.t.sol +++ b/testdata/default/fork/ForkSame_2.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract ForkTest is DSTest { address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; diff --git a/testdata/fork/LaunchFork.t.sol b/testdata/default/fork/LaunchFork.t.sol similarity index 100% rename from testdata/fork/LaunchFork.t.sol rename to testdata/default/fork/LaunchFork.t.sol diff --git a/testdata/fork/Transact.t.sol b/testdata/default/fork/Transact.t.sol similarity index 99% rename from testdata/fork/Transact.t.sol rename to testdata/default/fork/Transact.t.sol index 79c53fa3fd983..ec803906dd2fa 100644 --- a/testdata/fork/Transact.t.sol +++ b/testdata/default/fork/Transact.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; import "../logs/console.sol"; interface IERC20 { diff --git a/testdata/fs/Default.t.sol b/testdata/default/fs/Default.t.sol similarity index 85% rename from testdata/fs/Default.t.sol rename to testdata/default/fs/Default.t.sol index 379de1ea66b6d..5e776e696fb08 100644 --- a/testdata/fs/Default.t.sol +++ b/testdata/default/fs/Default.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract DefaultAccessTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -23,10 +23,10 @@ contract DefaultAccessTest is DSTest { string memory path = "fixtures/File/write_file.txt"; string memory data = "hello writable world"; - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.writeFile(path, data); - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.writeFileBinary(path, bytes(data)); } @@ -34,14 +34,14 @@ contract DefaultAccessTest is DSTest { string memory path = "fixtures/File/write_file.txt"; string memory data = "hello writable world"; - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.writeLine(path, data); } function testRemoveFile() public { string memory path = "fixtures/File/write_file.txt"; - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.removeFile(path); } } diff --git a/testdata/fs/Disabled.t.sol b/testdata/default/fs/Disabled.t.sol similarity index 81% rename from testdata/fs/Disabled.t.sol rename to testdata/default/fs/Disabled.t.sol index 40741644ecc8d..4efe9affcc3b5 100644 --- a/testdata/fs/Disabled.t.sol +++ b/testdata/default/fs/Disabled.t.sol @@ -2,40 +2,40 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract DisabledTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function testReadFile() public { string memory path = "fixtures/File/read.txt"; - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.readFile(path); } function testReadLine() public { string memory path = "fixtures/File/read.txt"; - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.readLine(path); } function testWriteFile() public { string memory path = "fixtures/File/write_file.txt"; string memory data = "hello writable world"; - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.writeFile(path, data); } function testWriteLine() public { string memory path = "fixtures/File/write_file.txt"; string memory data = "hello writable world"; - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.writeLine(path, data); } function testRemoveFile() public { string memory path = "fixtures/File/write_file.txt"; - vm.expectRevert(); + vm._expectCheatcodeRevert(); vm.removeFile(path); } } diff --git a/testdata/fuzz/Fuzz.t.sol b/testdata/default/fuzz/Fuzz.t.sol similarity index 100% rename from testdata/fuzz/Fuzz.t.sol rename to testdata/default/fuzz/Fuzz.t.sol diff --git a/testdata/fuzz/FuzzCollection.t.sol b/testdata/default/fuzz/FuzzCollection.t.sol similarity index 100% rename from testdata/fuzz/FuzzCollection.t.sol rename to testdata/default/fuzz/FuzzCollection.t.sol diff --git a/testdata/default/fuzz/FuzzFailurePersist.t.sol b/testdata/default/fuzz/FuzzFailurePersist.t.sol new file mode 100644 index 0000000000000..0823f29fb1655 --- /dev/null +++ b/testdata/default/fuzz/FuzzFailurePersist.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +struct TestTuple { + address user; + uint256 amount; +} + +contract FuzzFailurePersistTest is DSTest { + Vm vm = Vm(HEVM_ADDRESS); + + function test_persist_fuzzed_failure( + uint256 x, + int256 y, + address addr, + bool cond, + string calldata test, + TestTuple calldata tuple, + address[] calldata addresses + ) public { + // dummy assume to trigger runs + vm.assume(x > 1 && x < 1111111111111111111111111111); + vm.assume(y > 1 && y < 1111111111111111111111111111); + require(false); + } +} diff --git a/testdata/fuzz/FuzzInt.t.sol b/testdata/default/fuzz/FuzzInt.t.sol similarity index 67% rename from testdata/fuzz/FuzzInt.t.sol rename to testdata/default/fuzz/FuzzInt.t.sol index aac0825dbb5d9..071727f6da929 100644 --- a/testdata/fuzz/FuzzInt.t.sol +++ b/testdata/default/fuzz/FuzzInt.t.sol @@ -3,7 +3,8 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -// See https://github.com/foundry-rs/foundry/pull/735 for context +// https://github.com/foundry-rs/foundry/pull/735 behavior changed with https://github.com/foundry-rs/foundry/issues/3521 +// random values (instead edge cases) are generated if no fixtures defined contract FuzzNumbersTest is DSTest { function testPositive(int256) public { assertTrue(true); @@ -14,31 +15,31 @@ contract FuzzNumbersTest is DSTest { } function testNegative0(int256 val) public { - assertTrue(val != 0); + assertTrue(val == 0); } function testNegative1(int256 val) public { - assertTrue(val != -1); + assertTrue(val == -1); } function testNegative2(int128 val) public { - assertTrue(val != 1); + assertTrue(val == 1); } function testNegativeMax0(int256 val) public { - assertTrue(val != type(int256).max); + assertTrue(val == type(int256).max); } function testNegativeMax1(int256 val) public { - assertTrue(val != type(int256).max - 2); + assertTrue(val == type(int256).max - 2); } function testNegativeMin0(int256 val) public { - assertTrue(val != type(int256).min); + assertTrue(val == type(int256).min); } function testNegativeMin1(int256 val) public { - assertTrue(val != type(int256).min + 2); + assertTrue(val == type(int256).min + 2); } function testEquality(int256 x, int256 y) public { diff --git a/testdata/default/fuzz/FuzzPositive.t.sol b/testdata/default/fuzz/FuzzPositive.t.sol new file mode 100644 index 0000000000000..952a3b6992278 --- /dev/null +++ b/testdata/default/fuzz/FuzzPositive.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; + +contract FuzzPositive is DSTest { + function testSuccessChecker(uint256 val) public { + assertTrue(true); + } + + function testSuccessChecker2(int256 val) public { + assert(val == val); + } + + function testSuccessChecker3(uint32 val) public { + assert(val + 0 == val); + } +} diff --git a/testdata/fuzz/FuzzUint.t.sol b/testdata/default/fuzz/FuzzUint.t.sol similarity index 69% rename from testdata/fuzz/FuzzUint.t.sol rename to testdata/default/fuzz/FuzzUint.t.sol index 5ae90a57bba00..923c2980f2bc1 100644 --- a/testdata/fuzz/FuzzUint.t.sol +++ b/testdata/default/fuzz/FuzzUint.t.sol @@ -3,7 +3,8 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -// See https://github.com/foundry-rs/foundry/pull/735 for context +// https://github.com/foundry-rs/foundry/pull/735 behavior changed with https://github.com/foundry-rs/foundry/issues/3521 +// random values (instead edge cases) are generated if no fixtures defined contract FuzzNumbersTest is DSTest { function testPositive(uint256) public { assertTrue(true); @@ -14,19 +15,19 @@ contract FuzzNumbersTest is DSTest { } function testNegative0(uint256 val) public { - assertTrue(val != 0); + assertTrue(val == 0); } function testNegative2(uint256 val) public { - assertTrue(val != 2); + assertTrue(val == 2); } function testNegative2Max(uint256 val) public { - assertTrue(val != type(uint256).max - 2); + assertTrue(val == type(uint256).max - 2); } function testNegativeMax(uint256 val) public { - assertTrue(val != type(uint256).max); + assertTrue(val == type(uint256).max); } function testEquality(uint256 x, uint256 y) public { diff --git a/testdata/default/fuzz/invariant/common/InvariantAssume.t.sol b/testdata/default/fuzz/invariant/common/InvariantAssume.t.sol new file mode 100644 index 0000000000000..9808a870f7228 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantAssume.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Handler is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function doSomething(uint256 param) public { + vm.assume(param == 0); + } +} + +contract InvariantAssume is DSTest { + Handler handler; + + function setUp() public { + handler = new Handler(); + } + + function invariant_dummy() public {} +} diff --git a/testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol b/testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol new file mode 100644 index 0000000000000..5387b020d37d6 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +// https://github.com/foundry-rs/foundry/issues/5868 +contract Owned { + address public owner; + address private ownerCandidate; + + constructor() { + owner = msg.sender; + } + + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + modifier onlyOwnerCandidate() { + require(msg.sender == ownerCandidate); + _; + } + + function transferOwnership(address candidate) external onlyOwner { + ownerCandidate = candidate; + } + + function acceptOwnership() external onlyOwnerCandidate { + owner = ownerCandidate; + } +} + +contract Handler is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Owned owned; + + constructor(Owned _owned) { + owned = _owned; + } + + function transferOwnership(address sender, address candidate) external { + vm.assume(sender != address(0)); + vm.prank(sender); + owned.transferOwnership(candidate); + } + + function acceptOwnership(address sender) external { + vm.assume(sender != address(0)); + vm.prank(sender); + owned.acceptOwnership(); + } +} + +contract InvariantCalldataDictionary is DSTest { + address owner; + Owned owned; + Handler handler; + address[] actors; + + function setUp() public { + owner = address(this); + owned = new Owned(); + handler = new Handler(owned); + actors.push(owner); + actors.push(address(777)); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = handler.transferOwnership.selector; + selectors[1] = handler.acceptOwnership.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function fixtureSender() external returns (address[] memory) { + return actors; + } + + function fixtureCandidate() external returns (address[] memory) { + return actors; + } + + function invariant_owner_never_changes() public { + assertEq(owned.owner(), owner); + } +} diff --git a/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol b/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol new file mode 100644 index 0000000000000..737cf5ba9dd05 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract ContractWithCustomError { + error InvariantCustomError(uint256, string); + + function revertWithInvariantCustomError() external { + revert InvariantCustomError(111, "custom"); + } +} + +contract Handler is DSTest { + ContractWithCustomError target; + + constructor() { + target = new ContractWithCustomError(); + } + + function revertTarget() external { + target.revertWithInvariantCustomError(); + } +} + +contract InvariantCustomError is DSTest { + Handler handler; + + function setUp() external { + handler = new Handler(); + } + + function invariant_decode_error() public {} +} diff --git a/testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol b/testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol new file mode 100644 index 0000000000000..b3f1e17cb2497 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import "ds-test/test.sol"; + +contract Target { + bool ownerFound; + bool amountFound; + bool magicFound; + bool keyFound; + bool backupFound; + bool extraStringFound; + + function fuzzWithFixtures( + address owner_, + uint256 _amount, + int32 magic, + bytes32 key, + bytes memory backup, + string memory extra + ) external { + if (owner_ == address(0x6B175474E89094C44Da98b954EedeAC495271d0F)) { + ownerFound = true; + } + if (_amount == 1122334455) amountFound = true; + if (magic == -777) magicFound = true; + if (key == "abcd1234") keyFound = true; + if (keccak256(backup) == keccak256("qwerty1234")) backupFound = true; + if (keccak256(abi.encodePacked(extra)) == keccak256(abi.encodePacked("112233aabbccdd"))) { + extraStringFound = true; + } + } + + function isCompromised() public view returns (bool) { + return ownerFound && amountFound && magicFound && keyFound && backupFound && extraStringFound; + } +} + +/// Try to compromise target contract by finding all accepted values using fixtures. +contract InvariantFixtures is DSTest { + Target target; + address[] public fixture_owner_ = [address(0x6B175474E89094C44Da98b954EedeAC495271d0F)]; + uint256[] public fixture_amount = [1, 2, 1122334455]; + + function setUp() public { + target = new Target(); + } + + function fixtureMagic() external returns (int32[2] memory) { + int32[2] memory magic; + magic[0] = -777; + magic[1] = 777; + return magic; + } + + function fixtureKey() external pure returns (bytes32[] memory) { + bytes32[] memory keyFixture = new bytes32[](1); + keyFixture[0] = "abcd1234"; + return keyFixture; + } + + function fixtureBackup() external pure returns (bytes[] memory) { + bytes[] memory backupFixture = new bytes[](1); + backupFixture[0] = "qwerty1234"; + return backupFixture; + } + + function fixtureExtra() external pure returns (string[] memory) { + string[] memory extraFixture = new string[](1); + extraFixture[0] = "112233aabbccdd"; + return extraFixture; + } + + function invariant_target_not_compromised() public { + assertEq(target.isCompromised(), false); + } +} diff --git a/testdata/fuzz/invariant/common/InvariantHandlerFailure.t.sol b/testdata/default/fuzz/invariant/common/InvariantHandlerFailure.t.sol similarity index 100% rename from testdata/fuzz/invariant/common/InvariantHandlerFailure.t.sol rename to testdata/default/fuzz/invariant/common/InvariantHandlerFailure.t.sol diff --git a/testdata/fuzz/invariant/common/InvariantInnerContract.t.sol b/testdata/default/fuzz/invariant/common/InvariantInnerContract.t.sol similarity index 100% rename from testdata/fuzz/invariant/common/InvariantInnerContract.t.sol rename to testdata/default/fuzz/invariant/common/InvariantInnerContract.t.sol diff --git a/testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol b/testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol new file mode 100644 index 0000000000000..5469801362a98 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +// https://github.com/foundry-rs/foundry/issues/7219 + +contract Handler is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function thisFunctionReverts() external { + if (block.number < 10) {} else { + revert(); + } + } + + function advanceTime(uint256 blocks) external { + blocks = blocks % 10; + vm.roll(block.number + blocks); + vm.warp(block.timestamp + blocks * 12); + } +} + +contract InvariantPreserveState is DSTest { + Handler handler; + + function setUp() public { + handler = new Handler(); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = handler.thisFunctionReverts.selector; + selectors[1] = handler.advanceTime.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function invariant_preserve_state() public { + assertTrue(true); + } +} diff --git a/testdata/fuzz/invariant/common/InvariantReentrancy.t.sol b/testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol similarity index 64% rename from testdata/fuzz/invariant/common/InvariantReentrancy.t.sol rename to testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol index 086d5f99ab9e4..06b4b21d761ff 100644 --- a/testdata/fuzz/invariant/common/InvariantReentrancy.t.sol +++ b/testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol @@ -5,7 +5,9 @@ import "ds-test/test.sol"; contract Malicious { function world() public { - // Does not matter, since it will get overridden. + // add code so contract is accounted as valid sender + // see https://github.com/foundry-rs/foundry/issues/4245 + payable(msg.sender).transfer(1); } } @@ -39,6 +41,14 @@ contract InvariantReentrancy is DSTest { vuln = new Vulnerable(address(mal)); } + // do not include `mal` in identified contracts + // see https://github.com/foundry-rs/foundry/issues/4245 + function targetContracts() public view returns (address[] memory) { + address[] memory targets = new address[](1); + targets[0] = address(vuln); + return targets; + } + function invariantNotStolen() public { require(vuln.stolen() == false, "stolen"); } diff --git a/testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol b/testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol new file mode 100644 index 0000000000000..5699d9c455e89 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract ShrinkBigSequence { + uint256 cond; + + function work(uint256 x) public { + if (x % 2 != 0 && x < 9000) { + cond++; + } + } + + function checkCond() public view { + require(cond < 77, "condition met"); + } +} + +contract ShrinkBigSequenceTest is DSTest { + ShrinkBigSequence target; + + function setUp() public { + target = new ShrinkBigSequence(); + } + + function invariant_shrink_big_sequence() public view { + target.checkCond(); + } +} diff --git a/testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol b/testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol new file mode 100644 index 0000000000000..d971367b69988 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract ShrinkFailOnRevert { + uint256 cond; + + function work(uint256 x) public { + if (x % 2 != 0 && x < 9000) { + cond++; + } + require(cond < 10, "condition met"); + } +} + +contract ShrinkFailOnRevertTest is DSTest { + ShrinkFailOnRevert target; + + function setUp() public { + target = new ShrinkFailOnRevert(); + } + + function invariant_shrink_fail_on_revert() public view {} +} diff --git a/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol b/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol new file mode 100644 index 0000000000000..c189e2507629d --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } + + function decrement() public { + number--; + } + + function double() public { + number *= 2; + } + + function half() public { + number /= 2; + } + + function triple() public { + number *= 3; + } + + function third() public { + number /= 3; + } + + function quadruple() public { + number *= 4; + } + + function quarter() public { + number /= 4; + } +} + +contract Handler is DSTest { + Counter public counter; + + constructor(Counter _counter) { + counter = _counter; + counter.setNumber(0); + } + + function increment() public { + counter.increment(); + } + + function setNumber(uint256 x) public { + counter.setNumber(x); + } +} + +contract InvariantShrinkWithAssert is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Counter public counter; + Handler handler; + + function setUp() public { + counter = new Counter(); + handler = new Handler(counter); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = handler.increment.selector; + selectors[1] = handler.setNumber.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function invariant_with_assert() public { + assertTrue(counter.number() != 3); + } +} + +contract InvariantShrinkWithRequire is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Counter public counter; + Handler handler; + + function setUp() public { + counter = new Counter(); + handler = new Handler(counter); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = handler.increment.selector; + selectors[1] = handler.setNumber.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function invariant_with_require() public { + require(counter.number() != 3, "wrong counter"); + } +} diff --git a/testdata/fuzz/invariant/common/InvariantTest1.t.sol b/testdata/default/fuzz/invariant/common/InvariantTest1.t.sol similarity index 100% rename from testdata/fuzz/invariant/common/InvariantTest1.t.sol rename to testdata/default/fuzz/invariant/common/InvariantTest1.t.sol diff --git a/testdata/fuzz/invariant/storage/InvariantStorageTest.t.sol b/testdata/default/fuzz/invariant/storage/InvariantStorageTest.t.sol similarity index 100% rename from testdata/fuzz/invariant/storage/InvariantStorageTest.t.sol rename to testdata/default/fuzz/invariant/storage/InvariantStorageTest.t.sol diff --git a/testdata/fuzz/invariant/target/ExcludeContracts.t.sol b/testdata/default/fuzz/invariant/target/ExcludeContracts.t.sol similarity index 100% rename from testdata/fuzz/invariant/target/ExcludeContracts.t.sol rename to testdata/default/fuzz/invariant/target/ExcludeContracts.t.sol diff --git a/testdata/fuzz/invariant/target/ExcludeSenders.t.sol b/testdata/default/fuzz/invariant/target/ExcludeSenders.t.sol similarity index 100% rename from testdata/fuzz/invariant/target/ExcludeSenders.t.sol rename to testdata/default/fuzz/invariant/target/ExcludeSenders.t.sol diff --git a/testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol b/testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol new file mode 100644 index 0000000000000..7988d5c8a2c38 --- /dev/null +++ b/testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; + +interface Vm { + function etch(address target, bytes calldata newRuntimeBytecode) external; +} + +// https://github.com/foundry-rs/foundry/issues/5625 +// https://github.com/foundry-rs/foundry/issues/6166 +// `Target.wrongSelector` is not called when handler added as `targetContract` +// `Target.wrongSelector` is called (and test fails) when no `targetContract` set +contract Target { + uint256 count; + + function wrongSelector() external { + revert("wrong target selector called"); + } + + function goodSelector() external { + count++; + } +} + +contract Handler is DSTest { + function increment() public { + Target(0x6B175474E89094C44Da98b954EedeAC495271d0F).goodSelector(); + } +} + +contract ExplicitTargetContract is DSTest { + Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + Handler handler; + + function setUp() public { + Target target = new Target(); + bytes memory targetCode = address(target).code; + vm.etch(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), targetCode); + + handler = new Handler(); + } + + function targetContracts() public returns (address[] memory) { + address[] memory addrs = new address[](1); + addrs[0] = address(handler); + return addrs; + } + + function invariant_explicit_target() public {} +} + +contract DynamicTargetContract is DSTest { + Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + Handler handler; + + function setUp() public { + Target target = new Target(); + bytes memory targetCode = address(target).code; + vm.etch(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), targetCode); + + handler = new Handler(); + } + + function invariant_dynamic_targets() public {} +} diff --git a/testdata/fuzz/invariant/target/TargetContracts.t.sol b/testdata/default/fuzz/invariant/target/TargetContracts.t.sol similarity index 100% rename from testdata/fuzz/invariant/target/TargetContracts.t.sol rename to testdata/default/fuzz/invariant/target/TargetContracts.t.sol diff --git a/testdata/fuzz/invariant/target/TargetInterfaces.t.sol b/testdata/default/fuzz/invariant/target/TargetInterfaces.t.sol similarity index 100% rename from testdata/fuzz/invariant/target/TargetInterfaces.t.sol rename to testdata/default/fuzz/invariant/target/TargetInterfaces.t.sol diff --git a/testdata/fuzz/invariant/target/TargetSelectors.t.sol b/testdata/default/fuzz/invariant/target/TargetSelectors.t.sol similarity index 100% rename from testdata/fuzz/invariant/target/TargetSelectors.t.sol rename to testdata/default/fuzz/invariant/target/TargetSelectors.t.sol diff --git a/testdata/fuzz/invariant/target/TargetSenders.t.sol b/testdata/default/fuzz/invariant/target/TargetSenders.t.sol similarity index 100% rename from testdata/fuzz/invariant/target/TargetSenders.t.sol rename to testdata/default/fuzz/invariant/target/TargetSenders.t.sol diff --git a/testdata/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol b/testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol similarity index 91% rename from testdata/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol rename to testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol index ce81a51cf6bf0..bf457ab1709a5 100644 --- a/testdata/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol +++ b/testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol @@ -35,7 +35,7 @@ contract ExcludeArtifacts is DSTest { function excludeArtifacts() public returns (string[] memory) { string[] memory abis = new string[](1); - abis[0] = "fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol:Excluded"; + abis[0] = "default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol:Excluded"; return abis; } diff --git a/testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol b/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol similarity index 87% rename from testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol rename to testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol index e82e7b1a36e88..d7c8bcdfae4aa 100644 --- a/testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol +++ b/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol @@ -31,7 +31,7 @@ contract TargetArtifactSelectors is DSTest { FuzzAbiSelector[] memory targets = new FuzzAbiSelector[](1); bytes4[] memory selectors = new bytes4[](1); selectors[0] = Hi.no_change.selector; - targets[0] = FuzzAbiSelector("fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol:Hi", selectors); + targets[0] = FuzzAbiSelector("default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol:Hi", selectors); return targets; } diff --git a/testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol b/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol similarity index 84% rename from testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol rename to testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol index 5592aa84969dd..573350c6e0238 100644 --- a/testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol +++ b/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol @@ -51,11 +51,13 @@ contract TargetArtifactSelectors2 is DSTest { bytes4[] memory selectors_child = new bytes4[](1); selectors_child[0] = Child.change_parent.selector; - targets[0] = FuzzAbiSelector("fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:Child", selectors_child); + targets[0] = + FuzzAbiSelector("default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:Child", selectors_child); bytes4[] memory selectors_parent = new bytes4[](1); selectors_parent[0] = Parent.create.selector; - targets[1] = FuzzAbiSelector("fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:Parent", selectors_parent); + targets[1] = + FuzzAbiSelector("default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:Parent", selectors_parent); return targets; } diff --git a/testdata/fuzz/invariant/targetAbi/TargetArtifacts.t.sol b/testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol similarity index 91% rename from testdata/fuzz/invariant/targetAbi/TargetArtifacts.t.sol rename to testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol index d3eb2cf9dfa1d..ea86ab135b624 100644 --- a/testdata/fuzz/invariant/targetAbi/TargetArtifacts.t.sol +++ b/testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol @@ -30,7 +30,7 @@ contract TargetArtifacts is DSTest { function targetArtifacts() public returns (string[] memory) { string[] memory abis = new string[](1); - abis[0] = "fuzz/invariant/targetAbi/TargetArtifacts.t.sol:Targeted"; + abis[0] = "default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol:Targeted"; return abis; } diff --git a/testdata/inline/FuzzInlineConf.t.sol b/testdata/default/inline/FuzzInlineConf.t.sol similarity index 100% rename from testdata/inline/FuzzInlineConf.t.sol rename to testdata/default/inline/FuzzInlineConf.t.sol diff --git a/testdata/inline/InvariantInlineConf.t.sol b/testdata/default/inline/InvariantInlineConf.t.sol similarity index 100% rename from testdata/inline/InvariantInlineConf.t.sol rename to testdata/default/inline/InvariantInlineConf.t.sol diff --git a/testdata/default/linking/cycle/Cycle.t.sol b/testdata/default/linking/cycle/Cycle.t.sol new file mode 100644 index 0000000000000..424bc001fbab6 --- /dev/null +++ b/testdata/default/linking/cycle/Cycle.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +library Foo { + function foo() external { + Bar.bar(); + } + + function flum() external {} +} + +library Bar { + function bar() external { + Foo.flum(); + } +} diff --git a/testdata/linking/duplicate/Duplicate.t.sol b/testdata/default/linking/duplicate/Duplicate.t.sol similarity index 100% rename from testdata/linking/duplicate/Duplicate.t.sol rename to testdata/default/linking/duplicate/Duplicate.t.sol diff --git a/testdata/linking/nested/Nested.t.sol b/testdata/default/linking/nested/Nested.t.sol similarity index 100% rename from testdata/linking/nested/Nested.t.sol rename to testdata/default/linking/nested/Nested.t.sol diff --git a/testdata/linking/simple/Simple.t.sol b/testdata/default/linking/simple/Simple.t.sol similarity index 100% rename from testdata/linking/simple/Simple.t.sol rename to testdata/default/linking/simple/Simple.t.sol diff --git a/testdata/logs/DebugLogs.t.sol b/testdata/default/logs/DebugLogs.t.sol similarity index 100% rename from testdata/logs/DebugLogs.t.sol rename to testdata/default/logs/DebugLogs.t.sol diff --git a/testdata/logs/HardhatLogs.t.sol b/testdata/default/logs/HardhatLogs.t.sol similarity index 100% rename from testdata/logs/HardhatLogs.t.sol rename to testdata/default/logs/HardhatLogs.t.sol diff --git a/testdata/logs/console.sol b/testdata/default/logs/console.sol similarity index 100% rename from testdata/logs/console.sol rename to testdata/default/logs/console.sol diff --git a/testdata/repros/Issue2623.t.sol b/testdata/default/repros/Issue2623.t.sol similarity index 95% rename from testdata/repros/Issue2623.t.sol rename to testdata/default/repros/Issue2623.t.sol index cf91d10a54168..8534aeeafabff 100644 --- a/testdata/repros/Issue2623.t.sol +++ b/testdata/default/repros/Issue2623.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/2623 contract Issue2623Test is DSTest { diff --git a/testdata/repros/Issue2629.t.sol b/testdata/default/repros/Issue2629.t.sol similarity index 96% rename from testdata/repros/Issue2629.t.sol rename to testdata/default/repros/Issue2629.t.sol index 296ebfc325b2a..a1f430858380e 100644 --- a/testdata/repros/Issue2629.t.sol +++ b/testdata/default/repros/Issue2629.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/2629 contract Issue2629Test is DSTest { diff --git a/testdata/repros/Issue2723.t.sol b/testdata/default/repros/Issue2723.t.sol similarity index 95% rename from testdata/repros/Issue2723.t.sol rename to testdata/default/repros/Issue2723.t.sol index 6ecd7df8d3b90..c260f9467252e 100644 --- a/testdata/repros/Issue2723.t.sol +++ b/testdata/default/repros/Issue2723.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/2723 contract Issue2723Test is DSTest { diff --git a/testdata/repros/Issue2898.t.sol b/testdata/default/repros/Issue2898.t.sol similarity index 96% rename from testdata/repros/Issue2898.t.sol rename to testdata/default/repros/Issue2898.t.sol index 6f6eb5e35ce55..23de35bcdc059 100644 --- a/testdata/repros/Issue2898.t.sol +++ b/testdata/default/repros/Issue2898.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; import "../logs/console.sol"; // https://github.com/foundry-rs/foundry/issues/2898 diff --git a/testdata/repros/Issue2956.t.sol b/testdata/default/repros/Issue2956.t.sol similarity index 82% rename from testdata/repros/Issue2956.t.sol rename to testdata/default/repros/Issue2956.t.sol index 8e9841e2b5bb9..9d9e5f9ac5862 100644 --- a/testdata/repros/Issue2956.t.sol +++ b/testdata/default/repros/Issue2956.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/2956 contract Issue2956Test is DSTest { @@ -11,7 +11,7 @@ contract Issue2956Test is DSTest { uint256 fork2; function setUp() public { - fork1 = vm.createFork("https://goerli.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001", 7475589); + fork1 = vm.createFork("rpcAliasSepolia", 5565573); fork2 = vm.createFork("https://api.avax-test.network/ext/bc/C/rpc", 12880747); } @@ -28,7 +28,7 @@ contract Issue2956Test is DSTest { new Counter(); vm.selectFork(fork1); - assertEq(vm.getNonce(user), 3); + assertEq(vm.getNonce(user), 1); vm.prank(user); new Counter(); } diff --git a/testdata/repros/Issue2984.t.sol b/testdata/default/repros/Issue2984.t.sol similarity index 96% rename from testdata/repros/Issue2984.t.sol rename to testdata/default/repros/Issue2984.t.sol index 223866926b0bc..8e55d5dae4b17 100644 --- a/testdata/repros/Issue2984.t.sol +++ b/testdata/default/repros/Issue2984.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/2984 contract Issue2984Test is DSTest { diff --git a/testdata/repros/Issue3055.t.sol b/testdata/default/repros/Issue3055.t.sol similarity index 97% rename from testdata/repros/Issue3055.t.sol rename to testdata/default/repros/Issue3055.t.sol index 0a94e4d2a50b5..cacf5282f0581 100644 --- a/testdata/repros/Issue3055.t.sol +++ b/testdata/default/repros/Issue3055.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3055 contract Issue3055Test is DSTest { diff --git a/testdata/repros/Issue3077.t.sol b/testdata/default/repros/Issue3077.t.sol similarity index 97% rename from testdata/repros/Issue3077.t.sol rename to testdata/default/repros/Issue3077.t.sol index 36cfb071cad4b..cc76b57b6fb2e 100644 --- a/testdata/repros/Issue3077.t.sol +++ b/testdata/default/repros/Issue3077.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3077 abstract contract ZeroState is DSTest { diff --git a/testdata/repros/Issue3110.t.sol b/testdata/default/repros/Issue3110.t.sol similarity index 97% rename from testdata/repros/Issue3110.t.sol rename to testdata/default/repros/Issue3110.t.sol index 259a467d32946..7a2622427a615 100644 --- a/testdata/repros/Issue3110.t.sol +++ b/testdata/default/repros/Issue3110.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3110 abstract contract ZeroState is DSTest { diff --git a/testdata/repros/Issue3119.t.sol b/testdata/default/repros/Issue3119.t.sol similarity index 96% rename from testdata/repros/Issue3119.t.sol rename to testdata/default/repros/Issue3119.t.sol index 80f539660a3bf..5c94b4c5fde56 100644 --- a/testdata/repros/Issue3119.t.sol +++ b/testdata/default/repros/Issue3119.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3119 contract Issue3119Test is DSTest { diff --git a/testdata/repros/Issue3189.t.sol b/testdata/default/repros/Issue3189.t.sol similarity index 96% rename from testdata/repros/Issue3189.t.sol rename to testdata/default/repros/Issue3189.t.sol index 771b8f514c784..27ea0ac51cfd5 100644 --- a/testdata/repros/Issue3189.t.sol +++ b/testdata/default/repros/Issue3189.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3189 contract MyContract { diff --git a/testdata/repros/Issue3190.t.sol b/testdata/default/repros/Issue3190.t.sol similarity index 94% rename from testdata/repros/Issue3190.t.sol rename to testdata/default/repros/Issue3190.t.sol index b5d5c70e97240..4a9add5f5c616 100644 --- a/testdata/repros/Issue3190.t.sol +++ b/testdata/default/repros/Issue3190.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; import "../logs/console.sol"; // https://github.com/foundry-rs/foundry/issues/3190 diff --git a/testdata/repros/Issue3192.t.sol b/testdata/default/repros/Issue3192.t.sol similarity index 95% rename from testdata/repros/Issue3192.t.sol rename to testdata/default/repros/Issue3192.t.sol index 36841fd081579..0deb22f49e084 100644 --- a/testdata/repros/Issue3192.t.sol +++ b/testdata/default/repros/Issue3192.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3192 contract Issue3192Test is DSTest { diff --git a/testdata/repros/Issue3220.t.sol b/testdata/default/repros/Issue3220.t.sol similarity index 97% rename from testdata/repros/Issue3220.t.sol rename to testdata/default/repros/Issue3220.t.sol index acf75352d3877..b88d997c12051 100644 --- a/testdata/repros/Issue3220.t.sol +++ b/testdata/default/repros/Issue3220.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3220 contract Issue3220Test is DSTest { diff --git a/testdata/repros/Issue3221.t.sol b/testdata/default/repros/Issue3221.t.sol similarity index 82% rename from testdata/repros/Issue3221.t.sol rename to testdata/default/repros/Issue3221.t.sol index cc6f8039e87f9..4a9dd7be40997 100644 --- a/testdata/repros/Issue3221.t.sol +++ b/testdata/default/repros/Issue3221.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3221 contract Issue3221Test is DSTest { @@ -11,7 +11,7 @@ contract Issue3221Test is DSTest { uint256 fork2; function setUp() public { - fork1 = vm.createFork("https://goerli.infura.io/v3/b1d3925804e74152b316ca7da97060d3", 7475589); + fork1 = vm.createFork("rpcAliasSepolia", 5565573); fork2 = vm.createFork("https://api.avax-test.network/ext/bc/C/rpc", 12880747); } @@ -27,7 +27,7 @@ contract Issue3221Test is DSTest { new Counter(); vm.selectFork(fork1); - assertEq(vm.getNonce(user), 3); + assertEq(vm.getNonce(user), 1); vm.prank(user); new Counter(); } diff --git a/testdata/repros/Issue3223.t.sol b/testdata/default/repros/Issue3223.t.sol similarity index 76% rename from testdata/repros/Issue3223.t.sol rename to testdata/default/repros/Issue3223.t.sol index 14d46838e8330..d4c5da751f26b 100644 --- a/testdata/repros/Issue3223.t.sol +++ b/testdata/default/repros/Issue3223.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3223 contract Issue3223Test is DSTest { @@ -11,7 +11,7 @@ contract Issue3223Test is DSTest { uint256 fork2; function setUp() public { - fork1 = vm.createFork("https://goerli.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001", 7475589); + fork1 = vm.createFork("rpcAliasSepolia", 2362365); fork2 = vm.createFork("https://api.avax-test.network/ext/bc/C/rpc", 12880747); } @@ -25,9 +25,7 @@ contract Issue3223Test is DSTest { new Counter(); vm.selectFork(fork1); - assertEq(vm.getNonce(user), 3); - vm.prank(user); - new Counter(); + assertEq(vm.getNonce(user), 1); } } diff --git a/testdata/repros/Issue3347.t.sol b/testdata/default/repros/Issue3347.t.sol similarity index 91% rename from testdata/repros/Issue3347.t.sol rename to testdata/default/repros/Issue3347.t.sol index 66657ea626e8e..ed9be5f365b8e 100644 --- a/testdata/repros/Issue3347.t.sol +++ b/testdata/default/repros/Issue3347.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3347 contract Issue3347Test is DSTest { diff --git a/testdata/repros/Issue3437.t.sol b/testdata/default/repros/Issue3437.t.sol similarity index 93% rename from testdata/repros/Issue3437.t.sol rename to testdata/default/repros/Issue3437.t.sol index acd02ada74214..69f56ca8283dd 100644 --- a/testdata/repros/Issue3437.t.sol +++ b/testdata/default/repros/Issue3437.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3437 contract Issue3347Test is DSTest { diff --git a/testdata/repros/Issue3596.t.sol b/testdata/default/repros/Issue3596.t.sol similarity index 96% rename from testdata/repros/Issue3596.t.sol rename to testdata/default/repros/Issue3596.t.sol index 9a942d3424bb4..04ee470d70b37 100644 --- a/testdata/repros/Issue3596.t.sol +++ b/testdata/default/repros/Issue3596.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3596 contract Issue3596Test is DSTest { diff --git a/testdata/repros/Issue3653.t.sol b/testdata/default/repros/Issue3653.t.sol similarity index 77% rename from testdata/repros/Issue3653.t.sol rename to testdata/default/repros/Issue3653.t.sol index 6e52c49f8aa5c..b86f84c3e735d 100644 --- a/testdata/repros/Issue3653.t.sol +++ b/testdata/default/repros/Issue3653.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3653 contract Issue3653Test is DSTest { @@ -11,13 +11,13 @@ contract Issue3653Test is DSTest { Token token; constructor() { - fork = vm.createSelectFork("rpcAlias", 10); + fork = vm.createSelectFork("rpcAlias", 1000000); token = new Token(); vm.makePersistent(address(token)); } function testDummy() public { - assertEq(block.number, 10); + assertEq(block.number, 1000000); } } diff --git a/testdata/repros/Issue3661.t.sol b/testdata/default/repros/Issue3661.t.sol similarity index 100% rename from testdata/repros/Issue3661.t.sol rename to testdata/default/repros/Issue3661.t.sol diff --git a/testdata/repros/Issue3674.t.sol b/testdata/default/repros/Issue3674.t.sol similarity index 77% rename from testdata/repros/Issue3674.t.sol rename to testdata/default/repros/Issue3674.t.sol index 57216e8054811..813f73b5d8f0b 100644 --- a/testdata/repros/Issue3674.t.sol +++ b/testdata/default/repros/Issue3674.t.sol @@ -2,14 +2,14 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3674 contract Issue3674Test is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function testNonceCreateSelect() public { - vm.createSelectFork("https://goerli.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001"); + vm.createSelectFork("rpcAliasSepolia"); vm.createSelectFork("https://api.avax-test.network/ext/bc/C/rpc"); assert(vm.getNonce(msg.sender) > 0x17); diff --git a/testdata/repros/Issue3685.t.sol b/testdata/default/repros/Issue3685.t.sol similarity index 97% rename from testdata/repros/Issue3685.t.sol rename to testdata/default/repros/Issue3685.t.sol index 748367f659821..7e8f886d890cd 100644 --- a/testdata/repros/Issue3685.t.sol +++ b/testdata/default/repros/Issue3685.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; import "../logs/console.sol"; // https://github.com/foundry-rs/foundry/issues/3685 diff --git a/testdata/repros/Issue3703.t.sol b/testdata/default/repros/Issue3703.t.sol similarity index 98% rename from testdata/repros/Issue3703.t.sol rename to testdata/default/repros/Issue3703.t.sol index c941fe223902b..06ce6bcbe92af 100644 --- a/testdata/repros/Issue3703.t.sol +++ b/testdata/default/repros/Issue3703.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3703 contract Issue3703Test is DSTest { diff --git a/testdata/repros/Issue3708.t.sol b/testdata/default/repros/Issue3708.t.sol similarity index 97% rename from testdata/repros/Issue3708.t.sol rename to testdata/default/repros/Issue3708.t.sol index 7cf57cd0e082a..f5bdf48bf76a3 100644 --- a/testdata/repros/Issue3708.t.sol +++ b/testdata/default/repros/Issue3708.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3708 contract Issue3708Test is DSTest { diff --git a/testdata/repros/Issue3723.t.sol b/testdata/default/repros/Issue3723.t.sol similarity index 93% rename from testdata/repros/Issue3723.t.sol rename to testdata/default/repros/Issue3723.t.sol index 0f5d6694b7dfe..9ea3fe733c944 100644 --- a/testdata/repros/Issue3723.t.sol +++ b/testdata/default/repros/Issue3723.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3723 contract Issue3723Test is DSTest { diff --git a/testdata/repros/Issue3753.t.sol b/testdata/default/repros/Issue3753.t.sol similarity index 94% rename from testdata/repros/Issue3753.t.sol rename to testdata/default/repros/Issue3753.t.sol index 1edbb42b8059b..7af774baf49d3 100644 --- a/testdata/repros/Issue3753.t.sol +++ b/testdata/default/repros/Issue3753.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3753 contract Issue3753Test is DSTest { diff --git a/testdata/repros/Issue3792.t.sol b/testdata/default/repros/Issue3792.t.sol similarity index 96% rename from testdata/repros/Issue3792.t.sol rename to testdata/default/repros/Issue3792.t.sol index e01671f37d0fc..723329f937a1e 100644 --- a/testdata/repros/Issue3792.t.sol +++ b/testdata/default/repros/Issue3792.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract Config { address public test = 0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa; diff --git a/testdata/default/repros/Issue4402.t.sol b/testdata/default/repros/Issue4402.t.sol new file mode 100644 index 0000000000000..3bf0f33fb96fc --- /dev/null +++ b/testdata/default/repros/Issue4402.t.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/4402 +contract Issue4402Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testReadNonEmptyArray() public { + string memory path = "fixtures/Json/Issue4402.json"; + string memory json = vm.readFile(path); + address[] memory tokens = vm.parseJsonAddressArray(json, ".tokens"); + assertEq(tokens.length, 1); + + path = "fixtures/Toml/Issue4402.toml"; + string memory toml = vm.readFile(path); + tokens = vm.parseTomlAddressArray(toml, ".tokens"); + assertEq(tokens.length, 1); + } + + function testReadEmptyArray() public { + string memory path = "fixtures/Json/Issue4402.json"; + string memory json = vm.readFile(path); + + // Every one of these used to causes panic + address[] memory emptyAddressArray = vm.parseJsonAddressArray(json, ".empty"); + bool[] memory emptyBoolArray = vm.parseJsonBoolArray(json, ".empty"); + bytes[] memory emptyBytesArray = vm.parseJsonBytesArray(json, ".empty"); + bytes32[] memory emptyBytes32Array = vm.parseJsonBytes32Array(json, ".empty"); + string[] memory emptyStringArray = vm.parseJsonStringArray(json, ".empty"); + int256[] memory emptyIntArray = vm.parseJsonIntArray(json, ".empty"); + uint256[] memory emptyUintArray = vm.parseJsonUintArray(json, ".empty"); + + assertEq(emptyAddressArray.length, 0); + assertEq(emptyBoolArray.length, 0); + assertEq(emptyBytesArray.length, 0); + assertEq(emptyBytes32Array.length, 0); + assertEq(emptyStringArray.length, 0); + assertEq(emptyIntArray.length, 0); + assertEq(emptyUintArray.length, 0); + + path = "fixtures/Toml/Issue4402.toml"; + string memory toml = vm.readFile(path); + + // Every one of these used to causes panic + emptyAddressArray = vm.parseTomlAddressArray(toml, ".empty"); + emptyBoolArray = vm.parseTomlBoolArray(toml, ".empty"); + emptyBytesArray = vm.parseTomlBytesArray(toml, ".empty"); + emptyBytes32Array = vm.parseTomlBytes32Array(toml, ".empty"); + emptyStringArray = vm.parseTomlStringArray(toml, ".empty"); + emptyIntArray = vm.parseTomlIntArray(toml, ".empty"); + emptyUintArray = vm.parseTomlUintArray(toml, ".empty"); + + assertEq(emptyAddressArray.length, 0); + assertEq(emptyBoolArray.length, 0); + assertEq(emptyBytesArray.length, 0); + assertEq(emptyBytes32Array.length, 0); + assertEq(emptyStringArray.length, 0); + assertEq(emptyIntArray.length, 0); + assertEq(emptyUintArray.length, 0); + } +} diff --git a/testdata/repros/Issue4586.t.sol b/testdata/default/repros/Issue4586.t.sol similarity index 97% rename from testdata/repros/Issue4586.t.sol rename to testdata/default/repros/Issue4586.t.sol index 6eb615c029fa1..a41ba7a0485ea 100644 --- a/testdata/repros/Issue4586.t.sol +++ b/testdata/default/repros/Issue4586.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/4586 contract Issue4586Test is DSTest { diff --git a/testdata/repros/Issue4630.t.sol b/testdata/default/repros/Issue4630.t.sol similarity index 70% rename from testdata/repros/Issue4630.t.sol rename to testdata/default/repros/Issue4630.t.sol index a9334dc8e3850..4b9fe9c9b80ba 100644 --- a/testdata/repros/Issue4630.t.sol +++ b/testdata/default/repros/Issue4630.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/4630 contract Issue4630Test is DSTest { @@ -18,11 +18,7 @@ contract Issue4630Test is DSTest { function testMissingValue() public { string memory path = "fixtures/Json/Issue4630.json"; string memory json = vm.readFile(path); - vm.expectRevert(); - uint256 val = this.parseJsonUint(json, ".localempty.prop1"); - } - - function parseJsonUint(string memory json, string memory path) public returns (uint256) { - return vm.parseJsonUint(json, path); + vm._expectCheatcodeRevert(); + vm.parseJsonUint(json, ".localempty.prop1"); } } diff --git a/testdata/repros/Issue4640.t.sol b/testdata/default/repros/Issue4640.t.sol similarity index 94% rename from testdata/repros/Issue4640.t.sol rename to testdata/default/repros/Issue4640.t.sol index b16f4d071fd36..a875d000de979 100644 --- a/testdata/repros/Issue4640.t.sol +++ b/testdata/default/repros/Issue4640.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/4640 contract Issue4640Test is DSTest { diff --git a/testdata/repros/Issue4832.t.sol b/testdata/default/repros/Issue4832.t.sol similarity index 92% rename from testdata/repros/Issue4832.t.sol rename to testdata/default/repros/Issue4832.t.sol index 72f846873ff1b..192d805c1bc36 100644 --- a/testdata/repros/Issue4832.t.sol +++ b/testdata/default/repros/Issue4832.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/4832 contract Issue4832Test is DSTest { diff --git a/testdata/repros/Issue5038.t.sol b/testdata/default/repros/Issue5038.t.sol similarity index 99% rename from testdata/repros/Issue5038.t.sol rename to testdata/default/repros/Issue5038.t.sol index bee48f0b7fb12..834f8278375a4 100644 --- a/testdata/repros/Issue5038.t.sol +++ b/testdata/default/repros/Issue5038.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; struct Value { uint256 value; diff --git a/testdata/default/repros/Issue5529.t.sol b/testdata/default/repros/Issue5529.t.sol new file mode 100644 index 0000000000000..14ec7cfdbce60 --- /dev/null +++ b/testdata/default/repros/Issue5529.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} + +contract Issue5529Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + Counter public counter; + address public constant default_create2_factory = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + + function testCreate2FactoryUsedInTests() public { + address a = vm.computeCreate2Address(0, keccak256(type(Counter).creationCode), address(default_create2_factory)); + address b = address(new Counter{salt: 0}()); + require(a == b, "create2 address mismatch"); + } + + function testCreate2FactoryUsedWhenPranking() public { + vm.startPrank(address(1234)); + address a = vm.computeCreate2Address(0, keccak256(type(Counter).creationCode), address(default_create2_factory)); + address b = address(new Counter{salt: 0}()); + require(a == b, "create2 address mismatch"); + } +} diff --git a/testdata/repros/Issue5808.t.sol b/testdata/default/repros/Issue5808.t.sol similarity index 90% rename from testdata/repros/Issue5808.t.sol rename to testdata/default/repros/Issue5808.t.sol index e2a6a20888307..66ea82b30480e 100644 --- a/testdata/repros/Issue5808.t.sol +++ b/testdata/default/repros/Issue5808.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/5808 contract Issue5808Test is DSTest { @@ -10,7 +10,7 @@ contract Issue5808Test is DSTest { function testReadInt() public { string memory str1 = '["ffffffff","00000010"]'; - vm.expectRevert(); + vm._expectCheatcodeRevert(); int256[] memory ints1 = vm.parseJsonIntArray(str1, ""); string memory str2 = '["0xffffffff","0x00000010"]'; diff --git a/testdata/default/repros/Issue5929.t.sol b/testdata/default/repros/Issue5929.t.sol new file mode 100644 index 0000000000000..f1009f03b93c4 --- /dev/null +++ b/testdata/default/repros/Issue5929.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/5929 +contract Issue5929Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_transact_not_working() public { + vm.createSelectFork("rpcAlias", 15625301); + // https://etherscan.io/tx/0x96a129768ec66fd7d65114bf182f4e173bf0b73a44219adaf71f01381a3d0143 + vm.transact(hex"96a129768ec66fd7d65114bf182f4e173bf0b73a44219adaf71f01381a3d0143"); + } +} diff --git a/testdata/repros/Issue5935.t.sol b/testdata/default/repros/Issue5935.t.sol similarity index 97% rename from testdata/repros/Issue5935.t.sol rename to testdata/default/repros/Issue5935.t.sol index 8d6f8687b9339..95b6f8fd5b3a9 100644 --- a/testdata/repros/Issue5935.t.sol +++ b/testdata/default/repros/Issue5935.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0 <0.9.0; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract SimpleStorage { uint256 public value; diff --git a/testdata/repros/Issue5948.t.sol b/testdata/default/repros/Issue5948.t.sol similarity index 97% rename from testdata/repros/Issue5948.t.sol rename to testdata/default/repros/Issue5948.t.sol index b496caf66706d..992099fb1061a 100644 --- a/testdata/repros/Issue5948.t.sol +++ b/testdata/default/repros/Issue5948.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/5948 contract Issue5948Test is DSTest { diff --git a/testdata/repros/Issue6006.t.sol b/testdata/default/repros/Issue6006.t.sol similarity index 97% rename from testdata/repros/Issue6006.t.sol rename to testdata/default/repros/Issue6006.t.sol index 63e2cd5c6232f..dac37cd24b2c1 100644 --- a/testdata/repros/Issue6006.t.sol +++ b/testdata/default/repros/Issue6006.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6006 contract Issue6066Test is DSTest { diff --git a/testdata/default/repros/Issue6032.t.sol b/testdata/default/repros/Issue6032.t.sol new file mode 100644 index 0000000000000..fc230c47e183e --- /dev/null +++ b/testdata/default/repros/Issue6032.t.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/6032 +contract Issue6032Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testEtchFork() public { + // Deploy initial contract + Counter counter = new Counter(); + counter.setNumber(42); + + address counterAddress = address(counter); + // Enter the fork + vm.createSelectFork("rpcAlias"); + assert(counterAddress.code.length > 0); + // `Counter` is not deployed on the fork, which is expected. + + // Etch the contract into the fork. + bytes memory code = vm.getDeployedCode("Issue6032.t.sol:CounterEtched"); + vm.etch(counterAddress, code); + // `Counter` is now deployed on the fork. + assert(counterAddress.code.length > 0); + + // Now we can etch the code, but state will remain. + CounterEtched counterEtched = CounterEtched(counterAddress); + assertEq(counterEtched.numberHalf(), 21); + } +} + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } +} + +contract CounterEtched { + uint256 public number; + + function numberHalf() public view returns (uint256) { + return number / 2; + } +} diff --git a/testdata/repros/Issue6070.t.sol b/testdata/default/repros/Issue6070.t.sol similarity index 89% rename from testdata/repros/Issue6070.t.sol rename to testdata/default/repros/Issue6070.t.sol index dc068f22cf406..e699f5ca9f3b2 100644 --- a/testdata/repros/Issue6070.t.sol +++ b/testdata/default/repros/Issue6070.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6070 contract Issue6066Test is DSTest { @@ -10,7 +10,7 @@ contract Issue6066Test is DSTest { function testNonPrefixed() public { vm.setEnv("__FOUNDRY_ISSUE_6066", "abcd"); - vm.expectRevert( + vm._expectCheatcodeRevert( "failed parsing $__FOUNDRY_ISSUE_6066 as type `uint256`: missing hex prefix (\"0x\") for hex string" ); uint256 x = vm.envUint("__FOUNDRY_ISSUE_6066"); diff --git a/testdata/repros/Issue6115.t.sol b/testdata/default/repros/Issue6115.t.sol similarity index 100% rename from testdata/repros/Issue6115.t.sol rename to testdata/default/repros/Issue6115.t.sol diff --git a/testdata/repros/Issue6170.t.sol b/testdata/default/repros/Issue6170.t.sol similarity index 95% rename from testdata/repros/Issue6170.t.sol rename to testdata/default/repros/Issue6170.t.sol index 43f2067d6308c..543ca3142c9a8 100644 --- a/testdata/repros/Issue6170.t.sol +++ b/testdata/default/repros/Issue6170.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract Emitter { event Values(uint256 indexed a, uint256 indexed b); diff --git a/testdata/repros/Issue6180.t.sol b/testdata/default/repros/Issue6180.t.sol similarity index 95% rename from testdata/repros/Issue6180.t.sol rename to testdata/default/repros/Issue6180.t.sol index 7ff0684345120..591c60bdf8d0b 100644 --- a/testdata/repros/Issue6180.t.sol +++ b/testdata/default/repros/Issue6180.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6180 contract Issue6180Test is DSTest { diff --git a/testdata/default/repros/Issue6293.t.sol b/testdata/default/repros/Issue6293.t.sol new file mode 100644 index 0000000000000..303e8fbbe297a --- /dev/null +++ b/testdata/default/repros/Issue6293.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/6293 +contract Issue6293Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + constructor() { + require(address(this).balance > 0); + payable(address(1)).call{value: 1}(""); + } + + function test() public { + assertGt(address(this).balance, 0); + } +} diff --git a/testdata/repros/Issue6355.t.sol b/testdata/default/repros/Issue6355.t.sol similarity index 96% rename from testdata/repros/Issue6355.t.sol rename to testdata/default/repros/Issue6355.t.sol index 7271011c674de..d7830152a60a8 100644 --- a/testdata/repros/Issue6355.t.sol +++ b/testdata/default/repros/Issue6355.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6355 contract Issue6355Test is DSTest { diff --git a/testdata/repros/Issue6437.t.sol b/testdata/default/repros/Issue6437.t.sol similarity index 97% rename from testdata/repros/Issue6437.t.sol rename to testdata/default/repros/Issue6437.t.sol index 529c96d2e5e46..c18af2dfda841 100644 --- a/testdata/repros/Issue6437.t.sol +++ b/testdata/default/repros/Issue6437.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6437 contract Issue6437Test is DSTest { diff --git a/testdata/default/repros/Issue6501.t.sol b/testdata/default/repros/Issue6501.t.sol new file mode 100644 index 0000000000000..392f0f1abc8a9 --- /dev/null +++ b/testdata/default/repros/Issue6501.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "../logs/console.sol"; + +// https://github.com/foundry-rs/foundry/issues/6501 +contract Issue6501Test is DSTest { + function test_hhLogs() public { + console.log("a"); + console.log(uint256(1)); + console.log("b", uint256(2)); + } +} diff --git a/testdata/default/repros/Issue6538.t.sol b/testdata/default/repros/Issue6538.t.sol new file mode 100644 index 0000000000000..d83bbc850f5e1 --- /dev/null +++ b/testdata/default/repros/Issue6538.t.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/6538 +contract Issue6538Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_transact() public { + bytes32 lastHash = 0xdbdce1d5c14a6ca17f0e527ab762589d6a73f68697606ae0bb90df7ac9ec5087; + vm.createSelectFork("rpcAlias", lastHash); + bytes32 txhash = 0xadbe5cf9269a001d50990d0c29075b402bcc3a0b0f3258821881621b787b35c6; + vm.transact(txhash); + } +} diff --git a/testdata/default/repros/Issue6554.t.sol b/testdata/default/repros/Issue6554.t.sol new file mode 100644 index 0000000000000..c13ebc4a7d712 --- /dev/null +++ b/testdata/default/repros/Issue6554.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/6554 +contract Issue6554Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testPermissions() public { + vm.writeFile("./out/default/Issue6554.t.sol/cachedFile.txt", "cached data"); + string memory content = vm.readFile("./out/default/Issue6554.t.sol/cachedFile.txt"); + assertEq(content, "cached data"); + } +} diff --git a/testdata/default/repros/Issue6616.t.sol b/testdata/default/repros/Issue6616.t.sol new file mode 100644 index 0000000000000..24fa00e214eec --- /dev/null +++ b/testdata/default/repros/Issue6616.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/6616 +contract Issue6616Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testCreateForkRollLatestBlock() public { + vm.createSelectFork("rpcAlias"); + uint256 startBlock = block.number; + // this will create new forks and exit once a new latest block is found + for (uint256 i; i < 10; i++) { + vm.sleep(5000); + vm.createSelectFork("rpcAlias"); + if (block.number > startBlock) break; + } + assertGt(block.number, startBlock); + } +} diff --git a/testdata/default/repros/Issue6634.t.sol b/testdata/default/repros/Issue6634.t.sol new file mode 100644 index 0000000000000..22294f6df2338 --- /dev/null +++ b/testdata/default/repros/Issue6634.t.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../logs/console.sol"; + +contract Box { + uint256 public number; + + constructor(uint256 _number) { + number = _number; + } +} + +// https://github.com/foundry-rs/foundry/issues/6634 +contract Issue6634Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_Create2FactoryCallRecordedInStandardTest() public { + address CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + + vm.startStateDiffRecording(); + Box a = new Box{salt: 0}(1); + + Vm.AccountAccess[] memory called = vm.stopAndReturnStateDiff(); + address addr = vm.computeCreate2Address( + 0, keccak256(abi.encodePacked(type(Box).creationCode, uint256(1))), address(CREATE2_DEPLOYER) + ); + assertEq(addr, called[1].account, "state diff contract address is not correct"); + assertEq(address(a), called[1].account, "returned address is not correct"); + + assertEq(called.length, 2, "incorrect length"); + assertEq(uint256(called[0].kind), uint256(Vm.AccountAccessKind.Call), "first AccountAccess is incorrect kind"); + assertEq(called[0].account, CREATE2_DEPLOYER, "first AccountAccess account is incorrect"); + assertEq(called[0].accessor, address(this), "first AccountAccess accessor is incorrect"); + assertEq( + uint256(called[1].kind), uint256(Vm.AccountAccessKind.Create), "second AccountAccess is incorrect kind" + ); + assertEq(called[1].accessor, CREATE2_DEPLOYER, "second AccountAccess accessor is incorrect"); + assertEq(called[1].account, address(a), "second AccountAccess account is incorrect"); + } + + function test_Create2FactoryCallRecordedWhenPranking() public { + address CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + address accessor = address(0x5555); + + vm.startPrank(accessor); + vm.startStateDiffRecording(); + Box a = new Box{salt: 0}(1); + + Vm.AccountAccess[] memory called = vm.stopAndReturnStateDiff(); + address addr = vm.computeCreate2Address( + 0, keccak256(abi.encodePacked(type(Box).creationCode, uint256(1))), address(CREATE2_DEPLOYER) + ); + assertEq(addr, called[1].account, "state diff contract address is not correct"); + assertEq(address(a), called[1].account, "returned address is not correct"); + + assertEq(called.length, 2, "incorrect length"); + assertEq(uint256(called[0].kind), uint256(Vm.AccountAccessKind.Call), "first AccountAccess is incorrect kind"); + assertEq(called[0].account, CREATE2_DEPLOYER, "first AccountAccess accout is incorrect"); + assertEq(called[0].accessor, accessor, "first AccountAccess accessor is incorrect"); + assertEq( + uint256(called[1].kind), uint256(Vm.AccountAccessKind.Create), "second AccountAccess is incorrect kind" + ); + assertEq(called[1].accessor, CREATE2_DEPLOYER, "second AccountAccess accessor is incorrect"); + assertEq(called[1].account, address(a), "second AccountAccess account is incorrect"); + } + + function test_Create2FactoryCallRecordedWhenBroadcasting() public { + address CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + address accessor = address(0x5555); + + vm.startBroadcast(accessor); + vm.startStateDiffRecording(); + Box a = new Box{salt: 0}(1); + + Vm.AccountAccess[] memory called = vm.stopAndReturnStateDiff(); + address addr = vm.computeCreate2Address( + 0, keccak256(abi.encodePacked(type(Box).creationCode, uint256(1))), address(CREATE2_DEPLOYER) + ); + assertEq(addr, called[1].account, "state diff contract address is not correct"); + assertEq(address(a), called[1].account, "returned address is not correct"); + + assertEq(called.length, 2, "incorrect length"); + assertEq(uint256(called[0].kind), uint256(Vm.AccountAccessKind.Call), "first AccountAccess is incorrect kind"); + assertEq(called[0].account, CREATE2_DEPLOYER, "first AccountAccess accout is incorrect"); + assertEq(called[0].accessor, accessor, "first AccountAccess accessor is incorrect"); + assertEq( + uint256(called[1].kind), uint256(Vm.AccountAccessKind.Create), "second AccountAccess is incorrect kind" + ); + assertEq(called[1].accessor, CREATE2_DEPLOYER, "second AccountAccess accessor is incorrect"); + assertEq(called[1].account, address(a), "second AccountAccess account is incorrect"); + } +} diff --git a/testdata/default/repros/Issue6759.t.sol b/testdata/default/repros/Issue6759.t.sol new file mode 100644 index 0000000000000..a8039035e99d0 --- /dev/null +++ b/testdata/default/repros/Issue6759.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/6759 +contract Issue6759Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testCreateMulti() public { + uint256 fork1 = vm.createFork("rpcAlias", 10); + uint256 fork2 = vm.createFork("rpcAlias", 10); + uint256 fork3 = vm.createFork("rpcAlias", 10); + assert(fork1 != fork2); + assert(fork1 != fork3); + assert(fork2 != fork3); + } +} diff --git a/testdata/default/repros/Issue6966.t.sol b/testdata/default/repros/Issue6966.t.sol new file mode 100644 index 0000000000000..f7a8db7000acb --- /dev/null +++ b/testdata/default/repros/Issue6966.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; + +// https://github.com/foundry-rs/foundry/issues/6966 +// See also https://github.com/RustCrypto/elliptic-curves/issues/988#issuecomment-1817681013 +contract Issue6966Test is DSTest { + function testEcrecover() public { + bytes32 h = 0x0000000000000000000000000000000000000000000000000000000000000000; + uint8 v = 27; + bytes32 r = bytes32(0xf87fff3202dfeae34ce9cb8151ce2e176bee02a937baac6de85c4ea03d6a6618); + bytes32 s = bytes32(0xedf9ab5c7d3ec1df1c2b48600ab0a35f586e069e9a69c6cdeebc99920128d1a5); + assert(ecrecover(h, v, r, s) != address(0)); + } +} diff --git a/testdata/default/repros/Issue7481.t.sol b/testdata/default/repros/Issue7481.t.sol new file mode 100644 index 0000000000000..eb568dc946668 --- /dev/null +++ b/testdata/default/repros/Issue7481.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/7481 +// This test ensures that we don't panic +contract Issue7481Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testFailTransact() public { + vm.createSelectFork("rpcAlias", 19514903); + + // Transfer some funds to sender of tx being transacted to ensure that it appears in journaled state + payable(address(0x5C60cD7a3D50877Bfebd484750FBeb245D936dAD)).call{value: 1}(""); + vm.transact(0xccfd66fc409a633a99b5b75b0e9a2040fcf562d03d9bee3fefc1a5c0eb49c999); + + // Revert the current call to ensure that revm can revert state journal + revert("HERE"); + } +} diff --git a/testdata/default/script/broadcast/deploy.sol/31337/run-latest.json b/testdata/default/script/broadcast/deploy.sol/31337/run-latest.json new file mode 100644 index 0000000000000..1a8bccb392489 --- /dev/null +++ b/testdata/default/script/broadcast/deploy.sol/31337/run-latest.json @@ -0,0 +1 @@ +{"transactions":[{"hash":null,"type":"CREATE","contractName":null,"contractAddress":"0x731a10897d267e19b34503ad902d0a29173ba4b1","function":null,"arguments":null,"transaction":{"type":"0x02","from":"0x00a329c0648769a73afac7f9381e08fb43dbea72","gas":"0x54653","value":"0x0","data":"0x608060405234801561001057600080fd5b506103d9806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063d5dcf1271461003b578063f8194e4814610050575b600080fd5b61004e6100493660046100e9565b600155565b005b61006361005e366004610118565b610079565b60405161007091906101f9565b60405180910390f35b6060600061008783826102b5565b5060008260405160200161009b9190610375565b60405160208183030381529060405290507fefdeaaf566f7751d16a12c7fa8909eb74120f42cba334d07dd5246c48f1fba81816040516100db91906101f9565b60405180910390a192915050565b6000602082840312156100fb57600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561012a57600080fd5b813567ffffffffffffffff8082111561014257600080fd5b818401915084601f83011261015657600080fd5b81358181111561016857610168610102565b604051601f8201601f19908116603f0116810190838211818310171561019057610190610102565b816040528281528760208487010111156101a957600080fd5b826020860160208301376000928101602001929092525095945050505050565b60005b838110156101e45781810151838201526020016101cc565b838111156101f3576000848401525b50505050565b60208152600082518060208401526102188160408501602087016101c9565b601f01601f19169190910160400192915050565b600181811c9082168061024057607f821691505b60208210810361026057634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156102b057600081815260208120601f850160051c8101602086101561028d5750805b601f850160051c820191505b818110156102ac57828155600101610299565b5050505b505050565b815167ffffffffffffffff8111156102cf576102cf610102565b6102e3816102dd845461022c565b84610266565b602080601f83116001811461031857600084156103005750858301515b600019600386901b1c1916600185901b1785556102ac565b600085815260208120601f198616915b8281101561034757888601518255948401946001909101908401610328565b50858210156103655787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6502432b63637960d51b8152600082516103968160068501602087016101c9565b919091016006019291505056fea2646970667358221220a380cb042b6ca762a5a0f97e497c4cffa21c45dc21e2dab4107e5415921a704a64736f6c634300080f0033","nonce":"0x0","accessList":[]}},{"hash":null,"type":"CALL","contractName":null,"contractAddress":"0x731a10897d267e19b34503ad902d0a29173ba4b1","function":null,"arguments":null,"transaction":{"type":"0x02","from":"0x00a329c0648769a73afac7f9381e08fb43dbea72","to":"0x731a10897d267e19b34503ad902d0a29173ba4b1","gas":"0xef15","value":"0x0","data":"0xf8194e48000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000046a6f686e00000000000000000000000000000000000000000000000000000000","nonce":"0x1","accessList":[]}},{"hash":null,"type":"CALL","contractName":null,"contractAddress":"0x731a10897d267e19b34503ad902d0a29173ba4b1","function":null,"arguments":null,"transaction":{"type":"0x02","from":"0x00a329c0648769a73afac7f9381e08fb43dbea72","to":"0x731a10897d267e19b34503ad902d0a29173ba4b1","gas":"0xdcde","value":"0x0","data":"0xd5dcf127000000000000000000000000000000000000000000000000000000000000007b","nonce":"0x2","accessList":[]}}],"receipts":[],"libraries":[],"pending":[],"path":"broadcast/deploy.sol/31337/run-latest.json","returns":{},"timestamp":1658913881} \ No newline at end of file diff --git a/testdata/script/deploy.sol b/testdata/default/script/deploy.sol similarity index 89% rename from testdata/script/deploy.sol rename to testdata/default/script/deploy.sol index f05afe48706f6..013e009d3eaec 100644 --- a/testdata/script/deploy.sol +++ b/testdata/default/script/deploy.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity 0.8.18; -import {DSTest} from "../lib/ds-test/src/test.sol"; -import {Vm} from "../cheats/Vm.sol"; +import {DSTest} from "lib/ds-test/src/test.sol"; +import {Vm} from "cheats/Vm.sol"; contract Greeter { string name; diff --git a/testdata/spec/ShanghaiCompat.t.sol b/testdata/default/spec/ShanghaiCompat.t.sol similarity index 96% rename from testdata/spec/ShanghaiCompat.t.sol rename to testdata/default/spec/ShanghaiCompat.t.sol index f490441ef5fcd..02856a88fbec9 100644 --- a/testdata/spec/ShanghaiCompat.t.sol +++ b/testdata/default/spec/ShanghaiCompat.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.20; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract ShanghaiCompat is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/trace/ConflictingSignatures.t.sol b/testdata/default/trace/ConflictingSignatures.t.sol similarity index 97% rename from testdata/trace/ConflictingSignatures.t.sol rename to testdata/default/trace/ConflictingSignatures.t.sol index 896390212db0e..67dfd5d3afcc8 100644 --- a/testdata/trace/ConflictingSignatures.t.sol +++ b/testdata/default/trace/ConflictingSignatures.t.sol @@ -1,7 +1,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract ReturnsNothing { function func() public pure {} diff --git a/testdata/trace/Trace.t.sol b/testdata/default/trace/Trace.t.sol similarity index 98% rename from testdata/trace/Trace.t.sol rename to testdata/default/trace/Trace.t.sol index 2eefba7ba5dd9..d513e8637edd9 100644 --- a/testdata/trace/Trace.t.sol +++ b/testdata/default/trace/Trace.t.sol @@ -1,7 +1,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract RecursiveCall { TraceTest factory; diff --git a/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/creation_data.json b/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/creation_data.json new file mode 100644 index 0000000000000..899a44c1514b7 --- /dev/null +++ b/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x044b75f554b886a065b9567891e45c79542d7357","contractCreator":"0xf87bc5535602077d340806d71f805ea9907a843d","txHash":"0x9a89d2f5528bf07661e92f3f78a3311396f11f15da19e3ec4d880be1ad1a4bec"} \ No newline at end of file diff --git a/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/metadata.json b/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/metadata.json new file mode 100644 index 0000000000000..54aec7d6a61f2 --- /dev/null +++ b/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"contracts/InputStream.sol":{"content":"// SPDX-License-Identifier: UNLICENSED\n\npragma solidity 0.8.10;\n\nlibrary InputStream {\n function createStream(bytes memory data) internal pure returns (uint256 stream) {\n assembly {\n stream := mload(0x40)\n mstore(0x40, add(stream, 64))\n mstore(stream, data)\n let length := mload(data)\n mstore(add(stream, 32), add(data, length))\n }\n }\n\n function isNotEmpty(uint256 stream) internal pure returns (bool) {\n uint256 pos;\n uint256 finish;\n assembly {\n pos := mload(stream)\n finish := mload(add(stream, 32))\n }\n return pos < finish;\n }\n\n function readUint8(uint256 stream) internal pure returns (uint8 res) {\n assembly {\n let pos := mload(stream)\n pos := add(pos, 1)\n res := mload(pos)\n mstore(stream, pos)\n }\n }\n\n function readUint16(uint256 stream) internal pure returns (uint16 res) {\n assembly {\n let pos := mload(stream)\n pos := add(pos, 2)\n res := mload(pos)\n mstore(stream, pos)\n }\n }\n\n function readUint32(uint256 stream) internal pure returns (uint32 res) {\n assembly {\n let pos := mload(stream)\n pos := add(pos, 4)\n res := mload(pos)\n mstore(stream, pos)\n }\n }\n\n function readUint(uint256 stream) internal pure returns (uint256 res) {\n assembly {\n let pos := mload(stream)\n pos := add(pos, 32)\n res := mload(pos)\n mstore(stream, pos)\n }\n }\n\n function readAddress(uint256 stream) internal pure returns (address res) {\n assembly {\n let pos := mload(stream)\n pos := add(pos, 20)\n res := mload(pos)\n mstore(stream, pos)\n }\n }\n\n function readBytes(uint256 stream) internal pure returns (bytes memory res) {\n assembly {\n let pos := mload(stream)\n res := add(pos, 32)\n let length := mload(res)\n mstore(stream, add(res, length))\n }\n }\n}\n"},"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\nimport \"../extensions/draft-IERC20Permit.sol\";\nimport \"../../../utils/Address.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n using Address for address;\n\n function safeTransfer(\n IERC20 token,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n function safeTransferFrom(\n IERC20 token,\n address from,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n require(\n (value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n function safeIncreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n uint256 newAllowance = token.allowance(address(this), spender) + value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n\n function safeDecreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n unchecked {\n uint256 oldAllowance = token.allowance(address(this), spender);\n require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\n uint256 newAllowance = oldAllowance - value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n }\n\n function safePermit(\n IERC20Permit token,\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal {\n uint256 nonceBefore = token.nonces(owner);\n token.permit(owner, spender, value, deadline, v, r, s);\n uint256 nonceAfter = token.nonces(owner);\n require(nonceAfter == nonceBefore + 1, \"SafeERC20: permit did not succeed\");\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n if (returndata.length > 0) {\n // Return data is optional\n require(abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n }\n}\n"},"interfaces/IBentoBoxMinimal.sol":{"content":"// SPDX-License-Identifier: UNLICENSED\n\npragma solidity >=0.8.0;\n\nstruct Rebase {\n uint128 elastic;\n uint128 base;\n}\n\nstruct StrategyData {\n uint64 strategyStartDate;\n uint64 targetPercentage;\n uint128 balance; // the balance of the strategy that BentoBox thinks is in there\n}\n\n/// @notice A rebasing library\nlibrary RebaseLibrary {\n /// @notice Calculates the base value in relationship to `elastic` and `total`.\n function toBase(Rebase memory total, uint256 elastic) internal pure returns (uint256 base) {\n if (total.elastic == 0) {\n base = elastic;\n } else {\n base = (elastic * total.base) / total.elastic;\n }\n }\n\n /// @notice Calculates the elastic value in relationship to `base` and `total`.\n function toElastic(Rebase memory total, uint256 base) internal pure returns (uint256 elastic) {\n if (total.base == 0) {\n elastic = base;\n } else {\n elastic = (base * total.elastic) / total.base;\n }\n }\n}\n\n/// @notice Minimal BentoBox vault interface.\n/// @dev `token` is aliased as `address` from `IERC20` for simplicity.\ninterface IBentoBoxMinimal {\n /// @notice Balance per ERC-20 token per account in shares.\n function balanceOf(address, address) external view returns (uint256);\n\n /// @dev Helper function to represent an `amount` of `token` in shares.\n /// @param token The ERC-20 token.\n /// @param amount The `token` amount.\n /// @param roundUp If the result `share` should be rounded up.\n /// @return share The token amount represented in shares.\n function toShare(\n address token,\n uint256 amount,\n bool roundUp\n ) external view returns (uint256 share);\n\n /// @dev Helper function to represent shares back into the `token` amount.\n /// @param token The ERC-20 token.\n /// @param share The amount of shares.\n /// @param roundUp If the result should be rounded up.\n /// @return amount The share amount back into native representation.\n function toAmount(\n address token,\n uint256 share,\n bool roundUp\n ) external view returns (uint256 amount);\n\n /// @notice Registers this contract so that users can approve it for BentoBox.\n function registerProtocol() external;\n\n /// @notice Deposit an amount of `token` represented in either `amount` or `share`.\n /// @param token The ERC-20 token to deposit.\n /// @param from which account to pull the tokens.\n /// @param to which account to push the tokens.\n /// @param amount Token amount in native representation to deposit.\n /// @param share Token amount represented in shares to deposit. Takes precedence over `amount`.\n /// @return amountOut The amount deposited.\n /// @return shareOut The deposited amount represented in shares.\n function deposit(\n address token,\n address from,\n address to,\n uint256 amount,\n uint256 share\n ) external payable returns (uint256 amountOut, uint256 shareOut);\n\n /// @notice Withdraws an amount of `token` from a user account.\n /// @param token_ The ERC-20 token to withdraw.\n /// @param from which user to pull the tokens.\n /// @param to which user to push the tokens.\n /// @param amount of tokens. Either one of `amount` or `share` needs to be supplied.\n /// @param share Like above, but `share` takes precedence over `amount`.\n function withdraw(\n address token_,\n address from,\n address to,\n uint256 amount,\n uint256 share\n ) external returns (uint256 amountOut, uint256 shareOut);\n\n /// @notice Transfer shares from a user account to another one.\n /// @param token The ERC-20 token to transfer.\n /// @param from which user to pull the tokens.\n /// @param to which user to push the tokens.\n /// @param share The amount of `token` in shares.\n function transfer(\n address token,\n address from,\n address to,\n uint256 share\n ) external;\n\n /// @dev Reads the Rebase `totals`from storage for a given token\n function totals(address token) external view returns (Rebase memory total);\n\n function strategyData(address token) external view returns (StrategyData memory total);\n\n /// @dev Approves users' BentoBox assets to a \"master\" contract.\n function setMasterContractApproval(\n address user,\n address masterContract,\n bool approved,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n function harvest(\n address token,\n bool balance,\n uint256 maxChangeAmount\n ) external;\n}\n"},"@openzeppelin/contracts/utils/Address.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\n *\n * _Available since v4.8._\n */\n function verifyCallResultFromTarget(\n address target,\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n if (success) {\n if (returndata.length == 0) {\n // only check isContract if the call was successful and the return data is empty\n // otherwise we already know that it was a contract\n require(isContract(target), \"Address: call to non-contract\");\n }\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n /**\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason or using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n /// @solidity memory-safe-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n}\n"},"interfaces/IPool.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.5.0;\npragma experimental ABIEncoderV2;\n\n/// @notice Trident pool interface.\ninterface IPool {\n /// @notice Executes a swap from one token to another.\n /// @dev The input tokens must've already been sent to the pool.\n /// @param data ABI-encoded params that the pool requires.\n /// @return finalAmountOut The amount of output tokens that were sent to the user.\n function swap(bytes calldata data) external returns (uint256 finalAmountOut);\n\n /// @notice Executes a swap from one token to another with a callback.\n /// @dev This function allows borrowing the output tokens and sending the input tokens in the callback.\n /// @param data ABI-encoded params that the pool requires.\n /// @return finalAmountOut The amount of output tokens that were sent to the user.\n function flashSwap(bytes calldata data) external returns (uint256 finalAmountOut);\n\n /// @notice Mints liquidity tokens.\n /// @param data ABI-encoded params that the pool requires.\n /// @return liquidity The amount of liquidity tokens that were minted for the user.\n function mint(bytes calldata data) external returns (uint256 liquidity);\n\n /// @notice Burns liquidity tokens.\n /// @dev The input LP tokens must've already been sent to the pool.\n /// @param data ABI-encoded params that the pool requires.\n /// @return withdrawnAmounts The amount of various output tokens that were sent to the user.\n function burn(bytes calldata data) external returns (TokenAmount[] memory withdrawnAmounts);\n\n /// @notice Burns liquidity tokens for a single output token.\n /// @dev The input LP tokens must've already been sent to the pool.\n /// @param data ABI-encoded params that the pool requires.\n /// @return amountOut The amount of output tokens that were sent to the user.\n function burnSingle(bytes calldata data) external returns (uint256 amountOut);\n\n /// @return A unique identifier for the pool type.\n function poolIdentifier() external pure returns (bytes32);\n\n /// @return An array of tokens supported by the pool.\n function getAssets() external view returns (address[] memory);\n\n /// @notice Simulates a trade and returns the expected output.\n /// @dev The pool does not need to include a trade simulator directly in itself - it can use a library.\n /// @param data ABI-encoded params that the pool requires.\n /// @return finalAmountOut The amount of output tokens that will be sent to the user if the trade is executed.\n function getAmountOut(bytes calldata data) external view returns (uint256 finalAmountOut);\n\n /// @notice Simulates a trade and returns the expected output.\n /// @dev The pool does not need to include a trade simulator directly in itself - it can use a library.\n /// @param data ABI-encoded params that the pool requires.\n /// @return finalAmountIn The amount of input tokens that are required from the user if the trade is executed.\n function getAmountIn(bytes calldata data) external view returns (uint256 finalAmountIn);\n\n /// @dev This event must be emitted on all swaps.\n event Swap(address indexed recipient, address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut);\n\n /// @dev This struct frames output tokens for burns.\n struct TokenAmount {\n address token;\n uint256 amount;\n }\n}\n"},"@openzeppelin/contracts/token/ERC20/IERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) external returns (bool);\n}\n"},"contracts/RouteProcessor2.sol":{"content":"// SPDX-License-Identifier: UNLICENSED\n\npragma solidity 0.8.10;\n\nimport '../interfaces/IUniswapV2Pair.sol';\nimport '../interfaces/IUniswapV3Pool.sol';\nimport '../interfaces/ITridentCLPool.sol';\nimport '../interfaces/IBentoBoxMinimal.sol';\nimport '../interfaces/IPool.sol';\nimport '../interfaces/IWETH.sol';\nimport './InputStream.sol';\nimport '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';\n\naddress constant NATIVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;\naddress constant IMPOSSIBLE_POOL_ADDRESS = 0x0000000000000000000000000000000000000001;\n\n/// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)\nuint160 constant MIN_SQRT_RATIO = 4295128739;\n/// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)\nuint160 constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;\n\n/// @title A route processor for the Sushi Aggregator\n/// @author Ilya Lyalin\ncontract RouteProcessor2 {\n using SafeERC20 for IERC20;\n using InputStream for uint256;\n\n IBentoBoxMinimal public immutable bentoBox;\n address private lastCalledPool;\n\n uint private unlocked = 1;\n modifier lock() {\n require(unlocked == 1, 'RouteProcessor is locked');\n unlocked = 2;\n _;\n unlocked = 1;\n }\n\n constructor(address _bentoBox) {\n bentoBox = IBentoBoxMinimal(_bentoBox);\n lastCalledPool = IMPOSSIBLE_POOL_ADDRESS;\n }\n\n /// @notice For native unwrapping\n receive() external payable {}\n\n /// @notice Processes the route generated off-chain. Has a lock\n /// @param tokenIn Address of the input token\n /// @param amountIn Amount of the input token\n /// @param tokenOut Address of the output token\n /// @param amountOutMin Minimum amount of the output token\n /// @return amountOut Actual amount of the output token\n function processRoute(\n address tokenIn,\n uint256 amountIn,\n address tokenOut,\n uint256 amountOutMin,\n address to,\n bytes memory route\n ) external payable lock returns (uint256 amountOut) {\n return processRouteInternal(tokenIn, amountIn, tokenOut, amountOutMin, to, route);\n }\n\n /// @notice Transfers some value to and then processes the route\n /// @param transferValueTo Address where the value should be transferred\n /// @param amountValueTransfer How much value to transfer\n /// @param tokenIn Address of the input token\n /// @param amountIn Amount of the input token\n /// @param tokenOut Address of the output token\n /// @param amountOutMin Minimum amount of the output token\n /// @return amountOut Actual amount of the output token\n function transferValueAndprocessRoute(\n address payable transferValueTo,\n uint256 amountValueTransfer,\n address tokenIn,\n uint256 amountIn,\n address tokenOut,\n uint256 amountOutMin,\n address to,\n bytes memory route\n ) external payable lock returns (uint256 amountOut) {\n (bool success, bytes memory returnBytes) = transferValueTo.call{value: amountValueTransfer}('');\n require(success, string(abi.encodePacked(returnBytes)));\n return processRouteInternal(tokenIn, amountIn, tokenOut, amountOutMin, to, route);\n }\n\n /// @notice Processes the route generated off-chain\n /// @param tokenIn Address of the input token\n /// @param amountIn Amount of the input token\n /// @param tokenOut Address of the output token\n /// @param amountOutMin Minimum amount of the output token\n /// @return amountOut Actual amount of the output token\n function processRouteInternal(\n address tokenIn,\n uint256 amountIn,\n address tokenOut,\n uint256 amountOutMin,\n address to,\n bytes memory route\n ) private returns (uint256 amountOut) {\n uint256 balanceInInitial = tokenIn == NATIVE_ADDRESS ? address(this).balance : IERC20(tokenIn).balanceOf(msg.sender);\n uint256 balanceOutInitial = tokenOut == NATIVE_ADDRESS ? address(to).balance : IERC20(tokenOut).balanceOf(to);\n\n uint256 stream = InputStream.createStream(route);\n while (stream.isNotEmpty()) {\n uint8 commandCode = stream.readUint8();\n if (commandCode == 1) processMyERC20(stream);\n else if (commandCode == 2) processUserERC20(stream, amountIn);\n else if (commandCode == 3) processNative(stream);\n else if (commandCode == 4) processOnePool(stream);\n else if (commandCode == 5) processInsideBento(stream);\n else revert('RouteProcessor: Unknown command code');\n }\n\n uint256 balanceInFinal = tokenIn == NATIVE_ADDRESS ? address(this).balance : IERC20(tokenIn).balanceOf(msg.sender);\n require(balanceInFinal + amountIn >= balanceInInitial, 'RouteProcessor: Minimal imput balance violation');\n\n uint256 balanceOutFinal = tokenOut == NATIVE_ADDRESS ? address(to).balance : IERC20(tokenOut).balanceOf(to);\n require(balanceOutFinal >= balanceOutInitial + amountOutMin, 'RouteProcessor: Minimal ouput balance violation');\n\n amountOut = balanceOutFinal - balanceOutInitial;\n }\n\n /// @notice Processes native coin: call swap for all pools that swap from native coin\n /// @param stream Streamed process program\n function processNative(uint256 stream) private {\n uint256 amountTotal = address(this).balance;\n distributeAndSwap(stream, address(this), NATIVE_ADDRESS, amountTotal);\n }\n\n /// @notice Processes ERC20 token from this contract balance:\n /// @notice Call swap for all pools that swap from this token\n /// @param stream Streamed process program\n function processMyERC20(uint256 stream) private {\n address token = stream.readAddress();\n uint256 amountTotal = IERC20(token).balanceOf(address(this));\n unchecked {\n if (amountTotal > 0) amountTotal -= 1; // slot undrain protection\n }\n distributeAndSwap(stream, address(this), token, amountTotal);\n }\n \n /// @notice Processes ERC20 token from msg.sender balance:\n /// @notice Call swap for all pools that swap from this token\n /// @param stream Streamed process program\n /// @param amountTotal Amount of tokens to take from msg.sender\n function processUserERC20(uint256 stream, uint256 amountTotal) private {\n address token = stream.readAddress();\n distributeAndSwap(stream, msg.sender, token, amountTotal);\n }\n\n /// @notice Distributes amountTotal to several pools according to their shares and calls swap for each pool\n /// @param stream Streamed process program\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountTotal Total amount of tokenIn for swaps \n function distributeAndSwap(uint256 stream, address from, address tokenIn, uint256 amountTotal) private {\n uint8 num = stream.readUint8();\n unchecked {\n for (uint256 i = 0; i < num; ++i) {\n uint16 share = stream.readUint16();\n uint256 amount = (amountTotal * share) / 65535;\n amountTotal -= amount;\n swap(stream, from, tokenIn, amount);\n }\n }\n }\n\n /// @notice Processes ERC20 token for cases when the token has only one output pool\n /// @notice In this case liquidity is already at pool balance. This is an optimization\n /// @notice Call swap for all pools that swap from this token\n /// @param stream Streamed process program\n function processOnePool(uint256 stream) private {\n address token = stream.readAddress();\n swap(stream, address(this), token, 0);\n }\n\n /// @notice Processes Bento tokens \n /// @notice Call swap for all pools that swap from this token\n /// @param stream Streamed process program\n function processInsideBento(uint256 stream) private {\n address token = stream.readAddress();\n uint8 num = stream.readUint8();\n\n uint256 amountTotal = bentoBox.balanceOf(token, address(this));\n unchecked {\n if (amountTotal > 0) amountTotal -= 1; // slot undrain protection\n for (uint256 i = 0; i < num; ++i) {\n uint16 share = stream.readUint16();\n uint256 amount = (amountTotal * share) / 65535;\n amountTotal -= amount;\n swap(stream, address(this), token, amount);\n }\n }\n }\n\n /// @notice Makes swap\n /// @param stream Streamed process program\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function swap(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n uint8 poolType = stream.readUint8();\n if (poolType == 0) swapUniV2(stream, from, tokenIn, amountIn);\n else if (poolType == 1) swapUniV3(stream, from, tokenIn, amountIn);\n else if (poolType == 2) wrapNative(stream, from, tokenIn, amountIn);\n else if (poolType == 3) bentoBridge(stream, from, tokenIn, amountIn);\n else if (poolType == 4) swapTrident(stream, from, tokenIn, amountIn);\n else if (poolType == 5) swapTridentCL(stream, from, tokenIn, amountIn);\n else revert('RouteProcessor: Unknown pool type');\n }\n\n /// @notice Wraps/unwraps native token\n /// @param stream [direction & fake, recipient, wrapToken?]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function wrapNative(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n uint8 directionAndFake = stream.readUint8();\n address to = stream.readAddress();\n\n if (directionAndFake & 1 == 1) { // wrap native\n address wrapToken = stream.readAddress();\n if (directionAndFake & 2 == 0) IWETH(wrapToken).deposit{value: amountIn}();\n if (to != address(this)) IERC20(wrapToken).safeTransfer(to, amountIn);\n } else { // unwrap native\n if (directionAndFake & 2 == 0) {\n if (from != address(this)) IERC20(tokenIn).safeTransferFrom(from, address(this), amountIn);\n IWETH(tokenIn).withdraw(amountIn);\n }\n payable(to).transfer(address(this).balance);\n }\n }\n\n /// @notice Bridge/unbridge tokens to/from Bento\n /// @param stream [direction, recipient]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function bentoBridge(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n uint8 direction = stream.readUint8();\n address to = stream.readAddress();\n\n if (direction > 0) { // outside to Bento\n // deposit to arbitrary recipient is possible only from address(bentoBox)\n if (amountIn != 0) {\n if (from == address(this)) IERC20(tokenIn).safeTransfer(address(bentoBox), amountIn);\n else IERC20(tokenIn).safeTransferFrom(from, address(bentoBox), amountIn);\n } else {\n // tokens already are at address(bentoBox)\n amountIn = IERC20(tokenIn).balanceOf(address(bentoBox)) +\n bentoBox.strategyData(tokenIn).balance -\n bentoBox.totals(tokenIn).elastic;\n }\n bentoBox.deposit(tokenIn, address(bentoBox), to, amountIn, 0);\n } else { // Bento to outside\n if (amountIn > 0) {\n bentoBox.transfer(tokenIn, from, address(this), amountIn);\n } else amountIn = bentoBox.balanceOf(tokenIn, address(this));\n bentoBox.withdraw(tokenIn, address(this), to, 0, amountIn);\n }\n }\n\n /// @notice UniswapV2 pool swap\n /// @param stream [pool, direction, recipient]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function swapUniV2(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n address pool = stream.readAddress();\n uint8 direction = stream.readUint8();\n address to = stream.readAddress();\n\n (uint256 r0, uint256 r1, ) = IUniswapV2Pair(pool).getReserves();\n require(r0 > 0 && r1 > 0, 'Wrong pool reserves');\n (uint256 reserveIn, uint256 reserveOut) = direction == 1 ? (r0, r1) : (r1, r0);\n\n if (amountIn != 0) {\n if (from == address(this)) IERC20(tokenIn).safeTransfer(pool, amountIn);\n else IERC20(tokenIn).safeTransferFrom(from, pool, amountIn);\n } else amountIn = IERC20(tokenIn).balanceOf(pool) - reserveIn; // tokens already were transferred\n\n uint256 amountInWithFee = amountIn * 997;\n uint256 amountOut = (amountInWithFee * reserveOut) / (reserveIn * 1000 + amountInWithFee);\n (uint256 amount0Out, uint256 amount1Out) = direction == 1 ? (uint256(0), amountOut) : (amountOut, uint256(0));\n IUniswapV2Pair(pool).swap(amount0Out, amount1Out, to, new bytes(0));\n }\n\n /// @notice Trident pool swap\n /// @param stream [pool, swapData]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function swapTrident(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n address pool = stream.readAddress();\n bytes memory swapData = stream.readBytes();\n\n if (amountIn != 0) {\n bentoBox.transfer(tokenIn, from, pool, amountIn);\n }\n \n IPool(pool).swap(swapData);\n }\n\n /// @notice UniswapV3 pool swap\n /// @param stream [pool, direction, recipient]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function swapUniV3(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n address pool = stream.readAddress();\n bool zeroForOne = stream.readUint8() > 0;\n address recipient = stream.readAddress();\n\n lastCalledPool = pool;\n IUniswapV3Pool(pool).swap(\n recipient,\n zeroForOne,\n int256(amountIn),\n zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,\n abi.encode(tokenIn, from)\n );\n require(lastCalledPool == IMPOSSIBLE_POOL_ADDRESS, 'RouteProcessor.swapUniV3: unexpected'); // Just to be sure\n }\n\n /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.\n /// @dev In the implementation you must pay the pool tokens owed for the swap.\n /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.\n /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.\n /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by\n /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.\n /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by\n /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.\n /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call\n function uniswapV3SwapCallback(\n int256 amount0Delta,\n int256 amount1Delta,\n bytes calldata data\n ) external {\n require(msg.sender == lastCalledPool, 'RouteProcessor.uniswapV3SwapCallback: call from unknown source');\n lastCalledPool = IMPOSSIBLE_POOL_ADDRESS;\n (address tokenIn, address from) = abi.decode(data, (address, address));\n int256 amount = amount0Delta > 0 ? amount0Delta : amount1Delta;\n require(amount > 0, 'RouteProcessor.uniswapV3SwapCallback: not positive amount');\n\n if (from == address(this)) IERC20(tokenIn).safeTransfer(msg.sender, uint256(amount));\n else IERC20(tokenIn).safeTransferFrom(from, msg.sender, uint256(amount));\n }\n\n /// @notice TridentCL pool swap\n /// @param stream [pool, direction, recipient]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function swapTridentCL(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n address pool = stream.readAddress();\n bool zeroForOne = stream.readUint8() > 0;\n address recipient = stream.readAddress();\n\n lastCalledPool = pool;\n ITridentCLPool(pool).swap(\n recipient,\n zeroForOne,\n int256(amountIn),\n zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,\n false,\n abi.encode(tokenIn, from)\n );\n require(lastCalledPool == IMPOSSIBLE_POOL_ADDRESS, 'RouteProcessor.swapTridentCL: unexpected'); // Just to be sure\n }\n\n /// @notice Called to `msg.sender` after executing a swap via ITridentCLPool#swap.\n /// @dev In the implementation you must pay the pool tokens owed for the swap.\n /// The caller of this method must be checked to be a TridentCLPool deployed by the canonical TridentCLFactory.\n /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.\n /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by\n /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.\n /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by\n /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.\n /// @param data Any data passed through by the caller via the ITridentCLPoolActions#swap call\n function tridentCLSwapCallback(\n int256 amount0Delta,\n int256 amount1Delta,\n bytes calldata data\n ) external {\n require(msg.sender == lastCalledPool, 'RouteProcessor.TridentCLSwapCallback: call from unknown source');\n lastCalledPool = IMPOSSIBLE_POOL_ADDRESS;\n (address tokenIn, address from) = abi.decode(data, (address, address));\n int256 amount = amount0Delta > 0 ? amount0Delta : amount1Delta;\n require(amount > 0, 'RouteProcessor.TridentCLSwapCallback: not positive amount');\n\n if (from == address(this)) IERC20(tokenIn).safeTransfer(msg.sender, uint256(amount));\n else IERC20(tokenIn).safeTransferFrom(from, msg.sender, uint256(amount));\n }\n}\n"},"interfaces/ITridentCLPool.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity 0.8.10;\n\ninterface ITridentCLPool {\n function token0() external returns (address);\n function token1() external returns (address);\n\n function swap(\n address recipient,\n bool zeroForOne,\n int256 amountSpecified,\n uint160 sqrtPriceLimitX96,\n bool unwrapBento,\n bytes calldata data\n ) external returns (int256 amount0, int256 amount1);\n}\n"},"@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n */\ninterface IERC20Permit {\n /**\n * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\n * given ``owner``'s signed approval.\n *\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\n * ordering also apply here.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `deadline` must be a timestamp in the future.\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\n * over the EIP712-formatted function arguments.\n * - the signature must use ``owner``'s current nonce (see {nonces}).\n *\n * For more information on the signature format, see the\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\n * section].\n */\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n /**\n * @dev Returns the current nonce for `owner`. This value must be\n * included whenever a signature is generated for {permit}.\n *\n * Every successful call to {permit} increases ``owner``'s nonce by one. This\n * prevents a signature from being used multiple times.\n */\n function nonces(address owner) external view returns (uint256);\n\n /**\n * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\n */\n // solhint-disable-next-line func-name-mixedcase\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n}\n"},"interfaces/IUniswapV2Pair.sol":{"content":"// SPDX-License-Identifier: GPL-3.0\n\npragma solidity >=0.5.0;\n\ninterface IUniswapV2Pair {\n event Approval(address indexed owner, address indexed spender, uint value);\n event Transfer(address indexed from, address indexed to, uint value);\n\n function name() external pure returns (string memory);\n function symbol() external pure returns (string memory);\n function decimals() external pure returns (uint8);\n function totalSupply() external view returns (uint);\n function balanceOf(address owner) external view returns (uint);\n function allowance(address owner, address spender) external view returns (uint);\n\n function approve(address spender, uint value) external returns (bool);\n function transfer(address to, uint value) external returns (bool);\n function transferFrom(address from, address to, uint value) external returns (bool);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n function PERMIT_TYPEHASH() external pure returns (bytes32);\n function nonces(address owner) external view returns (uint);\n\n function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;\n\n event Mint(address indexed sender, uint amount0, uint amount1);\n event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);\n event Swap(\n address indexed sender,\n uint amount0In,\n uint amount1In,\n uint amount0Out,\n uint amount1Out,\n address indexed to\n );\n event Sync(uint112 reserve0, uint112 reserve1);\n\n function MINIMUM_LIQUIDITY() external pure returns (uint);\n function factory() external view returns (address);\n function token0() external view returns (address);\n function token1() external view returns (address);\n function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);\n function price0CumulativeLast() external view returns (uint);\n function price1CumulativeLast() external view returns (uint);\n function kLast() external view returns (uint);\n\n function mint(address to) external returns (uint liquidity);\n function burn(address to) external returns (uint amount0, uint amount1);\n function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;\n function skim(address to) external;\n function sync() external;\n\n function initialize(address, address) external;\n}"},"interfaces/IUniswapV3Pool.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity 0.8.10;\n\ninterface IUniswapV3Pool {\n function token0() external returns (address);\n function token1() external returns (address);\n\n function swap(\n address recipient,\n bool zeroForOne,\n int256 amountSpecified,\n uint160 sqrtPriceLimitX96,\n bytes calldata data\n ) external returns (int256 amount0, int256 amount1);\n}\n"},"interfaces/IWETH.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity 0.8.10;\n\ninterface IWETH {\n function deposit() external payable;\n\n function transfer(address to, uint256 value) external returns (bool);\n\n function withdraw(uint256) external;\n}\n"}},"settings":{"optimizer":{"enabled":true,"runs":10000000},"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}},"metadata":{"useLiteralContent":true},"libraries":{}}},"ABI":"[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_bentoBox\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"bentoBox\",\"outputs\":[{\"internalType\":\"contract IBentoBoxMinimal\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenIn\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"tokenOut\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountOutMin\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"route\",\"type\":\"bytes\"}],\"name\":\"processRoute\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountOut\",\"type\":\"uint256\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"transferValueTo\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountValueTransfer\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"tokenIn\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"tokenOut\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountOutMin\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"route\",\"type\":\"bytes\"}],\"name\":\"transferValueAndprocessRoute\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountOut\",\"type\":\"uint256\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int256\",\"name\":\"amount0Delta\",\"type\":\"int256\"},{\"internalType\":\"int256\",\"name\":\"amount1Delta\",\"type\":\"int256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"tridentCLSwapCallback\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int256\",\"name\":\"amount0Delta\",\"type\":\"int256\"},{\"internalType\":\"int256\",\"name\":\"amount1Delta\",\"type\":\"int256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"uniswapV3SwapCallback\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]","ContractName":"RouteProcessor2","CompilerVersion":"v0.8.10+commit.fc410830","OptimizationUsed":1,"Runs":10000000,"ConstructorArguments":"0x000000000000000000000000f5bce5077908a1b7370b9ae04adc565ebd643966","EVMVersion":"Default","Library":"","LicenseType":"","Proxy":0,"SwarmSource":""}] \ No newline at end of file diff --git a/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/creation_data.json b/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/creation_data.json new file mode 100644 index 0000000000000..e3433ef20019a --- /dev/null +++ b/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x35fb958109b70799a8f9bc2a8b1ee4cc62034193","contractCreator":"0x3e32324277e96b69750bc6f7c4ba27e122413e07","txHash":"0x41e3517f8262b55e1eb1707ba0760b603a70e89ea4a86eff56072fcc80c3d0a1"} \ No newline at end of file diff --git a/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/metadata.json b/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/metadata.json new file mode 100644 index 0000000000000..bd48a2efbea4c --- /dev/null +++ b/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":"/**\r\n *Submitted for verification at Etherscan.io on 2022-02-19\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-18\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-14\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-10\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-09\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-08\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-05\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-01-22\r\n*/\r\n\r\n// SPDX-License-Identifier: UNLICENSED\r\n/*\r\nmade by cty0312\r\n2022.01.22\r\n**/\r\n\r\npragma solidity >=0.7.0 <0.9.0;\r\n\r\nlibrary StringsUpgradeable {\r\n bytes16 private constant _HEX_SYMBOLS = \"0123456789abcdef\";\r\n\r\n /**\r\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\r\n */\r\n function toString(uint256 value) internal pure returns (string memory) {\r\n // Inspired by OraclizeAPI's implementation - MIT licence\r\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\r\n\r\n if (value == 0) {\r\n return \"0\";\r\n }\r\n uint256 temp = value;\r\n uint256 digits;\r\n while (temp != 0) {\r\n digits++;\r\n temp /= 10;\r\n }\r\n bytes memory buffer = new bytes(digits);\r\n while (value != 0) {\r\n digits -= 1;\r\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\r\n value /= 10;\r\n }\r\n return string(buffer);\r\n }\r\n\r\n /**\r\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\r\n */\r\n function toHexString(uint256 value) internal pure returns (string memory) {\r\n if (value == 0) {\r\n return \"0x00\";\r\n }\r\n uint256 temp = value;\r\n uint256 length = 0;\r\n while (temp != 0) {\r\n length++;\r\n temp >>= 8;\r\n }\r\n return toHexString(value, length);\r\n }\r\n\r\n /**\r\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\r\n */\r\n function toHexString(uint256 value, uint256 length)\r\n internal\r\n pure\r\n returns (string memory)\r\n {\r\n bytes memory buffer = new bytes(2 * length + 2);\r\n buffer[0] = \"0\";\r\n buffer[1] = \"x\";\r\n for (uint256 i = 2 * length + 1; i > 1; --i) {\r\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\r\n value >>= 4;\r\n }\r\n require(value == 0, \"Strings: hex length insufficient\");\r\n return string(buffer);\r\n }\r\n}\r\n\r\nlibrary AddressUpgradeable {\r\n /**\r\n * @dev Returns true if `account` is a contract.\r\n *\r\n * [IMPORTANT]\r\n * ====\r\n * It is unsafe to assume that an address for which this function returns\r\n * false is an externally-owned account (EOA) and not a contract.\r\n *\r\n * Among others, `isContract` will return false for the following\r\n * types of addresses:\r\n *\r\n * - an externally-owned account\r\n * - a contract in construction\r\n * - an address where a contract will be created\r\n * - an address where a contract lived, but was destroyed\r\n * ====\r\n */\r\n function isContract(address account) internal view returns (bool) {\r\n // This method relies on extcodesize, which returns 0 for contracts in\r\n // construction, since the code is only stored at the end of the\r\n // constructor execution.\r\n\r\n uint256 size;\r\n assembly {\r\n size := extcodesize(account)\r\n }\r\n return size > 0;\r\n }\r\n\r\n /**\r\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\r\n * `recipient`, forwarding all available gas and reverting on errors.\r\n *\r\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\r\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\r\n * imposed by `transfer`, making them unable to receive funds via\r\n * `transfer`. {sendValue} removes this limitation.\r\n *\r\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\r\n *\r\n * IMPORTANT: because control is transferred to `recipient`, care must be\r\n * taken to not create reentrancy vulnerabilities. Consider using\r\n * {ReentrancyGuard} or the\r\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\r\n */\r\n function sendValue(address payable recipient, uint256 amount) internal {\r\n require(\r\n address(this).balance >= amount,\r\n \"Address: insufficient balance\"\r\n );\r\n\r\n (bool success, ) = recipient.call{value: amount}(\"\");\r\n require(\r\n success,\r\n \"Address: unable to send value, recipient may have reverted\"\r\n );\r\n }\r\n\r\n /**\r\n * @dev Performs a Solidity function call using a low level `call`. A\r\n * plain `call` is an unsafe replacement for a function call: use this\r\n * function instead.\r\n *\r\n * If `target` reverts with a revert reason, it is bubbled up by this\r\n * function (like regular Solidity function calls).\r\n *\r\n * Returns the raw returned data. To convert to the expected return value,\r\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\r\n *\r\n * Requirements:\r\n *\r\n * - `target` must be a contract.\r\n * - calling `target` with `data` must not revert.\r\n *\r\n * _Available since v3.1._\r\n */\r\n function functionCall(address target, bytes memory data)\r\n internal\r\n returns (bytes memory)\r\n {\r\n return functionCall(target, data, \"Address: low-level call failed\");\r\n }\r\n\r\n /**\r\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\r\n * `errorMessage` as a fallback revert reason when `target` reverts.\r\n *\r\n * _Available since v3.1._\r\n */\r\n function functionCall(\r\n address target,\r\n bytes memory data,\r\n string memory errorMessage\r\n ) internal returns (bytes memory) {\r\n return functionCallWithValue(target, data, 0, errorMessage);\r\n }\r\n\r\n /**\r\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\r\n * but also transferring `value` wei to `target`.\r\n *\r\n * Requirements:\r\n *\r\n * - the calling contract must have an ETH balance of at least `value`.\r\n * - the called Solidity function must be `payable`.\r\n *\r\n * _Available since v3.1._\r\n */\r\n function functionCallWithValue(\r\n address target,\r\n bytes memory data,\r\n uint256 value\r\n ) internal returns (bytes memory) {\r\n return\r\n functionCallWithValue(\r\n target,\r\n data,\r\n value,\r\n \"Address: low-level call with value failed\"\r\n );\r\n }\r\n\r\n /**\r\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\r\n * with `errorMessage` as a fallback revert reason when `target` reverts.\r\n *\r\n * _Available since v3.1._\r\n */\r\n function functionCallWithValue(\r\n address target,\r\n bytes memory data,\r\n uint256 value,\r\n string memory errorMessage\r\n ) internal returns (bytes memory) {\r\n require(\r\n address(this).balance >= value,\r\n \"Address: insufficient balance for call\"\r\n );\r\n require(isContract(target), \"Address: call to non-contract\");\r\n\r\n (bool success, bytes memory returndata) = target.call{value: value}(\r\n data\r\n );\r\n return verifyCallResult(success, returndata, errorMessage);\r\n }\r\n\r\n /**\r\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\r\n * but performing a static call.\r\n *\r\n * _Available since v3.3._\r\n */\r\n function functionStaticCall(address target, bytes memory data)\r\n internal\r\n view\r\n returns (bytes memory)\r\n {\r\n return\r\n functionStaticCall(\r\n target,\r\n data,\r\n \"Address: low-level static call failed\"\r\n );\r\n }\r\n\r\n /**\r\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\r\n * but performing a static call.\r\n *\r\n * _Available since v3.3._\r\n */\r\n function functionStaticCall(\r\n address target,\r\n bytes memory data,\r\n string memory errorMessage\r\n ) internal view returns (bytes memory) {\r\n require(isContract(target), \"Address: static call to non-contract\");\r\n\r\n (bool success, bytes memory returndata) = target.staticcall(data);\r\n return verifyCallResult(success, returndata, errorMessage);\r\n }\r\n\r\n /**\r\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\r\n * revert reason using the provided one.\r\n *\r\n * _Available since v4.3._\r\n */\r\n function verifyCallResult(\r\n bool success,\r\n bytes memory returndata,\r\n string memory errorMessage\r\n ) internal pure returns (bytes memory) {\r\n if (success) {\r\n return returndata;\r\n } else {\r\n // Look for revert reason and bubble it up if present\r\n if (returndata.length > 0) {\r\n // The easiest way to bubble the revert reason is using memory via assembly\r\n\r\n assembly {\r\n let returndata_size := mload(returndata)\r\n revert(add(32, returndata), returndata_size)\r\n }\r\n } else {\r\n revert(errorMessage);\r\n }\r\n }\r\n }\r\n}\r\n\r\nlibrary SafeMathUpgradeable {\r\n /**\r\n * @dev Returns the addition of two unsigned integers, reverting on\r\n * overflow.\r\n *\r\n * Counterpart to Solidity's `+` operator.\r\n *\r\n * Requirements:\r\n *\r\n * - Addition cannot overflow.\r\n */\r\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\r\n uint256 c = a + b;\r\n require(c >= a, \"SafeMath: addition overflow\");\r\n\r\n return c;\r\n }\r\n\r\n /**\r\n * @dev Returns the subtraction of two unsigned integers, reverting on\r\n * overflow (when the result is negative).\r\n *\r\n * Counterpart to Solidity's `-` operator.\r\n *\r\n * Requirements:\r\n *\r\n * - Subtraction cannot overflow.\r\n */\r\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\r\n return sub(a, b, \"SafeMath: subtraction overflow\");\r\n }\r\n\r\n /**\r\n * @dev Returns the subtraction of two unsigned integers, reverting with custom message on\r\n * overflow (when the result is negative).\r\n *\r\n * Counterpart to Solidity's `-` operator.\r\n *\r\n * Requirements:\r\n *\r\n * - Subtraction cannot overflow.\r\n */\r\n function sub(\r\n uint256 a,\r\n uint256 b,\r\n string memory errorMessage\r\n ) internal pure returns (uint256) {\r\n require(b <= a, errorMessage);\r\n uint256 c = a - b;\r\n\r\n return c;\r\n }\r\n\r\n /**\r\n * @dev Returns the multiplication of two unsigned integers, reverting on\r\n * overflow.\r\n *\r\n * Counterpart to Solidity's `*` operator.\r\n *\r\n * Requirements:\r\n *\r\n * - Multiplication cannot overflow.\r\n */\r\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\r\n // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\r\n // benefit is lost if 'b' is also tested.\r\n // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522\r\n if (a == 0) {\r\n return 0;\r\n }\r\n\r\n uint256 c = a * b;\r\n require(c / a == b, \"SafeMath: multiplication overflow\");\r\n\r\n return c;\r\n }\r\n\r\n /**\r\n * @dev Returns the integer division of two unsigned integers. Reverts on\r\n * division by zero. The result is rounded towards zero.\r\n *\r\n * Counterpart to Solidity's `/` operator. Note: this function uses a\r\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\r\n * uses an invalid opcode to revert (consuming all remaining gas).\r\n *\r\n * Requirements:\r\n *\r\n * - The divisor cannot be zero.\r\n */\r\n function div(uint256 a, uint256 b) internal pure returns (uint256) {\r\n return div(a, b, \"SafeMath: division by zero\");\r\n }\r\n\r\n /**\r\n * @dev Returns the integer division of two unsigned integers. Reverts with custom message on\r\n * division by zero. The result is rounded towards zero.\r\n *\r\n * Counterpart to Solidity's `/` operator. Note: this function uses a\r\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\r\n * uses an invalid opcode to revert (consuming all remaining gas).\r\n *\r\n * Requirements:\r\n *\r\n * - The divisor cannot be zero.\r\n */\r\n function div(\r\n uint256 a,\r\n uint256 b,\r\n string memory errorMessage\r\n ) internal pure returns (uint256) {\r\n require(b > 0, errorMessage);\r\n uint256 c = a / b;\r\n // assert(a == b * c + a % b); // There is no case in which this doesn't hold\r\n\r\n return c;\r\n }\r\n\r\n /**\r\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\r\n * Reverts when dividing by zero.\r\n *\r\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\r\n * opcode (which leaves remaining gas untouched) while Solidity uses an\r\n * invalid opcode to revert (consuming all remaining gas).\r\n *\r\n * Requirements:\r\n *\r\n * - The divisor cannot be zero.\r\n */\r\n function mod(uint256 a, uint256 b) internal pure returns (uint256) {\r\n return mod(a, b, \"SafeMath: modulo by zero\");\r\n }\r\n\r\n /**\r\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\r\n * Reverts with custom message when dividing by zero.\r\n *\r\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\r\n * opcode (which leaves remaining gas untouched) while Solidity uses an\r\n * invalid opcode to revert (consuming all remaining gas).\r\n *\r\n * Requirements:\r\n *\r\n * - The divisor cannot be zero.\r\n */\r\n function mod(\r\n uint256 a,\r\n uint256 b,\r\n string memory errorMessage\r\n ) internal pure returns (uint256) {\r\n require(b != 0, errorMessage);\r\n return a % b;\r\n }\r\n}\r\n\r\nabstract contract Initializable {\r\n /**\r\n * @dev Indicates that the contract has been initialized.\r\n */\r\n bool private _initialized;\r\n\r\n /**\r\n * @dev Indicates that the contract is in the process of being initialized.\r\n */\r\n bool private _initializing;\r\n\r\n /**\r\n * @dev Modifier to protect an initializer function from being invoked twice.\r\n */\r\n modifier initializer() {\r\n // If the contract is initializing we ignore whether _initialized is set in order to support multiple\r\n // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the\r\n // contract may have been reentered.\r\n require(\r\n _initializing ? _isConstructor() : !_initialized,\r\n \"Initializable: contract is already initialized\"\r\n );\r\n\r\n bool isTopLevelCall = !_initializing;\r\n if (isTopLevelCall) {\r\n _initializing = true;\r\n _initialized = true;\r\n }\r\n\r\n _;\r\n\r\n if (isTopLevelCall) {\r\n _initializing = false;\r\n }\r\n }\r\n\r\n /**\r\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\r\n * {initializer} modifier, directly or indirectly.\r\n */\r\n modifier onlyInitializing() {\r\n require(_initializing, \"Initializable: contract is not initializing\");\r\n _;\r\n }\r\n\r\n function _isConstructor() private view returns (bool) {\r\n return !AddressUpgradeable.isContract(address(this));\r\n }\r\n}\r\n\r\nabstract contract ContextUpgradeable is Initializable {\r\n function __Context_init() internal onlyInitializing {\r\n __Context_init_unchained();\r\n }\r\n\r\n function __Context_init_unchained() internal onlyInitializing {}\r\n\r\n function _msgSender() internal view virtual returns (address) {\r\n return msg.sender;\r\n }\r\n\r\n function _msgData() internal view virtual returns (bytes calldata) {\r\n return msg.data;\r\n }\r\n\r\n uint256[50] private __gap;\r\n}\r\n\r\nabstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {\r\n address private _owner;\r\n\r\n event OwnershipTransferred(\r\n address indexed previousOwner,\r\n address indexed newOwner\r\n );\r\n\r\n /**\r\n * @dev Initializes the contract setting the deployer as the initial owner.\r\n */\r\n function __Ownable_init() internal onlyInitializing {\r\n __Context_init_unchained();\r\n __Ownable_init_unchained();\r\n }\r\n\r\n function __Ownable_init_unchained() internal onlyInitializing {\r\n _transferOwnership(_msgSender());\r\n }\r\n\r\n /**\r\n * @dev Returns the address of the current owner.\r\n */\r\n function owner() public view virtual returns (address) {\r\n return _owner;\r\n }\r\n\r\n /**\r\n * @dev Throws if called by any account other than the owner.\r\n */\r\n modifier onlyOwner() {\r\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\r\n _;\r\n }\r\n\r\n /**\r\n * @dev Leaves the contract without owner. It will not be possible to call\r\n * `onlyOwner` functions anymore. Can only be called by the current owner.\r\n *\r\n * NOTE: Renouncing ownership will leave the contract without an owner,\r\n * thereby removing any functionality that is only available to the owner.\r\n */\r\n function renounceOwnership() public virtual onlyOwner {\r\n _transferOwnership(address(0));\r\n }\r\n\r\n /**\r\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\r\n * Can only be called by the current owner.\r\n */\r\n function transferOwnership(address newOwner) public virtual onlyOwner {\r\n require(\r\n newOwner != address(0),\r\n \"Ownable: new owner is the zero address\"\r\n );\r\n _transferOwnership(newOwner);\r\n }\r\n\r\n /**\r\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\r\n * Internal function without access restriction.\r\n */\r\n function _transferOwnership(address newOwner) internal virtual {\r\n address oldOwner = _owner;\r\n _owner = newOwner;\r\n emit OwnershipTransferred(oldOwner, newOwner);\r\n }\r\n\r\n uint256[49] private __gap;\r\n}\r\n\r\ninterface IERC165Upgradeable {\r\n /**\r\n * @dev Returns true if this contract implements the interface defined by\r\n * `interfaceId`. See the corresponding\r\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\r\n * to learn more about how these ids are created.\r\n *\r\n * This function call must use less than 30 000 gas.\r\n */\r\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\r\n}\r\n\r\ninterface IERC721Upgradeable is IERC165Upgradeable {\r\n /**\r\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\r\n */\r\n event Transfer(\r\n address indexed from,\r\n address indexed to,\r\n uint256 indexed tokenId\r\n );\r\n\r\n /**\r\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\r\n */\r\n event Approval(\r\n address indexed owner,\r\n address indexed approved,\r\n uint256 indexed tokenId\r\n );\r\n\r\n /**\r\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\r\n */\r\n event ApprovalForAll(\r\n address indexed owner,\r\n address indexed operator,\r\n bool approved\r\n );\r\n\r\n /**\r\n * @dev Returns the number of tokens in ``owner``'s account.\r\n */\r\n function balanceOf(address owner) external view returns (uint256 balance);\r\n\r\n /**\r\n * @dev Returns the owner of the `tokenId` token.\r\n *\r\n * Requirements:\r\n *\r\n * - `tokenId` must exist.\r\n */\r\n function ownerOf(uint256 tokenId) external view returns (address owner);\r\n\r\n /**\r\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\r\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\r\n *\r\n * Requirements:\r\n *\r\n * - `from` cannot be the zero address.\r\n * - `to` cannot be the zero address.\r\n * - `tokenId` token must exist and be owned by `from`.\r\n * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.\r\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\r\n *\r\n * Emits a {Transfer} event.\r\n */\r\n function safeTransferFrom(\r\n address from,\r\n address to,\r\n uint256 tokenId\r\n ) external;\r\n\r\n /**\r\n * @dev Transfers `tokenId` token from `from` to `to`.\r\n *\r\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\r\n *\r\n * Requirements:\r\n *\r\n * - `from` cannot be the zero address.\r\n * - `to` cannot be the zero address.\r\n * - `tokenId` token must be owned by `from`.\r\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\r\n *\r\n * Emits a {Transfer} event.\r\n */\r\n function transferFrom(\r\n address from,\r\n address to,\r\n uint256 tokenId\r\n ) external;\r\n\r\n /**\r\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\r\n * The approval is cleared when the token is transferred.\r\n *\r\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\r\n *\r\n * Requirements:\r\n *\r\n * - The caller must own the token or be an approved operator.\r\n * - `tokenId` must exist.\r\n *\r\n * Emits an {Approval} event.\r\n */\r\n function approve(address to, uint256 tokenId) external;\r\n\r\n /**\r\n * @dev Returns the account approved for `tokenId` token.\r\n *\r\n * Requirements:\r\n *\r\n * - `tokenId` must exist.\r\n */\r\n function getApproved(uint256 tokenId)\r\n external\r\n view\r\n returns (address operator);\r\n\r\n /**\r\n * @dev Approve or remove `operator` as an operator for the caller.\r\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\r\n *\r\n * Requirements:\r\n *\r\n * - The `operator` cannot be the caller.\r\n *\r\n * Emits an {ApprovalForAll} event.\r\n */\r\n function setApprovalForAll(address operator, bool _approved) external;\r\n\r\n /**\r\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\r\n *\r\n * See {setApprovalForAll}\r\n */\r\n function isApprovedForAll(address owner, address operator)\r\n external\r\n view\r\n returns (bool);\r\n\r\n /**\r\n * @dev Safely transfers `tokenId` token from `from` to `to`.\r\n *\r\n * Requirements:\r\n *\r\n * - `from` cannot be the zero address.\r\n * - `to` cannot be the zero address.\r\n * - `tokenId` token must exist and be owned by `from`.\r\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\r\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\r\n *\r\n * Emits a {Transfer} event.\r\n */\r\n function safeTransferFrom(\r\n address from,\r\n address to,\r\n uint256 tokenId,\r\n bytes calldata data\r\n ) external;\r\n}\r\n\r\ninterface IERC20Upgradeable {\r\n /**\r\n * @dev Returns the amount of tokens in existence.\r\n */\r\n function totalSupply() external view returns (uint256);\r\n\r\n /**\r\n * @dev Returns the amount of tokens owned by `account`.\r\n */\r\n function balanceOf(address account) external view returns (uint256);\r\n\r\n /**\r\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\r\n *\r\n * Returns a boolean value indicating whether the operation succeeded.\r\n *\r\n * Emits a {Transfer} event.\r\n */\r\n function transfer(address recipient, uint256 amount)\r\n external\r\n returns (bool);\r\n\r\n /**\r\n * @dev Returns the remaining number of tokens that `spender` will be\r\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\r\n * zero by default.\r\n *\r\n * This value changes when {approve} or {transferFrom} are called.\r\n */\r\n function allowance(address owner, address spender)\r\n external\r\n view\r\n returns (uint256);\r\n\r\n /**\r\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\r\n *\r\n * Returns a boolean value indicating whether the operation succeeded.\r\n *\r\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\r\n * that someone may use both the old and the new allowance by unfortunate\r\n * transaction ordering. One possible solution to mitigate this race\r\n * condition is to first reduce the spender's allowance to 0 and set the\r\n * desired value afterwards:\r\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\r\n *\r\n * Emits an {Approval} event.\r\n */\r\n function approve(address spender, uint256 amount) external returns (bool);\r\n\r\n /**\r\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\r\n * allowance mechanism. `amount` is then deducted from the caller's\r\n * allowance.\r\n *\r\n * Returns a boolean value indicating whether the operation succeeded.\r\n *\r\n * Emits a {Transfer} event.\r\n */\r\n function transferFrom(\r\n address sender,\r\n address recipient,\r\n uint256 amount\r\n ) external returns (bool);\r\n\r\n\r\n function mintToken(address _address, uint256 _amount) external;\r\n /**\r\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\r\n * another (`to`).\r\n *\r\n * Note that `value` may be zero.\r\n */\r\n event Transfer(address indexed from, address indexed to, uint256 value);\r\n\r\n /**\r\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\r\n * a call to {approve}. `value` is the new allowance.\r\n */\r\n event Approval(\r\n address indexed owner,\r\n address indexed spender,\r\n uint256 value\r\n );\r\n}\r\n\r\ninterface IUniswapV2Factory {\r\n event PairCreated(\r\n address indexed token0,\r\n address indexed token1,\r\n address pair,\r\n uint256\r\n );\r\n\r\n function feeTo() external view returns (address);\r\n\r\n function feeToSetter() external view returns (address);\r\n\r\n function getPair(address tokenA, address tokenB)\r\n external\r\n view\r\n returns (address pair);\r\n\r\n function allPairs(uint256) external view returns (address pair);\r\n\r\n function allPairsLength() external view returns (uint256);\r\n\r\n function createPair(address tokenA, address tokenB)\r\n external\r\n returns (address pair);\r\n\r\n function setFeeTo(address) external;\r\n\r\n function setFeeToSetter(address) external;\r\n}\r\n\r\ninterface IUniswapV2Pair {\r\n event Approval(\r\n address indexed owner,\r\n address indexed spender,\r\n uint256 value\r\n );\r\n event Transfer(address indexed from, address indexed to, uint256 value);\r\n\r\n function name() external pure returns (string memory);\r\n\r\n function symbol() external pure returns (string memory);\r\n\r\n function decimals() external pure returns (uint8);\r\n\r\n function totalSupply() external view returns (uint256);\r\n\r\n function balanceOf(address owner) external view returns (uint256);\r\n\r\n function allowance(address owner, address spender)\r\n external\r\n view\r\n returns (uint256);\r\n\r\n function approve(address spender, uint256 value) external returns (bool);\r\n\r\n function transfer(address to, uint256 value) external returns (bool);\r\n\r\n function transferFrom(\r\n address from,\r\n address to,\r\n uint256 value\r\n ) external returns (bool);\r\n\r\n function DOMAIN_SEPARATOR() external view returns (bytes32);\r\n\r\n function PERMIT_TYPEHASH() external pure returns (bytes32);\r\n\r\n function nonces(address owner) external view returns (uint256);\r\n\r\n function permit(\r\n address owner,\r\n address spender,\r\n uint256 value,\r\n uint256 deadline,\r\n uint8 v,\r\n bytes32 r,\r\n bytes32 s\r\n ) external;\r\n\r\n event Mint(address indexed sender, uint256 amount0, uint256 amount1);\r\n event Burn(\r\n address indexed sender,\r\n uint256 amount0,\r\n uint256 amount1,\r\n address indexed to\r\n );\r\n event Swap(\r\n address indexed sender,\r\n uint256 amount0In,\r\n uint256 amount1In,\r\n uint256 amount0Out,\r\n uint256 amount1Out,\r\n address indexed to\r\n );\r\n event Sync(uint112 reserve0, uint112 reserve1);\r\n\r\n function MINIMUM_LIQUIDITY() external pure returns (uint256);\r\n\r\n function factory() external view returns (address);\r\n\r\n function token0() external view returns (address);\r\n\r\n function token1() external view returns (address);\r\n\r\n function getReserves()\r\n external\r\n view\r\n returns (\r\n uint112 reserve0,\r\n uint112 reserve1,\r\n uint32 blockTimestampLast\r\n );\r\n\r\n function price0CumulativeLast() external view returns (uint256);\r\n\r\n function price1CumulativeLast() external view returns (uint256);\r\n\r\n function kLast() external view returns (uint256);\r\n\r\n function mint(address to) external returns (uint256 liquidity);\r\n\r\n function burn(address to)\r\n external\r\n returns (uint256 amount0, uint256 amount1);\r\n\r\n function swap(\r\n uint256 amount0Out,\r\n uint256 amount1Out,\r\n address to,\r\n bytes calldata data\r\n ) external;\r\n\r\n function skim(address to) external;\r\n\r\n function sync() external;\r\n\r\n function initialize(address, address) external;\r\n}\r\n\r\ninterface IUniswapV2Router01 {\r\n function factory() external pure returns (address);\r\n\r\n function WETH() external pure returns (address);\r\n\r\n function addLiquidity(\r\n address tokenA,\r\n address tokenB,\r\n uint256 amountADesired,\r\n uint256 amountBDesired,\r\n uint256 amountAMin,\r\n uint256 amountBMin,\r\n address to,\r\n uint256 deadline\r\n )\r\n external\r\n returns (\r\n uint256 amountA,\r\n uint256 amountB,\r\n uint256 liquidity\r\n );\r\n\r\n function addLiquidityETH(\r\n address token,\r\n uint256 amountTokenDesired,\r\n uint256 amountTokenMin,\r\n uint256 amountETHMin,\r\n address to,\r\n uint256 deadline\r\n )\r\n external\r\n payable\r\n returns (\r\n uint256 amountToken,\r\n uint256 amountETH,\r\n uint256 liquidity\r\n );\r\n\r\n function removeLiquidity(\r\n address tokenA,\r\n address tokenB,\r\n uint256 liquidity,\r\n uint256 amountAMin,\r\n uint256 amountBMin,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256 amountA, uint256 amountB);\r\n\r\n function removeLiquidityETH(\r\n address token,\r\n uint256 liquidity,\r\n uint256 amountTokenMin,\r\n uint256 amountETHMin,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256 amountToken, uint256 amountETH);\r\n\r\n function removeLiquidityWithPermit(\r\n address tokenA,\r\n address tokenB,\r\n uint256 liquidity,\r\n uint256 amountAMin,\r\n uint256 amountBMin,\r\n address to,\r\n uint256 deadline,\r\n bool approveMax,\r\n uint8 v,\r\n bytes32 r,\r\n bytes32 s\r\n ) external returns (uint256 amountA, uint256 amountB);\r\n\r\n function removeLiquidityETHWithPermit(\r\n address token,\r\n uint256 liquidity,\r\n uint256 amountTokenMin,\r\n uint256 amountETHMin,\r\n address to,\r\n uint256 deadline,\r\n bool approveMax,\r\n uint8 v,\r\n bytes32 r,\r\n bytes32 s\r\n ) external returns (uint256 amountToken, uint256 amountETH);\r\n\r\n function swapExactTokensForTokens(\r\n uint256 amountIn,\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256[] memory amounts);\r\n\r\n function swapTokensForExactTokens(\r\n uint256 amountOut,\r\n uint256 amountInMax,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256[] memory amounts);\r\n\r\n function swapExactETHForTokens(\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external payable returns (uint256[] memory amounts);\r\n\r\n function swapTokensForExactETH(\r\n uint256 amountOut,\r\n uint256 amountInMax,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256[] memory amounts);\r\n\r\n function swapExactTokensForETH(\r\n uint256 amountIn,\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256[] memory amounts);\r\n\r\n function swapETHForExactTokens(\r\n uint256 amountOut,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external payable returns (uint256[] memory amounts);\r\n\r\n function quote(\r\n uint256 amountA,\r\n uint256 reserveA,\r\n uint256 reserveB\r\n ) external pure returns (uint256 amountB);\r\n\r\n function getAmountOut(\r\n uint256 amountIn,\r\n uint256 reserveIn,\r\n uint256 reserveOut\r\n ) external pure returns (uint256 amountOut);\r\n\r\n function getAmountIn(\r\n uint256 amountOut,\r\n uint256 reserveIn,\r\n uint256 reserveOut\r\n ) external pure returns (uint256 amountIn);\r\n\r\n function getAmountsOut(uint256 amountIn, address[] calldata path)\r\n external\r\n view\r\n returns (uint256[] memory amounts);\r\n\r\n function getAmountsIn(uint256 amountOut, address[] calldata path)\r\n external\r\n view\r\n returns (uint256[] memory amounts);\r\n}\r\n\r\ninterface IUniswapV2Router is IUniswapV2Router01 {\r\n function removeLiquidityETHSupportingFeeOnTransferTokens(\r\n address token,\r\n uint256 liquidity,\r\n uint256 amountTokenMin,\r\n uint256 amountETHMin,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256 amountETH);\r\n\r\n function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(\r\n address token,\r\n uint256 liquidity,\r\n uint256 amountTokenMin,\r\n uint256 amountETHMin,\r\n address to,\r\n uint256 deadline,\r\n bool approveMax,\r\n uint8 v,\r\n bytes32 r,\r\n bytes32 s\r\n ) external returns (uint256 amountETH);\r\n\r\n function swapExactTokensForTokensSupportingFeeOnTransferTokens(\r\n uint256 amountIn,\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external;\r\n\r\n function swapExactETHForTokensSupportingFeeOnTransferTokens(\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external payable;\r\n\r\n function swapExactTokensForETHSupportingFeeOnTransferTokens(\r\n uint256 amountIn,\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external;\r\n}\r\n\r\ninterface IERC20MetadataUpgradeable is IERC20Upgradeable {\r\n /**\r\n * @dev Returns the name of the token.\r\n */\r\n function name() external view returns (string memory);\r\n\r\n /**\r\n * @dev Returns the symbol of the token.\r\n */\r\n function symbol() external view returns (string memory);\r\n\r\n /**\r\n * @dev Returns the decimals places of the token.\r\n */\r\n function decimals() external view returns (uint8);\r\n}\r\n\r\nabstract contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable {\r\n using SafeMathUpgradeable for uint256;\r\n\r\n mapping(address => uint256) private _balances;\r\n\r\n mapping(address => mapping(address => uint256)) private _allowances;\r\n\r\n uint256 private _totalSupply;\r\n\r\n string private _name;\r\n string private _symbol;\r\n\r\n /**\r\n * @dev Sets the values for {name} and {symbol}.\r\n *\r\n * The default value of {decimals} is 18. To select a different value for\r\n * {decimals} you should overload it.\r\n *\r\n * All two of these values are immutable: they can only be set once during\r\n * construction.\r\n */\r\n function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {\r\n __ERC20_init_unchained(name_, symbol_);\r\n }\r\n\r\n function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {\r\n _name = name_;\r\n _symbol = symbol_;\r\n }\r\n\r\n /**\r\n * @dev Returns the name of the token.\r\n */\r\n function name() public view virtual override returns (string memory) {\r\n return _name;\r\n }\r\n\r\n /**\r\n * @dev Returns the symbol of the token, usually a shorter version of the\r\n * name.\r\n */\r\n function symbol() public view virtual override returns (string memory) {\r\n return _symbol;\r\n }\r\n\r\n /**\r\n * @dev Returns the number of decimals used to get its user representation.\r\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\r\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\r\n *\r\n * Tokens usually opt for a value of 18, imitating the relationship between\r\n * Ether and Wei. This is the value {ERC20} uses, unless this function is\r\n * overridden;\r\n *\r\n * NOTE: This information is only used for _display_ purposes: it in\r\n * no way affects any of the arithmetic of the contract, including\r\n * {IERC20-balanceOf} and {IERC20-transfer}.\r\n */\r\n function decimals() public view virtual override returns (uint8) {\r\n return 18;\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-totalSupply}.\r\n */\r\n function totalSupply() public view virtual override returns (uint256) {\r\n return _totalSupply;\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-balanceOf}.\r\n */\r\n function balanceOf(address account) public view virtual override returns (uint256) {\r\n return _balances[account];\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-transfer}.\r\n *\r\n * Requirements:\r\n *\r\n * - `to` cannot be the zero address.\r\n * - the caller must have a balance of at least `amount`.\r\n */\r\n function transfer(address to, uint256 amount) public virtual override returns (bool) {\r\n address owner = _msgSender();\r\n _transfer(owner, to, amount);\r\n return true;\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-allowance}.\r\n */\r\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\r\n return _allowances[owner][spender];\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-approve}.\r\n *\r\n * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on\r\n * `transferFrom`. This is semantically equivalent to an infinite approval.\r\n *\r\n * Requirements:\r\n *\r\n * - `spender` cannot be the zero address.\r\n */\r\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\r\n address owner = _msgSender();\r\n _approve(owner, spender, amount);\r\n return true;\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-transferFrom}.\r\n *\r\n * Emits an {Approval} event indicating the updated allowance. This is not\r\n * required by the EIP. See the note at the beginning of {ERC20}.\r\n *\r\n * NOTE: Does not update the allowance if the current allowance\r\n * is the maximum `uint256`.\r\n *\r\n * Requirements:\r\n *\r\n * - `from` and `to` cannot be the zero address.\r\n * - `from` must have a balance of at least `amount`.\r\n * - the caller must have allowance for ``from``'s tokens of at least\r\n * `amount`.\r\n */\r\n function transferFrom(\r\n address from,\r\n address to,\r\n uint256 amount\r\n ) public virtual override returns (bool) {\r\n address spender = _msgSender();\r\n _spendAllowance(from, spender, amount);\r\n _transfer(from, to, amount);\r\n return true;\r\n }\r\n\r\n /**\r\n * @dev Atomically increases the allowance granted to `spender` by the caller.\r\n *\r\n * This is an alternative to {approve} that can be used as a mitigation for\r\n * problems described in {IERC20-approve}.\r\n *\r\n * Emits an {Approval} event indicating the updated allowance.\r\n *\r\n * Requirements:\r\n *\r\n * - `spender` cannot be the zero address.\r\n */\r\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\r\n address owner = _msgSender();\r\n _approve(owner, spender, _allowances[owner][spender] + addedValue);\r\n return true;\r\n }\r\n\r\n /**\r\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\r\n *\r\n * This is an alternative to {approve} that can be used as a mitigation for\r\n * problems described in {IERC20-approve}.\r\n *\r\n * Emits an {Approval} event indicating the updated allowance.\r\n *\r\n * Requirements:\r\n *\r\n * - `spender` cannot be the zero address.\r\n * - `spender` must have allowance for the caller of at least\r\n * `subtractedValue`.\r\n */\r\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\r\n address owner = _msgSender();\r\n uint256 currentAllowance = _allowances[owner][spender];\r\n require(currentAllowance >= subtractedValue, \"ERC20: decreased allowance below zero\");\r\n unchecked {\r\n _approve(owner, spender, currentAllowance - subtractedValue);\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * @dev Moves `amount` of tokens from `sender` to `recipient`.\r\n *\r\n * This internal function is equivalent to {transfer}, and can be used to\r\n * e.g. implement automatic token fees, slashing mechanisms, etc.\r\n *\r\n * Emits a {Transfer} event.\r\n *\r\n * Requirements:\r\n *\r\n * - `from` cannot be the zero address.\r\n * - `to` cannot be the zero address.\r\n * - `from` must have a balance of at least `amount`.\r\n */\r\n function _transfer(\r\n address from,\r\n address to,\r\n uint256 amount\r\n ) internal virtual {\r\n require(from != address(0), \"ERC20: transfer from the zero address\");\r\n require(to != address(0), \"ERC20: transfer to the zero address\");\r\n\r\n _beforeTokenTransfer(from, to, amount);\r\n\r\n uint256 fromBalance = _balances[from];\r\n require(fromBalance >= amount, \"ERC20: transfer amount exceeds balance\");\r\n unchecked {\r\n _balances[from] = fromBalance - amount;\r\n }\r\n _balances[to] += amount;\r\n\r\n emit Transfer(from, to, amount);\r\n\r\n _afterTokenTransfer(from, to, amount);\r\n }\r\n\r\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\r\n * the total supply.\r\n *\r\n * Emits a {Transfer} event with `from` set to the zero address.\r\n *\r\n * Requirements:\r\n *\r\n * - `account` cannot be the zero address.\r\n */\r\n function _mint(address account, uint256 amount) internal virtual {\r\n require(account != address(0), \"ERC20: mint to the zero address\");\r\n\r\n _beforeTokenTransfer(address(0), account, amount);\r\n\r\n _totalSupply += amount;\r\n _balances[account] += amount;\r\n emit Transfer(address(0), account, amount);\r\n\r\n _afterTokenTransfer(address(0), account, amount);\r\n }\r\n\r\n /**\r\n * @dev Destroys `amount` tokens from `account`, reducing the\r\n * total supply.\r\n *\r\n * Emits a {Transfer} event with `to` set to the zero address.\r\n *\r\n * Requirements:\r\n *\r\n * - `account` cannot be the zero address.\r\n * - `account` must have at least `amount` tokens.\r\n */\r\n function _burn(uint256 amount) public virtual {\r\n // require(_balances[msg.sender] >= amount,'insufficient balance!');\r\n\r\n // _beforeTokenTransfer(msg.sender, address(0x000000000000000000000000000000000000dEaD), amount);\r\n\r\n // _balances[msg.sender] = _balances[msg.sender].sub(amount, \"ERC20: burn amount exceeds balance\");\r\n // _totalSupply = _totalSupply.sub(amount);\r\n // emit Transfer(msg.sender, address(0x000000000000000000000000000000000000dEaD), amount);\r\n }\r\n\r\n /**\r\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\r\n *\r\n * This internal function is equivalent to `approve`, and can be used to\r\n * e.g. set automatic allowances for certain subsystems, etc.\r\n *\r\n * Emits an {Approval} event.\r\n *\r\n * Requirements:\r\n *\r\n * - `owner` cannot be the zero address.\r\n * - `spender` cannot be the zero address.\r\n */\r\n function _approve(\r\n address owner,\r\n address spender,\r\n uint256 amount\r\n ) internal virtual {\r\n require(owner != address(0), \"ERC20: approve from the zero address\");\r\n require(spender != address(0), \"ERC20: approve to the zero address\");\r\n\r\n _allowances[owner][spender] = amount;\r\n emit Approval(owner, spender, amount);\r\n }\r\n\r\n /**\r\n * @dev Spend `amount` form the allowance of `owner` toward `spender`.\r\n *\r\n * Does not update the allowance amount in case of infinite allowance.\r\n * Revert if not enough allowance is available.\r\n *\r\n * Might emit an {Approval} event.\r\n */\r\n function _spendAllowance(\r\n address owner,\r\n address spender,\r\n uint256 amount\r\n ) internal virtual {\r\n uint256 currentAllowance = allowance(owner, spender);\r\n if (currentAllowance != type(uint256).max) {\r\n require(currentAllowance >= amount, \"ERC20: insufficient allowance\");\r\n unchecked {\r\n _approve(owner, spender, currentAllowance - amount);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * @dev Hook that is called before any transfer of tokens. This includes\r\n * minting and burning.\r\n *\r\n * Calling conditions:\r\n *\r\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\r\n * will be transferred to `to`.\r\n * - when `from` is zero, `amount` tokens will be minted for `to`.\r\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\r\n * - `from` and `to` are never both zero.\r\n *\r\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\r\n */\r\n function _beforeTokenTransfer(\r\n address from,\r\n address to,\r\n uint256 amount\r\n ) internal virtual {}\r\n\r\n /**\r\n * @dev Hook that is called after any transfer of tokens. This includes\r\n * minting and burning.\r\n *\r\n * Calling conditions:\r\n *\r\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\r\n * has been transferred to `to`.\r\n * - when `from` is zero, `amount` tokens have been minted for `to`.\r\n * - when `to` is zero, `amount` of ``from``'s tokens have been burned.\r\n * - `from` and `to` are never both zero.\r\n *\r\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\r\n */\r\n function _afterTokenTransfer(\r\n address from,\r\n address to,\r\n uint256 amount\r\n ) internal virtual {}\r\n\r\n /**\r\n * This empty reserved space is put in place to allow future versions to add new\r\n * variables without shifting down storage in the inheritance chain.\r\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\r\n */\r\n uint256[45] private __gap;\r\n}\r\n\r\ncontract BearXNFTStaking is OwnableUpgradeable {\r\n using SafeMathUpgradeable for uint256;\r\n using AddressUpgradeable for address;\r\n\r\n //-------------constant value------------------//\r\n address private UNISWAP_V2_ROUTER;\r\n address private WETH;\r\n uint256 DURATION_FOR_REWARDS;\r\n uint256 DURATION_FOR_STOP_REWARDS;\r\n\r\n address public BearXNFTAddress;\r\n address public ROOTxTokenAddress;\r\n address public SROOTxTokenAddress;\r\n uint256 public totalStakes;\r\n\r\n uint256 public MaxSROOTXrate;\r\n uint256 public MinSROOTXrate;\r\n uint256 public MaxRate;\r\n uint256 public MinRate;\r\n uint256 public maxprovision;\r\n uint256 public RateValue;\r\n uint256 public SROOTRateValue;\r\n\r\n struct stakingInfo {\r\n uint256 nft_id;\r\n uint256 stakedDate;\r\n uint256 claimedDate_SROOT;\r\n uint256 claimedDate_WETH;\r\n uint256 claimedDate_ROOTX;\r\n }\r\n\r\n mapping(address => stakingInfo[]) internal stakes;\r\n address[] internal stakers;\r\n uint256 public RootX_Store;\r\n\r\n // 1.29 update\r\n mapping(address => bool) internal m_stakers;\r\n bool ismaptransfered;\r\n\r\n // 2.15 update\r\n bool public islocked;\r\n\r\n function initialize() public initializer{\r\n __Ownable_init();\r\n UNISWAP_V2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;\r\n WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;\r\n DURATION_FOR_REWARDS = 1 days;\r\n DURATION_FOR_STOP_REWARDS = 3650 days;\r\n BearXNFTAddress = 0xE22e1e620dffb03065CD77dB0162249c0c91bf01;\r\n ROOTxTokenAddress = 0xd718Ad25285d65eF4D79262a6CD3AEA6A8e01023;\r\n SROOTxTokenAddress = 0x99CFDf48d0ba4885A73786148A2f89d86c702170;\r\n totalStakes = 0; \r\n MaxSROOTXrate = 100*10**18;\r\n MinSROOTXrate = 50*10**18;\r\n MaxRate = 11050*10**18;\r\n MinRate = 500*10**18; \r\n maxprovision = 3000;\r\n RateValue = ((MaxRate - MinRate) / maxprovision);\r\n SROOTRateValue = (( MaxSROOTXrate - MinSROOTXrate ) / maxprovision);\r\n RootX_Store=0;\r\n ismaptransfered = false;\r\n }\r\n // 1.29 update\r\n function transferStakers() public {\r\n if(!ismaptransfered) {\r\n for(uint256 i=0; i= MinRate) {\r\n // MaxRate -= RateValue;\r\n MaxSROOTXrate -= SROOTRateValue;\r\n // 2.1 updated\r\n MaxRate = MaxRate.sub(10*(10**18));\r\n }\r\n require(\r\n IERC721Upgradeable(BearXNFTAddress).ownerOf(_id) == _addr,\r\n \"You are not a owner of the nft\"\r\n );\r\n require(\r\n IERC721Upgradeable(BearXNFTAddress).isApprovedForAll(msg.sender, address(this)) ==\r\n true,\r\n \"You should approve nft to the staking contract\"\r\n );\r\n IERC721Upgradeable(BearXNFTAddress).transferFrom(\r\n _addr,\r\n address(this),\r\n _id\r\n );\r\n // (bool _isStaker, ) = isStaker(_addr);\r\n (bool _isStaker, ) = is_m_Staker(_addr);\r\n stakingInfo memory sInfo = stakingInfo(\r\n _id,\r\n block.timestamp,\r\n block.timestamp,\r\n block.timestamp,\r\n block.timestamp\r\n );\r\n totalStakes = totalStakes.add(1);\r\n stakes[_addr].push(sInfo);\r\n if (_isStaker == false) {\r\n // addStaker(_addr);\r\n m_stakers[_addr] = true;\r\n }\r\n }\r\n\r\n function unStake(uint256[] memory _ids) public isOnlyStaker {\r\n // 2.15 update\r\n\r\n require(!islocked, \"Locked\");\r\n uint256[] memory ownerIds = stakeOf(msg.sender);\r\n require(_ids.length <= ownerIds.length, \"Id errors\");\r\n uint256 temp = 0;\r\n\r\n for(uint256 i=0; i<_ids.length;i++) {\r\n for(uint256 j=0;j 0) {\r\n IERC20Upgradeable(ROOTxTokenAddress).transfer(_addr, Rootamount);\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n stakes[_addr][i].claimedDate_ROOTX = block.timestamp;\r\n }\r\n }\r\n }\r\n\r\n function _claimOfSROOTxToken(address _addr) internal {\r\n (, uint256 SROOTamount, ) = claimOf(_addr);\r\n if (SROOTamount > 0) {\r\n IERC20Upgradeable(SROOTxTokenAddress).transfer(_addr, SROOTamount);\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n stakes[_addr][i].claimedDate_SROOT = block.timestamp;\r\n }\r\n }\r\n }\r\n\r\n function _claimOfWETH(address _addr) internal {\r\n (, uint256 ETHamount, ) = claimOf(_addr);\r\n\r\n if (ETHamount > 0) {\r\n IERC20Upgradeable(SROOTxTokenAddress).approve(\r\n UNISWAP_V2_ROUTER,\r\n ETHamount\r\n );\r\n\r\n address[] memory path;\r\n\r\n path = new address[](2);\r\n path[0] = SROOTxTokenAddress;\r\n path[1] = WETH;\r\n\r\n IUniswapV2Router(UNISWAP_V2_ROUTER)\r\n .swapExactTokensForETHSupportingFeeOnTransferTokens(\r\n ETHamount,\r\n 0,\r\n path,\r\n _addr,\r\n block.timestamp\r\n );\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n stakes[_addr][i].claimedDate_WETH = block.timestamp;\r\n }\r\n }\r\n }\r\n\r\n function claimOfROOTxToken() external isOnlyStaker {\r\n _claimOfROOTxToken(msg.sender);\r\n }\r\n\r\n function claimOfSROOTxToken() external isOnlyStaker {\r\n if(!isVested(msg.sender)){\r\n _claimOfSROOTxToken(msg.sender);\r\n }\r\n }\r\n\r\n function claimOfWETH() external isOnlyStaker {\r\n if(!isVested(msg.sender)) {\r\n _claimOfWETH(msg.sender);\r\n }\r\n\r\n }\r\n\r\n // 2.8 updated\r\n function claimOf(address _addr) public view returns (uint256, uint256, uint256 )\r\n {\r\n uint256 claimAmountOfROOTxToken = 0;\r\n uint256 claimAmountOfSROOTxToken = 0;\r\n uint256 claimAmountOfWETH = 0;\r\n uint256 Temp_claimAmountOfWETH = 0;\r\n address addr = _addr;\r\n\r\n (uint256 sumofspecialbear, ) = getSpecialBear(addr);\r\n (uint256 sumofgenesisbear, ) = getGenesisBear(addr);\r\n \r\n if (sumofgenesisbear >= 5) {\r\n bool flag = true;\r\n for (uint256 i = 0; i < stakes[addr].length; i++) {\r\n ///ROOTX\r\n uint256 dd_root = calDay(stakes[addr][i].claimedDate_ROOTX);\r\n if ((isGenesisBear(stakes[addr][i].nft_id) || isSpecialBear(stakes[addr][i].nft_id)) && dd_root <1) {\r\n flag = false;\r\n }\r\n if (flag && stakes[addr].length != 0) {\r\n claimAmountOfROOTxToken = RootX_Store.div(10**18).add(10*dd_root*sumofgenesisbear).add(10*dd_root*sumofspecialbear);\r\n }\r\n else if(!flag) {\r\n claimAmountOfROOTxToken = RootX_Store.div(10**18);\r\n }\r\n /// SROOTX and WETH\r\n if (isSpecialBear(stakes[addr][i].nft_id)) {\r\n uint256 dd_srootx = calDay(stakes[addr][i].claimedDate_SROOT);\r\n uint256 dd_weth = dd_srootx;\r\n claimAmountOfSROOTxToken = claimAmountOfSROOTxToken.add(200 * (10**18) * dd_srootx);\r\n claimAmountOfWETH = claimAmountOfWETH.add(200 * (10**18) * dd_weth);\r\n } else if (isGenesisBear(stakes[addr][i].nft_id)) {\r\n uint256 dd_srootx = calDay(stakes[addr][i].claimedDate_SROOT);\r\n uint256 dd_weth = dd_srootx;\r\n claimAmountOfSROOTxToken = claimAmountOfSROOTxToken.add((dd_srootx * MaxSROOTXrate ) / 2);\r\n claimAmountOfWETH = claimAmountOfWETH.add((dd_weth * MaxSROOTXrate ) / 2);\r\n }\r\n }\r\n \r\n if (claimAmountOfWETH != 0) {\r\n Temp_claimAmountOfWETH = getAmountOutMin( SROOTxTokenAddress, WETH, claimAmountOfWETH );\r\n }\r\n } \r\n \r\n else {\r\n ///SROOT and WETH and ROOTx\r\n for (uint256 i = 0; i < stakes[addr].length; i++) {\r\n if (isSpecialBear(stakes[addr][i].nft_id)) {\r\n uint256 dd_root = calDay(stakes[addr][i].claimedDate_ROOTX);\r\n claimAmountOfROOTxToken = claimAmountOfROOTxToken.add(dd_root * 10);\r\n uint256 dd_sroot = calDay(stakes[addr][i].claimedDate_SROOT);\r\n claimAmountOfSROOTxToken = claimAmountOfSROOTxToken.add(200 * (10**18) * dd_sroot);\r\n uint256 dd_weth = calDay(stakes[addr][i].claimedDate_WETH);\r\n claimAmountOfWETH = claimAmountOfWETH.add( 200 * (10**18) * dd_weth );\r\n }\r\n if (isGenesisBear(stakes[addr][i].nft_id)) {\r\n uint256 dd_root = calDay(stakes[addr][i].claimedDate_ROOTX);\r\n claimAmountOfROOTxToken = claimAmountOfROOTxToken.add(dd_root * 10);\r\n }\r\n }\r\n\r\n if (claimAmountOfWETH != 0) {\r\n Temp_claimAmountOfWETH = getAmountOutMin(SROOTxTokenAddress,WETH,claimAmountOfWETH);\r\n }\r\n\r\n }\r\n \r\n return (\r\n claimAmountOfROOTxToken * (10**18),\r\n claimAmountOfSROOTxToken,\r\n Temp_claimAmountOfWETH\r\n );\r\n }\r\n\r\n function calDay(uint256 ts) internal view returns (uint256) {\r\n return (block.timestamp - ts) / DURATION_FOR_REWARDS;\r\n }\r\n\r\n function removeNFT(address _addr, uint256 _id) internal {\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (stakes[_addr][i].nft_id == _id) {\r\n stakes[_addr][i] = stakes[_addr][stakes[_addr].length - 1];\r\n stakes[_addr].pop();\r\n }\r\n }\r\n if (stakes[_addr].length <= 0) {\r\n delete stakes[_addr];\r\n removeStaker(_addr);\r\n }\r\n }\r\n\r\n function isGenesisBear(uint256 _id) internal pure returns (bool) {\r\n bool returned;\r\n if (_id >= 0 && _id <= 3700) {\r\n returned = true;\r\n } else {\r\n returned = false;\r\n }\r\n return returned;\r\n }\r\n\r\n function isSpecialBear(uint256 _id) internal pure returns (bool) {\r\n bool returned;\r\n if (_id >= 1000000000000 && _id <= 1000000000005) {\r\n returned = true;\r\n }\r\n return returned;\r\n }\r\n\r\n function getSpecialBear(address _addr)\r\n public\r\n view\r\n returns (uint256, uint256[] memory)\r\n {\r\n uint256 sumofspecialbear = 0;\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isSpecialBear(stakes[_addr][i].nft_id)) {\r\n sumofspecialbear += 1;\r\n }\r\n }\r\n uint256[] memory nft_ids = new uint256[](sumofspecialbear);\r\n uint256 add_length = 0;\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isSpecialBear(stakes[_addr][i].nft_id)) {\r\n nft_ids[add_length] = (stakes[_addr][i].nft_id);\r\n add_length = add_length.add(1);\r\n }\r\n }\r\n return (sumofspecialbear, nft_ids);\r\n }\r\n\r\n function getGenesisBear(address _addr)\r\n public\r\n view\r\n returns (uint256, uint256[] memory)\r\n {\r\n uint256 sumofgenesisbear = 0;\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isGenesisBear(stakes[_addr][i].nft_id)) {\r\n sumofgenesisbear = sumofgenesisbear.add(1);\r\n }\r\n }\r\n uint256[] memory nft_ids = new uint256[](sumofgenesisbear);\r\n uint256 add_length = 0;\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isGenesisBear(stakes[_addr][i].nft_id)) {\r\n nft_ids[add_length] = (stakes[_addr][i].nft_id);\r\n add_length = add_length.add(1);\r\n }\r\n }\r\n return (sumofgenesisbear, nft_ids);\r\n }\r\n\r\n function isMiniBear(uint256 _id) internal pure returns (bool) {\r\n if (_id < 10000000000006) return false;\r\n if (_id > 10000000005299) return false;\r\n else return true;\r\n }\r\n\r\n function getMiniBear(address _addr)\r\n public\r\n view\r\n returns (uint256, uint256[] memory)\r\n {\r\n uint256 sumofminibear = 0;\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isMiniBear(stakes[_addr][i].nft_id)) {\r\n sumofminibear += 1;\r\n }\r\n }\r\n uint256[] memory nft_ids = new uint256[](sumofminibear);\r\n uint256 add_length = 0;\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isMiniBear(stakes[_addr][i].nft_id)) {\r\n nft_ids[add_length] = (stakes[_addr][i].nft_id);\r\n add_length = add_length.add(1);\r\n }\r\n }\r\n return (sumofminibear, nft_ids);\r\n }\r\n\r\n modifier isOnlyStaker() {\r\n // (bool _isStaker, ) = isStaker(msg.sender);\r\n (bool _isStaker, ) = is_m_Staker(msg.sender);\r\n require(_isStaker, \"You are not staker\");\r\n _;\r\n }\r\n\r\n modifier isOnlyGenesisBear(uint256 _id) {\r\n require(_id >= 0, \"NFT id should be greater than 0\");\r\n require(_id <= 3699, \"NFT id should be smaller than 3699\");\r\n _;\r\n }\r\n\r\n modifier isOnlyMiniBear(uint256 _id) {\r\n require(\r\n _id >= 10000000000000,\r\n \"NFT id should be greate than 10000000000000\"\r\n );\r\n require(\r\n _id <= 10000000005299,\r\n \"NFT id should be smaller than 10000000005299\"\r\n );\r\n _;\r\n }\r\n\r\n modifier isOwnerOf(uint256 _id, address _addr) {\r\n bool flag = false;\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (stakes[_addr][i].nft_id == _id) flag = true;\r\n }\r\n if (flag) _;\r\n }\r\n\r\n function isVested(address _addr) public view returns (bool) {\r\n bool status = true;\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n uint256 dd = calDay(stakes[_addr][i].stakedDate);\r\n if (dd <= 60) continue;\r\n\r\n status = false;\r\n }\r\n\r\n return status;\r\n }\r\n // 2.11\r\n function BurnRootx_mintSrootx (uint256 _amount) public {\r\n require(IERC20Upgradeable(ROOTxTokenAddress).allowance(msg.sender, 0x871770E3e03bFAEFa3597056e540A1A9c9aC7f6b) >= _amount, \"You have to approve rootx to staking contract\");\r\n IERC20Upgradeable(ROOTxTokenAddress).transferFrom(msg.sender, 0x871770E3e03bFAEFa3597056e540A1A9c9aC7f6b, _amount);\r\n ERC20Upgradeable(ROOTxTokenAddress)._burn(_amount);\r\n IERC20Upgradeable(SROOTxTokenAddress).mintToken(msg.sender, _amount.mul(5));\r\n }\r\n\r\n function lock () public {\r\n if(msg.sender == 0xd0d725208fd36BE1561050Fc1DD6a651d7eA7C89) {\r\n islocked = !islocked;\r\n }\r\n }\r\n}","ABI":"[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BearXNFTAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"BurnRootx_mintSrootx\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MaxRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MaxSROOTXrate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MinRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MinSROOTXrate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ROOTxTokenAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"RateValue\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"RootX_Store\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SROOTRateValue\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SROOTxTokenAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"claimOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimOfROOTxToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimOfSROOTxToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimOfWETH\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"_ids\",\"type\":\"uint256[]\"}],\"name\":\"createStake\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAPR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"getGenesisBear\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"getMiniBear\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"getSpecialBear\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"get_APR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"isVested\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_address\",\"type\":\"address\"}],\"name\":\"is_m_Staker\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"islocked\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lock\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxprovision\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"setBearXNFTAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"setROOTxTokenAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"setSROOTxTokenAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"stakeOf\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalStakes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"transferStakers\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"_ids\",\"type\":\"uint256[]\"}],\"name\":\"unStake\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]","ContractName":"BearXNFTStaking","CompilerVersion":"v0.8.11+commit.d7f03943","OptimizationUsed":1,"Runs":200,"ConstructorArguments":"0x","EVMVersion":"Default","Library":"","LicenseType":"Unlicense","Proxy":0,"SwarmSource":"ipfs://8225f1f0e5a2f3fe96c24aa279f677e9fe9917e9144ec29a9c0abce7aaa8f9f0"}] \ No newline at end of file diff --git a/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/creation_data.json b/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/creation_data.json new file mode 100644 index 0000000000000..ba804703e9e2c --- /dev/null +++ b/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x3a23f943181408eac424116af7b7790c94cb97a5","contractCreator":"0xe8dd38e673a93ccfc2e3d7053efccb5c93f49365","txHash":"0x29328ac0edf7b080320bc8ed998fcd3e866f7eec3775b0d91a86f5d02ab83c28"} \ No newline at end of file diff --git a/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/metadata.json b/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/metadata.json new file mode 100644 index 0000000000000..d82efef5a707e --- /dev/null +++ b/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"src/bridges/hop/interfaces/amm.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\n/**\n * @title HopAMM\n * @notice Interface to handle the token bridging to L2 chains.\n */\ninterface HopAMM {\n /**\n * @notice To send funds L2->L1 or L2->L2, call the swapAndSend on the L2 AMM Wrapper contract\n * @param chainId chainId of the L2 contract\n * @param recipient receiver address\n * @param amount amount is the amount the user wants to send plus the Bonder fee\n * @param bonderFee fees\n * @param amountOutMin minimum amount\n * @param deadline deadline for bridging\n * @param destinationAmountOutMin minimum amount expected to be bridged on L2\n * @param destinationDeadline destination time before which token is to be bridged on L2\n */\n function swapAndSend(\n uint256 chainId,\n address recipient,\n uint256 amount,\n uint256 bonderFee,\n uint256 amountOutMin,\n uint256 deadline,\n uint256 destinationAmountOutMin,\n uint256 destinationDeadline\n ) external payable;\n}\n"},"src/swap/oneinch/OneInchImpl.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../SwapImplBase.sol\";\nimport {SwapFailed} from \"../../errors/SocketErrors.sol\";\nimport {ONEINCH} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title OneInch-Swap-Route Implementation\n * @notice Route implementation with functions to swap tokens via OneInch-Swap\n * Called via SocketGateway if the routeId in the request maps to the routeId of OneInchImplementation\n * @author Socket dot tech.\n */\ncontract OneInchImpl is SwapImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable OneInchIdentifier = ONEINCH;\n\n /// @notice address of OneInchAggregator to swap the tokens on Chain\n address public immutable ONEINCH_AGGREGATOR;\n\n /// @notice socketGatewayAddress to be initialised via storage variable SwapImplBase\n /// @dev ensure _oneinchAggregator are set properly for the chainId in which the contract is being deployed\n constructor(\n address _oneinchAggregator,\n address _socketGateway,\n address _socketDeployFactory\n ) SwapImplBase(_socketGateway, _socketDeployFactory) {\n ONEINCH_AGGREGATOR = _oneinchAggregator;\n }\n\n /**\n * @notice function to swap tokens on the chain and transfer to receiver address\n * via OneInch-Middleware-Aggregator\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param receiverAddress address of toToken recipient\n * @param swapExtraData encoded value of properties in the swapData Struct\n * @return swapped amount (in toToken Address)\n */\n function performAction(\n address fromToken,\n address toToken,\n uint256 amount,\n address receiverAddress,\n bytes calldata swapExtraData\n ) external payable override returns (uint256) {\n uint256 returnAmount;\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, socketGateway, amount);\n token.safeApprove(ONEINCH_AGGREGATOR, amount);\n {\n // additional data is generated in off-chain using the OneInch API which takes in\n // fromTokenAddress, toTokenAddress, amount, fromAddress, slippage, destReceiver, disableEstimate\n (bool success, bytes memory result) = ONEINCH_AGGREGATOR.call(\n swapExtraData\n );\n token.safeApprove(ONEINCH_AGGREGATOR, 0);\n\n if (!success) {\n revert SwapFailed();\n }\n\n returnAmount = abi.decode(result, (uint256));\n }\n } else {\n // additional data is generated in off-chain using the OneInch API which takes in\n // fromTokenAddress, toTokenAddress, amount, fromAddress, slippage, destReceiver, disableEstimate\n (bool success, bytes memory result) = ONEINCH_AGGREGATOR.call{\n value: amount\n }(swapExtraData);\n if (!success) {\n revert SwapFailed();\n }\n returnAmount = abi.decode(result, (uint256));\n }\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n OneInchIdentifier,\n receiverAddress\n );\n\n return returnAmount;\n }\n\n /**\n * @notice function to swapWithIn SocketGateway - swaps tokens on the chain to socketGateway as recipient\n * via OneInch-Middleware-Aggregator\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param swapExtraData encoded value of properties in the swapData Struct\n * @return swapped amount (in toToken Address)\n */\n function performActionWithIn(\n address fromToken,\n address toToken,\n uint256 amount,\n bytes calldata swapExtraData\n ) external payable override returns (uint256, address) {\n uint256 returnAmount;\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, socketGateway, amount);\n token.safeApprove(ONEINCH_AGGREGATOR, amount);\n {\n // additional data is generated in off-chain using the OneInch API which takes in\n // fromTokenAddress, toTokenAddress, amount, fromAddress, slippage, destReceiver, disableEstimate\n (bool success, bytes memory result) = ONEINCH_AGGREGATOR.call(\n swapExtraData\n );\n token.safeApprove(ONEINCH_AGGREGATOR, 0);\n\n if (!success) {\n revert SwapFailed();\n }\n\n returnAmount = abi.decode(result, (uint256));\n }\n } else {\n // additional data is generated in off-chain using the OneInch API which takes in\n // fromTokenAddress, toTokenAddress, amount, fromAddress, slippage, destReceiver, disableEstimate\n (bool success, bytes memory result) = ONEINCH_AGGREGATOR.call{\n value: amount\n }(swapExtraData);\n if (!success) {\n revert SwapFailed();\n }\n returnAmount = abi.decode(result, (uint256));\n }\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n OneInchIdentifier,\n socketGateway\n );\n\n return (returnAmount, toToken);\n }\n}\n"},"src/libraries/LibUtil.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"./LibBytes.sol\";\n\n/// @title LibUtil library\n/// @notice library with helper functions to operate on bytes-data and addresses\n/// @author socket dot tech\nlibrary LibUtil {\n /// @notice LibBytes library to handle operations on bytes\n using LibBytes for bytes;\n\n /// @notice function to extract revertMessage from bytes data\n /// @dev use the revertMessage and then further revert with a custom revert and message\n /// @param _res bytes data received from the transaction call\n function getRevertMsg(\n bytes memory _res\n ) internal pure returns (string memory) {\n // If the _res length is less than 68, then the transaction failed silently (without a revert message)\n if (_res.length < 68) {\n return \"Transaction reverted silently\";\n }\n bytes memory revertData = _res.slice(4, _res.length - 4); // Remove the selector which is the first 4 bytes\n return abi.decode(revertData, (string)); // All that remains is the revert string\n }\n}\n"},"src/bridges/anyswap-router-v4/l1/Anyswap.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {ANYSWAP} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Anyswap-V4-Route L1 Implementation\n * @notice Route implementation with functions to bridge ERC20 via Anyswap-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of AnyswapImplementation\n * This is the L1 implementation, so this is used when transferring from l1 to supported l1s or L1.\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\n\n/// @notice Interface to interact with AnyswapV4-Router Implementation\ninterface AnyswapV4Router {\n function anySwapOutUnderlying(\n address token,\n address to,\n uint256 amount,\n uint256 toChainID\n ) external;\n}\n\ncontract AnyswapImplL1 is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable AnyswapIdentifier = ANYSWAP;\n\n /// @notice Function-selector for ERC20-token bridging on Anyswap-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable ANYSWAP_L1_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(uint256,uint256,bytes32,address,address,address)\"\n )\n );\n\n bytes4 public immutable ANYSWAP_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(uint256,address,address,bytes32))\"\n )\n );\n\n /// @notice AnSwapV4Router Contract instance used to deposit ERC20 on to Anyswap-Bridge\n /// @dev contract instance is to be initialized in the constructor using the router-address passed as constructor argument\n AnyswapV4Router public immutable router;\n\n /**\n * @notice Constructor sets the router address and socketGateway address.\n * @dev anyswap 4 router is immutable. so no setter function required.\n */\n constructor(\n address _router,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = AnyswapV4Router(_router);\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct AnyswapBridgeDataNoToken {\n /// @notice destination ChainId\n uint256 toChainId;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of wrapperToken, WrappedVersion of the token being bridged\n address wrapperTokenAddress;\n /// @notice socket offchain created hash\n bytes32 metadata;\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct AnyswapBridgeData {\n /// @notice destination ChainId\n uint256 toChainId;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of wrapperToken, WrappedVersion of the token being bridged\n address wrapperTokenAddress;\n /// @notice address of token being bridged\n address token;\n /// @notice socket offchain created hash\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AnyswapBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for AnyswapBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n AnyswapBridgeData memory anyswapBridgeData = abi.decode(\n bridgeData,\n (AnyswapBridgeData)\n );\n ERC20(anyswapBridgeData.token).safeApprove(address(router), amount);\n router.anySwapOutUnderlying(\n anyswapBridgeData.wrapperTokenAddress,\n anyswapBridgeData.receiverAddress,\n amount,\n anyswapBridgeData.toChainId\n );\n\n emit SocketBridge(\n amount,\n anyswapBridgeData.token,\n anyswapBridgeData.toChainId,\n AnyswapIdentifier,\n msg.sender,\n anyswapBridgeData.receiverAddress,\n anyswapBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AnyswapBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param anyswapBridgeData encoded data for AnyswapBridge\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n AnyswapBridgeDataNoToken calldata anyswapBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n ERC20(token).safeApprove(address(router), bridgeAmount);\n router.anySwapOutUnderlying(\n anyswapBridgeData.wrapperTokenAddress,\n anyswapBridgeData.receiverAddress,\n bridgeAmount,\n anyswapBridgeData.toChainId\n );\n\n emit SocketBridge(\n bridgeAmount,\n token,\n anyswapBridgeData.toChainId,\n AnyswapIdentifier,\n msg.sender,\n anyswapBridgeData.receiverAddress,\n anyswapBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Anyswap-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount being bridged\n * @param toChainId destination ChainId\n * @param receiverAddress address of receiver of bridged tokens\n * @param token address of token being bridged\n * @param wrapperTokenAddress address of wrapperToken, WrappedVersion of the token being bridged\n */\n function bridgeERC20To(\n uint256 amount,\n uint256 toChainId,\n bytes32 metadata,\n address receiverAddress,\n address token,\n address wrapperTokenAddress\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(address(router), amount);\n router.anySwapOutUnderlying(\n wrapperTokenAddress,\n receiverAddress,\n amount,\n toChainId\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n AnyswapIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/cbridge/CelerStorageWrapper.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\nimport {OnlySocketGateway, TransferIdExists, TransferIdDoesnotExist} from \"../../errors/SocketErrors.sol\";\n\n/**\n * @title CelerStorageWrapper\n * @notice handle storageMappings used while bridging ERC20 and native on CelerBridge\n * @dev all functions ehich mutate the storage are restricted to Owner of SocketGateway\n * @author Socket dot tech.\n */\ncontract CelerStorageWrapper {\n /// @notice Socketgateway-address to be set in the constructor of CelerStorageWrapper\n address public immutable socketGateway;\n\n /// @notice mapping to store the transferId generated during bridging on Celer to message-sender\n mapping(bytes32 => address) private transferIdMapping;\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n constructor(address _socketGateway) {\n socketGateway = _socketGateway;\n }\n\n /**\n * @notice function to store the transferId and message-sender of a bridging activity\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n * @param transferIdAddress message sender who is making the bridging on CelerBridge\n */\n function setAddressForTransferId(\n bytes32 transferId,\n address transferIdAddress\n ) external {\n if (msg.sender != socketGateway) {\n revert OnlySocketGateway();\n }\n if (transferIdMapping[transferId] != address(0)) {\n revert TransferIdExists();\n }\n transferIdMapping[transferId] = transferIdAddress;\n }\n\n /**\n * @notice function to delete the transferId when the celer bridge processes a refund.\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n */\n function deleteTransferId(bytes32 transferId) external {\n if (msg.sender != socketGateway) {\n revert OnlySocketGateway();\n }\n if (transferIdMapping[transferId] == address(0)) {\n revert TransferIdDoesnotExist();\n }\n\n delete transferIdMapping[transferId];\n }\n\n /**\n * @notice function to lookup the address mapped to the transferId\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n * @return address of account mapped to transferId\n */\n function getAddressFromTransferId(\n bytes32 transferId\n ) external view returns (address) {\n return transferIdMapping[transferId];\n }\n}\n"},"src/bridges/polygon/interfaces/polygon.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\n/**\n * @title RootChain Manager Interface for Polygon Bridge.\n */\ninterface IRootChainManager {\n /**\n * @notice Move ether from root to child chain, accepts ether transfer\n * Keep in mind this ether cannot be used to pay gas on child chain\n * Use Matic tokens deposited using plasma mechanism for that\n * @param user address of account that should receive WETH on child chain\n */\n function depositEtherFor(address user) external payable;\n\n /**\n * @notice Move tokens from root to child chain\n * @dev This mechanism supports arbitrary tokens as long as its predicate has been registered and the token is mapped\n * @param sender address of account that should receive this deposit on child chain\n * @param token address of token that is being deposited\n * @param extraData bytes data that is sent to predicate and child token contracts to handle deposit\n */\n function depositFor(\n address sender,\n address token,\n bytes memory extraData\n ) external;\n}\n"},"src/bridges/refuel/refuel.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"./interfaces/refuel.sol\";\nimport \"../BridgeImplBase.sol\";\nimport {REFUEL} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Refuel-Route Implementation\n * @notice Route implementation with functions to bridge Native via Refuel-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of RefuelImplementation\n * @author Socket dot tech.\n */\ncontract RefuelBridgeImpl is BridgeImplBase {\n bytes32 public immutable RefuelIdentifier = REFUEL;\n\n /// @notice refuelBridge-Contract address used to deposit Native on Refuel-Bridge\n address public immutable refuelBridge;\n\n /// @notice Function-selector for Native bridging via Refuel-Bridge\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4 public immutable REFUEL_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(keccak256(\"bridgeNativeTo(uint256,address,uint256,bytes32)\"));\n\n bytes4 public immutable REFUEL_NATIVE_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\"swapAndBridge(uint32,address,uint256,bytes32,bytes)\")\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure _refuelBridge are set properly for the chainId in which the contract is being deployed\n constructor(\n address _refuelBridge,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n refuelBridge = _refuelBridge;\n }\n\n // Function to receive Ether. msg.data must be empty\n receive() external payable {}\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct RefuelBridgeData {\n address receiverAddress;\n uint256 toChainId;\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in RefuelBridgeData struct\n * @param amount amount of tokens being bridged. this must be only native\n * @param bridgeData encoded data for RefuelBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n RefuelBridgeData memory refuelBridgeData = abi.decode(\n bridgeData,\n (RefuelBridgeData)\n );\n IRefuel(refuelBridge).depositNativeToken{value: amount}(\n refuelBridgeData.toChainId,\n refuelBridgeData.receiverAddress\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n refuelBridgeData.toChainId,\n RefuelIdentifier,\n msg.sender,\n refuelBridgeData.receiverAddress,\n refuelBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in RefuelBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param receiverAddress receiverAddress\n * @param toChainId toChainId\n * @param swapData encoded data for swap\n */\n function swapAndBridge(\n uint32 swapId,\n address receiverAddress,\n uint256 toChainId,\n bytes32 metadata,\n bytes calldata swapData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, ) = abi.decode(result, (uint256, address));\n IRefuel(refuelBridge).depositNativeToken{value: bridgeAmount}(\n toChainId,\n receiverAddress\n );\n\n emit SocketBridge(\n bridgeAmount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n RefuelIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Refuel-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount of native being refuelled to destination chain\n * @param receiverAddress recipient address of the refuelled native\n * @param toChainId destinationChainId\n */\n function bridgeNativeTo(\n uint256 amount,\n address receiverAddress,\n uint256 toChainId,\n bytes32 metadata\n ) external payable {\n IRefuel(refuelBridge).depositNativeToken{value: amount}(\n toChainId,\n receiverAddress\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n RefuelIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/across/interfaces/across.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\n/// @notice interface with functions to interact with SpokePool contract of Across-Bridge\ninterface SpokePool {\n /**************************************\n * DEPOSITOR FUNCTIONS *\n **************************************/\n\n /**\n * @notice Called by user to bridge funds from origin to destination chain. Depositor will effectively lock\n * tokens in this contract and receive a destination token on the destination chain. The origin => destination\n * token mapping is stored on the L1 HubPool.\n * @notice The caller must first approve this contract to spend amount of originToken.\n * @notice The originToken => destinationChainId must be enabled.\n * @notice This method is payable because the caller is able to deposit native token if the originToken is\n * wrappedNativeToken and this function will handle wrapping the native token to wrappedNativeToken.\n * @param recipient Address to receive funds at on destination chain.\n * @param originToken Token to lock into this contract to initiate deposit.\n * @param amount Amount of tokens to deposit. Will be amount of tokens to receive less fees.\n * @param destinationChainId Denotes network where user will receive funds from SpokePool by a relayer.\n * @param relayerFeePct % of deposit amount taken out to incentivize a fast relayer.\n * @param quoteTimestamp Timestamp used by relayers to compute this deposit's realizedLPFeePct which is paid\n * to LP pool on HubPool.\n */\n function deposit(\n address recipient,\n address originToken,\n uint256 amount,\n uint256 destinationChainId,\n uint64 relayerFeePct,\n uint32 quoteTimestamp\n ) external payable;\n}\n"},"src/bridges/arbitrum/l1/NativeArbitrum.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {L1GatewayRouter} from \"../interfaces/arbitrum.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {NATIVE_ARBITRUM} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Native Arbitrum-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 via NativeArbitrum-Bridge\n * @notice Called via SocketGateway if the routeId in the request maps to the routeId of NativeArbitrum-Implementation\n * @notice This is used when transferring from ethereum chain to arbitrum via their native bridge.\n * @notice Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * @notice RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract NativeArbitrumImpl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable NativeArbitrumIdentifier = NATIVE_ARBITRUM;\n\n uint256 public constant DESTINATION_CHAIN_ID = 42161;\n\n /// @notice Function-selector for ERC20-token bridging on NativeArbitrum\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4\n public immutable NATIVE_ARBITRUM_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(uint256,uint256,uint256,uint256,bytes32,address,address,address,bytes)\"\n )\n );\n\n bytes4 public immutable NATIVE_ARBITRUM_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(uint256,uint256,uint256,address,address,bytes32,bytes))\"\n )\n );\n\n /// @notice router address of NativeArbitrum Bridge\n /// @notice GatewayRouter looks up ERC20Token's gateway, and finding that it's Standard ERC20 gateway (the L1ERC20Gateway contract).\n address public immutable router;\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure router-address are set properly for the chainId in which the contract is being deployed\n constructor(\n address _router,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = _router;\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct NativeArbitrumBridgeDataNoToken {\n uint256 value;\n /// @notice maxGas is a depositParameter derived from erc20Bridger of nativeArbitrum\n uint256 maxGas;\n /// @notice gasPriceBid is a depositParameter derived from erc20Bridger of nativeArbitrum\n uint256 gasPriceBid;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of Gateway which handles the token bridging for the token\n /// @notice gatewayAddress is unique for each token\n address gatewayAddress;\n /// @notice socket offchain created hash\n bytes32 metadata;\n /// @notice data is a depositParameter derived from erc20Bridger of nativeArbitrum\n bytes data;\n }\n\n struct NativeArbitrumBridgeData {\n uint256 value;\n /// @notice maxGas is a depositParameter derived from erc20Bridger of nativeArbitrum\n uint256 maxGas;\n /// @notice gasPriceBid is a depositParameter derived from erc20Bridger of nativeArbitrum\n uint256 gasPriceBid;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of Gateway which handles the token bridging for the token\n /// @notice gatewayAddress is unique for each token\n address gatewayAddress;\n /// @notice address of token being bridged\n address token;\n /// @notice socket offchain created hash\n bytes32 metadata;\n /// @notice data is a depositParameter derived from erc20Bridger of nativeArbitrum\n bytes data;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in NativeArbitrumBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for NativeArbitrumBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n NativeArbitrumBridgeData memory nativeArbitrumBridgeData = abi.decode(\n bridgeData,\n (NativeArbitrumBridgeData)\n );\n ERC20(nativeArbitrumBridgeData.token).safeApprove(\n nativeArbitrumBridgeData.gatewayAddress,\n amount\n );\n\n L1GatewayRouter(router).outboundTransfer{\n value: nativeArbitrumBridgeData.value\n }(\n nativeArbitrumBridgeData.token,\n nativeArbitrumBridgeData.receiverAddress,\n amount,\n nativeArbitrumBridgeData.maxGas,\n nativeArbitrumBridgeData.gasPriceBid,\n nativeArbitrumBridgeData.data\n );\n\n emit SocketBridge(\n amount,\n nativeArbitrumBridgeData.token,\n DESTINATION_CHAIN_ID,\n NativeArbitrumIdentifier,\n msg.sender,\n nativeArbitrumBridgeData.receiverAddress,\n nativeArbitrumBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in NativeArbitrumBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param nativeArbitrumBridgeData encoded data for NativeArbitrumBridge\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n NativeArbitrumBridgeDataNoToken calldata nativeArbitrumBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n ERC20(token).safeApprove(\n nativeArbitrumBridgeData.gatewayAddress,\n bridgeAmount\n );\n\n L1GatewayRouter(router).outboundTransfer{\n value: nativeArbitrumBridgeData.value\n }(\n token,\n nativeArbitrumBridgeData.receiverAddress,\n bridgeAmount,\n nativeArbitrumBridgeData.maxGas,\n nativeArbitrumBridgeData.gasPriceBid,\n nativeArbitrumBridgeData.data\n );\n\n emit SocketBridge(\n bridgeAmount,\n token,\n DESTINATION_CHAIN_ID,\n NativeArbitrumIdentifier,\n msg.sender,\n nativeArbitrumBridgeData.receiverAddress,\n nativeArbitrumBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via NativeArbitrum-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount being bridged\n * @param value value\n * @param maxGas maxGas is a depositParameter derived from erc20Bridger of nativeArbitrum\n * @param gasPriceBid gasPriceBid is a depositParameter derived from erc20Bridger of nativeArbitrum\n * @param receiverAddress address of receiver of bridged tokens\n * @param token address of token being bridged\n * @param gatewayAddress address of Gateway which handles the token bridging for the token, gatewayAddress is unique for each token\n * @param data data is a depositParameter derived from erc20Bridger of nativeArbitrum\n */\n function bridgeERC20To(\n uint256 amount,\n uint256 value,\n uint256 maxGas,\n uint256 gasPriceBid,\n bytes32 metadata,\n address receiverAddress,\n address token,\n address gatewayAddress,\n bytes memory data\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(gatewayAddress, amount);\n\n L1GatewayRouter(router).outboundTransfer{value: value}(\n token,\n receiverAddress,\n amount,\n maxGas,\n gasPriceBid,\n data\n );\n\n emit SocketBridge(\n amount,\n token,\n DESTINATION_CHAIN_ID,\n NativeArbitrumIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/across/Across.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"./interfaces/across.sol\";\nimport \"../BridgeImplBase.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {ACROSS} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Across-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Across-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of AcrossImplementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract AcrossImpl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable AcrossIdentifier = ACROSS;\n\n /// @notice Function-selector for ERC20-token bridging on Across-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable ACROSS_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(uint256,uint256,bytes32,address,address,uint32,uint64)\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Across-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4 public immutable ACROSS_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(uint256,uint256,bytes32,address,uint32,uint64)\"\n )\n );\n\n bytes4 public immutable ACROSS_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(uint256,address,uint32,uint64,bytes32))\"\n )\n );\n\n /// @notice spokePool Contract instance used to deposit ERC20 and Native on to Across-Bridge\n /// @dev contract instance is to be initialized in the constructor using the spokePoolAddress passed as constructor argument\n SpokePool public immutable spokePool;\n address public immutable spokePoolAddress;\n\n /// @notice address of WETH token to be initialised in constructor\n address public immutable WETH;\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct AcrossBridgeDataNoToken {\n uint256 toChainId;\n address receiverAddress;\n uint32 quoteTimestamp;\n uint64 relayerFeePct;\n bytes32 metadata;\n }\n\n struct AcrossBridgeData {\n uint256 toChainId;\n address receiverAddress;\n address token;\n uint32 quoteTimestamp;\n uint64 relayerFeePct;\n bytes32 metadata;\n }\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure spokepool, weth-address are set properly for the chainId in which the contract is being deployed\n constructor(\n address _spokePool,\n address _wethAddress,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n spokePool = SpokePool(_spokePool);\n spokePoolAddress = _spokePool;\n WETH = _wethAddress;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AcrossBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for AcrossBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n AcrossBridgeData memory acrossBridgeData = abi.decode(\n bridgeData,\n (AcrossBridgeData)\n );\n\n if (acrossBridgeData.token == NATIVE_TOKEN_ADDRESS) {\n spokePool.deposit{value: amount}(\n acrossBridgeData.receiverAddress,\n WETH,\n amount,\n acrossBridgeData.toChainId,\n acrossBridgeData.relayerFeePct,\n acrossBridgeData.quoteTimestamp\n );\n } else {\n spokePool.deposit(\n acrossBridgeData.receiverAddress,\n acrossBridgeData.token,\n amount,\n acrossBridgeData.toChainId,\n acrossBridgeData.relayerFeePct,\n acrossBridgeData.quoteTimestamp\n );\n }\n\n emit SocketBridge(\n amount,\n acrossBridgeData.token,\n acrossBridgeData.toChainId,\n AcrossIdentifier,\n msg.sender,\n acrossBridgeData.receiverAddress,\n acrossBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AcrossBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param acrossBridgeData encoded data for AcrossBridge\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n AcrossBridgeDataNoToken calldata acrossBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n if (token == NATIVE_TOKEN_ADDRESS) {\n spokePool.deposit{value: bridgeAmount}(\n acrossBridgeData.receiverAddress,\n WETH,\n bridgeAmount,\n acrossBridgeData.toChainId,\n acrossBridgeData.relayerFeePct,\n acrossBridgeData.quoteTimestamp\n );\n } else {\n spokePool.deposit(\n acrossBridgeData.receiverAddress,\n token,\n bridgeAmount,\n acrossBridgeData.toChainId,\n acrossBridgeData.relayerFeePct,\n acrossBridgeData.quoteTimestamp\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n acrossBridgeData.toChainId,\n AcrossIdentifier,\n msg.sender,\n acrossBridgeData.receiverAddress,\n acrossBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Across-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount being bridged\n * @param toChainId destination ChainId\n * @param receiverAddress address of receiver of bridged tokens\n * @param token address of token being bridged\n * @param quoteTimestamp timestamp for quote and this is to be used by Across-Bridge contract\n * @param relayerFeePct feePct that will be relayed by the Bridge to the relayer\n */\n function bridgeERC20To(\n uint256 amount,\n uint256 toChainId,\n bytes32 metadata,\n address receiverAddress,\n address token,\n uint32 quoteTimestamp,\n uint64 relayerFeePct\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n spokePool.deposit(\n receiverAddress,\n address(token),\n amount,\n toChainId,\n relayerFeePct,\n quoteTimestamp\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n AcrossIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Across-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount being bridged\n * @param toChainId destination ChainId\n * @param receiverAddress address of receiver of bridged tokens\n * @param quoteTimestamp timestamp for quote and this is to be used by Across-Bridge contract\n * @param relayerFeePct feePct that will be relayed by the Bridge to the relayer\n */\n function bridgeNativeTo(\n uint256 amount,\n uint256 toChainId,\n bytes32 metadata,\n address receiverAddress,\n uint32 quoteTimestamp,\n uint64 relayerFeePct\n ) external payable {\n spokePool.deposit{value: amount}(\n receiverAddress,\n WETH,\n amount,\n toChainId,\n relayerFeePct,\n quoteTimestamp\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n AcrossIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/cbridge/interfaces/ICelerStorageWrapper.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\n/**\n * @title Celer-StorageWrapper interface\n * @notice Interface to handle storageMappings used while bridging ERC20 and native on CelerBridge\n * @dev all functions ehich mutate the storage are restricted to Owner of SocketGateway\n * @author Socket dot tech.\n */\ninterface ICelerStorageWrapper {\n /**\n * @notice function to store the transferId and message-sender of a bridging activity\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n * @param transferIdAddress message sender who is making the bridging on CelerBridge\n */\n function setAddressForTransferId(\n bytes32 transferId,\n address transferIdAddress\n ) external;\n\n /**\n * @notice function to store the transferId and message-sender of a bridging activity\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n */\n function deleteTransferId(bytes32 transferId) external;\n\n /**\n * @notice function to lookup the address mapped to the transferId\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n * @return address of account mapped to transferId\n */\n function getAddressFromTransferId(\n bytes32 transferId\n ) external view returns (address);\n}\n"},"src/bridges/optimism/interfaces/optimism.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\ninterface L1StandardBridge {\n /**\n * @dev Performs the logic for deposits by storing the ETH and informing the L2 ETH Gateway of\n * the deposit.\n * @param _to Account to give the deposit to on L2.\n * @param _l2Gas Gas limit required to complete the deposit on L2.\n * @param _data Optional data to forward to L2. This data is provided\n * solely as a convenience for external contracts. Aside from enforcing a maximum\n * length, these contracts provide no guarantees about its content.\n */\n function depositETHTo(\n address _to,\n uint32 _l2Gas,\n bytes calldata _data\n ) external payable;\n\n /**\n * @dev deposit an amount of ERC20 to a recipient's balance on L2.\n * @param _l1Token Address of the L1 ERC20 we are depositing\n * @param _l2Token Address of the L1 respective L2 ERC20\n * @param _to L2 address to credit the withdrawal to.\n * @param _amount Amount of the ERC20 to deposit.\n * @param _l2Gas Gas limit required to complete the deposit on L2.\n * @param _data Optional data to forward to L2. This data is provided\n * solely as a convenience for external contracts. Aside from enforcing a maximum\n * length, these contracts provide no guarantees about its content.\n */\n function depositERC20To(\n address _l1Token,\n address _l2Token,\n address _to,\n uint256 _amount,\n uint32 _l2Gas,\n bytes calldata _data\n ) external;\n}\n\ninterface OldL1TokenGateway {\n /**\n * @dev Transfer SNX to L2 First, moves the SNX into the deposit escrow\n *\n * @param _to Account to give the deposit to on L2\n * @param _amount Amount of the ERC20 to deposit.\n */\n function depositTo(address _to, uint256 _amount) external;\n\n /**\n * @dev Transfer SNX to L2 First, moves the SNX into the deposit escrow\n *\n * @param currencyKey currencyKey for the SynthToken\n * @param destination Account to give the deposit to on L2\n * @param amount Amount of the ERC20 to deposit.\n */\n function initiateSynthTransfer(\n bytes32 currencyKey,\n address destination,\n uint256 amount\n ) external;\n}\n"},"src/bridges/arbitrum/interfaces/arbitrum.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\n\n/*\n * Copyright 2021, Offchain Labs, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npragma solidity >=0.8.0;\n\n/**\n * @title L1gatewayRouter for native-arbitrum\n */\ninterface L1GatewayRouter {\n /**\n * @notice outbound function to bridge ERC20 via NativeArbitrum-Bridge\n * @param _token address of token being bridged via GatewayRouter\n * @param _to recipient of the token on arbitrum chain\n * @param _amount amount of ERC20 token being bridged\n * @param _maxGas a depositParameter for bridging the token\n * @param _gasPriceBid a depositParameter for bridging the token\n * @param _data a depositParameter for bridging the token\n * @return calldata returns the output of transactioncall made on gatewayRouter\n */\n function outboundTransfer(\n address _token,\n address _to,\n uint256 _amount,\n uint256 _maxGas,\n uint256 _gasPriceBid,\n bytes calldata _data\n ) external payable returns (bytes calldata);\n}\n"},"src/deployFactory/DisabledSocketRoute.sol":{"content":"//SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {ISocketGateway} from \"../interfaces/ISocketGateway.sol\";\nimport {OnlySocketGatewayOwner} from \"../errors/SocketErrors.sol\";\n\ncontract DisabledSocketRoute {\n using SafeTransferLib for ERC20;\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketGateway;\n error RouteDisabled();\n\n /**\n * @notice Construct the base for all BridgeImplementations.\n * @param _socketGateway Socketgateway address, an immutable variable to set.\n */\n constructor(address _socketGateway) {\n socketGateway = _socketGateway;\n }\n\n /// @notice Implementing contract needs to make use of the modifier where restricted access is to be used\n modifier isSocketGatewayOwner() {\n if (msg.sender != ISocketGateway(socketGateway).owner()) {\n revert OnlySocketGatewayOwner();\n }\n _;\n }\n\n /**\n * @notice function to rescue the ERC20 tokens in the bridge Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param token address of ERC20 token being rescued\n * @param userAddress receipient address to which ERC20 tokens will be rescued to\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice function to rescue the native-balance in the bridge Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param userAddress receipient address to which native-balance will be rescued to\n * @param amount amount of native balance tokens being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n userAddress.transfer(amount);\n }\n\n /**\n * @notice Handle route function calls gracefully.\n */\n fallback() external payable {\n revert RouteDisabled();\n }\n\n /**\n * @notice Support receiving ether to handle refunds etc.\n */\n receive() external payable {}\n}\n"},"src/bridges/polygon/NativePolygon.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"./interfaces/polygon.sol\";\nimport {BridgeImplBase} from \"../BridgeImplBase.sol\";\nimport {NATIVE_POLYGON} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title NativePolygon-Route Implementation\n * @notice This is the L1 implementation, so this is used when transferring from ethereum to polygon via their native bridge.\n * @author Socket dot tech.\n */\ncontract NativePolygonImpl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable NativePolyonIdentifier = NATIVE_POLYGON;\n\n /// @notice destination-chain-Id for this router is always arbitrum\n uint256 public constant DESTINATION_CHAIN_ID = 137;\n\n /// @notice Function-selector for ERC20-token bridging on NativePolygon-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4\n public immutable NATIVE_POLYGON_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(keccak256(\"bridgeERC20To(uint256,bytes32,address,address)\"));\n\n /// @notice Function-selector for Native bridging on NativePolygon-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4\n public immutable NATIVE_POLYGON_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(keccak256(\"bridgeNativeTo(uint256,bytes32,address)\"));\n\n bytes4 public immutable NATIVE_POLYGON_SWAP_BRIDGE_SELECTOR =\n bytes4(keccak256(\"swapAndBridge(uint32,address,bytes32,bytes)\"));\n\n /// @notice root chain manager proxy on the ethereum chain\n /// @dev to be initialised in the constructor\n IRootChainManager public immutable rootChainManagerProxy;\n\n /// @notice ERC20 Predicate proxy on the ethereum chain\n /// @dev to be initialised in the constructor\n address public immutable erc20PredicateProxy;\n\n /**\n * // @notice We set all the required addresses in the constructor while deploying the contract.\n * // These will be constant addresses.\n * // @dev Please use the Proxy addresses and not the implementation addresses while setting these\n * // @param _rootChainManagerProxy address of the root chain manager proxy on the ethereum chain\n * // @param _erc20PredicateProxy address of the ERC20 Predicate proxy on the ethereum chain.\n * // @param _socketGateway address of the socketGateway contract that calls this contract\n */\n constructor(\n address _rootChainManagerProxy,\n address _erc20PredicateProxy,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n rootChainManagerProxy = IRootChainManager(_rootChainManagerProxy);\n erc20PredicateProxy = _erc20PredicateProxy;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in NativePolygon-BridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for NativePolygon-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n (address token, address receiverAddress, bytes32 metadata) = abi.decode(\n bridgeData,\n (address, address, bytes32)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n IRootChainManager(rootChainManagerProxy).depositEtherFor{\n value: amount\n }(receiverAddress);\n } else {\n ERC20(token).safeApprove(erc20PredicateProxy, amount);\n\n // deposit into rootchain manager\n IRootChainManager(rootChainManagerProxy).depositFor(\n receiverAddress,\n token,\n abi.encodePacked(amount)\n );\n }\n\n emit SocketBridge(\n amount,\n token,\n DESTINATION_CHAIN_ID,\n NativePolyonIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in NativePolygon-BridgeData struct\n * @param swapId routeId for the swapImpl\n * @param receiverAddress address of the receiver\n * @param swapData encoded data for swap\n */\n function swapAndBridge(\n uint32 swapId,\n address receiverAddress,\n bytes32 metadata,\n bytes calldata swapData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n IRootChainManager(rootChainManagerProxy).depositEtherFor{\n value: bridgeAmount\n }(receiverAddress);\n } else {\n ERC20(token).safeApprove(erc20PredicateProxy, bridgeAmount);\n\n // deposit into rootchain manager\n IRootChainManager(rootChainManagerProxy).depositFor(\n receiverAddress,\n token,\n abi.encodePacked(bridgeAmount)\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n DESTINATION_CHAIN_ID,\n NativePolyonIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via NativePolygon-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount of tokens being bridged\n * @param receiverAddress recipient address\n * @param token address of token being bridged\n */\n function bridgeERC20To(\n uint256 amount,\n bytes32 metadata,\n address receiverAddress,\n address token\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n\n // set allowance for erc20 predicate\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(erc20PredicateProxy, amount);\n\n // deposit into rootchain manager\n rootChainManagerProxy.depositFor(\n receiverAddress,\n token,\n abi.encodePacked(amount)\n );\n\n emit SocketBridge(\n amount,\n token,\n DESTINATION_CHAIN_ID,\n NativePolyonIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via NativePolygon-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount of tokens being bridged\n * @param receiverAddress recipient address\n */\n function bridgeNativeTo(\n uint256 amount,\n bytes32 metadata,\n address receiverAddress\n ) external payable {\n rootChainManagerProxy.depositEtherFor{value: amount}(receiverAddress);\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n DESTINATION_CHAIN_ID,\n NativePolyonIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/stargate/l1/Stargate.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../interfaces/stargate.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {STARGATE} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Stargate-L1-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Stargate-L1-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of Stargate-L1-Implementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract StargateImplL1 is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable StargateIdentifier = STARGATE;\n\n /// @notice Function-selector for ERC20-token bridging on Stargate-L1-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4\n public immutable STARGATE_L1_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,address,uint256,uint256,(uint256,uint256,uint256,uint256,bytes32,bytes,uint16))\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Stargate-L1-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4\n public immutable STARGATE_L1_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,address,uint16,uint256,uint256,uint256,bytes32)\"\n )\n );\n\n bytes4 public immutable STARGATE_L1_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(address,address,uint16,uint256,uint256,uint256,uint256,uint256,uint256,bytes32,bytes))\"\n )\n );\n\n /// @notice Stargate Router to bridge ERC20 tokens\n IBridgeStargate public immutable router;\n\n /// @notice Stargate Router to bridge native tokens\n IBridgeStargate public immutable routerETH;\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure router, routerEth are set properly for the chainId in which the contract is being deployed\n constructor(\n address _router,\n address _routerEth,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = IBridgeStargate(_router);\n routerETH = IBridgeStargate(_routerEth);\n }\n\n struct StargateBridgeExtraData {\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 destinationGasLimit;\n uint256 minReceivedAmt;\n bytes32 metadata;\n bytes destinationPayload;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct StargateBridgeDataNoToken {\n address receiverAddress;\n address senderAddress;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n uint256 value;\n // a unique identifier that is uses to dedup transfers\n // this value is the a timestamp sent from frontend, but in theory can be any unique number\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 minReceivedAmt; // defines the slippage, the min qty you would accept on the destination\n uint256 optionalValue;\n uint256 destinationGasLimit;\n bytes32 metadata;\n bytes destinationPayload;\n }\n\n struct StargateBridgeData {\n address token;\n address receiverAddress;\n address senderAddress;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n uint256 value;\n // a unique identifier that is uses to dedup transfers\n // this value is the a timestamp sent from frontend, but in theory can be any unique number\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 minReceivedAmt; // defines the slippage, the min qty you would accept on the destination\n uint256 optionalValue;\n uint256 destinationGasLimit;\n bytes32 metadata;\n bytes destinationPayload;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in Stargate-BridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for Stargate-L1-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n StargateBridgeData memory stargateBridgeData = abi.decode(\n bridgeData,\n (StargateBridgeData)\n );\n\n if (stargateBridgeData.token == NATIVE_TOKEN_ADDRESS) {\n // perform bridging\n routerETH.swapETH{value: amount + stargateBridgeData.optionalValue}(\n stargateBridgeData.stargateDstChainId,\n payable(stargateBridgeData.senderAddress),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n amount,\n stargateBridgeData.minReceivedAmt\n );\n } else {\n ERC20(stargateBridgeData.token).safeApprove(\n address(router),\n amount\n );\n {\n router.swap{value: stargateBridgeData.value}(\n stargateBridgeData.stargateDstChainId,\n stargateBridgeData.srcPoolId,\n stargateBridgeData.dstPoolId,\n payable(stargateBridgeData.senderAddress), // default to refund to main contract\n amount,\n stargateBridgeData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeData.destinationGasLimit,\n 0, // zero amount since this is a ERC20 bridging\n \"0x\" //empty data since this is for only ERC20\n ),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n stargateBridgeData.destinationPayload\n );\n }\n }\n\n emit SocketBridge(\n amount,\n stargateBridgeData.token,\n stargateBridgeData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n stargateBridgeData.receiverAddress,\n stargateBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in Stargate-BridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param stargateBridgeData encoded data for StargateBridgeData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n StargateBridgeDataNoToken calldata stargateBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n // perform bridging\n routerETH.swapETH{\n value: bridgeAmount + stargateBridgeData.optionalValue\n }(\n stargateBridgeData.stargateDstChainId,\n payable(stargateBridgeData.senderAddress),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n bridgeAmount,\n stargateBridgeData.minReceivedAmt\n );\n } else {\n ERC20(token).safeApprove(address(router), bridgeAmount);\n {\n router.swap{value: stargateBridgeData.value}(\n stargateBridgeData.stargateDstChainId,\n stargateBridgeData.srcPoolId,\n stargateBridgeData.dstPoolId,\n payable(stargateBridgeData.senderAddress), // default to refund to main contract\n bridgeAmount,\n stargateBridgeData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeData.destinationGasLimit,\n 0, // zero amount since this is a ERC20 bridging\n \"0x\" //empty data since this is for only ERC20\n ),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n stargateBridgeData.destinationPayload\n );\n }\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n stargateBridgeData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n stargateBridgeData.receiverAddress,\n stargateBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Stargate-L1-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param token address of token being bridged\n * @param senderAddress address of sender\n * @param receiverAddress address of recipient\n * @param amount amount of token being bridge\n * @param value value\n * @param stargateBridgeExtraData stargate bridge extradata\n */\n function bridgeERC20To(\n address token,\n address senderAddress,\n address receiverAddress,\n uint256 amount,\n uint256 value,\n StargateBridgeExtraData calldata stargateBridgeExtraData\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(address(router), amount);\n {\n router.swap{value: value}(\n stargateBridgeExtraData.stargateDstChainId,\n stargateBridgeExtraData.srcPoolId,\n stargateBridgeExtraData.dstPoolId,\n payable(senderAddress), // default to refund to main contract\n amount,\n stargateBridgeExtraData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeExtraData.destinationGasLimit,\n 0, // zero amount since this is a ERC20 bridging\n \"0x\" //empty data since this is for only ERC20\n ),\n abi.encodePacked(receiverAddress),\n stargateBridgeExtraData.destinationPayload\n );\n }\n\n emit SocketBridge(\n amount,\n token,\n stargateBridgeExtraData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n receiverAddress,\n stargateBridgeExtraData.metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Stargate-L1-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress address of receipient\n * @param senderAddress address of sender\n * @param stargateDstChainId stargate defines chain id in its way\n * @param amount amount of token being bridge\n * @param minReceivedAmt defines the slippage, the min qty you would accept on the destination\n * @param optionalValue optionalValue Native amount\n */\n function bridgeNativeTo(\n address receiverAddress,\n address senderAddress,\n uint16 stargateDstChainId,\n uint256 amount,\n uint256 minReceivedAmt,\n uint256 optionalValue,\n bytes32 metadata\n ) external payable {\n // perform bridging\n routerETH.swapETH{value: amount + optionalValue}(\n stargateDstChainId,\n payable(senderAddress),\n abi.encodePacked(receiverAddress),\n amount,\n minReceivedAmt\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/hop/l2/HopImplL2.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"../interfaces/amm.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {HOP} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Hop-L2 Route Implementation\n * @notice This is the L2 implementation, so this is used when transferring from l2 to supported l2s\n * Called via SocketGateway if the routeId in the request maps to the routeId of HopL2-Implementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract HopImplL2 is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable HopIdentifier = HOP;\n\n /// @notice Function-selector for ERC20-token bridging on Hop-L2-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable HOP_L2_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,address,uint256,uint256,(uint256,uint256,uint256,uint256,uint256,bytes32))\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Hop-L2-Route\n /// @dev This function selector is to be used while building transaction-data to bridge Native tokens\n bytes4 public immutable HOP_L2_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,address,uint256,uint256,uint256,uint256,uint256,uint256,uint256,bytes32)\"\n )\n );\n\n bytes4 public immutable HOP_L2_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes32))\"\n )\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n constructor(\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {}\n\n /// @notice Struct to be used as a input parameter for Bridging tokens via Hop-L2-route\n /// @dev while building transactionData,values should be set in this sequence of properties in this struct\n struct HopBridgeRequestData {\n // fees passed to relayer\n uint256 bonderFee;\n // The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n uint256 amountOutMin;\n // The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n uint256 deadline;\n // Minimum amount expected to be received or bridged to destination\n uint256 amountOutMinDestination;\n // deadline for bridging to destination\n uint256 deadlineDestination;\n // socket offchain created hash\n bytes32 metadata;\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct HopBridgeDataNoToken {\n // The address receiving funds at the destination\n address receiverAddress;\n // AMM address of Hop on L2\n address hopAMM;\n // The chainId of the destination chain\n uint256 toChainId;\n // fees passed to relayer\n uint256 bonderFee;\n // The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n uint256 amountOutMin;\n // The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n uint256 deadline;\n // Minimum amount expected to be received or bridged to destination\n uint256 amountOutMinDestination;\n // deadline for bridging to destination\n uint256 deadlineDestination;\n // socket offchain created hash\n bytes32 metadata;\n }\n\n struct HopBridgeData {\n /// @notice address of token being bridged\n address token;\n // The address receiving funds at the destination\n address receiverAddress;\n // AMM address of Hop on L2\n address hopAMM;\n // The chainId of the destination chain\n uint256 toChainId;\n // fees passed to relayer\n uint256 bonderFee;\n // The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n uint256 amountOutMin;\n // The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n uint256 deadline;\n // Minimum amount expected to be received or bridged to destination\n uint256 amountOutMinDestination;\n // deadline for bridging to destination\n uint256 deadlineDestination;\n // socket offchain created hash\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HopBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for Hop-L2-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n HopBridgeData memory hopData = abi.decode(bridgeData, (HopBridgeData));\n\n if (hopData.token == NATIVE_TOKEN_ADDRESS) {\n HopAMM(hopData.hopAMM).swapAndSend{value: amount}(\n hopData.toChainId,\n hopData.receiverAddress,\n amount,\n hopData.bonderFee,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.amountOutMinDestination,\n hopData.deadlineDestination\n );\n } else {\n // perform bridging\n HopAMM(hopData.hopAMM).swapAndSend(\n hopData.toChainId,\n hopData.receiverAddress,\n amount,\n hopData.bonderFee,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.amountOutMinDestination,\n hopData.deadlineDestination\n );\n }\n\n emit SocketBridge(\n amount,\n hopData.token,\n hopData.toChainId,\n HopIdentifier,\n msg.sender,\n hopData.receiverAddress,\n hopData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HopBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param hopData encoded data for HopData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n HopBridgeDataNoToken calldata hopData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n HopAMM(hopData.hopAMM).swapAndSend{value: bridgeAmount}(\n hopData.toChainId,\n hopData.receiverAddress,\n bridgeAmount,\n hopData.bonderFee,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.amountOutMinDestination,\n hopData.deadlineDestination\n );\n } else {\n // perform bridging\n HopAMM(hopData.hopAMM).swapAndSend(\n hopData.toChainId,\n hopData.receiverAddress,\n bridgeAmount,\n hopData.bonderFee,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.amountOutMinDestination,\n hopData.deadlineDestination\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n hopData.toChainId,\n HopIdentifier,\n msg.sender,\n hopData.receiverAddress,\n hopData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Hop-L2-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress The address receiving funds at the destination\n * @param token token being bridged\n * @param hopAMM AMM address of Hop on L2\n * @param amount The amount being bridged\n * @param toChainId The chainId of the destination chain\n * @param hopBridgeRequestData extraData for Bridging across Hop-L2\n */\n function bridgeERC20To(\n address receiverAddress,\n address token,\n address hopAMM,\n uint256 amount,\n uint256 toChainId,\n HopBridgeRequestData calldata hopBridgeRequestData\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n\n HopAMM(hopAMM).swapAndSend(\n toChainId,\n receiverAddress,\n amount,\n hopBridgeRequestData.bonderFee,\n hopBridgeRequestData.amountOutMin,\n hopBridgeRequestData.deadline,\n hopBridgeRequestData.amountOutMinDestination,\n hopBridgeRequestData.deadlineDestination\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n HopIdentifier,\n msg.sender,\n receiverAddress,\n hopBridgeRequestData.metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Hop-L2-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress The address receiving funds at the destination\n * @param hopAMM AMM address of Hop on L2\n * @param amount The amount being bridged\n * @param toChainId The chainId of the destination chain\n * @param bonderFee fees passed to relayer\n * @param amountOutMin The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n * @param deadline The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n * @param amountOutMinDestination Minimum amount expected to be received or bridged to destination\n * @param deadlineDestination deadline for bridging to destination\n */\n function bridgeNativeTo(\n address receiverAddress,\n address hopAMM,\n uint256 amount,\n uint256 toChainId,\n uint256 bonderFee,\n uint256 amountOutMin,\n uint256 deadline,\n uint256 amountOutMinDestination,\n uint256 deadlineDestination,\n bytes32 metadata\n ) external payable {\n // token address might not be indication thats why passed through extraData\n // perform bridging\n HopAMM(hopAMM).swapAndSend{value: amount}(\n toChainId,\n receiverAddress,\n amount,\n bonderFee,\n amountOutMin,\n deadline,\n amountOutMinDestination,\n deadlineDestination\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n HopIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/anyswap-router-v4/l2/Anyswap.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {ANYSWAP} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Anyswap-V4-Route L1 Implementation\n * @notice Route implementation with functions to bridge ERC20 via Anyswap-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of AnyswapImplementation\n * This is the L2 implementation, so this is used when transferring from l2.\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ninterface AnyswapV4Router {\n function anySwapOutUnderlying(\n address token,\n address to,\n uint256 amount,\n uint256 toChainID\n ) external;\n}\n\ncontract AnyswapL2Impl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable AnyswapIdentifier = ANYSWAP;\n\n /// @notice Function-selector for ERC20-token bridging on Anyswap-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable ANYSWAP_L2_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(uint256,uint256,bytes32,address,address,address)\"\n )\n );\n\n bytes4 public immutable ANYSWAP_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(uint256,address,address,bytes32))\"\n )\n );\n\n // polygon router multichain router v4\n AnyswapV4Router public immutable router;\n\n /**\n * @notice Constructor sets the router address and socketGateway address.\n * @dev anyswap v4 router is immutable. so no setter function required.\n */\n constructor(\n address _router,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = AnyswapV4Router(_router);\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct AnyswapBridgeDataNoToken {\n /// @notice destination ChainId\n uint256 toChainId;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of wrapperToken, WrappedVersion of the token being bridged\n address wrapperTokenAddress;\n /// @notice socket offchain created hash\n bytes32 metadata;\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct AnyswapBridgeData {\n /// @notice destination ChainId\n uint256 toChainId;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of wrapperToken, WrappedVersion of the token being bridged\n address wrapperTokenAddress;\n /// @notice address of token being bridged\n address token;\n /// @notice socket offchain created hash\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AnyswapBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for AnyswapBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n AnyswapBridgeData memory anyswapBridgeData = abi.decode(\n bridgeData,\n (AnyswapBridgeData)\n );\n ERC20(anyswapBridgeData.token).safeApprove(address(router), amount);\n router.anySwapOutUnderlying(\n anyswapBridgeData.wrapperTokenAddress,\n anyswapBridgeData.receiverAddress,\n amount,\n anyswapBridgeData.toChainId\n );\n\n emit SocketBridge(\n amount,\n anyswapBridgeData.token,\n anyswapBridgeData.toChainId,\n AnyswapIdentifier,\n msg.sender,\n anyswapBridgeData.receiverAddress,\n anyswapBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AnyswapBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param anyswapBridgeData encoded data for AnyswapBridge\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n AnyswapBridgeDataNoToken calldata anyswapBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n ERC20(token).safeApprove(address(router), bridgeAmount);\n router.anySwapOutUnderlying(\n anyswapBridgeData.wrapperTokenAddress,\n anyswapBridgeData.receiverAddress,\n bridgeAmount,\n anyswapBridgeData.toChainId\n );\n\n emit SocketBridge(\n bridgeAmount,\n token,\n anyswapBridgeData.toChainId,\n AnyswapIdentifier,\n msg.sender,\n anyswapBridgeData.receiverAddress,\n anyswapBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Anyswap-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount being bridged\n * @param toChainId destination ChainId\n * @param receiverAddress address of receiver of bridged tokens\n * @param token address of token being bridged\n * @param wrapperTokenAddress address of wrapperToken, WrappedVersion of the token being bridged\n */\n function bridgeERC20To(\n uint256 amount,\n uint256 toChainId,\n bytes32 metadata,\n address receiverAddress,\n address token,\n address wrapperTokenAddress\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(address(router), amount);\n router.anySwapOutUnderlying(\n wrapperTokenAddress,\n receiverAddress,\n amount,\n toChainId\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n AnyswapIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/hyphen/Hyphen.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"./interfaces/hyphen.sol\";\nimport \"../BridgeImplBase.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {HYPHEN} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Hyphen-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Hyphen-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of HyphenImplementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract HyphenImpl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable HyphenIdentifier = HYPHEN;\n\n /// @notice Function-selector for ERC20-token bridging on Hyphen-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable HYPHEN_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\"bridgeERC20To(uint256,bytes32,address,address,uint256)\")\n );\n\n /// @notice Function-selector for Native bridging on Hyphen-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4 public immutable HYPHEN_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(keccak256(\"bridgeNativeTo(uint256,bytes32,address,uint256)\"));\n\n bytes4 public immutable HYPHEN_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\"swapAndBridge(uint32,bytes,(address,uint256,bytes32))\")\n );\n\n /// @notice liquidityPoolManager - liquidityPool Manager of Hyphen used to bridge ERC20 and native\n /// @dev this is to be initialized in constructor with a valid deployed address of hyphen-liquidityPoolManager\n HyphenLiquidityPoolManager public immutable liquidityPoolManager;\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure liquidityPoolManager-address are set properly for the chainId in which the contract is being deployed\n constructor(\n address _liquidityPoolManager,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n liquidityPoolManager = HyphenLiquidityPoolManager(\n _liquidityPoolManager\n );\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct HyphenData {\n /// @notice address of token being bridged\n address token;\n /// @notice address of receiver\n address receiverAddress;\n /// @notice chainId of destination\n uint256 toChainId;\n /// @notice socket offchain created hash\n bytes32 metadata;\n }\n\n struct HyphenDataNoToken {\n /// @notice address of receiver\n address receiverAddress;\n /// @notice chainId of destination\n uint256 toChainId;\n /// @notice chainId of destination\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HyphenBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for HyphenBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n HyphenData memory hyphenData = abi.decode(bridgeData, (HyphenData));\n\n if (hyphenData.token == NATIVE_TOKEN_ADDRESS) {\n liquidityPoolManager.depositNative{value: amount}(\n hyphenData.receiverAddress,\n hyphenData.toChainId,\n \"SOCKET\"\n );\n } else {\n ERC20(hyphenData.token).safeApprove(\n address(liquidityPoolManager),\n amount\n );\n liquidityPoolManager.depositErc20(\n hyphenData.toChainId,\n hyphenData.token,\n hyphenData.receiverAddress,\n amount,\n \"SOCKET\"\n );\n }\n\n emit SocketBridge(\n amount,\n hyphenData.token,\n hyphenData.toChainId,\n HyphenIdentifier,\n msg.sender,\n hyphenData.receiverAddress,\n hyphenData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HyphenBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param hyphenData encoded data for hyphenData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n HyphenDataNoToken calldata hyphenData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n if (token == NATIVE_TOKEN_ADDRESS) {\n liquidityPoolManager.depositNative{value: bridgeAmount}(\n hyphenData.receiverAddress,\n hyphenData.toChainId,\n \"SOCKET\"\n );\n } else {\n ERC20(token).safeApprove(\n address(liquidityPoolManager),\n bridgeAmount\n );\n liquidityPoolManager.depositErc20(\n hyphenData.toChainId,\n token,\n hyphenData.receiverAddress,\n bridgeAmount,\n \"SOCKET\"\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n hyphenData.toChainId,\n HyphenIdentifier,\n msg.sender,\n hyphenData.receiverAddress,\n hyphenData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Hyphen-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount to be sent\n * @param receiverAddress address of the token to bridged to the destination chain.\n * @param token address of token being bridged\n * @param toChainId chainId of destination\n */\n function bridgeERC20To(\n uint256 amount,\n bytes32 metadata,\n address receiverAddress,\n address token,\n uint256 toChainId\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(address(liquidityPoolManager), amount);\n liquidityPoolManager.depositErc20(\n toChainId,\n token,\n receiverAddress,\n amount,\n \"SOCKET\"\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n HyphenIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Hyphen-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount to be sent\n * @param receiverAddress address of the token to bridged to the destination chain.\n * @param toChainId chainId of destination\n */\n function bridgeNativeTo(\n uint256 amount,\n bytes32 metadata,\n address receiverAddress,\n uint256 toChainId\n ) external payable {\n liquidityPoolManager.depositNative{value: amount}(\n receiverAddress,\n toChainId,\n \"SOCKET\"\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n HyphenIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/optimism/l1/NativeOptimism.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../interfaces/optimism.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {UnsupportedInterfaceId} from \"../../../errors/SocketErrors.sol\";\nimport {NATIVE_OPTIMISM} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title NativeOptimism-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via NativeOptimism-Bridge\n * Tokens are bridged from Ethereum to Optimism Chain.\n * Called via SocketGateway if the routeId in the request maps to the routeId of NativeOptimism-Implementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract NativeOptimismImpl is BridgeImplBase {\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable NativeOptimismIdentifier = NATIVE_OPTIMISM;\n\n uint256 public constant DESTINATION_CHAIN_ID = 10;\n\n /// @notice Function-selector for ERC20-token bridging on Native-Optimism-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4\n public immutable NATIVE_OPTIMISM_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,address,uint32,(bytes32,bytes32),uint256,uint256,address,bytes)\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Native-Optimism-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native balance\n bytes4\n public immutable NATIVE_OPTIMISM_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,address,uint32,uint256,bytes32,bytes)\"\n )\n );\n\n bytes4 public immutable NATIVE_OPTIMISM_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(uint256,bytes32,bytes32,address,address,uint32,address,bytes))\"\n )\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n constructor(\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {}\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct OptimismBridgeDataNoToken {\n // interfaceId to be set offchain which is used to select one of the 3 kinds of bridging (standard bridge / old standard / synthetic)\n uint256 interfaceId;\n // currencyKey of the token beingBridged\n bytes32 currencyKey;\n // socket offchain created hash\n bytes32 metadata;\n // address of receiver of bridged tokens\n address receiverAddress;\n /**\n * OptimismBridge that Performs the logic for deposits by informing the L2 Deposited Token\n * contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)\n */\n address customBridgeAddress;\n // Gas limit required to complete the deposit on L2.\n uint32 l2Gas;\n // Address of the L1 respective L2 ERC20\n address l2Token;\n // additional data , for ll contracts this will be 0x data or empty data\n bytes data;\n }\n\n struct OptimismBridgeData {\n // interfaceId to be set offchain which is used to select one of the 3 kinds of bridging (standard bridge / old standard / synthetic)\n uint256 interfaceId;\n // currencyKey of the token beingBridged\n bytes32 currencyKey;\n // socket offchain created hash\n bytes32 metadata;\n // address of receiver of bridged tokens\n address receiverAddress;\n /**\n * OptimismBridge that Performs the logic for deposits by informing the L2 Deposited Token\n * contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)\n */\n address customBridgeAddress;\n /// @notice address of token being bridged\n address token;\n // Gas limit required to complete the deposit on L2.\n uint32 l2Gas;\n // Address of the L1 respective L2 ERC20\n address l2Token;\n // additional data , for ll contracts this will be 0x data or empty data\n bytes data;\n }\n\n struct OptimismERC20Data {\n bytes32 currencyKey;\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in OptimismBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for Optimism-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n OptimismBridgeData memory optimismBridgeData = abi.decode(\n bridgeData,\n (OptimismBridgeData)\n );\n\n emit SocketBridge(\n amount,\n optimismBridgeData.token,\n DESTINATION_CHAIN_ID,\n NativeOptimismIdentifier,\n msg.sender,\n optimismBridgeData.receiverAddress,\n optimismBridgeData.metadata\n );\n if (optimismBridgeData.token == NATIVE_TOKEN_ADDRESS) {\n L1StandardBridge(optimismBridgeData.customBridgeAddress)\n .depositETHTo{value: amount}(\n optimismBridgeData.receiverAddress,\n optimismBridgeData.l2Gas,\n optimismBridgeData.data\n );\n } else {\n if (optimismBridgeData.interfaceId == 0) {\n revert UnsupportedInterfaceId();\n }\n\n ERC20(optimismBridgeData.token).safeApprove(\n optimismBridgeData.customBridgeAddress,\n amount\n );\n\n if (optimismBridgeData.interfaceId == 1) {\n // deposit into standard bridge\n L1StandardBridge(optimismBridgeData.customBridgeAddress)\n .depositERC20To(\n optimismBridgeData.token,\n optimismBridgeData.l2Token,\n optimismBridgeData.receiverAddress,\n amount,\n optimismBridgeData.l2Gas,\n optimismBridgeData.data\n );\n return;\n }\n\n // Deposit Using Old Standard - iOVM_L1TokenGateway(Example - SNX Token)\n if (optimismBridgeData.interfaceId == 2) {\n OldL1TokenGateway(optimismBridgeData.customBridgeAddress)\n .depositTo(optimismBridgeData.receiverAddress, amount);\n return;\n }\n\n if (optimismBridgeData.interfaceId == 3) {\n OldL1TokenGateway(optimismBridgeData.customBridgeAddress)\n .initiateSynthTransfer(\n optimismBridgeData.currencyKey,\n optimismBridgeData.receiverAddress,\n amount\n );\n return;\n }\n }\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in OptimismBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param optimismBridgeData encoded data for OptimismBridgeData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n OptimismBridgeDataNoToken calldata optimismBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n emit SocketBridge(\n bridgeAmount,\n token,\n DESTINATION_CHAIN_ID,\n NativeOptimismIdentifier,\n msg.sender,\n optimismBridgeData.receiverAddress,\n optimismBridgeData.metadata\n );\n if (token == NATIVE_TOKEN_ADDRESS) {\n L1StandardBridge(optimismBridgeData.customBridgeAddress)\n .depositETHTo{value: bridgeAmount}(\n optimismBridgeData.receiverAddress,\n optimismBridgeData.l2Gas,\n optimismBridgeData.data\n );\n } else {\n if (optimismBridgeData.interfaceId == 0) {\n revert UnsupportedInterfaceId();\n }\n\n ERC20(token).safeApprove(\n optimismBridgeData.customBridgeAddress,\n bridgeAmount\n );\n\n if (optimismBridgeData.interfaceId == 1) {\n // deposit into standard bridge\n L1StandardBridge(optimismBridgeData.customBridgeAddress)\n .depositERC20To(\n token,\n optimismBridgeData.l2Token,\n optimismBridgeData.receiverAddress,\n bridgeAmount,\n optimismBridgeData.l2Gas,\n optimismBridgeData.data\n );\n return;\n }\n\n // Deposit Using Old Standard - iOVM_L1TokenGateway(Example - SNX Token)\n if (optimismBridgeData.interfaceId == 2) {\n OldL1TokenGateway(optimismBridgeData.customBridgeAddress)\n .depositTo(\n optimismBridgeData.receiverAddress,\n bridgeAmount\n );\n return;\n }\n\n if (optimismBridgeData.interfaceId == 3) {\n OldL1TokenGateway(optimismBridgeData.customBridgeAddress)\n .initiateSynthTransfer(\n optimismBridgeData.currencyKey,\n optimismBridgeData.receiverAddress,\n bridgeAmount\n );\n return;\n }\n }\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via NativeOptimism-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param token address of token being bridged\n * @param receiverAddress address of receiver of bridged tokens\n * @param customBridgeAddress OptimismBridge that Performs the logic for deposits by informing the L2 Deposited Token\n * contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)\n * @param l2Gas Gas limit required to complete the deposit on L2.\n * @param optimismData extra data needed for optimism bridge\n * @param amount amount being bridged\n * @param interfaceId interfaceId to be set offchain which is used to select one of the 3 kinds of bridging (standard bridge / old standard / synthetic)\n * @param l2Token Address of the L1 respective L2 ERC20\n * @param data additional data , for ll contracts this will be 0x data or empty data\n */\n function bridgeERC20To(\n address token,\n address receiverAddress,\n address customBridgeAddress,\n uint32 l2Gas,\n OptimismERC20Data calldata optimismData,\n uint256 amount,\n uint256 interfaceId,\n address l2Token,\n bytes calldata data\n ) external payable {\n if (interfaceId == 0) {\n revert UnsupportedInterfaceId();\n }\n\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(customBridgeAddress, amount);\n\n emit SocketBridge(\n amount,\n token,\n DESTINATION_CHAIN_ID,\n NativeOptimismIdentifier,\n msg.sender,\n receiverAddress,\n optimismData.metadata\n );\n if (interfaceId == 1) {\n // deposit into standard bridge\n L1StandardBridge(customBridgeAddress).depositERC20To(\n token,\n l2Token,\n receiverAddress,\n amount,\n l2Gas,\n data\n );\n return;\n }\n\n // Deposit Using Old Standard - iOVM_L1TokenGateway(Example - SNX Token)\n if (interfaceId == 2) {\n OldL1TokenGateway(customBridgeAddress).depositTo(\n receiverAddress,\n amount\n );\n return;\n }\n\n if (interfaceId == 3) {\n OldL1TokenGateway(customBridgeAddress).initiateSynthTransfer(\n optimismData.currencyKey,\n receiverAddress,\n amount\n );\n return;\n }\n }\n\n /**\n * @notice function to handle native balance bridging to receipent via NativeOptimism-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress address of receiver of bridged tokens\n * @param customBridgeAddress OptimismBridge that Performs the logic for deposits by informing the L2 Deposited Token\n * contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)\n * @param l2Gas Gas limit required to complete the deposit on L2.\n * @param amount amount being bridged\n * @param data additional data , for ll contracts this will be 0x data or empty data\n */\n function bridgeNativeTo(\n address receiverAddress,\n address customBridgeAddress,\n uint32 l2Gas,\n uint256 amount,\n bytes32 metadata,\n bytes calldata data\n ) external payable {\n L1StandardBridge(customBridgeAddress).depositETHTo{value: amount}(\n receiverAddress,\n l2Gas,\n data\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n DESTINATION_CHAIN_ID,\n NativeOptimismIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/deployFactory/SocketDeployFactory.sol":{"content":"//SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"../utils/Ownable.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {ISocketBridgeBase} from \"../interfaces/ISocketBridgeBase.sol\";\n\n/**\n * @dev In the constructor, set up the initialization code for socket\n * contracts as well as the keccak256 hash of the given initialization code.\n * that will be used to deploy any transient contracts, which will deploy any\n * socket contracts that require the use of a constructor.\n *\n * Socket contract initialization code (29 bytes):\n *\n * 0x5860208158601c335a63aaf10f428752fa158151803b80938091923cf3\n *\n * Description:\n *\n * pc|op|name | [stack] | \n *\n * ** set the first stack item to zero - used later **\n * 00 58 getpc [0] <>\n *\n * ** set second stack item to 32, length of word returned from staticcall **\n * 01 60 push1\n * 02 20 outsize [0, 32] <>\n *\n * ** set third stack item to 0, position of word returned from staticcall **\n * 03 81 dup2 [0, 32, 0] <>\n *\n * ** set fourth stack item to 4, length of selector given to staticcall **\n * 04 58 getpc [0, 32, 0, 4] <>\n *\n * ** set fifth stack item to 28, position of selector given to staticcall **\n * 05 60 push1\n * 06 1c inpos [0, 32, 0, 4, 28] <>\n *\n * ** set the sixth stack item to msg.sender, target address for staticcall **\n * 07 33 caller [0, 32, 0, 4, 28, caller] <>\n *\n * ** set the seventh stack item to msg.gas, gas to forward for staticcall **\n * 08 5a gas [0, 32, 0, 4, 28, caller, gas] <>\n *\n * ** set the eighth stack item to selector, \"what\" to store via mstore **\n * 09 63 push4\n * 10 aaf10f42 selector [0, 32, 0, 4, 28, caller, gas, 0xaaf10f42] <>\n *\n * ** set the ninth stack item to 0, \"where\" to store via mstore ***\n * 11 87 dup8 [0, 32, 0, 4, 28, caller, gas, 0xaaf10f42, 0] <>\n *\n * ** call mstore, consume 8 and 9 from the stack, place selector in memory **\n * 12 52 mstore [0, 32, 0, 4, 0, caller, gas] <0xaaf10f42>\n *\n * ** call staticcall, consume items 2 through 7, place address in memory **\n * 13 fa staticcall [0, 1 (if successful)]
\n *\n * ** flip success bit in second stack item to set to 0 **\n * 14 15 iszero [0, 0]
\n *\n * ** push a third 0 to the stack, position of address in memory **\n * 15 81 dup2 [0, 0, 0]
\n *\n * ** place address from position in memory onto third stack item **\n * 16 51 mload [0, 0, address] <>\n *\n * ** place address to fourth stack item for extcodesize to consume **\n * 17 80 dup1 [0, 0, address, address] <>\n *\n * ** get extcodesize on fourth stack item for extcodecopy **\n * 18 3b extcodesize [0, 0, address, size] <>\n *\n * ** dup and swap size for use by return at end of init code **\n * 19 80 dup1 [0, 0, address, size, size] <>\n * 20 93 swap4 [size, 0, address, size, 0] <>\n *\n * ** push code position 0 to stack and reorder stack items for extcodecopy **\n * 21 80 dup1 [size, 0, address, size, 0, 0] <>\n * 22 91 swap2 [size, 0, address, 0, 0, size] <>\n * 23 92 swap3 [size, 0, size, 0, 0, address] <>\n *\n * ** call extcodecopy, consume four items, clone runtime code to memory **\n * 24 3c extcodecopy [size, 0] \n *\n * ** return to deploy final code in memory **\n * 25 f3 return [] *deployed!*\n */\ncontract SocketDeployFactory is Ownable {\n using SafeTransferLib for ERC20;\n address public immutable disabledRouteAddress;\n\n mapping(address => address) _implementations;\n mapping(uint256 => bool) isDisabled;\n mapping(uint256 => bool) isRouteDeployed;\n mapping(address => bool) canDisableRoute;\n\n event Deployed(address _addr);\n event DisabledRoute(address _addr);\n event Destroyed(address _addr);\n error ContractAlreadyDeployed();\n error NothingToDestroy();\n error AlreadyDisabled();\n error CannotBeDisabled();\n error OnlyDisabler();\n\n constructor(address _owner, address disabledRoute) Ownable(_owner) {\n disabledRouteAddress = disabledRoute;\n canDisableRoute[_owner] = true;\n }\n\n modifier onlyDisabler() {\n if (!canDisableRoute[msg.sender]) {\n revert OnlyDisabler();\n }\n _;\n }\n\n function addDisablerAddress(address disabler) external onlyOwner {\n canDisableRoute[disabler] = true;\n }\n\n function removeDisablerAddress(address disabler) external onlyOwner {\n canDisableRoute[disabler] = false;\n }\n\n /**\n * @notice Deploys a route contract at predetermined location\n * @notice Caller must first deploy the route contract at another location and pass its address as implementation.\n * @param routeId route identifier\n * @param implementationContract address of deployed route contract. Its byte code will be copied to predetermined location.\n */\n function deploy(\n uint256 routeId,\n address implementationContract\n ) external onlyOwner returns (address) {\n // assign the initialization code for the socket contract.\n\n bytes memory initCode = (\n hex\"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3\"\n );\n\n // determine the address of the socket contract.\n address routeContractAddress = _getContractAddress(routeId);\n\n if (isRouteDeployed[routeId]) {\n revert ContractAlreadyDeployed();\n }\n\n isRouteDeployed[routeId] = true;\n\n //first we deploy the code we want to deploy on a separate address\n // store the implementation to be retrieved by the socket contract.\n _implementations[routeContractAddress] = implementationContract;\n address addr;\n assembly {\n let encoded_data := add(0x20, initCode) // load initialization code.\n let encoded_size := mload(initCode) // load init code's length.\n addr := create2(0, encoded_data, encoded_size, routeId) // routeId is used as salt\n }\n require(\n addr == routeContractAddress,\n \"Failed to deploy the new socket contract.\"\n );\n emit Deployed(addr);\n return addr;\n }\n\n /**\n * @notice Destroy the route deployed at a location.\n * @param routeId route identifier to be destroyed.\n */\n function destroy(uint256 routeId) external onlyDisabler {\n // determine the address of the socket contract.\n _destroy(routeId);\n }\n\n /**\n * @notice Deploy a disabled contract at destroyed route to handle it gracefully.\n * @param routeId route identifier to be disabled.\n */\n function disableRoute(\n uint256 routeId\n ) external onlyDisabler returns (address) {\n return _disableRoute(routeId);\n }\n\n /**\n * @notice Destroy a list of routeIds\n * @param routeIds array of routeIds to be destroyed.\n */\n function multiDestroy(uint256[] calldata routeIds) external onlyDisabler {\n for (uint32 index = 0; index < routeIds.length; ) {\n _destroy(routeIds[index]);\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice Deploy a disabled contract at list of routeIds.\n * @param routeIds array of routeIds to be disabled.\n */\n function multiDisableRoute(\n uint256[] calldata routeIds\n ) external onlyDisabler {\n for (uint32 index = 0; index < routeIds.length; ) {\n _disableRoute(routeIds[index]);\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @dev External view function for calculating a socket contract address\n * given a particular routeId.\n */\n function getContractAddress(\n uint256 routeId\n ) external view returns (address) {\n // determine the address of the socket contract.\n return _getContractAddress(routeId);\n }\n\n //those two functions are getting called by the socket Contract\n function getImplementation()\n external\n view\n returns (address implementation)\n {\n return _implementations[msg.sender];\n }\n\n function _disableRoute(uint256 routeId) internal returns (address) {\n // assign the initialization code for the socket contract.\n bytes memory initCode = (\n hex\"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3\"\n );\n\n // determine the address of the socket contract.\n address routeContractAddress = _getContractAddress(routeId);\n\n if (!isRouteDeployed[routeId]) {\n revert CannotBeDisabled();\n }\n\n if (isDisabled[routeId]) {\n revert AlreadyDisabled();\n }\n\n isDisabled[routeId] = true;\n\n //first we deploy the code we want to deploy on a separate address\n // store the implementation to be retrieved by the socket contract.\n _implementations[routeContractAddress] = disabledRouteAddress;\n address addr;\n assembly {\n let encoded_data := add(0x20, initCode) // load initialization code.\n let encoded_size := mload(initCode) // load init code's length.\n addr := create2(0, encoded_data, encoded_size, routeId) // routeId is used as salt.\n }\n require(\n addr == routeContractAddress,\n \"Failed to deploy the new socket contract.\"\n );\n emit Deployed(addr);\n return addr;\n }\n\n function _destroy(uint256 routeId) internal {\n // determine the address of the socket contract.\n address routeContractAddress = _getContractAddress(routeId);\n\n if (!isRouteDeployed[routeId]) {\n revert NothingToDestroy();\n }\n ISocketBridgeBase(routeContractAddress).killme();\n emit Destroyed(routeContractAddress);\n }\n\n /**\n * @dev Internal view function for calculating a socket contract address\n * given a particular routeId.\n */\n function _getContractAddress(\n uint256 routeId\n ) internal view returns (address) {\n // determine the address of the socket contract.\n\n bytes memory initCode = (\n hex\"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3\"\n );\n return\n address(\n uint160( // downcast to match the address type.\n uint256( // convert to uint to truncate upper digits.\n keccak256( // compute the CREATE2 hash using 4 inputs.\n abi.encodePacked( // pack all inputs to the hash together.\n hex\"ff\", // start with 0xff to distinguish from RLP.\n address(this), // this contract will be the caller.\n routeId, // the routeId is used as salt.\n keccak256(abi.encodePacked(initCode)) // the init code hash.\n )\n )\n )\n )\n );\n }\n\n /**\n * @notice Rescues the ERC20 token to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param token address of the ERC20 token being rescued\n * @param userAddress address to which ERC20 is to be rescued\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external onlyOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice Rescues the native balance to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param userAddress address to which native-balance is to be rescued\n * @param amount amount of native-balance being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external onlyOwner {\n userAddress.transfer(amount);\n }\n}\n"},"src/interfaces/ISocketController.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/**\n * @title ISocketController\n * @notice Interface for SocketController functions.\n * @dev functions can be added here for invocation from external contracts or off-chain\n * only restriction is that this should have functions to manage controllers\n * @author Socket dot tech.\n */\ninterface ISocketController {\n /**\n * @notice Add controller to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure controllerAddress is a verified controller implementation address\n * @param _controllerAddress The address of controller implementation contract deployed\n * @return Id of the controller added to the controllers-mapping in socketGateway storage\n */\n function addController(\n address _controllerAddress\n ) external returns (uint32);\n\n /**\n * @notice disable controller by setting ZeroAddress to the entry in controllers-mapping\n identified by controllerId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param _controllerId The Id of controller-implementation in the controllers mapping\n */\n function disableController(uint32 _controllerId) external;\n\n /**\n * @notice Get controllerImplementation address mapped to the controllerId\n * @param _controllerId controllerId is the key in the mapping for controllers\n * @return controller-implementation address\n */\n function getController(uint32 _controllerId) external returns (address);\n}\n"},"lib/solmate/src/utils/SafeTransferLib.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-only\npragma solidity >=0.8.0;\n\nimport {ERC20} from \"../tokens/ERC20.sol\";\n\n/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.\n/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)\n/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.\n/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.\nlibrary SafeTransferLib {\n /*//////////////////////////////////////////////////////////////\n ETH OPERATIONS\n //////////////////////////////////////////////////////////////*/\n\n function safeTransferETH(address to, uint256 amount) internal {\n bool success;\n\n /// @solidity memory-safe-assembly\n assembly {\n // Transfer the ETH and store if it succeeded or not.\n success := call(gas(), to, amount, 0, 0, 0, 0)\n }\n\n require(success, \"ETH_TRANSFER_FAILED\");\n }\n\n /*//////////////////////////////////////////////////////////////\n ERC20 OPERATIONS\n //////////////////////////////////////////////////////////////*/\n\n function safeTransferFrom(\n ERC20 token,\n address from,\n address to,\n uint256 amount\n ) internal {\n bool success;\n\n /// @solidity memory-safe-assembly\n assembly {\n // Get a pointer to some free memory.\n let freeMemoryPointer := mload(0x40)\n\n // Write the abi-encoded calldata into memory, beginning with the function selector.\n mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)\n mstore(add(freeMemoryPointer, 4), from) // Append the \"from\" argument.\n mstore(add(freeMemoryPointer, 36), to) // Append the \"to\" argument.\n mstore(add(freeMemoryPointer, 68), amount) // Append the \"amount\" argument.\n\n success := and(\n // Set success to whether the call reverted, if not we check it either\n // returned exactly 1 (can't just be non-zero data), or had no return data.\n or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),\n // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.\n // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.\n // Counterintuitively, this call must be positioned second to the or() call in the\n // surrounding and() call or else returndatasize() will be zero during the computation.\n call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)\n )\n }\n\n require(success, \"TRANSFER_FROM_FAILED\");\n }\n\n function safeTransfer(\n ERC20 token,\n address to,\n uint256 amount\n ) internal {\n bool success;\n\n /// @solidity memory-safe-assembly\n assembly {\n // Get a pointer to some free memory.\n let freeMemoryPointer := mload(0x40)\n\n // Write the abi-encoded calldata into memory, beginning with the function selector.\n mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)\n mstore(add(freeMemoryPointer, 4), to) // Append the \"to\" argument.\n mstore(add(freeMemoryPointer, 36), amount) // Append the \"amount\" argument.\n\n success := and(\n // Set success to whether the call reverted, if not we check it either\n // returned exactly 1 (can't just be non-zero data), or had no return data.\n or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),\n // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.\n // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.\n // Counterintuitively, this call must be positioned second to the or() call in the\n // surrounding and() call or else returndatasize() will be zero during the computation.\n call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)\n )\n }\n\n require(success, \"TRANSFER_FAILED\");\n }\n\n function safeApprove(\n ERC20 token,\n address to,\n uint256 amount\n ) internal {\n bool success;\n\n /// @solidity memory-safe-assembly\n assembly {\n // Get a pointer to some free memory.\n let freeMemoryPointer := mload(0x40)\n\n // Write the abi-encoded calldata into memory, beginning with the function selector.\n mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)\n mstore(add(freeMemoryPointer, 4), to) // Append the \"to\" argument.\n mstore(add(freeMemoryPointer, 36), amount) // Append the \"amount\" argument.\n\n success := and(\n // Set success to whether the call reverted, if not we check it either\n // returned exactly 1 (can't just be non-zero data), or had no return data.\n or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),\n // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.\n // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.\n // Counterintuitively, this call must be positioned second to the or() call in the\n // surrounding and() call or else returndatasize() will be zero during the computation.\n call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)\n )\n }\n\n require(success, \"APPROVE_FAILED\");\n }\n}\n"},"src/controllers/BaseController.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {ISocketRequest} from \"../interfaces/ISocketRequest.sol\";\nimport {ISocketRoute} from \"../interfaces/ISocketRoute.sol\";\n\n/// @title BaseController Controller\n/// @notice Base contract for all controller contracts\nabstract contract BaseController {\n /// @notice Address used to identify if it is a native token transfer or not\n address public immutable NATIVE_TOKEN_ADDRESS =\n address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);\n\n /// @notice Address used to identify if it is a Zero address\n address public immutable NULL_ADDRESS = address(0);\n\n /// @notice FunctionSelector used to delegatecall from swap to the function of bridge router implementation\n bytes4 public immutable BRIDGE_AFTER_SWAP_SELECTOR =\n bytes4(keccak256(\"bridgeAfterSwap(uint256,bytes)\"));\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketGatewayAddress;\n\n /// @notice immutable variable with instance of SocketRoute to access route functions\n ISocketRoute public immutable socketRoute;\n\n /**\n * @notice Construct the base for all controllers.\n * @param _socketGatewayAddress Socketgateway address, an immutable variable to set.\n * @notice initialize the immutable variables of SocketRoute, SocketGateway\n */\n constructor(address _socketGatewayAddress) {\n socketGatewayAddress = _socketGatewayAddress;\n socketRoute = ISocketRoute(_socketGatewayAddress);\n }\n\n /**\n * @notice Construct the base for all BridgeImplementations.\n * @param routeId routeId mapped to the routrImplementation\n * @param data transactionData generated with arguments of bridgeRequest (offchain or by caller)\n * @return returns the bytes response of the route execution (bridging, refuel or swap executions)\n */\n function _executeRoute(\n uint32 routeId,\n bytes memory data\n ) internal returns (bytes memory) {\n (bool success, bytes memory result) = socketRoute\n .getRoute(routeId)\n .delegatecall(data);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n return result;\n }\n}\n"},"src/interfaces/ISocketRoute.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/**\n * @title ISocketRoute\n * @notice Interface for routeManagement functions in SocketGateway.\n * @author Socket dot tech.\n */\ninterface ISocketRoute {\n /**\n * @notice Add route to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure routeAddress is a verified bridge or middleware implementation address\n * @param routeAddress The address of bridge or middleware implementation contract deployed\n * @return Id of the route added to the routes-mapping in socketGateway storage\n */\n function addRoute(address routeAddress) external returns (uint256);\n\n /**\n * @notice disable a route by setting ZeroAddress to the entry in routes-mapping\n identified by routeId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param routeId The Id of route-implementation in the routes mapping\n */\n function disableRoute(uint32 routeId) external;\n\n /**\n * @notice Get routeImplementation address mapped to the routeId\n * @param routeId routeId is the key in the mapping for routes\n * @return route-implementation address\n */\n function getRoute(uint32 routeId) external view returns (address);\n}\n"},"src/SocketGatewayDeployment.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\npragma experimental ABIEncoderV2;\n\nimport \"./utils/Ownable.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {LibUtil} from \"./libraries/LibUtil.sol\";\nimport \"./libraries/LibBytes.sol\";\nimport {ISocketRoute} from \"./interfaces/ISocketRoute.sol\";\nimport {ISocketRequest} from \"./interfaces/ISocketRequest.sol\";\nimport {ISocketGateway} from \"./interfaces/ISocketGateway.sol\";\nimport {IncorrectBridgeRatios, ZeroAddressNotAllowed, ArrayLengthMismatch} from \"./errors/SocketErrors.sol\";\n\n/// @title SocketGatewayContract\n/// @notice Socketgateway is a contract with entrypoint functions for all interactions with socket liquidity layer\n/// @author Socket Team\ncontract SocketGateway is Ownable {\n using LibBytes for bytes;\n using LibBytes for bytes4;\n using SafeTransferLib for ERC20;\n\n /// @notice FunctionSelector used to delegatecall from swap to the function of bridge router implementation\n bytes4 public immutable BRIDGE_AFTER_SWAP_SELECTOR =\n bytes4(keccak256(\"bridgeAfterSwap(uint256,bytes)\"));\n\n /// @notice storage variable to keep track of total number of routes registered in socketgateway\n uint32 public routesCount = 385;\n\n /// @notice storage variable to keep track of total number of controllers registered in socketgateway\n uint32 public controllerCount;\n\n address public immutable disabledRouteAddress;\n\n uint256 public constant CENT_PERCENT = 100e18;\n\n /// @notice storage mapping for route implementation addresses\n mapping(uint32 => address) public routes;\n\n /// storage mapping for controller implemenation addresses\n mapping(uint32 => address) public controllers;\n\n // Events ------------------------------------------------------------------------------------------------------->\n\n /// @notice Event emitted when a router is added to socketgateway\n event NewRouteAdded(uint32 indexed routeId, address indexed route);\n\n /// @notice Event emitted when a route is disabled\n event RouteDisabled(uint32 indexed routeId);\n\n /// @notice Event emitted when ownership transfer is requested by socket-gateway-owner\n event OwnershipTransferRequested(\n address indexed _from,\n address indexed _to\n );\n\n /// @notice Event emitted when a controller is added to socketgateway\n event ControllerAdded(\n uint32 indexed controllerId,\n address indexed controllerAddress\n );\n\n /// @notice Event emitted when a controller is disabled\n event ControllerDisabled(uint32 indexed controllerId);\n\n constructor(address _owner, address _disabledRoute) Ownable(_owner) {\n disabledRouteAddress = _disabledRoute;\n }\n\n // Able to receive ether\n // solhint-disable-next-line no-empty-blocks\n receive() external payable {}\n\n /*******************************************\n * EXTERNAL AND PUBLIC FUNCTIONS *\n *******************************************/\n\n /**\n * @notice executes functions in the routes identified using routeId and functionSelectorData\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in routeData to be built using the function-selector defined as a\n * constant in the route implementation contract\n * @param routeId route identifier\n * @param routeData functionSelectorData generated using the function-selector defined in the route Implementation\n */\n function executeRoute(\n uint32 routeId,\n bytes calldata routeData\n ) external payable returns (bytes memory) {\n (bool success, bytes memory result) = addressAt(routeId).delegatecall(\n routeData\n );\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n return result;\n }\n\n /**\n * @notice swaps a token on sourceChain and split it across multiple bridge-recipients\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being swapped\n * @dev ensure the swap-data and bridge-data is generated using the function-selector defined as a constant in the implementation address\n * @param swapMultiBridgeRequest request\n */\n function swapAndMultiBridge(\n ISocketRequest.SwapMultiBridgeRequest calldata swapMultiBridgeRequest\n ) external payable {\n uint256 requestLength = swapMultiBridgeRequest.bridgeRouteIds.length;\n\n if (\n requestLength != swapMultiBridgeRequest.bridgeImplDataItems.length\n ) {\n revert ArrayLengthMismatch();\n }\n uint256 ratioAggregate;\n for (uint256 index = 0; index < requestLength; ) {\n ratioAggregate += swapMultiBridgeRequest.bridgeRatios[index];\n }\n\n if (ratioAggregate != CENT_PERCENT) {\n revert IncorrectBridgeRatios();\n }\n\n (bool swapSuccess, bytes memory swapResult) = addressAt(\n swapMultiBridgeRequest.swapRouteId\n ).delegatecall(swapMultiBridgeRequest.swapImplData);\n\n if (!swapSuccess) {\n assembly {\n revert(add(swapResult, 32), mload(swapResult))\n }\n }\n\n uint256 amountReceivedFromSwap = abi.decode(swapResult, (uint256));\n\n uint256 bridgedAmount;\n\n for (uint256 index = 0; index < requestLength; ) {\n uint256 bridgingAmount;\n\n // if it is the last bridge request, bridge the remaining amount\n if (index == requestLength - 1) {\n bridgingAmount = amountReceivedFromSwap - bridgedAmount;\n } else {\n // bridging amount is the multiplication of bridgeRatio and amountReceivedFromSwap\n bridgingAmount =\n (amountReceivedFromSwap *\n swapMultiBridgeRequest.bridgeRatios[index]) /\n (CENT_PERCENT);\n }\n\n // update the bridged amount, this would be used for computation for last bridgeRequest\n bridgedAmount += bridgingAmount;\n\n bytes memory bridgeImpldata = abi.encodeWithSelector(\n BRIDGE_AFTER_SWAP_SELECTOR,\n bridgingAmount,\n swapMultiBridgeRequest.bridgeImplDataItems[index]\n );\n\n (bool bridgeSuccess, bytes memory bridgeResult) = addressAt(\n swapMultiBridgeRequest.bridgeRouteIds[index]\n ).delegatecall(bridgeImpldata);\n\n if (!bridgeSuccess) {\n assembly {\n revert(add(bridgeResult, 32), mload(bridgeResult))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice sequentially executes functions in the routes identified using routeId and functionSelectorData\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in each dataItem to be built using the function-selector defined as a\n * constant in the route implementation contract\n * @param routeIds a list of route identifiers\n * @param dataItems a list of functionSelectorData generated using the function-selector defined in the route Implementation\n */\n function executeRoutes(\n uint32[] calldata routeIds,\n bytes[] calldata dataItems\n ) external payable {\n uint256 routeIdslength = routeIds.length;\n if (routeIdslength != dataItems.length) revert ArrayLengthMismatch();\n for (uint256 index = 0; index < routeIdslength; ) {\n (bool success, bytes memory result) = addressAt(routeIds[index])\n .delegatecall(dataItems[index]);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice execute a controller function identified using the controllerId in the request\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in request to be built using the function-selector defined as a\n * constant in the controller implementation contract\n * @param socketControllerRequest socketControllerRequest with controllerId to identify the\n * controllerAddress and byteData constructed using functionSelector\n * of the function being invoked\n * @return bytes data received from the call delegated to controller\n */\n function executeController(\n ISocketGateway.SocketControllerRequest calldata socketControllerRequest\n ) external payable returns (bytes memory) {\n (bool success, bytes memory result) = controllers[\n socketControllerRequest.controllerId\n ].delegatecall(socketControllerRequest.data);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n return result;\n }\n\n /**\n * @notice sequentially executes all controller requests\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in each controller-request to be built using the function-selector defined as a\n * constant in the controller implementation contract\n * @param controllerRequests a list of socketControllerRequest\n * Each controllerRequest contains controllerId to identify the controllerAddress and\n * byteData constructed using functionSelector of the function being invoked\n */\n function executeControllers(\n ISocketGateway.SocketControllerRequest[] calldata controllerRequests\n ) external payable {\n for (uint32 index = 0; index < controllerRequests.length; ) {\n (bool success, bytes memory result) = controllers[\n controllerRequests[index].controllerId\n ].delegatecall(controllerRequests[index].data);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**************************************\n * ADMIN FUNCTIONS *\n **************************************/\n\n /**\n * @notice Add route to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure routeAddress is a verified bridge or middleware implementation address\n * @param routeAddress The address of bridge or middleware implementation contract deployed\n * @return Id of the route added to the routes-mapping in socketGateway storage\n */\n function addRoute(\n address routeAddress\n ) external onlyOwner returns (uint32) {\n uint32 routeId = routesCount;\n routes[routeId] = routeAddress;\n\n routesCount += 1;\n\n emit NewRouteAdded(routeId, routeAddress);\n\n return routeId;\n }\n\n /**\n * @notice Give Infinite or 0 approval to bridgeRoute for the tokenAddress\n This is a restricted function to be called by only socketGatewayOwner\n */\n\n function setApprovalForRouters(\n address[] memory routeAddresses,\n address[] memory tokenAddresses,\n bool isMax\n ) external onlyOwner {\n for (uint32 index = 0; index < routeAddresses.length; ) {\n ERC20(tokenAddresses[index]).approve(\n routeAddresses[index],\n isMax ? type(uint256).max : 0\n );\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice Add controller to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure controllerAddress is a verified controller implementation address\n * @param controllerAddress The address of controller implementation contract deployed\n * @return Id of the controller added to the controllers-mapping in socketGateway storage\n */\n function addController(\n address controllerAddress\n ) external onlyOwner returns (uint32) {\n uint32 controllerId = controllerCount;\n\n controllers[controllerId] = controllerAddress;\n\n controllerCount += 1;\n\n emit ControllerAdded(controllerId, controllerAddress);\n\n return controllerId;\n }\n\n /**\n * @notice disable controller by setting ZeroAddress to the entry in controllers-mapping\n identified by controllerId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param controllerId The Id of controller-implementation in the controllers mapping\n */\n function disableController(uint32 controllerId) public onlyOwner {\n controllers[controllerId] = disabledRouteAddress;\n emit ControllerDisabled(controllerId);\n }\n\n /**\n * @notice disable a route by setting ZeroAddress to the entry in routes-mapping\n identified by routeId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param routeId The Id of route-implementation in the routes mapping\n */\n function disableRoute(uint32 routeId) external onlyOwner {\n routes[routeId] = disabledRouteAddress;\n emit RouteDisabled(routeId);\n }\n\n /*******************************************\n * RESTRICTED RESCUE FUNCTIONS *\n *******************************************/\n\n /**\n * @notice Rescues the ERC20 token to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param token address of the ERC20 token being rescued\n * @param userAddress address to which ERC20 is to be rescued\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external onlyOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice Rescues the native balance to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param userAddress address to which native-balance is to be rescued\n * @param amount amount of native-balance being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external onlyOwner {\n userAddress.transfer(amount);\n }\n\n /*******************************************\n * VIEW FUNCTIONS *\n *******************************************/\n\n /**\n * @notice Get routeImplementation address mapped to the routeId\n * @param routeId routeId is the key in the mapping for routes\n * @return route-implementation address\n */\n function getRoute(uint32 routeId) public view returns (address) {\n return addressAt(routeId);\n }\n\n /**\n * @notice Get controllerImplementation address mapped to the controllerId\n * @param controllerId controllerId is the key in the mapping for controllers\n * @return controller-implementation address\n */\n function getController(uint32 controllerId) public view returns (address) {\n return controllers[controllerId];\n }\n\n function addressAt(uint32 routeId) public view returns (address) {\n if (routeId < 385) {\n if (routeId < 257) {\n if (routeId < 129) {\n if (routeId < 65) {\n if (routeId < 33) {\n if (routeId < 17) {\n if (routeId < 9) {\n if (routeId < 5) {\n if (routeId < 3) {\n if (routeId == 1) {\n return\n 0x8cd6BaCDAe46B449E2e5B34e348A4eD459c84D50;\n } else {\n return\n 0x31524750Cd865fF6A3540f232754Fb974c18585C;\n }\n } else {\n if (routeId == 3) {\n return\n 0xEd9b37342BeC8f3a2D7b000732ec87498aA6EC6a;\n } else {\n return\n 0xE8704Ef6211F8988Ccbb11badC89841808d66890;\n }\n }\n } else {\n if (routeId < 7) {\n if (routeId == 5) {\n return\n 0x9aFF58C460a461578C433e11C4108D1c4cF77761;\n } else {\n return\n 0x2D1733886cFd465B0B99F1492F40847495f334C5;\n }\n } else {\n if (routeId == 7) {\n return\n 0x715497Be4D130F04B8442F0A1F7a9312D4e54FC4;\n } else {\n return\n 0x90C8a40c38E633B5B0e0d0585b9F7FA05462CaaF;\n }\n }\n }\n } else {\n if (routeId < 13) {\n if (routeId < 11) {\n if (routeId == 9) {\n return\n 0xa402b70FCfF3F4a8422B93Ef58E895021eAdE4F6;\n } else {\n return\n 0xc1B718522E15CD42C4Ac385a929fc2B51f5B892e;\n }\n } else {\n if (routeId == 11) {\n return\n 0xa97bf2f7c26C43c010c349F52f5eA5dC49B2DD38;\n } else {\n return\n 0x969423d71b62C81d2f28d707364c9Dc4a0764c53;\n }\n }\n } else {\n if (routeId < 15) {\n if (routeId == 13) {\n return\n 0xF86729934C083fbEc8C796068A1fC60701Ea1207;\n } else {\n return\n 0xD7cC2571F5823caCA26A42690D2BE7803DD5393f;\n }\n } else {\n if (routeId == 15) {\n return\n 0x7c8837a279bbbf7d8B93413763176de9F65d5bB9;\n } else {\n return\n 0x13b81C27B588C07D04458ed7dDbdbD26D1e39bcc;\n }\n }\n }\n }\n } else {\n if (routeId < 25) {\n if (routeId < 21) {\n if (routeId < 19) {\n if (routeId == 17) {\n return\n 0x52560Ac678aFA1345D15474287d16Dc1eA3F78aE;\n } else {\n return\n 0x1E31e376551459667cd7643440c1b21CE69065A0;\n }\n } else {\n if (routeId == 19) {\n return\n 0xc57D822CB3288e7b97EF8f8af0EcdcD1B783529B;\n } else {\n return\n 0x2197A1D9Af24b4d6a64Bff95B4c29Fcd3Ff28C30;\n }\n }\n } else {\n if (routeId < 23) {\n if (routeId == 21) {\n return\n 0xE3700feAa5100041Bf6b7AdBA1f72f647809Fd00;\n } else {\n return\n 0xc02E8a0Fdabf0EeFCEA025163d90B5621E2b9948;\n }\n } else {\n if (routeId == 23) {\n return\n 0xF5144235E2926cAb3c69b30113254Fa632f72d62;\n } else {\n return\n 0xBa3F92313B00A1f7Bc53b2c24EB195c8b2F57682;\n }\n }\n }\n } else {\n if (routeId < 29) {\n if (routeId < 27) {\n if (routeId == 25) {\n return\n 0x77a6856fe1fFA5bEB55A1d2ED86E27C7c482CB76;\n } else {\n return\n 0x4826Ff4e01E44b1FCEFBfb38cd96687Eb7786b44;\n }\n } else {\n if (routeId == 27) {\n return\n 0x55FF3f5493cf5e80E76DEA7E327b9Cd8440Af646;\n } else {\n return\n 0xF430Db544bE9770503BE4aa51997aA19bBd5BA4f;\n }\n }\n } else {\n if (routeId < 31) {\n if (routeId == 29) {\n return\n 0x0f166446ce1484EE3B0663E7E67DF10F5D240115;\n } else {\n return\n 0x6365095D92537f242Db5EdFDd572745E72aC33d9;\n }\n } else {\n if (routeId == 31) {\n return\n 0x5c7BC93f06ce3eAe75ADf55E10e23d2c1dE5Bc65;\n } else {\n return\n 0xe46383bAD90d7A08197ccF08972e9DCdccCE9BA4;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 49) {\n if (routeId < 41) {\n if (routeId < 37) {\n if (routeId < 35) {\n if (routeId == 33) {\n return\n 0xf0f21710c071E3B728bdc4654c3c0b873aAaa308;\n } else {\n return\n 0x63Bc9ed3AcAAeB0332531C9fB03b0a2352E9Ff25;\n }\n } else {\n if (routeId == 35) {\n return\n 0xd1CE808625CB4007a1708824AE82CdB0ece57De9;\n } else {\n return\n 0x57BbB148112f4ba224841c3FE018884171004661;\n }\n }\n } else {\n if (routeId < 39) {\n if (routeId == 37) {\n return\n 0x037f7d6933036F34DFabd40Ff8e4D789069f92e3;\n } else {\n return\n 0xeF978c280915CfF3Dca4EDfa8932469e40ADA1e1;\n }\n } else {\n if (routeId == 39) {\n return\n 0x92ee9e071B13f7ecFD62B7DED404A16CBc223CD3;\n } else {\n return\n 0x94Ae539c186e41ed762271338Edf140414D1E442;\n }\n }\n }\n } else {\n if (routeId < 45) {\n if (routeId < 43) {\n if (routeId == 41) {\n return\n 0x30A64BBe4DdBD43dA2368EFd1eB2d80C10d84DAb;\n } else {\n return\n 0x3aEABf81c1Dc4c1b73d5B2a95410f126426FB596;\n }\n } else {\n if (routeId == 43) {\n return\n 0x25b08aB3D0C8ea4cC9d967b79688C6D98f3f563a;\n } else {\n return\n 0xea40cB15C9A3BBd27af6474483886F7c0c9AE406;\n }\n }\n } else {\n if (routeId < 47) {\n if (routeId == 45) {\n return\n 0x9580113Cc04e5a0a03359686304EF3A80b936Dd3;\n } else {\n return\n 0xD211c826d568957F3b66a3F4d9c5f68cCc66E619;\n }\n } else {\n if (routeId == 47) {\n return\n 0xCEE24D0635c4C56315d133b031984d4A6f509476;\n } else {\n return\n 0x3922e6B987983229798e7A20095EC372744d4D4c;\n }\n }\n }\n }\n } else {\n if (routeId < 57) {\n if (routeId < 53) {\n if (routeId < 51) {\n if (routeId == 49) {\n return\n 0x2d92D03413d296e1F31450479349757187F2a2b7;\n } else {\n return\n 0x0fe5308eE90FC78F45c89dB6053eA859097860CA;\n }\n } else {\n if (routeId == 51) {\n return\n 0x08Ba68e067C0505bAF0C1311E0cFB2B1B59b969c;\n } else {\n return\n 0x9bee5DdDF75C24897374f92A534B7A6f24e97f4a;\n }\n }\n } else {\n if (routeId < 55) {\n if (routeId == 53) {\n return\n 0x1FC5A90B232208704B930c1edf82FFC6ACc02734;\n } else {\n return\n 0x5b1B0417cb44c761C2a23ee435d011F0214b3C85;\n }\n } else {\n if (routeId == 55) {\n return\n 0x9d70cDaCA12A738C283020760f449D7816D592ec;\n } else {\n return\n 0x95a23b9CB830EcCFDDD5dF56A4ec665e3381Fa12;\n }\n }\n }\n } else {\n if (routeId < 61) {\n if (routeId < 59) {\n if (routeId == 57) {\n return\n 0x483a957Cf1251c20e096C35c8399721D1200A3Fc;\n } else {\n return\n 0xb4AD39Cb293b0Ec7FEDa743442769A7FF04987CD;\n }\n } else {\n if (routeId == 59) {\n return\n 0x4C543AD78c1590D81BAe09Fc5B6Df4132A2461d0;\n } else {\n return\n 0x471d5E5195c563902781734cfe1FF3981F8B6c86;\n }\n }\n } else {\n if (routeId < 63) {\n if (routeId == 61) {\n return\n 0x1B12a54B5E606D95B8B8D123c9Cb09221Ee37584;\n } else {\n return\n 0xE4127cC550baC433646a7D998775a84daC16c7f3;\n }\n } else {\n if (routeId == 63) {\n return\n 0xecb1b55AB12E7dd788D585c6C5cD61B5F87be836;\n } else {\n return\n 0xf91ef487C5A1579f70601b6D347e19756092eEBf;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 97) {\n if (routeId < 81) {\n if (routeId < 73) {\n if (routeId < 69) {\n if (routeId < 67) {\n if (routeId == 65) {\n return\n 0x34a16a7e9BADEEFD4f056310cbE0b1423Fa1b760;\n } else {\n return\n 0x60E10E80c7680f429dBbC232830BEcd3D623c4CF;\n }\n } else {\n if (routeId == 67) {\n return\n 0x66465285B8D65362A1d86CE00fE2bE949Fd6debF;\n } else {\n return\n 0x5aB231B7e1A3A74a48f67Ab7bde5Cdd4267022E0;\n }\n }\n } else {\n if (routeId < 71) {\n if (routeId == 69) {\n return\n 0x3A1C3633eE79d43366F5c67802a746aFD6b162Ba;\n } else {\n return\n 0x0C4BfCbA8dC3C811437521a80E81e41DAF479039;\n }\n } else {\n if (routeId == 71) {\n return\n 0x6caf25d2e139C5431a1FA526EAf8d73ff2e6252C;\n } else {\n return\n 0x74ad21e09FDa68638CE14A3009A79B6D16574257;\n }\n }\n }\n } else {\n if (routeId < 77) {\n if (routeId < 75) {\n if (routeId == 73) {\n return\n 0xD4923A61008894b99cc1CD3407eF9524f02aA0Ca;\n } else {\n return\n 0x6F159b5EB823BD415886b9271aA2A723a00a1987;\n }\n } else {\n if (routeId == 75) {\n return\n 0x742a8aA42E7bfB4554dE30f4Fb07FFb6f2068863;\n } else {\n return\n 0x4AE9702d3360400E47B446e76DE063ACAb930101;\n }\n }\n } else {\n if (routeId < 79) {\n if (routeId == 77) {\n return\n 0x0E19a0a44ddA7dAD854ec5Cc867d16869c4E80F4;\n } else {\n return\n 0xE021A51968f25148F726E326C88d2556c5647557;\n }\n } else {\n if (routeId == 79) {\n return\n 0x64287BDDDaeF4d94E4599a3D882bed29E6Ada4B6;\n } else {\n return\n 0xcBB57Fd2e19cc7e9D444d5b4325A2F1047d0C73f;\n }\n }\n }\n }\n } else {\n if (routeId < 89) {\n if (routeId < 85) {\n if (routeId < 83) {\n if (routeId == 81) {\n return\n 0x373DE80DF7D82cFF6D76F29581b360C56331e957;\n } else {\n return\n 0x0466356E131AD61596a51F86BAd1C03A328960D8;\n }\n } else {\n if (routeId == 83) {\n return\n 0x01726B960992f1b74311b248E2a922fC707d43A6;\n } else {\n return\n 0x2E21bdf9A4509b89795BCE7E132f248a75814CEc;\n }\n }\n } else {\n if (routeId < 87) {\n if (routeId == 85) {\n return\n 0x769512b23aEfF842379091d3B6E4B5456F631D42;\n } else {\n return\n 0xe7eD9be946a74Ec19325D39C6EEb57887ccB2B0D;\n }\n } else {\n if (routeId == 87) {\n return\n 0xc4D01Ec357c2b511d10c15e6b6974380F0E62e67;\n } else {\n return\n 0x5bC49CC9dD77bECF2fd3A3C55611e84E69AFa3AE;\n }\n }\n }\n } else {\n if (routeId < 93) {\n if (routeId < 91) {\n if (routeId == 89) {\n return\n 0x48bcD879954fA14e7DbdAeb56F79C1e9DDcb69ec;\n } else {\n return\n 0xE929bDde21b462572FcAA4de6F49B9D3246688D0;\n }\n } else {\n if (routeId == 91) {\n return\n 0x85Aae300438222f0e3A9Bc870267a5633A9438bd;\n } else {\n return\n 0x51f72E1096a81C55cd142d66d39B688C657f9Be8;\n }\n }\n } else {\n if (routeId < 95) {\n if (routeId == 93) {\n return\n 0x3A8a05BF68ac54B01E6C0f492abF97465F3d15f9;\n } else {\n return\n 0x145aA67133F0c2C36b9771e92e0B7655f0D59040;\n }\n } else {\n if (routeId == 95) {\n return\n 0xa030315d7DB11F9892758C9e7092D841e0ADC618;\n } else {\n return\n 0xdF1f8d81a3734bdDdEfaC6Ca1596E081e57c3044;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 113) {\n if (routeId < 105) {\n if (routeId < 101) {\n if (routeId < 99) {\n if (routeId == 97) {\n return\n 0xFF2833123B58aa05d04D7fb99f5FB768B2b435F8;\n } else {\n return\n 0xc8f09c1fD751C570233765f71b0e280d74e6e743;\n }\n } else {\n if (routeId == 99) {\n return\n 0x3026DA6Ceca2E5A57A05153653D9212FFAaA49d8;\n } else {\n return\n 0xdE68Ee703dE0D11f67B0cE5891cB4a903de6D160;\n }\n }\n } else {\n if (routeId < 103) {\n if (routeId == 101) {\n return\n 0xE23a7730e81FB4E87A6D0bd9f63EE77ac86C3DA4;\n } else {\n return\n 0x8b1DBe04aD76a7d8bC079cACd3ED4D99B897F4a0;\n }\n } else {\n if (routeId == 103) {\n return\n 0xBB227240FA459b69C6889B2b8cb1BE76F118061f;\n } else {\n return\n 0xC062b9b3f0dB28BB8afAfcD4d075729344114ffe;\n }\n }\n }\n } else {\n if (routeId < 109) {\n if (routeId < 107) {\n if (routeId == 105) {\n return\n 0x553188Aa45f5FDB83EC4Ca485982F8fC082480D1;\n } else {\n return\n 0x0109d83D746EaCb6d4014953D9E12d6ca85e330b;\n }\n } else {\n if (routeId == 107) {\n return\n 0x45B1bEd29812F5bf6711074ACD180B2aeB783AD9;\n } else {\n return\n 0xdA06eC8c19aea31D77F60299678Cba40E743e1aD;\n }\n }\n } else {\n if (routeId < 111) {\n if (routeId == 109) {\n return\n 0x3cC5235c97d975a9b4FD4501B3446c981ea3D855;\n } else {\n return\n 0xa1827267d6Bd989Ff38580aE3d9deff6Acf19163;\n }\n } else {\n if (routeId == 111) {\n return\n 0x3663CAA0433A3D4171b3581Cf2410702840A735A;\n } else {\n return\n 0x7575D0a7614F655BA77C74a72a43bbd4fA6246a3;\n }\n }\n }\n }\n } else {\n if (routeId < 121) {\n if (routeId < 117) {\n if (routeId < 115) {\n if (routeId == 113) {\n return\n 0x2516Defc18bc07089c5dAFf5eafD7B0EF64611E2;\n } else {\n return\n 0xfec5FF08E20fbc107a97Af2D38BD0025b84ee233;\n }\n } else {\n if (routeId == 115) {\n return\n 0x0FB5763a87242B25243e23D73f55945fE787523A;\n } else {\n return\n 0xe4C00db89678dBf8391f430C578Ca857Dd98aDE1;\n }\n }\n } else {\n if (routeId < 119) {\n if (routeId == 117) {\n return\n 0x8F2A22061F9F35E64f14523dC1A5f8159e6a21B7;\n } else {\n return\n 0x18e4b838ae966917E20E9c9c5Ad359cDD38303bB;\n }\n } else {\n if (routeId == 119) {\n return\n 0x61ACb1d3Dcb3e3429832A164Cc0fC9849fb75A4a;\n } else {\n return\n 0x7681e3c8e7A41DCA55C257cc0d1Ae757f5530E65;\n }\n }\n }\n } else {\n if (routeId < 125) {\n if (routeId < 123) {\n if (routeId == 121) {\n return\n 0x806a2AB9748C3D1DB976550890E3f528B7E8Faec;\n } else {\n return\n 0xBDb8A5DD52C2c239fbC31E9d43B763B0197028FF;\n }\n } else {\n if (routeId == 123) {\n return\n 0x474EC9203706010B9978D6bD0b105D36755e4848;\n } else {\n return\n 0x8dfd0D829b303F2239212E591a0F92a32880f36E;\n }\n }\n } else {\n if (routeId < 127) {\n if (routeId == 125) {\n return\n 0xad4BcE9745860B1adD6F1Bd34a916f050E4c82C2;\n } else {\n return\n 0xBC701115b9fe14bC8CC5934cdC92517173e308C4;\n }\n } else {\n if (routeId == 127) {\n return\n 0x0D1918d786Db8546a11aDeD475C98370E06f255E;\n } else {\n return\n 0xee44f57cD6936DB55B99163f3Df367B01EdA785a;\n }\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 193) {\n if (routeId < 161) {\n if (routeId < 145) {\n if (routeId < 137) {\n if (routeId < 133) {\n if (routeId < 131) {\n if (routeId == 129) {\n return\n 0x63044521fe5a1e488D7eD419cD0e35b7C24F2aa7;\n } else {\n return\n 0x410085E73BD85e90d97b84A68C125aDB9F91f85b;\n }\n } else {\n if (routeId == 131) {\n return\n 0x7913fe97E07C7A397Ec274Ab1d4E2622C88EC5D1;\n } else {\n return\n 0x977f9fE93c064DCf54157406DaABC3a722e8184C;\n }\n }\n } else {\n if (routeId < 135) {\n if (routeId == 133) {\n return\n 0xCD2236468722057cFbbABad2db3DEA9c20d5B01B;\n } else {\n return\n 0x17c7287A491cf5Ff81E2678cF2BfAE4333F6108c;\n }\n } else {\n if (routeId == 135) {\n return\n 0x354D9a5Dbf96c71B79a265F03B595C6Fdc04dadd;\n } else {\n return\n 0xb4e409EB8e775eeFEb0344f9eee884cc7ed21c69;\n }\n }\n }\n } else {\n if (routeId < 141) {\n if (routeId < 139) {\n if (routeId == 137) {\n return\n 0xa1a3c4670Ad69D9be4ab2D39D1231FEC2a63b519;\n } else {\n return\n 0x4589A22199870729C1be5CD62EE93BeD858113E6;\n }\n } else {\n if (routeId == 139) {\n return\n 0x8E7b864dB26Bd6C798C38d4Ba36EbA0d6602cF11;\n } else {\n return\n 0xA2D17C7260a4CB7b9854e89Fc367E80E87872a2d;\n }\n }\n } else {\n if (routeId < 143) {\n if (routeId == 141) {\n return\n 0xC7F0EDf0A1288627b0432304918A75e9084CBD46;\n } else {\n return\n 0xE4B4EF1f9A4aBFEdB371fA7a6143993B15d4df25;\n }\n } else {\n if (routeId == 143) {\n return\n 0xfe3D84A2Ef306FEBb5452441C9BDBb6521666F6A;\n } else {\n return\n 0x8A12B6C64121920110aE58F7cd67DfEc21c6a4C3;\n }\n }\n }\n }\n } else {\n if (routeId < 153) {\n if (routeId < 149) {\n if (routeId < 147) {\n if (routeId == 145) {\n return\n 0x76c4d9aFC4717a2BAac4e5f26CccF02351f7a3DA;\n } else {\n return\n 0xd4719BA550E397aeAcca1Ad2201c1ba69024FAAf;\n }\n } else {\n if (routeId == 147) {\n return\n 0x9646126Ce025224d1682C227d915a386efc0A1Fb;\n } else {\n return\n 0x4DD8Af2E3F2044842f0247920Bc4BABb636915ea;\n }\n }\n } else {\n if (routeId < 151) {\n if (routeId == 149) {\n return\n 0x8e8a327183Af0cf8C2ece9F0ed547C42A160D409;\n } else {\n return\n 0x9D49614CaE1C685C71678CA6d8CDF7584bfd0740;\n }\n } else {\n if (routeId == 151) {\n return\n 0x5a00ef257394cbc31828d48655E3d39e9c11c93d;\n } else {\n return\n 0xC9a2751b38d3dDD161A41Ca0135C5C6c09EC1d56;\n }\n }\n }\n } else {\n if (routeId < 157) {\n if (routeId < 155) {\n if (routeId == 153) {\n return\n 0x7e1c261640a525C94Ca4f8c25b48CF754DD83590;\n } else {\n return\n 0x409Fe24ba6F6BD5aF31C1aAf8059b986A3158233;\n }\n } else {\n if (routeId == 155) {\n return\n 0x704Cf5BFDADc0f55fDBb53B6ed8B582E018A72A2;\n } else {\n return\n 0x3982bF65d7d6E77E3b6661cd6F6468c247512737;\n }\n }\n } else {\n if (routeId < 159) {\n if (routeId == 157) {\n return\n 0x3982b9f26FFD67a13Ee371e2C0a9Da338BA70E7f;\n } else {\n return\n 0x6D834AB385900c1f49055D098e90264077FbC4f2;\n }\n } else {\n if (routeId == 159) {\n return\n 0x11FE5F70779A094B7166B391e1Fb73d422eF4e4d;\n } else {\n return\n 0xD347e4E47280d21F13B73D89c6d16f867D50DD13;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 177) {\n if (routeId < 169) {\n if (routeId < 165) {\n if (routeId < 163) {\n if (routeId == 161) {\n return\n 0xb6035eDD53DDA28d8B69b4ae9836E40C80306CD7;\n } else {\n return\n 0x54c884e6f5C7CcfeCA990396c520C858c922b6CA;\n }\n } else {\n if (routeId == 163) {\n return\n 0x5eA93E240b083d686558Ed607BC013d88057cE46;\n } else {\n return\n 0x4C7131eE812De685cBe4e2cCb033d46ecD46612E;\n }\n }\n } else {\n if (routeId < 167) {\n if (routeId == 165) {\n return\n 0xc1a5Be9F0c33D8483801D702111068669f81fF91;\n } else {\n return\n 0x9E5fAb91455Be5E5b2C05967E73F456c8118B1Fc;\n }\n } else {\n if (routeId == 167) {\n return\n 0x3d9A05927223E0DC2F382831770405885e22F0d8;\n } else {\n return\n 0x6303A011fB6063f5B1681cb5a9938EA278dc6128;\n }\n }\n }\n } else {\n if (routeId < 173) {\n if (routeId < 171) {\n if (routeId == 169) {\n return\n 0xe9c60795c90C66797e4c8E97511eA07CdAda32bE;\n } else {\n return\n 0xD56cC98e69A1e13815818b466a8aA6163d84234A;\n }\n } else {\n if (routeId == 171) {\n return\n 0x47EbB9D36a6e40895316cD894E4860D774E2c531;\n } else {\n return\n 0xA5EB293629410065d14a7B1663A67829b0618292;\n }\n }\n } else {\n if (routeId < 175) {\n if (routeId == 173) {\n return\n 0x1b3B4C8146F939cE00899db8B3ddeF0062b7E023;\n } else {\n return\n 0x257Bbc11653625EbfB6A8587eF4f4FBe49828EB3;\n }\n } else {\n if (routeId == 175) {\n return\n 0x44cc979C01b5bB1eAC21301E73C37200dFD06F59;\n } else {\n return\n 0x2972fDF43352225D82754C0174Ff853819D1ef2A;\n }\n }\n }\n }\n } else {\n if (routeId < 185) {\n if (routeId < 181) {\n if (routeId < 179) {\n if (routeId == 177) {\n return\n 0x3e54144f032648A04D62d79f7B4b93FF3aC2333b;\n } else {\n return\n 0x444016102dB8adbE73C3B6703a1ea7F2f75A510D;\n }\n } else {\n if (routeId == 179) {\n return\n 0xac079143f98a6eb744Fde34541ebF243DF5B5dED;\n } else {\n return\n 0xAe9010767Fb112d29d35CEdfba2b372Ad7A308d3;\n }\n }\n } else {\n if (routeId < 183) {\n if (routeId == 181) {\n return\n 0xfE0BCcF9cCC2265D5fB3450743f17DfE57aE1e56;\n } else {\n return\n 0x04ED8C0545716119437a45386B1d691C63234C7D;\n }\n } else {\n if (routeId == 183) {\n return\n 0x636c14013e531A286Bc4C848da34585f0bB73d59;\n } else {\n return\n 0x2Fa67fc7ECC5cAA01C653d3BFeA98ecc5db9C42A;\n }\n }\n }\n } else {\n if (routeId < 189) {\n if (routeId < 187) {\n if (routeId == 185) {\n return\n 0x23e9a0FC180818aA872D2079a985217017E97bd9;\n } else {\n return\n 0x79A95c3Ef81b3ae64ee03A9D5f73e570495F164E;\n }\n } else {\n if (routeId == 187) {\n return\n 0xa7EA0E88F04a84ba0ad1E396cb07Fa3fDAD7dF6D;\n } else {\n return\n 0xd23cA1278a2B01a3C0Ca1a00d104b11c1Ebe6f42;\n }\n }\n } else {\n if (routeId < 191) {\n if (routeId == 189) {\n return\n 0x707bc4a9FA2E349AED5df4e9f5440C15aA9D14Bd;\n } else {\n return\n 0x7E290F2dd539Ac6CE58d8B4C2B944931a1fD3612;\n }\n } else {\n if (routeId == 191) {\n return\n 0x707AA5503088Ce06Ba450B6470A506122eA5c8eF;\n } else {\n return\n 0xFbB3f7BF680deeb149f4E7BC30eA3DDfa68F3C3f;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 225) {\n if (routeId < 209) {\n if (routeId < 201) {\n if (routeId < 197) {\n if (routeId < 195) {\n if (routeId == 193) {\n return\n 0xDE74aD8cCC3dbF14992f49Cf24f36855912f4934;\n } else {\n return\n 0x409BA83df7777F070b2B50a10a41DE2468d2a3B3;\n }\n } else {\n if (routeId == 195) {\n return\n 0x5CB7Be90A5DD7CfDa54e87626e254FE8C18255B4;\n } else {\n return\n 0x0A684fE12BC64fb72B59d0771a566F49BC090356;\n }\n }\n } else {\n if (routeId < 199) {\n if (routeId == 197) {\n return\n 0xDf30048d91F8FA2bCfC54952B92bFA8e161D3360;\n } else {\n return\n 0x050825Fff032a547C47061CF0696FDB0f65AEa5D;\n }\n } else {\n if (routeId == 199) {\n return\n 0xd55e671dAC1f03d366d8535073ada5DB2Aab1Ea2;\n } else {\n return\n 0x9470C704A9616c8Cd41c595Fcd2181B6fe2183C2;\n }\n }\n }\n } else {\n if (routeId < 205) {\n if (routeId < 203) {\n if (routeId == 201) {\n return\n 0x2D9ffD275181F5865d5e11CbB4ced1521C4dF9f1;\n } else {\n return\n 0x816d28Dec10ec95DF5334f884dE85cA6215918d8;\n }\n } else {\n if (routeId == 203) {\n return\n 0xd1f87267c4A43835E666dd69Df077e578A3b6299;\n } else {\n return\n 0x39E89Bde9DACbe5468C025dE371FbDa12bDeBAB1;\n }\n }\n } else {\n if (routeId < 207) {\n if (routeId == 205) {\n return\n 0x7b40A3207956ecad6686E61EfcaC48912FcD0658;\n } else {\n return\n 0x090cF10D793B1Efba9c7D76115878814B663859A;\n }\n } else {\n if (routeId == 207) {\n return\n 0x312A59c06E41327878F2063eD0e9c282C1DA3AfC;\n } else {\n return\n 0x4F1188f46236DD6B5de11Ebf2a9fF08716E7DeB6;\n }\n }\n }\n }\n } else {\n if (routeId < 217) {\n if (routeId < 213) {\n if (routeId < 211) {\n if (routeId == 209) {\n return\n 0x0A6F9a3f4fA49909bBfb4339cbE12B42F53BbBeD;\n } else {\n return\n 0x01d13d7aCaCbB955B81935c80ffF31e14BdFa71f;\n }\n } else {\n if (routeId == 211) {\n return\n 0x691a14Fa6C7360422EC56dF5876f84d4eDD7f00A;\n } else {\n return\n 0x97Aad18d886d181a9c726B3B6aE15a0A69F5aF73;\n }\n }\n } else {\n if (routeId < 215) {\n if (routeId == 213) {\n return\n 0x2917241371D2099049Fa29432DC46735baEC33b4;\n } else {\n return\n 0x5F20F20F7890c2e383E29D4147C9695A371165f5;\n }\n } else {\n if (routeId == 215) {\n return\n 0xeC0a60e639958335662C5219A320cCEbb56C6077;\n } else {\n return\n 0x96d63CF5062975C09845d17ec672E10255866053;\n }\n }\n }\n } else {\n if (routeId < 221) {\n if (routeId < 219) {\n if (routeId == 217) {\n return\n 0xFF57429e57D383939CAB50f09ABBfB63C0e6c9AD;\n } else {\n return\n 0x18E393A7c8578fb1e235C242076E50013cDdD0d7;\n }\n } else {\n if (routeId == 219) {\n return\n 0xE7E5238AF5d61f52E9B4ACC025F713d1C0216507;\n } else {\n return\n 0x428401D4d0F25A2EE1DA4d5366cB96Ded425D9bD;\n }\n }\n } else {\n if (routeId < 223) {\n if (routeId == 221) {\n return\n 0x42E5733551ff1Ee5B48Aa9fc2B61Af9b58C812E6;\n } else {\n return\n 0x64Df9c7A0551B056d860Bc2419Ca4c1EF75320bE;\n }\n } else {\n if (routeId == 223) {\n return\n 0x46006925506145611bBf0263243D8627dAf26B0F;\n } else {\n return\n 0x8D64BE884314662804eAaB884531f5C50F4d500c;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 241) {\n if (routeId < 233) {\n if (routeId < 229) {\n if (routeId < 227) {\n if (routeId == 225) {\n return\n 0x157a62D92D07B5ce221A5429645a03bBaCE85373;\n } else {\n return\n 0xaF037D33e1F1F2F87309B425fe8a9d895Ef3722B;\n }\n } else {\n if (routeId == 227) {\n return\n 0x921D1154E494A2f7218a37ad7B17701f94b4B40e;\n } else {\n return\n 0xF282b4555186d8Dea51B8b3F947E1E0568d09bc4;\n }\n }\n } else {\n if (routeId < 231) {\n if (routeId == 229) {\n return\n 0xa794E2E1869765a4600b3DFd8a4ebcF16350f6B6;\n } else {\n return\n 0xFEFb048e20c5652F7940A49B1980E0125Ec4D358;\n }\n } else {\n if (routeId == 231) {\n return\n 0x220104b641971e9b25612a8F001bf48AbB23f1cF;\n } else {\n return\n 0xcB9D373Bb54A501B35dd3be5bF4Ba43cA31F7035;\n }\n }\n }\n } else {\n if (routeId < 237) {\n if (routeId < 235) {\n if (routeId == 233) {\n return\n 0x37D627F56e3FF36aC316372109ea82E03ac97DAc;\n } else {\n return\n 0x4E81355FfB4A271B4EA59ff78da2b61c7833161f;\n }\n } else {\n if (routeId == 235) {\n return\n 0xADd8D65cAF6Cc9ad73127B49E16eA7ac29d91e87;\n } else {\n return\n 0x630F9b95626487dfEAe3C97A44DB6C59cF35d996;\n }\n }\n } else {\n if (routeId < 239) {\n if (routeId == 237) {\n return\n 0x78CE2BC8238B679680A67FCB98C5A60E4ec17b2D;\n } else {\n return\n 0xA38D776028eD1310b9A6b086f67F788201762E21;\n }\n } else {\n if (routeId == 239) {\n return\n 0x7Bb5178827B76B86753Ed62a0d662c72cEcb1bD3;\n } else {\n return\n 0x4faC26f61C76eC5c3D43b43eDfAFF0736Ae0e3da;\n }\n }\n }\n }\n } else {\n if (routeId < 249) {\n if (routeId < 245) {\n if (routeId < 243) {\n if (routeId == 241) {\n return\n 0x791Bb49bfFA7129D6889FDB27744422Ac4571A85;\n } else {\n return\n 0x26766fFEbb5fa564777913A6f101dF019AB32afa;\n }\n } else {\n if (routeId == 243) {\n return\n 0x05e98E5e95b4ECBbbAf3258c3999Cc81ed8048Be;\n } else {\n return\n 0xC5c4621e52f1D6A1825A5ed4F95855401a3D9C6b;\n }\n }\n } else {\n if (routeId < 247) {\n if (routeId == 245) {\n return\n 0xfcb15f909BA7FC7Ea083503Fb4c1020203c107EB;\n } else {\n return\n 0xbD27603279d969c74f2486ad14E71080829DFd38;\n }\n } else {\n if (routeId == 247) {\n return\n 0xff2f756BcEcC1A55BFc09a30cc5F64720458cFCB;\n } else {\n return\n 0x3bfB968FEbC12F4e8420B2d016EfcE1E615f7246;\n }\n }\n }\n } else {\n if (routeId < 253) {\n if (routeId < 251) {\n if (routeId == 249) {\n return\n 0x982EE9Ffe23051A2ec945ed676D864fa8345222b;\n } else {\n return\n 0xe101899100785E74767d454FFF0131277BaD48d9;\n }\n } else {\n if (routeId == 251) {\n return\n 0x4F730C0c6b3B5B7d06ca511379f4Aa5BfB2E9525;\n } else {\n return\n 0x5499c36b365795e4e0Ef671aF6C2ce26D7c78265;\n }\n }\n } else {\n if (routeId < 255) {\n if (routeId == 253) {\n return\n 0x8AF51F7237Fc8fB2fc3E700488a94a0aC6Ad8b5a;\n } else {\n return\n 0xda8716df61213c0b143F2849785FB85928084857;\n }\n } else {\n if (routeId == 255) {\n return\n 0xF040Cf9b1ebD11Bf28e04e80740DF3DDe717e4f5;\n } else {\n return\n 0xB87ba32f759D14023C7520366B844dF7f0F036C2;\n }\n }\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 321) {\n if (routeId < 289) {\n if (routeId < 273) {\n if (routeId < 265) {\n if (routeId < 261) {\n if (routeId < 259) {\n if (routeId == 257) {\n return\n 0x0Edde681b8478F0c3194f468EdD2dB5e75c65CDD;\n } else {\n return\n 0x59C70900Fca06eE2aCE1BDd5A8D0Af0cc3BBA720;\n }\n } else {\n if (routeId == 259) {\n return\n 0x8041F0f180D17dD07087199632c45E17AeB0BAd5;\n } else {\n return\n 0x4fB4727064BA595995DD516b63b5921Df9B93aC6;\n }\n }\n } else {\n if (routeId < 263) {\n if (routeId == 261) {\n return\n 0x86e98b594565857eD098864F560915C0dAfd6Ea1;\n } else {\n return\n 0x70f8818E8B698EFfeCd86A513a4c87c0c380Bef6;\n }\n } else {\n if (routeId == 263) {\n return\n 0x78Ed227c8A897A21Da2875a752142dd80d865158;\n } else {\n return\n 0xd02A30BB5C3a8C51d2751A029a6fcfDE2Af9fbc6;\n }\n }\n }\n } else {\n if (routeId < 269) {\n if (routeId < 267) {\n if (routeId == 265) {\n return\n 0x0F00d5c5acb24e975e2a56730609f7F40aa763b8;\n } else {\n return\n 0xC3e2091edc2D3D9D98ba09269138b617B536834A;\n }\n } else {\n if (routeId == 267) {\n return\n 0xa6FbaF7F30867C9633908998ea8C3da28920E75C;\n } else {\n return\n 0xE6dDdcD41E2bBe8122AE32Ac29B8fbAB79CD21d9;\n }\n }\n } else {\n if (routeId < 271) {\n if (routeId == 269) {\n return\n 0x537aa8c1Ef6a8Eaf039dd6e1Eb67694a48195cE4;\n } else {\n return\n 0x96ABAC485fd2D0B03CF4a10df8BD58b8dED28300;\n }\n } else {\n if (routeId == 271) {\n return\n 0xda8e7D46d04Bd4F62705Cd80355BDB6d441DafFD;\n } else {\n return\n 0xbE50018E7a5c67E2e5f5414393e971CC96F293f2;\n }\n }\n }\n }\n } else {\n if (routeId < 281) {\n if (routeId < 277) {\n if (routeId < 275) {\n if (routeId == 273) {\n return\n 0xa1b3907D6CB542a4cbe2eE441EfFAA909FAb62C3;\n } else {\n return\n 0x6d08ee8511C0237a515013aC389e7B3968Cb1753;\n }\n } else {\n if (routeId == 275) {\n return\n 0x22faa5B5Fe43eAdbB52745e35a5cdA8bD5F96bbA;\n } else {\n return\n 0x7a673eB74D79e4868D689E7852abB5f93Ec2fD4b;\n }\n }\n } else {\n if (routeId < 279) {\n if (routeId == 277) {\n return\n 0x0b8531F8AFD4190b76F3e10deCaDb84c98b4d419;\n } else {\n return\n 0x78eABC743A93583DeE403D6b84795490e652216B;\n }\n } else {\n if (routeId == 279) {\n return\n 0x3A95D907b2a7a8604B59BccA08585F58Afe0Aa64;\n } else {\n return\n 0xf4271f0C8c9Af0F06A80b8832fa820ccE64FAda8;\n }\n }\n }\n } else {\n if (routeId < 285) {\n if (routeId < 283) {\n if (routeId == 281) {\n return\n 0x74b2DF841245C3748c0d31542e1335659a25C33b;\n } else {\n return\n 0xdFC99Fd0Ad7D16f30f295a5EEFcE029E04d0fa65;\n }\n } else {\n if (routeId == 283) {\n return\n 0xE992416b6aC1144eD8148a9632973257839027F6;\n } else {\n return\n 0x54ce55ba954E981BB1fd9399054B35Ce1f2C0816;\n }\n }\n } else {\n if (routeId < 287) {\n if (routeId == 285) {\n return\n 0xD4AB52f9e7E5B315Bd7471920baD04F405Ab1c38;\n } else {\n return\n 0x3670C990994d12837e95eE127fE2f06FD3E2104B;\n }\n } else {\n if (routeId == 287) {\n return\n 0xDcf190B09C47E4f551E30BBb79969c3FdEA1e992;\n } else {\n return\n 0xa65057B967B59677237e57Ab815B209744b9bc40;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 305) {\n if (routeId < 297) {\n if (routeId < 293) {\n if (routeId < 291) {\n if (routeId == 289) {\n return\n 0x6Efc86B40573e4C7F28659B13327D55ae955C483;\n } else {\n return\n 0x06BcC25CF8e0E72316F53631b3aA7134E9f73Ae0;\n }\n } else {\n if (routeId == 291) {\n return\n 0x710b6414E1D53882b1FCD3A168aD5Ccd435fc6D0;\n } else {\n return\n 0x5Ebb2C3d78c4e9818074559e7BaE7FCc99781DC1;\n }\n }\n } else {\n if (routeId < 295) {\n if (routeId == 293) {\n return\n 0xAf0a409c3AEe0bD08015cfb29D89E90b6e89A88F;\n } else {\n return\n 0x522559d8b99773C693B80cE06DF559036295Ce44;\n }\n } else {\n if (routeId == 295) {\n return\n 0xB65290A5Bae838aaa7825c9ECEC68041841a1B64;\n } else {\n return\n 0x801b8F2068edd5Bcb659E6BDa0c425909043C420;\n }\n }\n }\n } else {\n if (routeId < 301) {\n if (routeId < 299) {\n if (routeId == 297) {\n return\n 0x29b5F00515d093627E0B7bd0b5c8E84F6b4cDb87;\n } else {\n return\n 0x652839Ae74683cbF9f1293F1019D938F87464D3E;\n }\n } else {\n if (routeId == 299) {\n return\n 0x5Bc95dCebDDE9B79F2b6DC76121BC7936eF8D666;\n } else {\n return\n 0x90db359CEA62E53051158Ab5F99811C0a07Fe686;\n }\n }\n } else {\n if (routeId < 303) {\n if (routeId == 301) {\n return\n 0x2c3625EedadbDcDbB5330eb0d17b3C39ff269807;\n } else {\n return\n 0xC3f0324471b5c9d415acD625b8d8694a4e48e001;\n }\n } else {\n if (routeId == 303) {\n return\n 0x8C60e7E05fa0FfB6F720233736f245134685799d;\n } else {\n return\n 0x98fAF2c09aa4EBb995ad0B56152993E7291a500e;\n }\n }\n }\n }\n } else {\n if (routeId < 313) {\n if (routeId < 309) {\n if (routeId < 307) {\n if (routeId == 305) {\n return\n 0x802c1063a861414dFAEc16bacb81429FC0d40D6e;\n } else {\n return\n 0x11C4AeFCC0dC156f64195f6513CB1Fb3Be0Ae056;\n }\n } else {\n if (routeId == 307) {\n return\n 0xEff1F3258214E31B6B4F640b4389d55715C3Be2B;\n } else {\n return\n 0x47e379Abe8DDFEA4289aBa01235EFF7E93758fd7;\n }\n }\n } else {\n if (routeId < 311) {\n if (routeId == 309) {\n return\n 0x3CC26384c3eA31dDc8D9789e8872CeA6F20cD3ff;\n } else {\n return\n 0xEdd9EFa6c69108FAA4611097d643E20Ba0Ed1634;\n }\n } else {\n if (routeId == 311) {\n return\n 0xCb93525CA5f3D371F74F3D112bC19526740717B8;\n } else {\n return\n 0x7071E0124EB4438137e60dF1b8DD8Af1BfB362cF;\n }\n }\n }\n } else {\n if (routeId < 317) {\n if (routeId < 315) {\n if (routeId == 313) {\n return\n 0x4691096EB0b78C8F4b4A8091E5B66b18e1835c10;\n } else {\n return\n 0x8d953c9b2d1C2137CF95992079f3A77fCd793272;\n }\n } else {\n if (routeId == 315) {\n return\n 0xbdCc2A3Bf6e3Ba49ff86595e6b2b8D70d8368c92;\n } else {\n return\n 0x95E6948aB38c61b2D294E8Bd896BCc4cCC0713cf;\n }\n }\n } else {\n if (routeId < 319) {\n if (routeId == 317) {\n return\n 0x607b27C881fFEE4Cb95B1c5862FaE7224ccd0b4A;\n } else {\n return\n 0x09D28aFA166e566A2Ee1cB834ea8e78C7E627eD2;\n }\n } else {\n if (routeId == 319) {\n return\n 0x9c01449b38bDF0B263818401044Fb1401B29fDfA;\n } else {\n return\n 0x1F7723599bbB658c051F8A39bE2688388d22ceD6;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 353) {\n if (routeId < 337) {\n if (routeId < 329) {\n if (routeId < 325) {\n if (routeId < 323) {\n if (routeId == 321) {\n return\n 0x52B71603f7b8A5d15B4482e965a0619aa3210194;\n } else {\n return\n 0x01c0f072CB210406653752FecFA70B42dA9173a2;\n }\n } else {\n if (routeId == 323) {\n return\n 0x3021142f021E943e57fc1886cAF58D06147D09A6;\n } else {\n return\n 0xe6f2AF38e76AB09Db59225d97d3E770942D3D842;\n }\n }\n } else {\n if (routeId < 327) {\n if (routeId == 325) {\n return\n 0x06a25554e5135F08b9e2eD1DEC1fc3CEd52e0B48;\n } else {\n return\n 0x71d75e670EE3511C8290C705E0620126B710BF8D;\n }\n } else {\n if (routeId == 327) {\n return\n 0x8b9cE142b80FeA7c932952EC533694b1DF9B3c54;\n } else {\n return\n 0xd7Be24f32f39231116B3fDc483C2A12E1521f73B;\n }\n }\n }\n } else {\n if (routeId < 333) {\n if (routeId < 331) {\n if (routeId == 329) {\n return\n 0xb40cafBC4797d4Ff64087E087F6D2e661f954CbE;\n } else {\n return\n 0xBdDCe7771EfEe81893e838f62204A4c76D72757e;\n }\n } else {\n if (routeId == 331) {\n return\n 0x5d3D299EA7Fd4F39AcDb336E26631Dfee41F9287;\n } else {\n return\n 0x6BfEE09E1Fc0684e0826A9A0dC1352a14B136FAC;\n }\n }\n } else {\n if (routeId < 335) {\n if (routeId == 333) {\n return\n 0xd0001bB8E2Cb661436093f96458a4358B5156E3c;\n } else {\n return\n 0x1867c6485CfD1eD448988368A22bfB17a7747293;\n }\n } else {\n if (routeId == 335) {\n return\n 0x8997EF9F95dF24aB67703AB6C262aABfeEBE33bD;\n } else {\n return\n 0x1e39E9E601922deD91BCFc8F78836302133465e2;\n }\n }\n }\n }\n } else {\n if (routeId < 345) {\n if (routeId < 341) {\n if (routeId < 339) {\n if (routeId == 337) {\n return\n 0x8A8ec6CeacFf502a782216774E5AF3421562C6ff;\n } else {\n return\n 0x3B8FC561df5415c8DC01e97Ee6E38435A8F9C40A;\n }\n } else {\n if (routeId == 339) {\n return\n 0xD5d5f5B37E67c43ceA663aEDADFFc3a93a2065B0;\n } else {\n return\n 0xCC8F55EC43B4f25013CE1946FBB740c43Be5B96D;\n }\n }\n } else {\n if (routeId < 343) {\n if (routeId == 341) {\n return\n 0x18f586E816eEeDbb57B8011239150367561B58Fb;\n } else {\n return\n 0xd0CD802B19c1a52501cb2f07d656e3Cd7B0Ce124;\n }\n } else {\n if (routeId == 343) {\n return\n 0xe0AeD899b39C6e4f2d83e4913a1e9e0cf6368abE;\n } else {\n return\n 0x0606e1b6c0f1A398C38825DCcc4678a7Cbc2737c;\n }\n }\n }\n } else {\n if (routeId < 349) {\n if (routeId < 347) {\n if (routeId == 345) {\n return\n 0x2d188e85b27d18EF80f16686EA1593ABF7Ed2A63;\n } else {\n return\n 0x64412292fA4A135a3300E24366E99ff59Db2eAc1;\n }\n } else {\n if (routeId == 347) {\n return\n 0x38b74c173f3733E8b90aAEf0e98B89791266149F;\n } else {\n return\n 0x36DAA49A79aaEF4E7a217A11530D3cCD84414124;\n }\n }\n } else {\n if (routeId < 351) {\n if (routeId == 349) {\n return\n 0x10f088FE2C88F90270E4449c46c8B1b232511d58;\n } else {\n return\n 0x4FeDbd25B58586838ABD17D10272697dF1dC3087;\n }\n } else {\n if (routeId == 351) {\n return\n 0x685278209248CB058E5cEe93e37f274A80Faf6eb;\n } else {\n return\n 0xDd9F8F1eeC3955f78168e2Fb2d1e808fa8A8f15b;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 369) {\n if (routeId < 361) {\n if (routeId < 357) {\n if (routeId < 355) {\n if (routeId == 353) {\n return\n 0x7392aEeFD5825aaC28817031dEEBbFaAA20983D9;\n } else {\n return\n 0x0Cc182555E00767D6FB8AD161A10d0C04C476d91;\n }\n } else {\n if (routeId == 355) {\n return\n 0x90E52837d56715c79FD592E8D58bFD20365798b2;\n } else {\n return\n 0x6F4451DE14049B6770ad5BF4013118529e68A40C;\n }\n }\n } else {\n if (routeId < 359) {\n if (routeId == 357) {\n return\n 0x89B97ef2aFAb9ed9c7f0FDb095d02E6840b52d9c;\n } else {\n return\n 0x92A5cC5C42d94d3e23aeB1214fFf43Db2B97759E;\n }\n } else {\n if (routeId == 359) {\n return\n 0x63ddc52F135A1dcBA831EAaC11C63849F018b739;\n } else {\n return\n 0x692A691533B571C2c54C1D7F8043A204b3d8120E;\n }\n }\n }\n } else {\n if (routeId < 365) {\n if (routeId < 363) {\n if (routeId == 361) {\n return\n 0x97c7492CF083969F61C6f302d45c8270391b921c;\n } else {\n return\n 0xDeFD2B8643553dAd19548eB14fd94A57F4B9e543;\n }\n } else {\n if (routeId == 363) {\n return\n 0x30645C04205cA3f670B67b02F971B088930ACB8C;\n } else {\n return\n 0xA6f80ed2d607Cd67aEB4109B64A0BEcc4D7d03CF;\n }\n }\n } else {\n if (routeId < 367) {\n if (routeId == 365) {\n return\n 0xBbbbC6c276eB3F7E674f2D39301509236001c42f;\n } else {\n return\n 0xC20E77d349FB40CE88eB01824e2873ad9f681f3C;\n }\n } else {\n if (routeId == 367) {\n return\n 0x5fCfD9a962De19294467C358C1FA55082285960b;\n } else {\n return\n 0x4D87BD6a0E4E5cc6332923cb3E85fC71b287F58A;\n }\n }\n }\n }\n } else {\n if (routeId < 377) {\n if (routeId < 373) {\n if (routeId < 371) {\n if (routeId == 369) {\n return\n 0x3AA5B757cd6Dde98214E56D57Dde7fcF0F7aB04E;\n } else {\n return\n 0xe28eFCE7192e11a2297f44059113C1fD6967b2d4;\n }\n } else {\n if (routeId == 371) {\n return\n 0x3251cAE10a1Cf246e0808D76ACC26F7B5edA0eE5;\n } else {\n return\n 0xbA2091cc9357Cf4c4F25D64F30d1b4Ba3A5a174B;\n }\n }\n } else {\n if (routeId < 375) {\n if (routeId == 373) {\n return\n 0x49c8e1Da9693692096F63C82D11b52d738566d55;\n } else {\n return\n 0xA0731615aB5FFF451031E9551367A4F7dB27b39c;\n }\n } else {\n if (routeId == 375) {\n return\n 0xFb214541888671AE1403CecC1D59763a12fc1609;\n } else {\n return\n 0x1D6bCB17642E2336405df73dF22F07688cAec020;\n }\n }\n }\n } else {\n if (routeId < 381) {\n if (routeId < 379) {\n if (routeId == 377) {\n return\n 0xfC9c0C7bfe187120fF7f4E21446161794A617a9e;\n } else {\n return\n 0xBa5bF37678EeE2dAB17AEf9D898153258252250E;\n }\n } else {\n if (routeId == 379) {\n return\n 0x7c55690bd2C9961576A32c02f8EB29ed36415Ec7;\n } else {\n return\n 0xcA40073E868E8Bc611aEc8Fe741D17E68Fe422f6;\n }\n }\n } else {\n if (routeId < 383) {\n if (routeId == 381) {\n return\n 0x31641bAFb87E9A58f78835050a7BE56921986339;\n } else {\n return\n 0xA54766424f6dA74b45EbCc5Bf0Bd1D74D2CCcaAB;\n }\n } else {\n if (routeId == 383) {\n return\n 0xc7bBa57F8C179EDDBaa62117ddA360e28f3F8252;\n } else {\n return\n 0x5e663ED97ea77d393B8858C90d0683bF180E0ffd;\n }\n }\n }\n }\n }\n }\n }\n }\n }\n\n if (routes[routeId] == address(0)) revert ZeroAddressNotAllowed();\n return routes[routeId];\n }\n\n /// @notice fallback function to handle swap, bridge execution\n /// @dev ensure routeId is converted to bytes4 and sent as msg.sig in the transaction\n fallback() external payable {\n address routeAddress = addressAt(uint32(msg.sig));\n\n bytes memory result;\n\n assembly {\n // copy function selector and any arguments\n calldatacopy(0, 4, sub(calldatasize(), 4))\n // execute function call using the facet\n result := delegatecall(\n gas(),\n routeAddress,\n 0,\n sub(calldatasize(), 4),\n 0,\n 0\n )\n // get any return value\n returndatacopy(0, 0, returndatasize())\n // return any return value or error back to the caller\n switch result\n case 0 {\n revert(0, returndatasize())\n }\n default {\n return(0, returndatasize())\n }\n }\n }\n}\n"},"src/bridges/hop/interfaces/IHopL1Bridge.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/**\n * @title L1Bridge Hop Interface\n * @notice L1 Hop Bridge, Used to transfer from L1 to L2s.\n */\ninterface IHopL1Bridge {\n /**\n * @notice `amountOutMin` and `deadline` should be 0 when no swap is intended at the destination.\n * @notice `amount` is the total amount the user wants to send including the relayer fee\n * @dev Send tokens to a supported layer-2 to mint hToken and optionally swap the hToken in the\n * AMM at the destination.\n * @param chainId The chainId of the destination chain\n * @param recipient The address receiving funds at the destination\n * @param amount The amount being sent\n * @param amountOutMin The minimum amount received after attempting to swap in the destination\n * AMM market. 0 if no swap is intended.\n * @param deadline The deadline for swapping in the destination AMM market. 0 if no\n * swap is intended.\n * @param relayer The address of the relayer at the destination.\n * @param relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `amount`.\n */\n function sendToL2(\n uint256 chainId,\n address recipient,\n uint256 amount,\n uint256 amountOutMin,\n uint256 deadline,\n address relayer,\n uint256 relayerFee\n ) external payable;\n}\n"},"src/bridges/refuel/interfaces/refuel.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\n/// @notice interface with functions to interact with Refuel contract\ninterface IRefuel {\n /**\n * @notice function to deposit nativeToken to Destination-address on destinationChain\n * @param destinationChainId chainId of the Destination chain\n * @param _to recipient address\n */\n function depositNativeToken(\n uint256 destinationChainId,\n address _to\n ) external payable;\n}\n"},"src/bridges/stargate/interfaces/stargate.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity >=0.8.0;\n\n/**\n * @title IBridgeStargate Interface Contract.\n * @notice Interface used by Stargate-L1 and L2 Router implementations\n * @dev router and routerETH addresses will be distinct for L1 and L2\n */\ninterface IBridgeStargate {\n // @notice Struct to hold the additional-data for bridging ERC20 token\n struct lzTxObj {\n // gas limit to bridge the token in Stargate to destinationChain\n uint256 dstGasForCall;\n // destination nativeAmount, this is always set as 0\n uint256 dstNativeAmount;\n // destination nativeAddress, this is always set as 0x\n bytes dstNativeAddr;\n }\n\n /// @notice function in stargate bridge which is used to bridge ERC20 tokens to recipient on destinationChain\n function swap(\n uint16 _dstChainId,\n uint256 _srcPoolId,\n uint256 _dstPoolId,\n address payable _refundAddress,\n uint256 _amountLD,\n uint256 _minAmountLD,\n lzTxObj memory _lzTxParams,\n bytes calldata _to,\n bytes calldata _payload\n ) external payable;\n\n /// @notice function in stargate bridge which is used to bridge native tokens to recipient on destinationChain\n function swapETH(\n uint16 _dstChainId, // destination Stargate chainId\n address payable _refundAddress, // refund additional messageFee to this address\n bytes calldata _toAddress, // the receiver of the destination ETH\n uint256 _amountLD, // the amount, in Local Decimals, to be swapped\n uint256 _minAmountLD // the minimum amount accepted out on destination\n ) external payable;\n}\n"},"src/controllers/FeesTakerController.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {BaseController} from \"./BaseController.sol\";\nimport {ISocketRequest} from \"../interfaces/ISocketRequest.sol\";\n\n/**\n * @title FeesTaker-Controller Implementation\n * @notice Controller with composed actions to deduct-fees followed by Refuel, Swap and Bridge\n * to be executed Sequentially and this is atomic\n * @author Socket dot tech.\n */\ncontract FeesTakerController is BaseController {\n using SafeTransferLib for ERC20;\n\n /// @notice event emitted upon fee-deduction to fees-taker address\n event SocketFeesDeducted(\n uint256 fees,\n address feesToken,\n address feesTaker\n );\n\n /// @notice Function-selector to invoke deduct-fees and swap token\n /// @dev This function selector is to be used while building transaction-data\n bytes4 public immutable FEES_TAKER_SWAP_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\"takeFeesAndSwap((address,address,uint256,uint32,bytes))\")\n );\n\n /// @notice Function-selector to invoke deduct-fees and bridge token\n /// @dev This function selector is to be used while building transaction-data\n bytes4 public immutable FEES_TAKER_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"takeFeesAndBridge((address,address,uint256,uint32,bytes))\"\n )\n );\n\n /// @notice Function-selector to invoke deduct-fees and bridge multiple tokens\n /// @dev This function selector is to be used while building transaction-data\n bytes4 public immutable FEES_TAKER_MULTI_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"takeFeesAndMultiBridge((address,address,uint256,uint32[],bytes[]))\"\n )\n );\n\n /// @notice Function-selector to invoke deduct-fees followed by swapping of a token and bridging the swapped bridge\n /// @dev This function selector is to be used while building transaction-data\n bytes4 public immutable FEES_TAKER_SWAP_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"takeFeeAndSwapAndBridge((address,address,uint256,uint32,bytes,uint32,bytes))\"\n )\n );\n\n /// @notice Function-selector to invoke deduct-fees refuel\n /// @notice followed by swapping of a token and bridging the swapped bridge\n /// @dev This function selector is to be used while building transaction-data\n bytes4 public immutable FEES_TAKER_REFUEL_SWAP_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"takeFeeAndRefuelAndSwapAndBridge((address,address,uint256,uint32,bytes,uint32,bytes,uint32,bytes))\"\n )\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BaseController\n constructor(\n address _socketGatewayAddress\n ) BaseController(_socketGatewayAddress) {}\n\n /**\n * @notice function to deduct-fees to fees-taker address on source-chain and swap token\n * @dev ensure correct function selector is used to generate transaction-data for bridgeRequest\n * @param ftsRequest feesTakerSwapRequest object generated either off-chain or the calling contract using\n * the function-selector FEES_TAKER_SWAP_FUNCTION_SELECTOR\n * @return output bytes from the swap operation (last operation in the composed actions)\n */\n function takeFeesAndSwap(\n ISocketRequest.FeesTakerSwapRequest calldata ftsRequest\n ) external payable returns (bytes memory) {\n if (ftsRequest.feesToken == NATIVE_TOKEN_ADDRESS) {\n //transfer the native amount to the feeTakerAddress\n payable(ftsRequest.feesTakerAddress).transfer(\n ftsRequest.feesAmount\n );\n } else {\n //transfer feesAmount to feesTakerAddress\n ERC20(ftsRequest.feesToken).safeTransferFrom(\n msg.sender,\n ftsRequest.feesTakerAddress,\n ftsRequest.feesAmount\n );\n }\n\n emit SocketFeesDeducted(\n ftsRequest.feesAmount,\n ftsRequest.feesTakerAddress,\n ftsRequest.feesToken\n );\n\n //call bridge function (executeRoute for the swapRequestData)\n return _executeRoute(ftsRequest.routeId, ftsRequest.swapRequestData);\n }\n\n /**\n * @notice function to deduct-fees to fees-taker address on source-chain and bridge amount to destinationChain\n * @dev ensure correct function selector is used to generate transaction-data for bridgeRequest\n * @param ftbRequest feesTakerBridgeRequest object generated either off-chain or the calling contract using\n * the function-selector FEES_TAKER_BRIDGE_FUNCTION_SELECTOR\n * @return output bytes from the bridge operation (last operation in the composed actions)\n */\n function takeFeesAndBridge(\n ISocketRequest.FeesTakerBridgeRequest calldata ftbRequest\n ) external payable returns (bytes memory) {\n if (ftbRequest.feesToken == NATIVE_TOKEN_ADDRESS) {\n //transfer the native amount to the feeTakerAddress\n payable(ftbRequest.feesTakerAddress).transfer(\n ftbRequest.feesAmount\n );\n } else {\n //transfer feesAmount to feesTakerAddress\n ERC20(ftbRequest.feesToken).safeTransferFrom(\n msg.sender,\n ftbRequest.feesTakerAddress,\n ftbRequest.feesAmount\n );\n }\n\n emit SocketFeesDeducted(\n ftbRequest.feesAmount,\n ftbRequest.feesTakerAddress,\n ftbRequest.feesToken\n );\n\n //call bridge function (executeRoute for the bridgeData)\n return _executeRoute(ftbRequest.routeId, ftbRequest.bridgeRequestData);\n }\n\n /**\n * @notice function to deduct-fees to fees-taker address on source-chain and bridge amount to destinationChain\n * @notice multiple bridge-requests are to be generated and sequence and number of routeIds should match with the bridgeData array\n * @dev ensure correct function selector is used to generate transaction-data for bridgeRequest\n * @param ftmbRequest feesTakerMultiBridgeRequest object generated either off-chain or the calling contract using\n * the function-selector FEES_TAKER_MULTI_BRIDGE_FUNCTION_SELECTOR\n */\n function takeFeesAndMultiBridge(\n ISocketRequest.FeesTakerMultiBridgeRequest calldata ftmbRequest\n ) external payable {\n if (ftmbRequest.feesToken == NATIVE_TOKEN_ADDRESS) {\n //transfer the native amount to the feeTakerAddress\n payable(ftmbRequest.feesTakerAddress).transfer(\n ftmbRequest.feesAmount\n );\n } else {\n //transfer feesAmount to feesTakerAddress\n ERC20(ftmbRequest.feesToken).safeTransferFrom(\n msg.sender,\n ftmbRequest.feesTakerAddress,\n ftmbRequest.feesAmount\n );\n }\n\n emit SocketFeesDeducted(\n ftmbRequest.feesAmount,\n ftmbRequest.feesTakerAddress,\n ftmbRequest.feesToken\n );\n\n // multiple bridge-requests are to be generated and sequence and number of routeIds should match with the bridgeData array\n for (\n uint256 index = 0;\n index < ftmbRequest.bridgeRouteIds.length;\n ++index\n ) {\n //call bridge function (executeRoute for the bridgeData)\n _executeRoute(\n ftmbRequest.bridgeRouteIds[index],\n ftmbRequest.bridgeRequestDataItems[index]\n );\n }\n }\n\n /**\n * @notice function to deduct-fees to fees-taker address on source-chain followed by swap the amount on sourceChain followed by\n * bridging the swapped amount to destinationChain\n * @dev while generating implData for swap and bridgeRequests, ensure correct function selector is used\n * bridge action corresponds to the bridgeAfterSwap function of the bridgeImplementation\n * @param fsbRequest feesTakerSwapBridgeRequest object generated either off-chain or the calling contract using\n * the function-selector FEES_TAKER_SWAP_BRIDGE_FUNCTION_SELECTOR\n */\n function takeFeeAndSwapAndBridge(\n ISocketRequest.FeesTakerSwapBridgeRequest calldata fsbRequest\n ) external payable returns (bytes memory) {\n if (fsbRequest.feesToken == NATIVE_TOKEN_ADDRESS) {\n //transfer the native amount to the feeTakerAddress\n payable(fsbRequest.feesTakerAddress).transfer(\n fsbRequest.feesAmount\n );\n } else {\n //transfer feesAmount to feesTakerAddress\n ERC20(fsbRequest.feesToken).safeTransferFrom(\n msg.sender,\n fsbRequest.feesTakerAddress,\n fsbRequest.feesAmount\n );\n }\n\n emit SocketFeesDeducted(\n fsbRequest.feesAmount,\n fsbRequest.feesTakerAddress,\n fsbRequest.feesToken\n );\n\n // execute swap operation\n bytes memory swapResponseData = _executeRoute(\n fsbRequest.swapRouteId,\n fsbRequest.swapData\n );\n\n uint256 swapAmount = abi.decode(swapResponseData, (uint256));\n\n // swapped amount is to be bridged to the recipient on destinationChain\n bytes memory bridgeImpldata = abi.encodeWithSelector(\n BRIDGE_AFTER_SWAP_SELECTOR,\n swapAmount,\n fsbRequest.bridgeData\n );\n\n // execute bridge operation and return the byte-data from response of bridge operation\n return _executeRoute(fsbRequest.bridgeRouteId, bridgeImpldata);\n }\n\n /**\n * @notice function to deduct-fees to fees-taker address on source-chain followed by refuel followed by\n * swap the amount on sourceChain followed by bridging the swapped amount to destinationChain\n * @dev while generating implData for refuel, swap and bridge Requests, ensure correct function selector is used\n * bridge action corresponds to the bridgeAfterSwap function of the bridgeImplementation\n * @param frsbRequest feesTakerRefuelSwapBridgeRequest object generated either off-chain or the calling contract using\n * the function-selector FEES_TAKER_REFUEL_SWAP_BRIDGE_FUNCTION_SELECTOR\n */\n function takeFeeAndRefuelAndSwapAndBridge(\n ISocketRequest.FeesTakerRefuelSwapBridgeRequest calldata frsbRequest\n ) external payable returns (bytes memory) {\n if (frsbRequest.feesToken == NATIVE_TOKEN_ADDRESS) {\n //transfer the native amount to the feeTakerAddress\n payable(frsbRequest.feesTakerAddress).transfer(\n frsbRequest.feesAmount\n );\n } else {\n //transfer feesAmount to feesTakerAddress\n ERC20(frsbRequest.feesToken).safeTransferFrom(\n msg.sender,\n frsbRequest.feesTakerAddress,\n frsbRequest.feesAmount\n );\n }\n\n emit SocketFeesDeducted(\n frsbRequest.feesAmount,\n frsbRequest.feesTakerAddress,\n frsbRequest.feesToken\n );\n\n // refuel is also done via bridge execution via refuelRouteImplementation identified by refuelRouteId\n _executeRoute(frsbRequest.refuelRouteId, frsbRequest.refuelData);\n\n // execute swap operation\n bytes memory swapResponseData = _executeRoute(\n frsbRequest.swapRouteId,\n frsbRequest.swapData\n );\n\n uint256 swapAmount = abi.decode(swapResponseData, (uint256));\n\n // swapped amount is to be bridged to the recipient on destinationChain\n bytes memory bridgeImpldata = abi.encodeWithSelector(\n BRIDGE_AFTER_SWAP_SELECTOR,\n swapAmount,\n frsbRequest.bridgeData\n );\n\n // execute bridge operation and return the byte-data from response of bridge operation\n return _executeRoute(frsbRequest.bridgeRouteId, bridgeImpldata);\n }\n}\n"},"src/errors/SocketErrors.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nerror CelerRefundNotReady();\nerror OnlySocketDeployer();\nerror OnlySocketGatewayOwner();\nerror OnlySocketGateway();\nerror OnlyOwner();\nerror OnlyNominee();\nerror TransferIdExists();\nerror TransferIdDoesnotExist();\nerror Address0Provided();\nerror SwapFailed();\nerror UnsupportedInterfaceId();\nerror InvalidCelerRefund();\nerror CelerAlreadyRefunded();\nerror IncorrectBridgeRatios();\nerror ZeroAddressNotAllowed();\nerror ArrayLengthMismatch();\n"},"src/bridges/hop/l1/HopImplL1.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport \"../interfaces/IHopL1Bridge.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {HOP} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Hop-L1 Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Hop-Bridge from L1 to Supported L2s\n * Called via SocketGateway if the routeId in the request maps to the routeId of HopImplementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract HopImplL1 is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable HopIdentifier = HOP;\n\n /// @notice Function-selector for ERC20-token bridging on Hop-L1-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable HOP_L1_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,address,address,uint256,uint256,uint256,uint256,(uint256,bytes32))\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Hop-L1-Route\n /// @dev This function selector is to be used while building transaction-data to bridge Native tokens\n bytes4 public immutable HOP_L1_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,address,address,uint256,uint256,uint256,uint256,uint256,bytes32)\"\n )\n );\n\n bytes4 public immutable HOP_L1_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(address,address,address,uint256,uint256,uint256,uint256,bytes32))\"\n )\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n constructor(\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {}\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct HopDataNoToken {\n // The address receiving funds at the destination\n address receiverAddress;\n // address of the Hop-L1-Bridge to handle bridging the tokens\n address l1bridgeAddr;\n // relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `_amount`.\n address relayer;\n // The chainId of the destination chain\n uint256 toChainId;\n // The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n uint256 amountOutMin;\n // The amount distributed to the relayer at the destination. This is subtracted from the `amount`.\n uint256 relayerFee;\n // The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n uint256 deadline;\n // socket offchain created hash\n bytes32 metadata;\n }\n\n struct HopData {\n /// @notice address of token being bridged\n address token;\n // The address receiving funds at the destination\n address receiverAddress;\n // address of the Hop-L1-Bridge to handle bridging the tokens\n address l1bridgeAddr;\n // relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `_amount`.\n address relayer;\n // The chainId of the destination chain\n uint256 toChainId;\n // The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n uint256 amountOutMin;\n // The amount distributed to the relayer at the destination. This is subtracted from the `amount`.\n uint256 relayerFee;\n // The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n uint256 deadline;\n // socket offchain created hash\n bytes32 metadata;\n }\n\n struct HopERC20Data {\n uint256 deadline;\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HopBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for Hop-L1-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n HopData memory hopData = abi.decode(bridgeData, (HopData));\n\n if (hopData.token == NATIVE_TOKEN_ADDRESS) {\n IHopL1Bridge(hopData.l1bridgeAddr).sendToL2{value: amount}(\n hopData.toChainId,\n hopData.receiverAddress,\n amount,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.relayer,\n hopData.relayerFee\n );\n } else {\n ERC20(hopData.token).safeApprove(hopData.l1bridgeAddr, amount);\n\n // perform bridging\n IHopL1Bridge(hopData.l1bridgeAddr).sendToL2(\n hopData.toChainId,\n hopData.receiverAddress,\n amount,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.relayer,\n hopData.relayerFee\n );\n }\n\n emit SocketBridge(\n amount,\n hopData.token,\n hopData.toChainId,\n HopIdentifier,\n msg.sender,\n hopData.receiverAddress,\n hopData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HopBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param hopData encoded data for HopData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n HopDataNoToken calldata hopData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n IHopL1Bridge(hopData.l1bridgeAddr).sendToL2{value: bridgeAmount}(\n hopData.toChainId,\n hopData.receiverAddress,\n bridgeAmount,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.relayer,\n hopData.relayerFee\n );\n } else {\n ERC20(token).safeApprove(hopData.l1bridgeAddr, bridgeAmount);\n\n // perform bridging\n IHopL1Bridge(hopData.l1bridgeAddr).sendToL2(\n hopData.toChainId,\n hopData.receiverAddress,\n bridgeAmount,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.relayer,\n hopData.relayerFee\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n hopData.toChainId,\n HopIdentifier,\n msg.sender,\n hopData.receiverAddress,\n hopData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Hop-L1-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress The address receiving funds at the destination\n * @param token token being bridged\n * @param l1bridgeAddr address of the Hop-L1-Bridge to handle bridging the tokens\n * @param relayer The amount distributed to the relayer at the destination. This is subtracted from the `_amount`.\n * @param toChainId The chainId of the destination chain\n * @param amount The amount being sent\n * @param amountOutMin The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n * @param relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `amount`.\n * @param hopData extra data needed to build the tx\n */\n function bridgeERC20To(\n address receiverAddress,\n address token,\n address l1bridgeAddr,\n address relayer,\n uint256 toChainId,\n uint256 amount,\n uint256 amountOutMin,\n uint256 relayerFee,\n HopERC20Data calldata hopData\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(l1bridgeAddr, amount);\n\n // perform bridging\n IHopL1Bridge(l1bridgeAddr).sendToL2(\n toChainId,\n receiverAddress,\n amount,\n amountOutMin,\n hopData.deadline,\n relayer,\n relayerFee\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n HopIdentifier,\n msg.sender,\n receiverAddress,\n hopData.metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Hop-L1-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress The address receiving funds at the destination\n * @param l1bridgeAddr address of the Hop-L1-Bridge to handle bridging the tokens\n * @param relayer The amount distributed to the relayer at the destination. This is subtracted from the `_amount`.\n * @param toChainId The chainId of the destination chain\n * @param amount The amount being sent\n * @param amountOutMin The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n * @param relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `amount`.\n * @param deadline The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n */\n function bridgeNativeTo(\n address receiverAddress,\n address l1bridgeAddr,\n address relayer,\n uint256 toChainId,\n uint256 amount,\n uint256 amountOutMin,\n uint256 relayerFee,\n uint256 deadline,\n bytes32 metadata\n ) external payable {\n IHopL1Bridge(l1bridgeAddr).sendToL2{value: amount}(\n toChainId,\n receiverAddress,\n amount,\n amountOutMin,\n deadline,\n relayer,\n relayerFee\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n HopIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/static/RouteIdentifiers.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\nbytes32 constant ACROSS = keccak256(\"Across\");\n\nbytes32 constant ANYSWAP = keccak256(\"Anyswap\");\n\nbytes32 constant CBRIDGE = keccak256(\"CBridge\");\n\nbytes32 constant HOP = keccak256(\"Hop\");\n\nbytes32 constant HYPHEN = keccak256(\"Hyphen\");\n\nbytes32 constant NATIVE_OPTIMISM = keccak256(\"NativeOptimism\");\n\nbytes32 constant NATIVE_ARBITRUM = keccak256(\"NativeArbitrum\");\n\nbytes32 constant NATIVE_POLYGON = keccak256(\"NativePolygon\");\n\nbytes32 constant REFUEL = keccak256(\"Refuel\");\n\nbytes32 constant STARGATE = keccak256(\"Stargate\");\n\nbytes32 constant ONEINCH = keccak256(\"OneInch\");\n\nbytes32 constant ZEROX = keccak256(\"Zerox\");\n\nbytes32 constant RAINBOW = keccak256(\"Rainbow\");\n"},"src/SocketGateway.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\npragma experimental ABIEncoderV2;\n\nimport \"./utils/Ownable.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {LibUtil} from \"./libraries/LibUtil.sol\";\nimport \"./libraries/LibBytes.sol\";\nimport {ISocketRoute} from \"./interfaces/ISocketRoute.sol\";\nimport {ISocketRequest} from \"./interfaces/ISocketRequest.sol\";\nimport {ISocketGateway} from \"./interfaces/ISocketGateway.sol\";\nimport {IncorrectBridgeRatios, ZeroAddressNotAllowed, ArrayLengthMismatch} from \"./errors/SocketErrors.sol\";\n\n/// @title SocketGatewayContract\n/// @notice Socketgateway is a contract with entrypoint functions for all interactions with socket liquidity layer\n/// @author Socket Team\ncontract SocketGatewayTemplate is Ownable {\n using LibBytes for bytes;\n using LibBytes for bytes4;\n using SafeTransferLib for ERC20;\n\n /// @notice FunctionSelector used to delegatecall from swap to the function of bridge router implementation\n bytes4 public immutable BRIDGE_AFTER_SWAP_SELECTOR =\n bytes4(keccak256(\"bridgeAfterSwap(uint256,bytes)\"));\n\n /// @notice storage variable to keep track of total number of routes registered in socketgateway\n uint32 public routesCount = 385;\n\n /// @notice storage variable to keep track of total number of controllers registered in socketgateway\n uint32 public controllerCount;\n\n address public immutable disabledRouteAddress;\n\n uint256 public constant CENT_PERCENT = 100e18;\n\n /// @notice storage mapping for route implementation addresses\n mapping(uint32 => address) public routes;\n\n /// storage mapping for controller implemenation addresses\n mapping(uint32 => address) public controllers;\n\n // Events ------------------------------------------------------------------------------------------------------->\n\n /// @notice Event emitted when a router is added to socketgateway\n event NewRouteAdded(uint32 indexed routeId, address indexed route);\n\n /// @notice Event emitted when a route is disabled\n event RouteDisabled(uint32 indexed routeId);\n\n /// @notice Event emitted when ownership transfer is requested by socket-gateway-owner\n event OwnershipTransferRequested(\n address indexed _from,\n address indexed _to\n );\n\n /// @notice Event emitted when a controller is added to socketgateway\n event ControllerAdded(\n uint32 indexed controllerId,\n address indexed controllerAddress\n );\n\n /// @notice Event emitted when a controller is disabled\n event ControllerDisabled(uint32 indexed controllerId);\n\n constructor(address _owner, address _disabledRoute) Ownable(_owner) {\n disabledRouteAddress = _disabledRoute;\n }\n\n // Able to receive ether\n // solhint-disable-next-line no-empty-blocks\n receive() external payable {}\n\n /*******************************************\n * EXTERNAL AND PUBLIC FUNCTIONS *\n *******************************************/\n\n /**\n * @notice executes functions in the routes identified using routeId and functionSelectorData\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in routeData to be built using the function-selector defined as a\n * constant in the route implementation contract\n * @param routeId route identifier\n * @param routeData functionSelectorData generated using the function-selector defined in the route Implementation\n */\n function executeRoute(\n uint32 routeId,\n bytes calldata routeData\n ) external payable returns (bytes memory) {\n (bool success, bytes memory result) = addressAt(routeId).delegatecall(\n routeData\n );\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n return result;\n }\n\n /**\n * @notice swaps a token on sourceChain and split it across multiple bridge-recipients\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being swapped\n * @dev ensure the swap-data and bridge-data is generated using the function-selector defined as a constant in the implementation address\n * @param swapMultiBridgeRequest request\n */\n function swapAndMultiBridge(\n ISocketRequest.SwapMultiBridgeRequest calldata swapMultiBridgeRequest\n ) external payable {\n uint256 requestLength = swapMultiBridgeRequest.bridgeRouteIds.length;\n\n if (\n requestLength != swapMultiBridgeRequest.bridgeImplDataItems.length\n ) {\n revert ArrayLengthMismatch();\n }\n uint256 ratioAggregate;\n for (uint256 index = 0; index < requestLength; ) {\n ratioAggregate += swapMultiBridgeRequest.bridgeRatios[index];\n }\n\n if (ratioAggregate != CENT_PERCENT) {\n revert IncorrectBridgeRatios();\n }\n\n (bool swapSuccess, bytes memory swapResult) = addressAt(\n swapMultiBridgeRequest.swapRouteId\n ).delegatecall(swapMultiBridgeRequest.swapImplData);\n\n if (!swapSuccess) {\n assembly {\n revert(add(swapResult, 32), mload(swapResult))\n }\n }\n\n uint256 amountReceivedFromSwap = abi.decode(swapResult, (uint256));\n\n uint256 bridgedAmount;\n\n for (uint256 index = 0; index < requestLength; ) {\n uint256 bridgingAmount;\n\n // if it is the last bridge request, bridge the remaining amount\n if (index == requestLength - 1) {\n bridgingAmount = amountReceivedFromSwap - bridgedAmount;\n } else {\n // bridging amount is the multiplication of bridgeRatio and amountReceivedFromSwap\n bridgingAmount =\n (amountReceivedFromSwap *\n swapMultiBridgeRequest.bridgeRatios[index]) /\n (CENT_PERCENT);\n }\n\n // update the bridged amount, this would be used for computation for last bridgeRequest\n bridgedAmount += bridgingAmount;\n\n bytes memory bridgeImpldata = abi.encodeWithSelector(\n BRIDGE_AFTER_SWAP_SELECTOR,\n bridgingAmount,\n swapMultiBridgeRequest.bridgeImplDataItems[index]\n );\n\n (bool bridgeSuccess, bytes memory bridgeResult) = addressAt(\n swapMultiBridgeRequest.bridgeRouteIds[index]\n ).delegatecall(bridgeImpldata);\n\n if (!bridgeSuccess) {\n assembly {\n revert(add(bridgeResult, 32), mload(bridgeResult))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice sequentially executes functions in the routes identified using routeId and functionSelectorData\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in each dataItem to be built using the function-selector defined as a\n * constant in the route implementation contract\n * @param routeIds a list of route identifiers\n * @param dataItems a list of functionSelectorData generated using the function-selector defined in the route Implementation\n */\n function executeRoutes(\n uint32[] calldata routeIds,\n bytes[] calldata dataItems\n ) external payable {\n uint256 routeIdslength = routeIds.length;\n if (routeIdslength != dataItems.length) revert ArrayLengthMismatch();\n for (uint256 index = 0; index < routeIdslength; ) {\n (bool success, bytes memory result) = addressAt(routeIds[index])\n .delegatecall(dataItems[index]);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice execute a controller function identified using the controllerId in the request\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in request to be built using the function-selector defined as a\n * constant in the controller implementation contract\n * @param socketControllerRequest socketControllerRequest with controllerId to identify the\n * controllerAddress and byteData constructed using functionSelector\n * of the function being invoked\n * @return bytes data received from the call delegated to controller\n */\n function executeController(\n ISocketGateway.SocketControllerRequest calldata socketControllerRequest\n ) external payable returns (bytes memory) {\n (bool success, bytes memory result) = controllers[\n socketControllerRequest.controllerId\n ].delegatecall(socketControllerRequest.data);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n return result;\n }\n\n /**\n * @notice sequentially executes all controller requests\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in each controller-request to be built using the function-selector defined as a\n * constant in the controller implementation contract\n * @param controllerRequests a list of socketControllerRequest\n * Each controllerRequest contains controllerId to identify the controllerAddress and\n * byteData constructed using functionSelector of the function being invoked\n */\n function executeControllers(\n ISocketGateway.SocketControllerRequest[] calldata controllerRequests\n ) external payable {\n for (uint32 index = 0; index < controllerRequests.length; ) {\n (bool success, bytes memory result) = controllers[\n controllerRequests[index].controllerId\n ].delegatecall(controllerRequests[index].data);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**************************************\n * ADMIN FUNCTIONS *\n **************************************/\n\n /**\n * @notice Add route to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure routeAddress is a verified bridge or middleware implementation address\n * @param routeAddress The address of bridge or middleware implementation contract deployed\n * @return Id of the route added to the routes-mapping in socketGateway storage\n */\n function addRoute(\n address routeAddress\n ) external onlyOwner returns (uint32) {\n uint32 routeId = routesCount;\n routes[routeId] = routeAddress;\n\n routesCount += 1;\n\n emit NewRouteAdded(routeId, routeAddress);\n\n return routeId;\n }\n\n /**\n * @notice Give Infinite or 0 approval to bridgeRoute for the tokenAddress\n This is a restricted function to be called by only socketGatewayOwner\n */\n\n function setApprovalForRouters(\n address[] memory routeAddresses,\n address[] memory tokenAddresses,\n bool isMax\n ) external onlyOwner {\n for (uint32 index = 0; index < routeAddresses.length; ) {\n ERC20(tokenAddresses[index]).approve(\n routeAddresses[index],\n isMax ? type(uint256).max : 0\n );\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice Add controller to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure controllerAddress is a verified controller implementation address\n * @param controllerAddress The address of controller implementation contract deployed\n * @return Id of the controller added to the controllers-mapping in socketGateway storage\n */\n function addController(\n address controllerAddress\n ) external onlyOwner returns (uint32) {\n uint32 controllerId = controllerCount;\n\n controllers[controllerId] = controllerAddress;\n\n controllerCount += 1;\n\n emit ControllerAdded(controllerId, controllerAddress);\n\n return controllerId;\n }\n\n /**\n * @notice disable controller by setting ZeroAddress to the entry in controllers-mapping\n identified by controllerId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param controllerId The Id of controller-implementation in the controllers mapping\n */\n function disableController(uint32 controllerId) public onlyOwner {\n controllers[controllerId] = disabledRouteAddress;\n emit ControllerDisabled(controllerId);\n }\n\n /**\n * @notice disable a route by setting ZeroAddress to the entry in routes-mapping\n identified by routeId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param routeId The Id of route-implementation in the routes mapping\n */\n function disableRoute(uint32 routeId) external onlyOwner {\n routes[routeId] = disabledRouteAddress;\n emit RouteDisabled(routeId);\n }\n\n /*******************************************\n * RESTRICTED RESCUE FUNCTIONS *\n *******************************************/\n\n /**\n * @notice Rescues the ERC20 token to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param token address of the ERC20 token being rescued\n * @param userAddress address to which ERC20 is to be rescued\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external onlyOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice Rescues the native balance to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param userAddress address to which native-balance is to be rescued\n * @param amount amount of native-balance being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external onlyOwner {\n userAddress.transfer(amount);\n }\n\n /*******************************************\n * VIEW FUNCTIONS *\n *******************************************/\n\n /**\n * @notice Get routeImplementation address mapped to the routeId\n * @param routeId routeId is the key in the mapping for routes\n * @return route-implementation address\n */\n function getRoute(uint32 routeId) public view returns (address) {\n return addressAt(routeId);\n }\n\n /**\n * @notice Get controllerImplementation address mapped to the controllerId\n * @param controllerId controllerId is the key in the mapping for controllers\n * @return controller-implementation address\n */\n function getController(uint32 controllerId) public view returns (address) {\n return controllers[controllerId];\n }\n\n function addressAt(uint32 routeId) public view returns (address) {\n if (routeId < 385) {\n if (routeId < 257) {\n if (routeId < 129) {\n if (routeId < 65) {\n if (routeId < 33) {\n if (routeId < 17) {\n if (routeId < 9) {\n if (routeId < 5) {\n if (routeId < 3) {\n if (routeId == 1) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 3) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 7) {\n if (routeId == 5) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 7) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 13) {\n if (routeId < 11) {\n if (routeId == 9) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 11) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 15) {\n if (routeId == 13) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 15) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 25) {\n if (routeId < 21) {\n if (routeId < 19) {\n if (routeId == 17) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 19) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 23) {\n if (routeId == 21) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 23) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 29) {\n if (routeId < 27) {\n if (routeId == 25) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 27) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 31) {\n if (routeId == 29) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 31) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 49) {\n if (routeId < 41) {\n if (routeId < 37) {\n if (routeId < 35) {\n if (routeId == 33) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 35) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 39) {\n if (routeId == 37) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 39) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 45) {\n if (routeId < 43) {\n if (routeId == 41) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 43) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 47) {\n if (routeId == 45) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 47) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 57) {\n if (routeId < 53) {\n if (routeId < 51) {\n if (routeId == 49) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 51) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 55) {\n if (routeId == 53) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 55) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 61) {\n if (routeId < 59) {\n if (routeId == 57) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 59) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 63) {\n if (routeId == 61) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 63) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 97) {\n if (routeId < 81) {\n if (routeId < 73) {\n if (routeId < 69) {\n if (routeId < 67) {\n if (routeId == 65) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 67) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 71) {\n if (routeId == 69) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 71) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 77) {\n if (routeId < 75) {\n if (routeId == 73) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 75) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 79) {\n if (routeId == 77) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 79) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 89) {\n if (routeId < 85) {\n if (routeId < 83) {\n if (routeId == 81) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 83) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 87) {\n if (routeId == 85) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 87) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 93) {\n if (routeId < 91) {\n if (routeId == 89) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 91) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 95) {\n if (routeId == 93) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 95) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 113) {\n if (routeId < 105) {\n if (routeId < 101) {\n if (routeId < 99) {\n if (routeId == 97) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 99) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 103) {\n if (routeId == 101) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 103) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 109) {\n if (routeId < 107) {\n if (routeId == 105) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 107) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 111) {\n if (routeId == 109) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 111) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 121) {\n if (routeId < 117) {\n if (routeId < 115) {\n if (routeId == 113) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 115) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 119) {\n if (routeId == 117) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 119) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 125) {\n if (routeId < 123) {\n if (routeId == 121) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 123) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 127) {\n if (routeId == 125) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 127) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 193) {\n if (routeId < 161) {\n if (routeId < 145) {\n if (routeId < 137) {\n if (routeId < 133) {\n if (routeId < 131) {\n if (routeId == 129) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 131) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 135) {\n if (routeId == 133) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 135) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 141) {\n if (routeId < 139) {\n if (routeId == 137) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 139) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 143) {\n if (routeId == 141) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 143) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 153) {\n if (routeId < 149) {\n if (routeId < 147) {\n if (routeId == 145) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 147) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 151) {\n if (routeId == 149) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 151) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 157) {\n if (routeId < 155) {\n if (routeId == 153) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 155) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 159) {\n if (routeId == 157) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 159) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 177) {\n if (routeId < 169) {\n if (routeId < 165) {\n if (routeId < 163) {\n if (routeId == 161) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 163) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 167) {\n if (routeId == 165) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 167) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 173) {\n if (routeId < 171) {\n if (routeId == 169) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 171) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 175) {\n if (routeId == 173) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 175) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 185) {\n if (routeId < 181) {\n if (routeId < 179) {\n if (routeId == 177) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 179) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 183) {\n if (routeId == 181) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 183) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 189) {\n if (routeId < 187) {\n if (routeId == 185) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 187) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 191) {\n if (routeId == 189) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 191) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 225) {\n if (routeId < 209) {\n if (routeId < 201) {\n if (routeId < 197) {\n if (routeId < 195) {\n if (routeId == 193) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 195) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 199) {\n if (routeId == 197) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 199) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 205) {\n if (routeId < 203) {\n if (routeId == 201) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 203) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 207) {\n if (routeId == 205) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 207) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 217) {\n if (routeId < 213) {\n if (routeId < 211) {\n if (routeId == 209) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 211) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 215) {\n if (routeId == 213) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 215) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 221) {\n if (routeId < 219) {\n if (routeId == 217) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 219) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 223) {\n if (routeId == 221) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 223) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 241) {\n if (routeId < 233) {\n if (routeId < 229) {\n if (routeId < 227) {\n if (routeId == 225) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 227) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 231) {\n if (routeId == 229) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 231) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 237) {\n if (routeId < 235) {\n if (routeId == 233) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 235) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 239) {\n if (routeId == 237) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 239) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 249) {\n if (routeId < 245) {\n if (routeId < 243) {\n if (routeId == 241) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 243) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 247) {\n if (routeId == 245) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 247) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 253) {\n if (routeId < 251) {\n if (routeId == 249) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 251) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 255) {\n if (routeId == 253) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 255) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 321) {\n if (routeId < 289) {\n if (routeId < 273) {\n if (routeId < 265) {\n if (routeId < 261) {\n if (routeId < 259) {\n if (routeId == 257) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 259) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 263) {\n if (routeId == 261) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 263) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 269) {\n if (routeId < 267) {\n if (routeId == 265) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 267) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 271) {\n if (routeId == 269) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 271) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 281) {\n if (routeId < 277) {\n if (routeId < 275) {\n if (routeId == 273) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 275) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 279) {\n if (routeId == 277) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 279) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 285) {\n if (routeId < 283) {\n if (routeId == 281) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 283) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 287) {\n if (routeId == 285) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 287) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 305) {\n if (routeId < 297) {\n if (routeId < 293) {\n if (routeId < 291) {\n if (routeId == 289) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 291) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 295) {\n if (routeId == 293) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 295) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 301) {\n if (routeId < 299) {\n if (routeId == 297) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 299) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 303) {\n if (routeId == 301) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 303) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 313) {\n if (routeId < 309) {\n if (routeId < 307) {\n if (routeId == 305) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 307) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 311) {\n if (routeId == 309) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 311) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 317) {\n if (routeId < 315) {\n if (routeId == 313) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 315) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 319) {\n if (routeId == 317) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 319) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 353) {\n if (routeId < 337) {\n if (routeId < 329) {\n if (routeId < 325) {\n if (routeId < 323) {\n if (routeId == 321) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 323) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 327) {\n if (routeId == 325) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 327) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 333) {\n if (routeId < 331) {\n if (routeId == 329) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 331) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 335) {\n if (routeId == 333) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 335) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 345) {\n if (routeId < 341) {\n if (routeId < 339) {\n if (routeId == 337) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 339) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 343) {\n if (routeId == 341) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 343) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 349) {\n if (routeId < 347) {\n if (routeId == 345) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 347) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 351) {\n if (routeId == 349) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 351) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 369) {\n if (routeId < 361) {\n if (routeId < 357) {\n if (routeId < 355) {\n if (routeId == 353) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 355) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 359) {\n if (routeId == 357) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 359) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 365) {\n if (routeId < 363) {\n if (routeId == 361) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 363) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 367) {\n if (routeId == 365) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 367) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 377) {\n if (routeId < 373) {\n if (routeId < 371) {\n if (routeId == 369) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 371) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 375) {\n if (routeId == 373) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 375) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 381) {\n if (routeId < 379) {\n if (routeId == 377) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 379) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 383) {\n if (routeId == 381) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 383) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n }\n }\n }\n\n if (routes[routeId] == address(0)) revert ZeroAddressNotAllowed();\n return routes[routeId];\n }\n\n /// @notice fallback function to handle swap, bridge execution\n /// @dev ensure routeId is converted to bytes4 and sent as msg.sig in the transaction\n fallback() external payable {\n address routeAddress = addressAt(uint32(msg.sig));\n\n bytes memory result;\n\n assembly {\n // copy function selector and any arguments\n calldatacopy(0, 4, sub(calldatasize(), 4))\n // execute function call using the facet\n result := delegatecall(\n gas(),\n routeAddress,\n 0,\n sub(calldatasize(), 4),\n 0,\n 0\n )\n // get any return value\n returndatacopy(0, 0, returndatasize())\n // return any return value or error back to the caller\n switch result\n case 0 {\n revert(0, returndatasize())\n }\n default {\n return(0, returndatasize())\n }\n }\n }\n}\n"},"src/swap/rainbow/Rainbow.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../SwapImplBase.sol\";\nimport {Address0Provided, SwapFailed} from \"../../errors/SocketErrors.sol\";\nimport {RAINBOW} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Rainbow-Swap-Route Implementation\n * @notice Route implementation with functions to swap tokens via Rainbow-Swap\n * Called via SocketGateway if the routeId in the request maps to the routeId of RainbowImplementation\n * @author Socket dot tech.\n */\ncontract RainbowSwapImpl is SwapImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable RainbowIdentifier = RAINBOW;\n\n /// @notice unique name to identify the router, used to emit event upon successful bridging\n bytes32 public immutable NAME = keccak256(\"Rainbow-Router\");\n\n /// @notice address of rainbow-swap-aggregator to swap the tokens on Chain\n address payable public immutable rainbowSwapAggregator;\n\n /// @notice socketGatewayAddress to be initialised via storage variable SwapImplBase\n /// @notice rainbow swap aggregator contract is payable to allow ethereum swaps\n /// @dev ensure _rainbowSwapAggregator are set properly for the chainId in which the contract is being deployed\n constructor(\n address _rainbowSwapAggregator,\n address _socketGateway,\n address _socketDeployFactory\n ) SwapImplBase(_socketGateway, _socketDeployFactory) {\n rainbowSwapAggregator = payable(_rainbowSwapAggregator);\n }\n\n receive() external payable {}\n\n fallback() external payable {}\n\n /**\n * @notice function to swap tokens on the chain and transfer to receiver address\n * @notice This method is payable because the caller is doing token transfer and swap operation\n * @param fromToken address of token being Swapped\n * @param toToken address of token that recipient will receive after swap\n * @param amount amount of fromToken being swapped\n * @param receiverAddress recipient-address\n * @param swapExtraData additional Data to perform Swap via Rainbow-Aggregator\n * @return swapped amount (in toToken Address)\n */\n function performAction(\n address fromToken,\n address toToken,\n uint256 amount,\n address receiverAddress,\n bytes calldata swapExtraData\n ) external payable override returns (uint256) {\n if (fromToken == address(0)) {\n revert Address0Provided();\n }\n\n bytes memory swapCallData = abi.decode(swapExtraData, (bytes));\n\n uint256 _initialBalanceTokenOut;\n uint256 _finalBalanceTokenOut;\n\n ERC20 toTokenERC20 = ERC20(toToken);\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _initialBalanceTokenOut = toTokenERC20.balanceOf(socketGateway);\n } else {\n _initialBalanceTokenOut = address(this).balance;\n }\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, socketGateway, amount);\n token.safeApprove(rainbowSwapAggregator, amount);\n\n // solhint-disable-next-line\n (bool success, ) = rainbowSwapAggregator.call(swapCallData);\n\n if (!success) {\n revert SwapFailed();\n }\n\n token.safeApprove(rainbowSwapAggregator, 0);\n } else {\n (bool success, ) = rainbowSwapAggregator.call{value: amount}(\n swapCallData\n );\n if (!success) {\n revert SwapFailed();\n }\n }\n\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _finalBalanceTokenOut = toTokenERC20.balanceOf(socketGateway);\n } else {\n _finalBalanceTokenOut = address(this).balance;\n }\n\n uint256 returnAmount = _finalBalanceTokenOut - _initialBalanceTokenOut;\n\n if (toToken == NATIVE_TOKEN_ADDRESS) {\n payable(receiverAddress).transfer(returnAmount);\n } else {\n toTokenERC20.transfer(receiverAddress, returnAmount);\n }\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n RainbowIdentifier,\n receiverAddress\n );\n\n return returnAmount;\n }\n\n /**\n * @notice function to swapWithIn SocketGateway - swaps tokens on the chain to socketGateway as recipient\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param swapExtraData encoded value of properties in the swapData Struct\n * @return swapped amount (in toToken Address)\n */\n function performActionWithIn(\n address fromToken,\n address toToken,\n uint256 amount,\n bytes calldata swapExtraData\n ) external payable override returns (uint256, address) {\n if (fromToken == address(0)) {\n revert Address0Provided();\n }\n\n bytes memory swapCallData = abi.decode(swapExtraData, (bytes));\n\n uint256 _initialBalanceTokenOut;\n uint256 _finalBalanceTokenOut;\n\n ERC20 toTokenERC20 = ERC20(toToken);\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _initialBalanceTokenOut = toTokenERC20.balanceOf(socketGateway);\n } else {\n _initialBalanceTokenOut = address(this).balance;\n }\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, socketGateway, amount);\n token.safeApprove(rainbowSwapAggregator, amount);\n\n // solhint-disable-next-line\n (bool success, ) = rainbowSwapAggregator.call(swapCallData);\n\n if (!success) {\n revert SwapFailed();\n }\n\n token.safeApprove(rainbowSwapAggregator, 0);\n } else {\n (bool success, ) = rainbowSwapAggregator.call{value: amount}(\n swapCallData\n );\n if (!success) {\n revert SwapFailed();\n }\n }\n\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _finalBalanceTokenOut = toTokenERC20.balanceOf(socketGateway);\n } else {\n _finalBalanceTokenOut = address(this).balance;\n }\n\n uint256 returnAmount = _finalBalanceTokenOut - _initialBalanceTokenOut;\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n RainbowIdentifier,\n socketGateway\n );\n\n return (returnAmount, toToken);\n }\n}\n"},"src/interfaces/ISocketBridgeBase.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\ninterface ISocketBridgeBase {\n function killme() external;\n}\n"},"src/interfaces/ISocketRequest.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/**\n * @title ISocketRoute\n * @notice Interface with Request DataStructures to invoke controller functions.\n * @author Socket dot tech.\n */\ninterface ISocketRequest {\n struct SwapMultiBridgeRequest {\n uint32 swapRouteId;\n bytes swapImplData;\n uint32[] bridgeRouteIds;\n bytes[] bridgeImplDataItems;\n uint256[] bridgeRatios;\n bytes[] eventDataItems;\n }\n\n // Datastructure for Refuel-Swap-Bridge function\n struct RefuelSwapBridgeRequest {\n uint32 refuelRouteId;\n bytes refuelData;\n uint32 swapRouteId;\n bytes swapData;\n uint32 bridgeRouteId;\n bytes bridgeData;\n }\n\n // Datastructure for DeductFees-Swap function\n struct FeesTakerSwapRequest {\n address feesTakerAddress;\n address feesToken;\n uint256 feesAmount;\n uint32 routeId;\n bytes swapRequestData;\n }\n\n // Datastructure for DeductFees-Bridge function\n struct FeesTakerBridgeRequest {\n address feesTakerAddress;\n address feesToken;\n uint256 feesAmount;\n uint32 routeId;\n bytes bridgeRequestData;\n }\n\n // Datastructure for DeductFees-MultiBridge function\n struct FeesTakerMultiBridgeRequest {\n address feesTakerAddress;\n address feesToken;\n uint256 feesAmount;\n uint32[] bridgeRouteIds;\n bytes[] bridgeRequestDataItems;\n }\n\n // Datastructure for DeductFees-Swap-Bridge function\n struct FeesTakerSwapBridgeRequest {\n address feesTakerAddress;\n address feesToken;\n uint256 feesAmount;\n uint32 swapRouteId;\n bytes swapData;\n uint32 bridgeRouteId;\n bytes bridgeData;\n }\n\n // Datastructure for DeductFees-Refuel-Swap-Bridge function\n struct FeesTakerRefuelSwapBridgeRequest {\n address feesTakerAddress;\n address feesToken;\n uint256 feesAmount;\n uint32 refuelRouteId;\n bytes refuelData;\n uint32 swapRouteId;\n bytes swapData;\n uint32 bridgeRouteId;\n bytes bridgeData;\n }\n}\n"},"src/utils/Ownable.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-only\npragma solidity ^0.8.4;\n\nimport {OnlyOwner, OnlyNominee} from \"../errors/SocketErrors.sol\";\n\nabstract contract Ownable {\n address private _owner;\n address private _nominee;\n\n event OwnerNominated(address indexed nominee);\n event OwnerClaimed(address indexed claimer);\n\n constructor(address owner_) {\n _claimOwner(owner_);\n }\n\n modifier onlyOwner() {\n if (msg.sender != _owner) {\n revert OnlyOwner();\n }\n _;\n }\n\n function owner() public view returns (address) {\n return _owner;\n }\n\n function nominee() public view returns (address) {\n return _nominee;\n }\n\n function nominateOwner(address nominee_) external {\n if (msg.sender != _owner) {\n revert OnlyOwner();\n }\n _nominee = nominee_;\n emit OwnerNominated(_nominee);\n }\n\n function claimOwner() external {\n if (msg.sender != _nominee) {\n revert OnlyNominee();\n }\n _claimOwner(msg.sender);\n }\n\n function _claimOwner(address claimer_) internal {\n _owner = claimer_;\n _nominee = address(0);\n emit OwnerClaimed(claimer_);\n }\n}\n"},"lib/solmate/src/tokens/ERC20.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-only\npragma solidity >=0.8.0;\n\n/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.\n/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)\n/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)\n/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.\nabstract contract ERC20 {\n /*//////////////////////////////////////////////////////////////\n EVENTS\n //////////////////////////////////////////////////////////////*/\n\n event Transfer(address indexed from, address indexed to, uint256 amount);\n\n event Approval(address indexed owner, address indexed spender, uint256 amount);\n\n /*//////////////////////////////////////////////////////////////\n METADATA STORAGE\n //////////////////////////////////////////////////////////////*/\n\n string public name;\n\n string public symbol;\n\n uint8 public immutable decimals;\n\n /*//////////////////////////////////////////////////////////////\n ERC20 STORAGE\n //////////////////////////////////////////////////////////////*/\n\n uint256 public totalSupply;\n\n mapping(address => uint256) public balanceOf;\n\n mapping(address => mapping(address => uint256)) public allowance;\n\n /*//////////////////////////////////////////////////////////////\n EIP-2612 STORAGE\n //////////////////////////////////////////////////////////////*/\n\n uint256 internal immutable INITIAL_CHAIN_ID;\n\n bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;\n\n mapping(address => uint256) public nonces;\n\n /*//////////////////////////////////////////////////////////////\n CONSTRUCTOR\n //////////////////////////////////////////////////////////////*/\n\n constructor(\n string memory _name,\n string memory _symbol,\n uint8 _decimals\n ) {\n name = _name;\n symbol = _symbol;\n decimals = _decimals;\n\n INITIAL_CHAIN_ID = block.chainid;\n INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();\n }\n\n /*//////////////////////////////////////////////////////////////\n ERC20 LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function approve(address spender, uint256 amount) public virtual returns (bool) {\n allowance[msg.sender][spender] = amount;\n\n emit Approval(msg.sender, spender, amount);\n\n return true;\n }\n\n function transfer(address to, uint256 amount) public virtual returns (bool) {\n balanceOf[msg.sender] -= amount;\n\n // Cannot overflow because the sum of all user\n // balances can't exceed the max uint256 value.\n unchecked {\n balanceOf[to] += amount;\n }\n\n emit Transfer(msg.sender, to, amount);\n\n return true;\n }\n\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) public virtual returns (bool) {\n uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.\n\n if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;\n\n balanceOf[from] -= amount;\n\n // Cannot overflow because the sum of all user\n // balances can't exceed the max uint256 value.\n unchecked {\n balanceOf[to] += amount;\n }\n\n emit Transfer(from, to, amount);\n\n return true;\n }\n\n /*//////////////////////////////////////////////////////////////\n EIP-2612 LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) public virtual {\n require(deadline >= block.timestamp, \"PERMIT_DEADLINE_EXPIRED\");\n\n // Unchecked because the only math done is incrementing\n // the owner's nonce which cannot realistically overflow.\n unchecked {\n address recoveredAddress = ecrecover(\n keccak256(\n abi.encodePacked(\n \"\\x19\\x01\",\n DOMAIN_SEPARATOR(),\n keccak256(\n abi.encode(\n keccak256(\n \"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\"\n ),\n owner,\n spender,\n value,\n nonces[owner]++,\n deadline\n )\n )\n )\n ),\n v,\n r,\n s\n );\n\n require(recoveredAddress != address(0) && recoveredAddress == owner, \"INVALID_SIGNER\");\n\n allowance[recoveredAddress][spender] = value;\n }\n\n emit Approval(owner, spender, value);\n }\n\n function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {\n return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();\n }\n\n function computeDomainSeparator() internal view virtual returns (bytes32) {\n return\n keccak256(\n abi.encode(\n keccak256(\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\"),\n keccak256(bytes(name)),\n keccak256(\"1\"),\n block.chainid,\n address(this)\n )\n );\n }\n\n /*//////////////////////////////////////////////////////////////\n INTERNAL MINT/BURN LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function _mint(address to, uint256 amount) internal virtual {\n totalSupply += amount;\n\n // Cannot overflow because the sum of all user\n // balances can't exceed the max uint256 value.\n unchecked {\n balanceOf[to] += amount;\n }\n\n emit Transfer(address(0), to, amount);\n }\n\n function _burn(address from, uint256 amount) internal virtual {\n balanceOf[from] -= amount;\n\n // Cannot underflow because a user's balance\n // will never be larger than the total supply.\n unchecked {\n totalSupply -= amount;\n }\n\n emit Transfer(from, address(0), amount);\n }\n}\n"},"src/controllers/RefuelSwapAndBridgeController.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {ISocketRequest} from \"../interfaces/ISocketRequest.sol\";\nimport {ISocketRoute} from \"../interfaces/ISocketRoute.sol\";\nimport {BaseController} from \"./BaseController.sol\";\n\n/**\n * @title RefuelSwapAndBridge Controller Implementation\n * @notice Controller with composed actions for Refuel,Swap and Bridge to be executed Sequentially and this is atomic\n * @author Socket dot tech.\n */\ncontract RefuelSwapAndBridgeController is BaseController {\n /// @notice Function-selector to invoke refuel-swap-bridge function\n /// @dev This function selector is to be used while buidling transaction-data\n bytes4 public immutable REFUEL_SWAP_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"refuelAndSwapAndBridge((uint32,bytes,uint32,bytes,uint32,bytes))\"\n )\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BaseController\n constructor(\n address _socketGatewayAddress\n ) BaseController(_socketGatewayAddress) {}\n\n /**\n * @notice function to handle refuel followed by Swap and Bridge actions\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param rsbRequest Request with data to execute refuel followed by swap and bridge\n * @return output data from bridging operation\n */\n function refuelAndSwapAndBridge(\n ISocketRequest.RefuelSwapBridgeRequest calldata rsbRequest\n ) public payable returns (bytes memory) {\n _executeRoute(rsbRequest.refuelRouteId, rsbRequest.refuelData);\n\n // refuel is also a bridging activity via refuel-route-implementation\n bytes memory swapResponseData = _executeRoute(\n rsbRequest.swapRouteId,\n rsbRequest.swapData\n );\n\n uint256 swapAmount = abi.decode(swapResponseData, (uint256));\n\n //sequence of arguments for implData: amount, token, data\n // Bridging the swapAmount received in the preceeding step\n bytes memory bridgeImpldata = abi.encodeWithSelector(\n BRIDGE_AFTER_SWAP_SELECTOR,\n swapAmount,\n rsbRequest.bridgeData\n );\n\n return _executeRoute(rsbRequest.bridgeRouteId, bridgeImpldata);\n }\n}\n"},"src/interfaces/ISocketGateway.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/**\n * @title ISocketGateway\n * @notice Interface for SocketGateway functions.\n * @dev functions can be added here for invocation from external contracts or off-chain\n * @author Socket dot tech.\n */\ninterface ISocketGateway {\n /**\n * @notice Request-struct for controllerRequests\n * @dev ensure the value for data is generated using the function-selectors defined in the controllerImplementation contracts\n */\n struct SocketControllerRequest {\n // controllerId is the id mapped to the controllerAddress\n uint32 controllerId;\n // transactionImplData generated off-chain or by caller using function-selector of the controllerContract\n bytes data;\n }\n\n // @notice view to get owner-address\n function owner() external view returns (address);\n}\n"},"src/libraries/Pb.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity ^0.8.4;\n\n// runtime proto sol library\nlibrary Pb {\n enum WireType {\n Varint,\n Fixed64,\n LengthDelim,\n StartGroup,\n EndGroup,\n Fixed32\n }\n\n struct Buffer {\n uint256 idx; // the start index of next read. when idx=b.length, we're done\n bytes b; // hold serialized proto msg, readonly\n }\n\n // create a new in-memory Buffer object from raw msg bytes\n function fromBytes(\n bytes memory raw\n ) internal pure returns (Buffer memory buf) {\n buf.b = raw;\n buf.idx = 0;\n }\n\n // whether there are unread bytes\n function hasMore(Buffer memory buf) internal pure returns (bool) {\n return buf.idx < buf.b.length;\n }\n\n // decode current field number and wiretype\n function decKey(\n Buffer memory buf\n ) internal pure returns (uint256 tag, WireType wiretype) {\n uint256 v = decVarint(buf);\n tag = v / 8;\n wiretype = WireType(v & 7);\n }\n\n // read varint from current buf idx, move buf.idx to next read, return the int value\n function decVarint(Buffer memory buf) internal pure returns (uint256 v) {\n bytes10 tmp; // proto int is at most 10 bytes (7 bits can be used per byte)\n bytes memory bb = buf.b; // get buf.b mem addr to use in assembly\n v = buf.idx; // use v to save one additional uint variable\n assembly {\n tmp := mload(add(add(bb, 32), v)) // load 10 bytes from buf.b[buf.idx] to tmp\n }\n uint256 b; // store current byte content\n v = 0; // reset to 0 for return value\n for (uint256 i = 0; i < 10; i++) {\n assembly {\n b := byte(i, tmp) // don't use tmp[i] because it does bound check and costs extra\n }\n v |= (b & 0x7F) << (i * 7);\n if (b & 0x80 == 0) {\n buf.idx += i + 1;\n return v;\n }\n }\n revert(); // i=10, invalid varint stream\n }\n\n // read length delimited field and return bytes\n function decBytes(\n Buffer memory buf\n ) internal pure returns (bytes memory b) {\n uint256 len = decVarint(buf);\n uint256 end = buf.idx + len;\n require(end <= buf.b.length); // avoid overflow\n b = new bytes(len);\n bytes memory bufB = buf.b; // get buf.b mem addr to use in assembly\n uint256 bStart;\n uint256 bufBStart = buf.idx;\n assembly {\n bStart := add(b, 32)\n bufBStart := add(add(bufB, 32), bufBStart)\n }\n for (uint256 i = 0; i < len; i += 32) {\n assembly {\n mstore(add(bStart, i), mload(add(bufBStart, i)))\n }\n }\n buf.idx = end;\n }\n\n // move idx pass current value field, to beginning of next tag or msg end\n function skipValue(Buffer memory buf, WireType wire) internal pure {\n if (wire == WireType.Varint) {\n decVarint(buf);\n } else if (wire == WireType.LengthDelim) {\n uint256 len = decVarint(buf);\n buf.idx += len; // skip len bytes value data\n require(buf.idx <= buf.b.length); // avoid overflow\n } else {\n revert();\n } // unsupported wiretype\n }\n\n function _uint256(bytes memory b) internal pure returns (uint256 v) {\n require(b.length <= 32); // b's length must be smaller than or equal to 32\n assembly {\n v := mload(add(b, 32))\n } // load all 32bytes to v\n v = v >> (8 * (32 - b.length)); // only first b.length is valid\n }\n\n function _address(bytes memory b) internal pure returns (address v) {\n v = _addressPayable(b);\n }\n\n function _addressPayable(\n bytes memory b\n ) internal pure returns (address payable v) {\n require(b.length == 20);\n //load 32bytes then shift right 12 bytes\n assembly {\n v := div(mload(add(b, 32)), 0x1000000000000000000000000)\n }\n }\n\n function _bytes32(bytes memory b) internal pure returns (bytes32 v) {\n require(b.length == 32);\n assembly {\n v := mload(add(b, 32))\n }\n }\n}\n"},"src/bridges/BridgeImplBase.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {ISocketGateway} from \"../interfaces/ISocketGateway.sol\";\nimport {ISocketRoute} from \"../interfaces/ISocketRoute.sol\";\nimport {OnlySocketGatewayOwner, OnlySocketDeployer} from \"../errors/SocketErrors.sol\";\n\n/**\n * @title Abstract Implementation Contract.\n * @notice All Bridge Implementation will follow this interface.\n */\nabstract contract BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n /// @notice Address used to identify if it is a native token transfer or not\n address public immutable NATIVE_TOKEN_ADDRESS =\n address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketGateway;\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketDeployFactory;\n\n /// @notice immutable variable with instance of SocketRoute to access route functions\n ISocketRoute public immutable socketRoute;\n\n /// @notice FunctionSelector used to delegatecall from swap to the function of bridge router implementation\n bytes4 public immutable BRIDGE_AFTER_SWAP_SELECTOR =\n bytes4(keccak256(\"bridgeAfterSwap(uint256,bytes)\"));\n\n /****************************************\n * EVENTS *\n ****************************************/\n\n event SocketBridge(\n uint256 amount,\n address token,\n uint256 toChainId,\n bytes32 bridgeName,\n address sender,\n address receiver,\n bytes32 metadata\n );\n\n /**\n * @notice Construct the base for all BridgeImplementations.\n * @param _socketGateway Socketgateway address, an immutable variable to set.\n * @param _socketDeployFactory Socket Deploy Factory address, an immutable variable to set.\n */\n constructor(address _socketGateway, address _socketDeployFactory) {\n socketGateway = _socketGateway;\n socketDeployFactory = _socketDeployFactory;\n socketRoute = ISocketRoute(_socketGateway);\n }\n\n /****************************************\n * MODIFIERS *\n ****************************************/\n\n /// @notice Implementing contract needs to make use of the modifier where restricted access is to be used\n modifier isSocketGatewayOwner() {\n if (msg.sender != ISocketGateway(socketGateway).owner()) {\n revert OnlySocketGatewayOwner();\n }\n _;\n }\n\n /// @notice Implementing contract needs to make use of the modifier where restricted access is to be used\n modifier isSocketDeployFactory() {\n if (msg.sender != socketDeployFactory) {\n revert OnlySocketDeployer();\n }\n _;\n }\n\n /****************************************\n * RESTRICTED FUNCTIONS *\n ****************************************/\n\n /**\n * @notice function to rescue the ERC20 tokens in the bridge Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param token address of ERC20 token being rescued\n * @param userAddress receipient address to which ERC20 tokens will be rescued to\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice function to rescue the native-balance in the bridge Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param userAddress receipient address to which native-balance will be rescued to\n * @param amount amount of native balance tokens being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n userAddress.transfer(amount);\n }\n\n function killme() external isSocketDeployFactory {\n selfdestruct(payable(msg.sender));\n }\n\n /******************************\n * VIRTUAL FUNCTIONS *\n *****************************/\n\n /**\n * @notice function to bridge which is succeeding the swap function\n * @notice this function is to be used only when bridging as a succeeding step\n * @notice All bridge implementation contracts must implement this function\n * @notice bridge-implementations will have a bridge specific struct with properties used in bridging\n * @param bridgeData encoded value of properties in the bridgeData Struct\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable virtual;\n}\n"},"src/bridges/cbridge/CelerImpl.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"../../libraries/Pb.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"./interfaces/cbridge.sol\";\nimport \"./interfaces/ICelerStorageWrapper.sol\";\nimport {TransferIdExists, InvalidCelerRefund, CelerAlreadyRefunded, CelerRefundNotReady} from \"../../errors/SocketErrors.sol\";\nimport {BridgeImplBase} from \"../BridgeImplBase.sol\";\nimport {CBRIDGE} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Celer-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Celer-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of CelerImplementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract CelerImpl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable CBridgeIdentifier = CBRIDGE;\n\n /// @notice Utility to perform operation on Buffer\n using Pb for Pb.Buffer;\n\n /// @notice Function-selector for ERC20-token bridging on Celer-Route\n /// @dev This function selector is to be used while building transaction-data to bridge ERC20 tokens\n bytes4 public immutable CELER_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,uint256,bytes32,uint64,uint64,uint32)\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Celer-Route\n /// @dev This function selector is to be used while building transaction-data to bridge Native tokens\n bytes4 public immutable CELER_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,uint256,bytes32,uint64,uint64,uint32)\"\n )\n );\n\n bytes4 public immutable CELER_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(address,uint64,uint32,uint64,bytes32))\"\n )\n );\n\n /// @notice router Contract instance used to deposit ERC20 and Native on to Celer-Bridge\n /// @dev contract instance is to be initialized in the constructor using the routerAddress passed as constructor argument\n ICBridge public immutable router;\n\n /// @notice celerStorageWrapper Contract instance used to store the transferId generated during ERC20 and Native bridge on to Celer-Bridge\n /// @dev contract instance is to be initialized in the constructor using the celerStorageWrapperAddress passed as constructor argument\n ICelerStorageWrapper public immutable celerStorageWrapper;\n\n /// @notice WETH token address\n address public immutable weth;\n\n /// @notice chainId used during generation of transferId generated while bridging ERC20 and Native on to Celer-Bridge\n /// @dev this is to be initialised in the constructor\n uint64 public immutable chainId;\n\n struct WithdrawMsg {\n uint64 chainid; // tag: 1\n uint64 seqnum; // tag: 2\n address receiver; // tag: 3\n address token; // tag: 4\n uint256 amount; // tag: 5\n bytes32 refid; // tag: 6\n }\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure routerAddress, weth-address, celerStorageWrapperAddress are set properly for the chainId in which the contract is being deployed\n constructor(\n address _routerAddress,\n address _weth,\n address _celerStorageWrapperAddress,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = ICBridge(_routerAddress);\n celerStorageWrapper = ICelerStorageWrapper(_celerStorageWrapperAddress);\n weth = _weth;\n chainId = uint64(block.chainid);\n }\n\n // Function to receive Ether. msg.data must be empty\n receive() external payable {}\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct CelerBridgeDataNoToken {\n address receiverAddress;\n uint64 toChainId;\n uint32 maxSlippage;\n uint64 nonce;\n bytes32 metadata;\n }\n\n struct CelerBridgeData {\n address token;\n address receiverAddress;\n uint64 toChainId;\n uint32 maxSlippage;\n uint64 nonce;\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for CelerBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n CelerBridgeData memory celerBridgeData = abi.decode(\n bridgeData,\n (CelerBridgeData)\n );\n\n if (celerBridgeData.token == NATIVE_TOKEN_ADDRESS) {\n // transferId is generated using the request-params and nonce of the account\n // transferId should be unique for each request and this is used while handling refund from celerBridge\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n celerBridgeData.receiverAddress,\n weth,\n amount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n chainId\n )\n );\n\n // transferId is stored in CelerStorageWrapper with in a mapping where key is transferId and value is the msg-sender\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n\n router.sendNative{value: amount}(\n celerBridgeData.receiverAddress,\n amount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n celerBridgeData.maxSlippage\n );\n } else {\n // transferId is generated using the request-params and nonce of the account\n // transferId should be unique for each request and this is used while handling refund from celerBridge\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n celerBridgeData.receiverAddress,\n celerBridgeData.token,\n amount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n chainId\n )\n );\n\n // transferId is stored in CelerStorageWrapper with in a mapping where key is transferId and value is the msg-sender\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n router.send(\n celerBridgeData.receiverAddress,\n celerBridgeData.token,\n amount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n celerBridgeData.maxSlippage\n );\n }\n\n emit SocketBridge(\n amount,\n celerBridgeData.token,\n celerBridgeData.toChainId,\n CBridgeIdentifier,\n msg.sender,\n celerBridgeData.receiverAddress,\n celerBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param celerBridgeData encoded data for CelerBridgeData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n CelerBridgeDataNoToken calldata celerBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n // transferId is generated using the request-params and nonce of the account\n // transferId should be unique for each request and this is used while handling refund from celerBridge\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n celerBridgeData.receiverAddress,\n weth,\n bridgeAmount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n chainId\n )\n );\n\n // transferId is stored in CelerStorageWrapper with in a mapping where key is transferId and value is the msg-sender\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n\n router.sendNative{value: bridgeAmount}(\n celerBridgeData.receiverAddress,\n bridgeAmount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n celerBridgeData.maxSlippage\n );\n } else {\n // transferId is generated using the request-params and nonce of the account\n // transferId should be unique for each request and this is used while handling refund from celerBridge\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n celerBridgeData.receiverAddress,\n token,\n bridgeAmount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n chainId\n )\n );\n\n // transferId is stored in CelerStorageWrapper with in a mapping where key is transferId and value is the msg-sender\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n router.send(\n celerBridgeData.receiverAddress,\n token,\n bridgeAmount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n celerBridgeData.maxSlippage\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n celerBridgeData.toChainId,\n CBridgeIdentifier,\n msg.sender,\n celerBridgeData.receiverAddress,\n celerBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Celer-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress address of recipient\n * @param token address of token being bridged\n * @param amount amount of token for bridging\n * @param toChainId destination ChainId\n * @param nonce nonce of the sender-account address\n * @param maxSlippage maximum Slippage for the bridging\n */\n function bridgeERC20To(\n address receiverAddress,\n address token,\n uint256 amount,\n bytes32 metadata,\n uint64 toChainId,\n uint64 nonce,\n uint32 maxSlippage\n ) external payable {\n /// @notice transferId is generated using the request-params and nonce of the account\n /// @notice transferId should be unique for each request and this is used while handling refund from celerBridge\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n receiverAddress,\n token,\n amount,\n toChainId,\n nonce,\n chainId\n )\n );\n\n /// @notice stored in the CelerStorageWrapper contract\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n router.send(\n receiverAddress,\n token,\n amount,\n toChainId,\n nonce,\n maxSlippage\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n CBridgeIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Celer-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress address of recipient\n * @param amount amount of token for bridging\n * @param toChainId destination ChainId\n * @param nonce nonce of the sender-account address\n * @param maxSlippage maximum Slippage for the bridging\n */\n function bridgeNativeTo(\n address receiverAddress,\n uint256 amount,\n bytes32 metadata,\n uint64 toChainId,\n uint64 nonce,\n uint32 maxSlippage\n ) external payable {\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n receiverAddress,\n weth,\n amount,\n toChainId,\n nonce,\n chainId\n )\n );\n\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n\n router.sendNative{value: amount}(\n receiverAddress,\n amount,\n toChainId,\n nonce,\n maxSlippage\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n CBridgeIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle refund from CelerBridge-Router\n * @param _request request data generated offchain using the celer-SDK\n * @param _sigs generated offchain using the celer-SDK\n * @param _signers generated offchain using the celer-SDK\n * @param _powers generated offchain using the celer-SDK\n */\n function refundCelerUser(\n bytes calldata _request,\n bytes[] calldata _sigs,\n address[] calldata _signers,\n uint256[] calldata _powers\n ) external payable {\n WithdrawMsg memory request = decWithdrawMsg(_request);\n bytes32 transferId = keccak256(\n abi.encodePacked(\n request.chainid,\n request.seqnum,\n request.receiver,\n request.token,\n request.amount\n )\n );\n uint256 _initialNativeBalance = address(this).balance;\n uint256 _initialTokenBalance = ERC20(request.token).balanceOf(\n address(this)\n );\n if (!router.withdraws(transferId)) {\n router.withdraw(_request, _sigs, _signers, _powers);\n }\n\n if (request.receiver != socketGateway) {\n revert InvalidCelerRefund();\n }\n\n address _receiver = celerStorageWrapper.getAddressFromTransferId(\n request.refid\n );\n celerStorageWrapper.deleteTransferId(request.refid);\n\n if (_receiver == address(0)) {\n revert CelerAlreadyRefunded();\n }\n\n uint256 _nativeBalanceAfter = address(this).balance;\n uint256 _tokenBalanceAfter = ERC20(request.token).balanceOf(\n address(this)\n );\n if (_nativeBalanceAfter > _initialNativeBalance) {\n if ((_nativeBalanceAfter - _initialNativeBalance) != request.amount)\n revert CelerRefundNotReady();\n payable(_receiver).transfer(request.amount);\n return;\n }\n\n if (_tokenBalanceAfter > _initialTokenBalance) {\n if ((_tokenBalanceAfter - _initialTokenBalance) != request.amount)\n revert CelerRefundNotReady();\n ERC20(request.token).safeTransfer(_receiver, request.amount);\n return;\n }\n\n revert CelerRefundNotReady();\n }\n\n function decWithdrawMsg(\n bytes memory raw\n ) internal pure returns (WithdrawMsg memory m) {\n Pb.Buffer memory buf = Pb.fromBytes(raw);\n\n uint256 tag;\n Pb.WireType wire;\n while (buf.hasMore()) {\n (tag, wire) = buf.decKey();\n if (false) {}\n // solidity has no switch/case\n else if (tag == 1) {\n m.chainid = uint64(buf.decVarint());\n } else if (tag == 2) {\n m.seqnum = uint64(buf.decVarint());\n } else if (tag == 3) {\n m.receiver = Pb._address(buf.decBytes());\n } else if (tag == 4) {\n m.token = Pb._address(buf.decBytes());\n } else if (tag == 5) {\n m.amount = Pb._uint256(buf.decBytes());\n } else if (tag == 6) {\n m.refid = Pb._bytes32(buf.decBytes());\n } else {\n buf.skipValue(wire);\n } // skip value of unknown tag\n }\n } // end decoder WithdrawMsg\n}\n"},"src/libraries/LibBytes.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n// Functions taken out from https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol\nlibrary LibBytes {\n // solhint-disable no-inline-assembly\n\n // LibBytes specific errors\n error SliceOverflow();\n error SliceOutOfBounds();\n error AddressOutOfBounds();\n error UintOutOfBounds();\n\n // -------------------------\n\n function concat(\n bytes memory _preBytes,\n bytes memory _postBytes\n ) internal pure returns (bytes memory) {\n bytes memory tempBytes;\n\n assembly {\n // Get a location of some free memory and store it in tempBytes as\n // Solidity does for memory variables.\n tempBytes := mload(0x40)\n\n // Store the length of the first bytes array at the beginning of\n // the memory for tempBytes.\n let length := mload(_preBytes)\n mstore(tempBytes, length)\n\n // Maintain a memory counter for the current write location in the\n // temp bytes array by adding the 32 bytes for the array length to\n // the starting location.\n let mc := add(tempBytes, 0x20)\n // Stop copying when the memory counter reaches the length of the\n // first bytes array.\n let end := add(mc, length)\n\n for {\n // Initialize a copy counter to the start of the _preBytes data,\n // 32 bytes into its memory.\n let cc := add(_preBytes, 0x20)\n } lt(mc, end) {\n // Increase both counters by 32 bytes each iteration.\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n // Write the _preBytes data into the tempBytes memory 32 bytes\n // at a time.\n mstore(mc, mload(cc))\n }\n\n // Add the length of _postBytes to the current length of tempBytes\n // and store it as the new length in the first 32 bytes of the\n // tempBytes memory.\n length := mload(_postBytes)\n mstore(tempBytes, add(length, mload(tempBytes)))\n\n // Move the memory counter back from a multiple of 0x20 to the\n // actual end of the _preBytes data.\n mc := end\n // Stop copying when the memory counter reaches the new combined\n // length of the arrays.\n end := add(mc, length)\n\n for {\n let cc := add(_postBytes, 0x20)\n } lt(mc, end) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n mstore(mc, mload(cc))\n }\n\n // Update the free-memory pointer by padding our last write location\n // to 32 bytes: add 31 bytes to the end of tempBytes to move to the\n // next 32 byte block, then round down to the nearest multiple of\n // 32. If the sum of the length of the two arrays is zero then add\n // one before rounding down to leave a blank 32 bytes (the length block with 0).\n mstore(\n 0x40,\n and(\n add(add(end, iszero(add(length, mload(_preBytes)))), 31),\n not(31) // Round down to the nearest 32 bytes.\n )\n )\n }\n\n return tempBytes;\n }\n\n function slice(\n bytes memory _bytes,\n uint256 _start,\n uint256 _length\n ) internal pure returns (bytes memory) {\n if (_length + 31 < _length) {\n revert SliceOverflow();\n }\n if (_bytes.length < _start + _length) {\n revert SliceOutOfBounds();\n }\n\n bytes memory tempBytes;\n\n assembly {\n switch iszero(_length)\n case 0 {\n // Get a location of some free memory and store it in tempBytes as\n // Solidity does for memory variables.\n tempBytes := mload(0x40)\n\n // The first word of the slice result is potentially a partial\n // word read from the original array. To read it, we calculate\n // the length of that partial word and start copying that many\n // bytes into the array. The first word we copy will start with\n // data we don't care about, but the last `lengthmod` bytes will\n // land at the beginning of the contents of the new array. When\n // we're done copying, we overwrite the full first word with\n // the actual length of the slice.\n let lengthmod := and(_length, 31)\n\n // The multiplication in the next line is necessary\n // because when slicing multiples of 32 bytes (lengthmod == 0)\n // the following copy loop was copying the origin's length\n // and then ending prematurely not copying everything it should.\n let mc := add(\n add(tempBytes, lengthmod),\n mul(0x20, iszero(lengthmod))\n )\n let end := add(mc, _length)\n\n for {\n // The multiplication in the next line has the same exact purpose\n // as the one above.\n let cc := add(\n add(\n add(_bytes, lengthmod),\n mul(0x20, iszero(lengthmod))\n ),\n _start\n )\n } lt(mc, end) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n mstore(mc, mload(cc))\n }\n\n mstore(tempBytes, _length)\n\n //update free-memory pointer\n //allocating the array padded to 32 bytes like the compiler does now\n mstore(0x40, and(add(mc, 31), not(31)))\n }\n //if we want a zero-length slice let's just return a zero-length array\n default {\n tempBytes := mload(0x40)\n //zero out the 32 bytes slice we are about to return\n //we need to do it because Solidity does not garbage collect\n mstore(tempBytes, 0)\n\n mstore(0x40, add(tempBytes, 0x20))\n }\n }\n\n return tempBytes;\n }\n}\n"},"src/bridges/hyphen/interfaces/hyphen.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\n/**\n * @title HyphenLiquidityPoolManager\n * @notice interface with functions to bridge ERC20 and Native via Hyphen-Bridge\n * @author Socket dot tech.\n */\ninterface HyphenLiquidityPoolManager {\n /**\n * @dev Function used to deposit tokens into pool to initiate a cross chain token transfer.\n * @param toChainId Chain id where funds needs to be transfered\n * @param tokenAddress ERC20 Token address that needs to be transfered\n * @param receiver Address on toChainId where tokens needs to be transfered\n * @param amount Amount of token being transfered\n */\n function depositErc20(\n uint256 toChainId,\n address tokenAddress,\n address receiver,\n uint256 amount,\n string calldata tag\n ) external;\n\n /**\n * @dev Function used to deposit native token into pool to initiate a cross chain token transfer.\n * @param receiver Address on toChainId where tokens needs to be transfered\n * @param toChainId Chain id where funds needs to be transfered\n */\n function depositNative(\n address receiver,\n uint256 toChainId,\n string calldata tag\n ) external payable;\n}\n"},"src/swap/SwapImplBase.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {ISocketGateway} from \"../interfaces/ISocketGateway.sol\";\nimport {OnlySocketGatewayOwner, OnlySocketDeployer} from \"../errors/SocketErrors.sol\";\n\n/**\n * @title Abstract Implementation Contract.\n * @notice All Swap Implementation will follow this interface.\n * @author Socket dot tech.\n */\nabstract contract SwapImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n /// @notice Address used to identify if it is a native token transfer or not\n address public immutable NATIVE_TOKEN_ADDRESS =\n address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketGateway;\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketDeployFactory;\n\n /// @notice FunctionSelector used to delegatecall to the performAction function of swap-router-implementation\n bytes4 public immutable SWAP_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\"performAction(address,address,uint256,address,bytes)\")\n );\n\n /// @notice FunctionSelector used to delegatecall to the performActionWithIn function of swap-router-implementation\n bytes4 public immutable SWAP_WITHIN_FUNCTION_SELECTOR =\n bytes4(keccak256(\"performActionWithIn(address,address,uint256,bytes)\"));\n\n /****************************************\n * EVENTS *\n ****************************************/\n\n event SocketSwapTokens(\n address fromToken,\n address toToken,\n uint256 buyAmount,\n uint256 sellAmount,\n bytes32 routeName,\n address receiver\n );\n\n /**\n * @notice Construct the base for all SwapImplementations.\n * @param _socketGateway Socketgateway address, an immutable variable to set.\n */\n constructor(address _socketGateway, address _socketDeployFactory) {\n socketGateway = _socketGateway;\n socketDeployFactory = _socketDeployFactory;\n }\n\n /****************************************\n * MODIFIERS *\n ****************************************/\n\n /// @notice Implementing contract needs to make use of the modifier where restricted access is to be used\n modifier isSocketGatewayOwner() {\n if (msg.sender != ISocketGateway(socketGateway).owner()) {\n revert OnlySocketGatewayOwner();\n }\n _;\n }\n\n /// @notice Implementing contract needs to make use of the modifier where restricted access is to be used\n modifier isSocketDeployFactory() {\n if (msg.sender != socketDeployFactory) {\n revert OnlySocketDeployer();\n }\n _;\n }\n\n /****************************************\n * RESTRICTED FUNCTIONS *\n ****************************************/\n\n /**\n * @notice function to rescue the ERC20 tokens in the Swap-Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param token address of ERC20 token being rescued\n * @param userAddress receipient address to which ERC20 tokens will be rescued to\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice function to rescue the native-balance in the Swap-Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param userAddress receipient address to which native-balance will be rescued to\n * @param amount amount of native balance tokens being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n userAddress.transfer(amount);\n }\n\n function killme() external isSocketDeployFactory {\n selfdestruct(payable(msg.sender));\n }\n\n /******************************\n * VIRTUAL FUNCTIONS *\n *****************************/\n\n /**\n * @notice function to swap tokens on the chain\n * All swap implementation contracts must implement this function\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param receiverAddress recipient address of toToken\n * @param data encoded value of properties in the swapData Struct\n */\n function performAction(\n address fromToken,\n address toToken,\n uint256 amount,\n address receiverAddress,\n bytes memory data\n ) external payable virtual returns (uint256);\n\n /**\n * @notice function to swapWith - swaps tokens on the chain to socketGateway as recipient\n * All swap implementation contracts must implement this function\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param swapExtraData encoded value of properties in the swapData Struct\n */\n function performActionWithIn(\n address fromToken,\n address toToken,\n uint256 amount,\n bytes memory swapExtraData\n ) external payable virtual returns (uint256, address);\n}\n"},"src/swap/zerox/ZeroXSwapImpl.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../SwapImplBase.sol\";\nimport {Address0Provided, SwapFailed} from \"../../errors/SocketErrors.sol\";\nimport {ZEROX} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title ZeroX-Swap-Route Implementation\n * @notice Route implementation with functions to swap tokens via ZeroX-Swap\n * Called via SocketGateway if the routeId in the request maps to the routeId of ZeroX-Swap-Implementation\n * @author Socket dot tech.\n */\ncontract ZeroXSwapImpl is SwapImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable ZeroXIdentifier = ZEROX;\n\n /// @notice unique name to identify the router, used to emit event upon successful bridging\n bytes32 public immutable NAME = keccak256(\"Zerox-Router\");\n\n /// @notice address of ZeroX-Exchange-Proxy to swap the tokens on Chain\n address payable public immutable zeroXExchangeProxy;\n\n /// @notice socketGatewayAddress to be initialised via storage variable SwapImplBase\n /// @notice ZeroXExchangeProxy contract is payable to allow ethereum swaps\n /// @dev ensure _zeroXExchangeProxy are set properly for the chainId in which the contract is being deployed\n constructor(\n address _zeroXExchangeProxy,\n address _socketGateway,\n address _socketDeployFactory\n ) SwapImplBase(_socketGateway, _socketDeployFactory) {\n zeroXExchangeProxy = payable(_zeroXExchangeProxy);\n }\n\n receive() external payable {}\n\n fallback() external payable {}\n\n /**\n * @notice function to swap tokens on the chain and transfer to receiver address\n * @dev This is called only when there is a request for a swap.\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken is to be swapped\n * @param amount amount to be swapped\n * @param receiverAddress address of toToken recipient\n * @param swapExtraData data required for zeroX Exchange to get the swap done\n */\n function performAction(\n address fromToken,\n address toToken,\n uint256 amount,\n address receiverAddress,\n bytes calldata swapExtraData\n ) external payable override returns (uint256) {\n if (fromToken == address(0)) {\n revert Address0Provided();\n }\n\n bytes memory swapCallData = abi.decode(swapExtraData, (bytes));\n\n uint256 _initialBalanceTokenOut;\n uint256 _finalBalanceTokenOut;\n\n ERC20 erc20ToToken = ERC20(toToken);\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _initialBalanceTokenOut = erc20ToToken.balanceOf(address(this));\n } else {\n _initialBalanceTokenOut = address(this).balance;\n }\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, address(this), amount);\n token.safeApprove(zeroXExchangeProxy, amount);\n\n // solhint-disable-next-line\n (bool success, ) = zeroXExchangeProxy.call(swapCallData);\n\n if (!success) {\n revert SwapFailed();\n }\n\n token.safeApprove(zeroXExchangeProxy, 0);\n } else {\n (bool success, ) = zeroXExchangeProxy.call{value: amount}(\n swapCallData\n );\n if (!success) {\n revert SwapFailed();\n }\n }\n\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _finalBalanceTokenOut = erc20ToToken.balanceOf(address(this));\n } else {\n _finalBalanceTokenOut = address(this).balance;\n }\n\n uint256 returnAmount = _finalBalanceTokenOut - _initialBalanceTokenOut;\n\n if (toToken == NATIVE_TOKEN_ADDRESS) {\n payable(receiverAddress).transfer(returnAmount);\n } else {\n erc20ToToken.transfer(receiverAddress, returnAmount);\n }\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n ZeroXIdentifier,\n receiverAddress\n );\n\n return returnAmount;\n }\n\n /**\n * @notice function to swapWithIn SocketGateway - swaps tokens on the chain to socketGateway as recipient\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param swapExtraData encoded value of properties in the swapData Struct\n * @return swapped amount (in toToken Address)\n */\n function performActionWithIn(\n address fromToken,\n address toToken,\n uint256 amount,\n bytes calldata swapExtraData\n ) external payable override returns (uint256, address) {\n if (fromToken == address(0)) {\n revert Address0Provided();\n }\n\n bytes memory swapCallData = abi.decode(swapExtraData, (bytes));\n\n uint256 _initialBalanceTokenOut;\n uint256 _finalBalanceTokenOut;\n\n ERC20 erc20ToToken = ERC20(toToken);\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _initialBalanceTokenOut = erc20ToToken.balanceOf(address(this));\n } else {\n _initialBalanceTokenOut = address(this).balance;\n }\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, address(this), amount);\n token.safeApprove(zeroXExchangeProxy, amount);\n\n // solhint-disable-next-line\n (bool success, ) = zeroXExchangeProxy.call(swapCallData);\n\n if (!success) {\n revert SwapFailed();\n }\n\n token.safeApprove(zeroXExchangeProxy, 0);\n } else {\n (bool success, ) = zeroXExchangeProxy.call{value: amount}(\n swapCallData\n );\n if (!success) {\n revert SwapFailed();\n }\n }\n\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _finalBalanceTokenOut = erc20ToToken.balanceOf(address(this));\n } else {\n _finalBalanceTokenOut = address(this).balance;\n }\n\n uint256 returnAmount = _finalBalanceTokenOut - _initialBalanceTokenOut;\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n ZeroXIdentifier,\n socketGateway\n );\n\n return (returnAmount, toToken);\n }\n}\n"},"src/bridges/cbridge/interfaces/cbridge.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\ninterface ICBridge {\n function send(\n address _receiver,\n address _token,\n uint256 _amount,\n uint64 _dstChinId,\n uint64 _nonce,\n uint32 _maxSlippage\n ) external;\n\n function sendNative(\n address _receiver,\n uint256 _amount,\n uint64 _dstChinId,\n uint64 _nonce,\n uint32 _maxSlippage\n ) external payable;\n\n function withdraws(bytes32 withdrawId) external view returns (bool);\n\n function withdraw(\n bytes calldata _wdmsg,\n bytes[] calldata _sigs,\n address[] calldata _signers,\n uint256[] calldata _powers\n ) external;\n}\n"},"src/bridges/stargate/l2/Stargate.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../interfaces/stargate.sol\";\nimport \"../../../errors/SocketErrors.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {STARGATE} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Stargate-L2-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Stargate-L2-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of Stargate-L2-Implementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract StargateImplL2 is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable StargateIdentifier = STARGATE;\n\n /// @notice Function-selector for ERC20-token bridging on Stargate-L2-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4\n public immutable STARGATE_L2_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,address,uint256,uint256,uint256,(uint256,uint256,uint256,uint256,bytes32,bytes,uint16))\"\n )\n );\n\n bytes4 public immutable STARGATE_L1_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(address,address,uint16,uint256,uint256,uint256,uint256,uint256,uint256,bytes32,bytes))\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Stargate-L2-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4\n public immutable STARGATE_L2_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,address,uint16,uint256,uint256,uint256,bytes32)\"\n )\n );\n\n /// @notice Stargate Router to bridge ERC20 tokens\n IBridgeStargate public immutable router;\n\n /// @notice Stargate Router to bridge native tokens\n IBridgeStargate public immutable routerETH;\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure router, routerEth are set properly for the chainId in which the contract is being deployed\n constructor(\n address _router,\n address _routerEth,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = IBridgeStargate(_router);\n routerETH = IBridgeStargate(_routerEth);\n }\n\n /// @notice Struct to be used as a input parameter for Bridging tokens via Stargate-L2-route\n /// @dev while building transactionData,values should be set in this sequence of properties in this struct\n struct StargateBridgeExtraData {\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 destinationGasLimit;\n uint256 minReceivedAmt;\n bytes32 metadata;\n bytes destinationPayload;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct StargateBridgeDataNoToken {\n address receiverAddress;\n address senderAddress;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n uint256 value;\n // a unique identifier that is uses to dedup transfers\n // this value is the a timestamp sent from frontend, but in theory can be any unique number\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 minReceivedAmt; // defines the slippage, the min qty you would accept on the destination\n uint256 optionalValue;\n uint256 destinationGasLimit;\n bytes32 metadata;\n bytes destinationPayload;\n }\n\n struct StargateBridgeData {\n address token;\n address receiverAddress;\n address senderAddress;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n uint256 value;\n // a unique identifier that is uses to dedup transfers\n // this value is the a timestamp sent from frontend, but in theory can be any unique number\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 minReceivedAmt; // defines the slippage, the min qty you would accept on the destination\n uint256 optionalValue;\n uint256 destinationGasLimit;\n bytes32 metadata;\n bytes destinationPayload;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in Stargate-BridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for Stargate-L1-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n StargateBridgeData memory stargateBridgeData = abi.decode(\n bridgeData,\n (StargateBridgeData)\n );\n\n if (stargateBridgeData.token == NATIVE_TOKEN_ADDRESS) {\n // perform bridging\n routerETH.swapETH{value: amount + stargateBridgeData.optionalValue}(\n stargateBridgeData.stargateDstChainId,\n payable(stargateBridgeData.senderAddress),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n amount,\n stargateBridgeData.minReceivedAmt\n );\n } else {\n ERC20(stargateBridgeData.token).safeApprove(\n address(router),\n amount\n );\n {\n router.swap{value: stargateBridgeData.value}(\n stargateBridgeData.stargateDstChainId,\n stargateBridgeData.srcPoolId,\n stargateBridgeData.dstPoolId,\n payable(stargateBridgeData.senderAddress), // default to refund to main contract\n amount,\n stargateBridgeData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeData.destinationGasLimit,\n 0, // zero amount since this is a ERC20 bridging\n \"0x\" //empty data since this is for only ERC20\n ),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n stargateBridgeData.destinationPayload\n );\n }\n }\n\n emit SocketBridge(\n amount,\n stargateBridgeData.token,\n stargateBridgeData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n stargateBridgeData.receiverAddress,\n stargateBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swapping.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in Stargate-BridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param stargateBridgeData encoded data for StargateBridgeData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n StargateBridgeDataNoToken calldata stargateBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n routerETH.swapETH{\n value: bridgeAmount + stargateBridgeData.optionalValue\n }(\n stargateBridgeData.stargateDstChainId,\n payable(stargateBridgeData.senderAddress),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n bridgeAmount,\n stargateBridgeData.minReceivedAmt\n );\n } else {\n ERC20(token).safeApprove(address(router), bridgeAmount);\n {\n router.swap{value: stargateBridgeData.value}(\n stargateBridgeData.stargateDstChainId,\n stargateBridgeData.srcPoolId,\n stargateBridgeData.dstPoolId,\n payable(stargateBridgeData.senderAddress), // default to refund to main contract\n bridgeAmount,\n stargateBridgeData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeData.destinationGasLimit,\n 0,\n \"0x\"\n ),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n stargateBridgeData.destinationPayload\n );\n }\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n stargateBridgeData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n stargateBridgeData.receiverAddress,\n stargateBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Stargate-L1-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param token address of token being bridged\n * @param senderAddress address of sender\n * @param receiverAddress address of recipient\n * @param amount amount of token being bridge\n * @param value value\n * @param optionalValue optionalValue\n * @param stargateBridgeExtraData stargate bridge extradata\n */\n function bridgeERC20To(\n address token,\n address senderAddress,\n address receiverAddress,\n uint256 amount,\n uint256 value,\n uint256 optionalValue,\n StargateBridgeExtraData calldata stargateBridgeExtraData\n ) external payable {\n // token address might not be indication thats why passed through extraData\n if (token == NATIVE_TOKEN_ADDRESS) {\n // perform bridging\n routerETH.swapETH{value: amount + optionalValue}(\n stargateBridgeExtraData.stargateDstChainId,\n payable(senderAddress),\n abi.encodePacked(receiverAddress),\n amount,\n stargateBridgeExtraData.minReceivedAmt\n );\n } else {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(address(router), amount);\n {\n router.swap{value: value}(\n stargateBridgeExtraData.stargateDstChainId,\n stargateBridgeExtraData.srcPoolId,\n stargateBridgeExtraData.dstPoolId,\n payable(senderAddress), // default to refund to main contract\n amount,\n stargateBridgeExtraData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeExtraData.destinationGasLimit,\n 0, // zero amount since this is a ERC20 bridging\n \"0x\" //empty data since this is for only ERC20\n ),\n abi.encodePacked(receiverAddress),\n stargateBridgeExtraData.destinationPayload\n );\n }\n }\n\n emit SocketBridge(\n amount,\n token,\n stargateBridgeExtraData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n receiverAddress,\n stargateBridgeExtraData.metadata\n );\n }\n\n function bridgeNativeTo(\n address receiverAddress,\n address senderAddress,\n uint16 stargateDstChainId,\n uint256 amount,\n uint256 minReceivedAmt,\n uint256 optionalValue,\n bytes32 metadata\n ) external payable {\n // perform bridging\n routerETH.swapETH{value: amount + optionalValue}(\n stargateDstChainId,\n payable(senderAddress),\n abi.encodePacked(receiverAddress),\n amount,\n minReceivedAmt\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"}},"settings":{"optimizer":{"enabled":true,"runs":1000000},"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}},"metadata":{"useLiteralContent":true},"libraries":{}}},"ABI":"[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_disabledRoute\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"ArrayLengthMismatch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"IncorrectBridgeRatios\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyNominee\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddressNotAllowed\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"controllerAddress\",\"type\":\"address\"}],\"name\":\"ControllerAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"}],\"name\":\"ControllerDisabled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"route\",\"type\":\"address\"}],\"name\":\"NewRouteAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"}],\"name\":\"OwnerClaimed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"nominee\",\"type\":\"address\"}],\"name\":\"OwnerNominated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"}],\"name\":\"RouteDisabled\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"BRIDGE_AFTER_SWAP_SELECTOR\",\"outputs\":[{\"internalType\":\"bytes4\",\"name\":\"\",\"type\":\"bytes4\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"CENT_PERCENT\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"controllerAddress\",\"type\":\"address\"}],\"name\":\"addController\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"routeAddress\",\"type\":\"address\"}],\"name\":\"addRoute\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"}],\"name\":\"addressAt\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"controllerCount\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"controllers\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"}],\"name\":\"disableController\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"}],\"name\":\"disableRoute\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"disabledRouteAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"struct ISocketGateway.SocketControllerRequest\",\"name\":\"socketControllerRequest\",\"type\":\"tuple\"}],\"name\":\"executeController\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"struct ISocketGateway.SocketControllerRequest[]\",\"name\":\"controllerRequests\",\"type\":\"tuple[]\"}],\"name\":\"executeControllers\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"routeData\",\"type\":\"bytes\"}],\"name\":\"executeRoute\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"routeIds\",\"type\":\"uint32[]\"},{\"internalType\":\"bytes[]\",\"name\":\"dataItems\",\"type\":\"bytes[]\"}],\"name\":\"executeRoutes\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"}],\"name\":\"getController\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"}],\"name\":\"getRoute\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"nominee_\",\"type\":\"address\"}],\"name\":\"nominateOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"nominee\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"userAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"rescueEther\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"userAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"rescueFunds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"routes\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"routesCount\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"routeAddresses\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"tokenAddresses\",\"type\":\"address[]\"},{\"internalType\":\"bool\",\"name\":\"isMax\",\"type\":\"bool\"}],\"name\":\"setApprovalForRouters\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"swapRouteId\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"swapImplData\",\"type\":\"bytes\"},{\"internalType\":\"uint32[]\",\"name\":\"bridgeRouteIds\",\"type\":\"uint32[]\"},{\"internalType\":\"bytes[]\",\"name\":\"bridgeImplDataItems\",\"type\":\"bytes[]\"},{\"internalType\":\"uint256[]\",\"name\":\"bridgeRatios\",\"type\":\"uint256[]\"},{\"internalType\":\"bytes[]\",\"name\":\"eventDataItems\",\"type\":\"bytes[]\"}],\"internalType\":\"struct ISocketRequest.SwapMultiBridgeRequest\",\"name\":\"swapMultiBridgeRequest\",\"type\":\"tuple\"}],\"name\":\"swapAndMultiBridge\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]","ContractName":"SocketGateway","CompilerVersion":"v0.8.7+commit.e28d00a7","OptimizationUsed":1,"Runs":1000000,"ConstructorArguments":"0x000000000000000000000000e8dd38e673a93ccfc2e3d7053efccb5c93f493650000000000000000000000000f34a522ff82151c90679b73211955068fd854f1","EVMVersion":"Default","Library":"","LicenseType":"","Proxy":1,"Implementation":"0xa3c4e32af0da5efaddb20cc9fb26159f55c8c42f","SwarmSource":""}] \ No newline at end of file diff --git a/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/creation_data.json b/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/creation_data.json new file mode 100644 index 0000000000000..08b838e2bc418 --- /dev/null +++ b/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x71356e37e0368bd10bfdbf41dc052fe5fa24cd05","contractCreator":"0xaa1d342354d755ec515f40e7d5e83cb4184bb9ee","txHash":"0x1c800c2c2d5230823602cae6896a8db1ab7d1341ca697c6c64d9f0edf11dabe2"} \ No newline at end of file diff --git a/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/metadata.json b/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/metadata.json new file mode 100644 index 0000000000000..08318ba41edfe --- /dev/null +++ b/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"@openzeppelin/contracts/access/IAccessControl.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev External interface of AccessControl declared to support ERC165 detection.\n */\ninterface IAccessControl {\n /**\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\n *\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\n * {RoleAdminChanged} not being emitted signaling this.\n *\n * _Available since v3.1._\n */\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\n\n /**\n * @dev Emitted when `account` is granted `role`.\n *\n * `sender` is the account that originated the contract call, an admin role\n * bearer except when using {AccessControl-_setupRole}.\n */\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Emitted when `account` is revoked `role`.\n *\n * `sender` is the account that originated the contract call:\n * - if using `revokeRole`, it is the admin role bearer\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\n */\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) external view returns (bool);\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been granted `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) external;\n}\n"},"@openzeppelin/contracts/token/ERC721/IERC721.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC721/IERC721.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../../utils/introspection/IERC165.sol\";\n\n/**\n * @dev Required interface of an ERC721 compliant contract.\n */\ninterface IERC721 is IERC165 {\n /**\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\n */\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\n */\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\n */\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\n\n /**\n * @dev Returns the number of tokens in ``owner``'s account.\n */\n function balanceOf(address owner) external view returns (uint256 balance);\n\n /**\n * @dev Returns the owner of the `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function ownerOf(uint256 tokenId) external view returns (address owner);\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n /**\n * @dev Transfers `tokenId` token from `from` to `to`.\n *\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n /**\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\n * The approval is cleared when the token is transferred.\n *\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\n *\n * Requirements:\n *\n * - The caller must own the token or be an approved operator.\n * - `tokenId` must exist.\n *\n * Emits an {Approval} event.\n */\n function approve(address to, uint256 tokenId) external;\n\n /**\n * @dev Returns the account approved for `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function getApproved(uint256 tokenId) external view returns (address operator);\n\n /**\n * @dev Approve or remove `operator` as an operator for the caller.\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\n *\n * Requirements:\n *\n * - The `operator` cannot be the caller.\n *\n * Emits an {ApprovalForAll} event.\n */\n function setApprovalForAll(address operator, bool _approved) external;\n\n /**\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\n *\n * See {setApprovalForAll}\n */\n function isApprovedForAll(address owner, address operator) external view returns (bool);\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId,\n bytes calldata data\n ) external;\n}\n"},"@openzeppelin/contracts/access/IAccessControlEnumerable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\n\n/**\n * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.\n */\ninterface IAccessControlEnumerable is IAccessControl {\n /**\n * @dev Returns one of the accounts that have `role`. `index` must be a\n * value between 0 and {getRoleMemberCount}, non-inclusive.\n *\n * Role bearers are not sorted in any particular way, and their ordering may\n * change at any point.\n *\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\n * you perform all queries on the same block. See the following\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\n * for more information.\n */\n function getRoleMember(bytes32 role, uint256 index) external view returns (address);\n\n /**\n * @dev Returns the number of accounts that have `role`. Can be used\n * together with {getRoleMember} to enumerate all bearers of a role.\n */\n function getRoleMemberCount(bytes32 role) external view returns (uint256);\n}\n"},"@openzeppelin/contracts/utils/StorageSlot.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Library for reading and writing primitive types to specific storage slots.\n *\n * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.\n * This library helps with reading and writing to such slots without the need for inline assembly.\n *\n * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.\n *\n * Example usage to set ERC1967 implementation slot:\n * ```\n * contract ERC1967 {\n * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n *\n * function _getImplementation() internal view returns (address) {\n * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\n * }\n *\n * function _setImplementation(address newImplementation) internal {\n * require(Address.isContract(newImplementation), \"ERC1967: new implementation is not a contract\");\n * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\n * }\n * }\n * ```\n *\n * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._\n */\nlibrary StorageSlot {\n struct AddressSlot {\n address value;\n }\n\n struct BooleanSlot {\n bool value;\n }\n\n struct Bytes32Slot {\n bytes32 value;\n }\n\n struct Uint256Slot {\n uint256 value;\n }\n\n /**\n * @dev Returns an `AddressSlot` with member `value` located at `slot`.\n */\n function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `BooleanSlot` with member `value` located at `slot`.\n */\n function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.\n */\n function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `Uint256Slot` with member `value` located at `slot`.\n */\n function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {\n assembly {\n r.slot := slot\n }\n }\n}\n"},"@openzeppelin/contracts/utils/cryptography/ECDSA.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/ECDSA.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../Strings.sol\";\n\n/**\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\n *\n * These functions can be used to verify that a message was signed by the holder\n * of the private keys of a given address.\n */\nlibrary ECDSA {\n enum RecoverError {\n NoError,\n InvalidSignature,\n InvalidSignatureLength,\n InvalidSignatureS,\n InvalidSignatureV\n }\n\n function _throwError(RecoverError error) private pure {\n if (error == RecoverError.NoError) {\n return; // no error: do nothing\n } else if (error == RecoverError.InvalidSignature) {\n revert(\"ECDSA: invalid signature\");\n } else if (error == RecoverError.InvalidSignatureLength) {\n revert(\"ECDSA: invalid signature length\");\n } else if (error == RecoverError.InvalidSignatureS) {\n revert(\"ECDSA: invalid signature 's' value\");\n } else if (error == RecoverError.InvalidSignatureV) {\n revert(\"ECDSA: invalid signature 'v' value\");\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature` or error string. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n *\n * Documentation for signature generation:\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\n *\n * _Available since v4.3._\n */\n function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {\n // Check the signature length\n // - case 65: r,s,v signature (standard)\n // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._\n if (signature.length == 65) {\n bytes32 r;\n bytes32 s;\n uint8 v;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n assembly {\n r := mload(add(signature, 0x20))\n s := mload(add(signature, 0x40))\n v := byte(0, mload(add(signature, 0x60)))\n }\n return tryRecover(hash, v, r, s);\n } else if (signature.length == 64) {\n bytes32 r;\n bytes32 vs;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n assembly {\n r := mload(add(signature, 0x20))\n vs := mload(add(signature, 0x40))\n }\n return tryRecover(hash, r, vs);\n } else {\n return (address(0), RecoverError.InvalidSignatureLength);\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature`. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n */\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, signature);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\n *\n * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address, RecoverError) {\n bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);\n uint8 v = uint8((uint256(vs) >> 255) + 27);\n return tryRecover(hash, v, r, s);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\n *\n * _Available since v4.2._\n */\n function recover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, r, vs);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\n * `r` and `s` signature fields separately.\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address, RecoverError) {\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\n // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\n //\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\n // these malleable signatures as well.\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\n return (address(0), RecoverError.InvalidSignatureS);\n }\n if (v != 27 && v != 28) {\n return (address(0), RecoverError.InvalidSignatureV);\n }\n\n // If the signature is valid (and not malleable), return the signer address\n address signer = ecrecover(hash, v, r, s);\n if (signer == address(0)) {\n return (address(0), RecoverError.InvalidSignature);\n }\n\n return (signer, RecoverError.NoError);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function recover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, v, r, s);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\n // 32 is the length in bytes of hash,\n // enforced by the type signature above\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", hash));\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from `s`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n\", Strings.toString(s.length), s));\n }\n\n /**\n * @dev Returns an Ethereum Signed Typed Data, created from a\n * `domainSeparator` and a `structHash`. This produces hash corresponding\n * to the one signed with the\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]\n * JSON-RPC method as part of EIP-712.\n *\n * See {recover}.\n */\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19\\x01\", domainSeparator, structHash));\n }\n}\n"},"contracts/v0.8/extensions/GatewayV2.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"@openzeppelin/contracts/security/Pausable.sol\";\nimport \"../interfaces/IQuorum.sol\";\nimport \"../interfaces/IWeightedValidator.sol\";\nimport \"./HasProxyAdmin.sol\";\n\nabstract contract GatewayV2 is HasProxyAdmin, Pausable, IQuorum {\n /// @dev Emitted when the validator contract address is updated.\n event ValidatorContractUpdated(IWeightedValidator);\n\n uint256 internal _num;\n uint256 internal _denom;\n\n IWeightedValidator public validatorContract;\n uint256 public nonce;\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n */\n uint256[50] private ______gap;\n\n /**\n * @dev See {IQuorum-getThreshold}.\n */\n function getThreshold() external view virtual returns (uint256, uint256) {\n return (_num, _denom);\n }\n\n /**\n * @dev See {IQuorum-checkThreshold}.\n */\n function checkThreshold(uint256 _voteWeight) external view virtual returns (bool) {\n return _voteWeight * _denom >= _num * validatorContract.totalWeights();\n }\n\n /**\n * @dev See {IQuorum-setThreshold}.\n */\n function setThreshold(uint256 _numerator, uint256 _denominator)\n external\n virtual\n onlyAdmin\n returns (uint256, uint256)\n {\n return _setThreshold(_numerator, _denominator);\n }\n\n /**\n * @dev Triggers paused state.\n */\n function pause() external onlyAdmin {\n _pause();\n }\n\n /**\n * @dev Triggers unpaused state.\n */\n function unpause() external onlyAdmin {\n _unpause();\n }\n\n /**\n * @dev Sets validator contract address.\n *\n * Requirements:\n * - The method caller is admin.\n *\n * Emits the `ValidatorContractUpdated` event.\n *\n */\n function setValidatorContract(IWeightedValidator _validatorContract) external virtual onlyAdmin {\n _setValidatorContract(_validatorContract);\n }\n\n /**\n * @dev See {IQuorum-minimumVoteWeight}.\n */\n function minimumVoteWeight() public view virtual returns (uint256) {\n return _minimumVoteWeight(validatorContract.totalWeights());\n }\n\n /**\n * @dev Sets validator contract address.\n *\n * Emits the `ValidatorContractUpdated` event.\n *\n */\n function _setValidatorContract(IWeightedValidator _validatorContract) internal virtual {\n validatorContract = _validatorContract;\n emit ValidatorContractUpdated(_validatorContract);\n }\n\n /**\n * @dev Sets threshold and returns the old one.\n *\n * Emits the `ThresholdUpdated` event.\n *\n */\n function _setThreshold(uint256 _numerator, uint256 _denominator)\n internal\n virtual\n returns (uint256 _previousNum, uint256 _previousDenom)\n {\n require(_numerator <= _denominator, \"GatewayV2: invalid threshold\");\n _previousNum = _num;\n _previousDenom = _denom;\n _num = _numerator;\n _denom = _denominator;\n emit ThresholdUpdated(nonce++, _numerator, _denominator, _previousNum, _previousDenom);\n }\n\n /**\n * @dev Returns minimum vote weight.\n */\n function _minimumVoteWeight(uint256 _totalWeight) internal view virtual returns (uint256) {\n return (_num * _totalWeight + _denom - 1) / _denom;\n }\n}\n"},"@openzeppelin/contracts/proxy/utils/Initializable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (proxy/utils/Initializable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../../utils/Address.sol\";\n\n/**\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\n *\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\n *\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\n *\n * [CAUTION]\n * ====\n * Avoid leaving a contract uninitialized.\n *\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\n * contract, which may impact the proxy. To initialize the implementation contract, you can either invoke the\n * initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed:\n *\n * [.hljs-theme-light.nopadding]\n * ```\n * /// @custom:oz-upgrades-unsafe-allow constructor\n * constructor() initializer {}\n * ```\n * ====\n */\nabstract contract Initializable {\n /**\n * @dev Indicates that the contract has been initialized.\n */\n bool private _initialized;\n\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool private _initializing;\n\n /**\n * @dev Modifier to protect an initializer function from being invoked twice.\n */\n modifier initializer() {\n // If the contract is initializing we ignore whether _initialized is set in order to support multiple\n // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the\n // contract may have been reentered.\n require(_initializing ? _isConstructor() : !_initialized, \"Initializable: contract is already initialized\");\n\n bool isTopLevelCall = !_initializing;\n if (isTopLevelCall) {\n _initializing = true;\n _initialized = true;\n }\n\n _;\n\n if (isTopLevelCall) {\n _initializing = false;\n }\n }\n\n /**\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\n * {initializer} modifier, directly or indirectly.\n */\n modifier onlyInitializing() {\n require(_initializing, \"Initializable: contract is not initializing\");\n _;\n }\n\n function _isConstructor() private view returns (bool) {\n return !Address.isContract(address(this));\n }\n}\n"},"contracts/v0.8/extensions/WithdrawalLimitation.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"./GatewayV2.sol\";\n\nabstract contract WithdrawalLimitation is GatewayV2 {\n /// @dev Emitted when the high-tier vote weight threshold is updated\n event HighTierVoteWeightThresholdUpdated(\n uint256 indexed nonce,\n uint256 indexed numerator,\n uint256 indexed denominator,\n uint256 previousNumerator,\n uint256 previousDenominator\n );\n /// @dev Emitted when the thresholds for high-tier withdrawals that requires high-tier vote weights are updated\n event HighTierThresholdsUpdated(address[] tokens, uint256[] thresholds);\n /// @dev Emitted when the thresholds for locked withdrawals are updated\n event LockedThresholdsUpdated(address[] tokens, uint256[] thresholds);\n /// @dev Emitted when the fee percentages to unlock withdraw are updated\n event UnlockFeePercentagesUpdated(address[] tokens, uint256[] percentages);\n /// @dev Emitted when the daily limit thresholds are updated\n event DailyWithdrawalLimitsUpdated(address[] tokens, uint256[] limits);\n\n uint256 public constant _MAX_PERCENTAGE = 1_000_000;\n\n uint256 internal _highTierVWNum;\n uint256 internal _highTierVWDenom;\n\n /// @dev Mapping from mainchain token => the amount thresholds for high-tier withdrawals that requires high-tier vote weights\n mapping(address => uint256) public highTierThreshold;\n /// @dev Mapping from mainchain token => the amount thresholds to lock withdrawal\n mapping(address => uint256) public lockedThreshold;\n /// @dev Mapping from mainchain token => unlock fee percentages for unlocker\n /// @notice Values 0-1,000,000 map to 0%-100%\n mapping(address => uint256) public unlockFeePercentages;\n /// @dev Mapping from mainchain token => daily limit amount for withdrawal\n mapping(address => uint256) public dailyWithdrawalLimit;\n /// @dev Mapping from token address => today withdrawal amount\n mapping(address => uint256) public lastSyncedWithdrawal;\n /// @dev Mapping from token address => last date synced to record the `lastSyncedWithdrawal`\n mapping(address => uint256) public lastDateSynced;\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n */\n uint256[50] private ______gap;\n\n /**\n * @dev Override {GatewayV2-setThreshold}.\n *\n * Requirements:\n * - The high-tier vote weight threshold must equal to or larger than the normal threshold.\n *\n */\n function setThreshold(uint256 _numerator, uint256 _denominator)\n external\n virtual\n override\n onlyAdmin\n returns (uint256 _previousNum, uint256 _previousDenom)\n {\n (_previousNum, _previousDenom) = _setThreshold(_numerator, _denominator);\n _verifyThresholds();\n }\n\n /**\n * @dev Returns the high-tier vote weight threshold.\n */\n function getHighTierVoteWeightThreshold() external view virtual returns (uint256, uint256) {\n return (_highTierVWNum, _highTierVWDenom);\n }\n\n /**\n * @dev Checks whether the `_voteWeight` passes the high-tier vote weight threshold.\n */\n function checkHighTierVoteWeightThreshold(uint256 _voteWeight) external view virtual returns (bool) {\n return _voteWeight * _highTierVWDenom >= _highTierVWNum * validatorContract.totalWeights();\n }\n\n /**\n * @dev Sets high-tier vote weight threshold and returns the old one.\n *\n * Requirements:\n * - The method caller is admin.\n * - The high-tier vote weight threshold must equal to or larger than the normal threshold.\n *\n * Emits the `HighTierVoteWeightThresholdUpdated` event.\n *\n */\n function setHighTierVoteWeightThreshold(uint256 _numerator, uint256 _denominator)\n external\n virtual\n onlyAdmin\n returns (uint256 _previousNum, uint256 _previousDenom)\n {\n (_previousNum, _previousDenom) = _setHighTierVoteWeightThreshold(_numerator, _denominator);\n _verifyThresholds();\n }\n\n /**\n * @dev Sets the thresholds for high-tier withdrawals that requires high-tier vote weights.\n *\n * Requirements:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `HighTierThresholdsUpdated` event.\n *\n */\n function setHighTierThresholds(address[] calldata _tokens, uint256[] calldata _thresholds)\n external\n virtual\n onlyAdmin\n {\n require(_tokens.length > 0, \"WithdrawalLimitation: invalid array length\");\n _setHighTierThresholds(_tokens, _thresholds);\n }\n\n /**\n * @dev Sets the amount thresholds to lock withdrawal.\n *\n * Requirements:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `LockedThresholdsUpdated` event.\n *\n */\n function setLockedThresholds(address[] calldata _tokens, uint256[] calldata _thresholds) external virtual onlyAdmin {\n require(_tokens.length > 0, \"WithdrawalLimitation: invalid array length\");\n _setLockedThresholds(_tokens, _thresholds);\n }\n\n /**\n * @dev Sets fee percentages to unlock withdrawal.\n *\n * Requirements:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `UnlockFeePercentagesUpdated` event.\n *\n */\n function setUnlockFeePercentages(address[] calldata _tokens, uint256[] calldata _percentages)\n external\n virtual\n onlyAdmin\n {\n require(_tokens.length > 0, \"WithdrawalLimitation: invalid array length\");\n _setUnlockFeePercentages(_tokens, _percentages);\n }\n\n /**\n * @dev Sets daily limit amounts for the withdrawals.\n *\n * Requirements:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `DailyWithdrawalLimitsUpdated` event.\n *\n */\n function setDailyWithdrawalLimits(address[] calldata _tokens, uint256[] calldata _limits) external virtual onlyAdmin {\n require(_tokens.length > 0, \"WithdrawalLimitation: invalid array length\");\n _setDailyWithdrawalLimits(_tokens, _limits);\n }\n\n /**\n * @dev Checks whether the withdrawal reaches the limitation.\n */\n function reachedWithdrawalLimit(address _token, uint256 _quantity) external view virtual returns (bool) {\n return _reachedWithdrawalLimit(_token, _quantity);\n }\n\n /**\n * @dev Sets high-tier vote weight threshold and returns the old one.\n *\n * Emits the `HighTierVoteWeightThresholdUpdated` event.\n *\n */\n function _setHighTierVoteWeightThreshold(uint256 _numerator, uint256 _denominator)\n internal\n returns (uint256 _previousNum, uint256 _previousDenom)\n {\n require(_numerator <= _denominator, \"WithdrawalLimitation: invalid threshold\");\n _previousNum = _highTierVWNum;\n _previousDenom = _highTierVWDenom;\n _highTierVWNum = _numerator;\n _highTierVWDenom = _denominator;\n emit HighTierVoteWeightThresholdUpdated(nonce++, _numerator, _denominator, _previousNum, _previousDenom);\n }\n\n /**\n * @dev Sets the thresholds for high-tier withdrawals that requires high-tier vote weights.\n *\n * Requirements:\n * - The array lengths are equal.\n *\n * Emits the `HighTierThresholdsUpdated` event.\n *\n */\n function _setHighTierThresholds(address[] calldata _tokens, uint256[] calldata _thresholds) internal virtual {\n require(_tokens.length == _thresholds.length, \"WithdrawalLimitation: invalid array length\");\n for (uint256 _i; _i < _tokens.length; _i++) {\n highTierThreshold[_tokens[_i]] = _thresholds[_i];\n }\n emit HighTierThresholdsUpdated(_tokens, _thresholds);\n }\n\n /**\n * @dev Sets the amount thresholds to lock withdrawal.\n *\n * Requirements:\n * - The array lengths are equal.\n *\n * Emits the `LockedThresholdsUpdated` event.\n *\n */\n function _setLockedThresholds(address[] calldata _tokens, uint256[] calldata _thresholds) internal virtual {\n require(_tokens.length == _thresholds.length, \"WithdrawalLimitation: invalid array length\");\n for (uint256 _i; _i < _tokens.length; _i++) {\n lockedThreshold[_tokens[_i]] = _thresholds[_i];\n }\n emit LockedThresholdsUpdated(_tokens, _thresholds);\n }\n\n /**\n * @dev Sets fee percentages to unlock withdrawal.\n *\n * Requirements:\n * - The array lengths are equal.\n * - The percentage is equal to or less than 100_000.\n *\n * Emits the `UnlockFeePercentagesUpdated` event.\n *\n */\n function _setUnlockFeePercentages(address[] calldata _tokens, uint256[] calldata _percentages) internal virtual {\n require(_tokens.length == _percentages.length, \"WithdrawalLimitation: invalid array length\");\n for (uint256 _i; _i < _tokens.length; _i++) {\n require(_percentages[_i] <= _MAX_PERCENTAGE, \"WithdrawalLimitation: invalid percentage\");\n unlockFeePercentages[_tokens[_i]] = _percentages[_i];\n }\n emit UnlockFeePercentagesUpdated(_tokens, _percentages);\n }\n\n /**\n * @dev Sets daily limit amounts for the withdrawals.\n *\n * Requirements:\n * - The array lengths are equal.\n *\n * Emits the `DailyWithdrawalLimitsUpdated` event.\n *\n */\n function _setDailyWithdrawalLimits(address[] calldata _tokens, uint256[] calldata _limits) internal virtual {\n require(_tokens.length == _limits.length, \"WithdrawalLimitation: invalid array length\");\n for (uint256 _i; _i < _tokens.length; _i++) {\n dailyWithdrawalLimit[_tokens[_i]] = _limits[_i];\n }\n emit DailyWithdrawalLimitsUpdated(_tokens, _limits);\n }\n\n /**\n * @dev Checks whether the withdrawal reaches the daily limitation.\n *\n * Requirements:\n * - The daily withdrawal threshold should not apply for locked withdrawals.\n *\n */\n function _reachedWithdrawalLimit(address _token, uint256 _quantity) internal view virtual returns (bool) {\n if (_lockedWithdrawalRequest(_token, _quantity)) {\n return false;\n }\n\n uint256 _currentDate = block.timestamp / 1 days;\n if (_currentDate > lastDateSynced[_token]) {\n return dailyWithdrawalLimit[_token] <= _quantity;\n } else {\n return dailyWithdrawalLimit[_token] <= lastSyncedWithdrawal[_token] + _quantity;\n }\n }\n\n /**\n * @dev Record withdrawal token.\n */\n function _recordWithdrawal(address _token, uint256 _quantity) internal virtual {\n uint256 _currentDate = block.timestamp / 1 days;\n if (_currentDate > lastDateSynced[_token]) {\n lastDateSynced[_token] = _currentDate;\n lastSyncedWithdrawal[_token] = _quantity;\n } else {\n lastSyncedWithdrawal[_token] += _quantity;\n }\n }\n\n /**\n * @dev Returns whether the withdrawal request is locked or not.\n */\n function _lockedWithdrawalRequest(address _token, uint256 _quantity) internal view virtual returns (bool) {\n return lockedThreshold[_token] <= _quantity;\n }\n\n /**\n * @dev Computes fee percentage.\n */\n function _computeFeePercentage(uint256 _amount, uint256 _percentage) internal view virtual returns (uint256) {\n return (_amount * _percentage) / _MAX_PERCENTAGE;\n }\n\n /**\n * @dev Returns high-tier vote weight.\n */\n function _highTierVoteWeight(uint256 _totalWeight) internal view virtual returns (uint256) {\n return (_highTierVWNum * _totalWeight + _highTierVWDenom - 1) / _highTierVWDenom;\n }\n\n /**\n * @dev Validates whether the high-tier vote weight threshold is larger than the normal threshold.\n */\n function _verifyThresholds() internal view {\n require(_num * _highTierVWDenom <= _highTierVWNum * _denom, \"WithdrawalLimitation: invalid thresholds\");\n }\n}\n"},"contracts/v0.8/interfaces/MappedTokenConsumer.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"../library/Token.sol\";\n\ninterface MappedTokenConsumer {\n struct MappedToken {\n Token.Standard erc;\n address tokenAddr;\n }\n}\n"},"contracts/v0.8/library/Token.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC721/IERC721.sol\";\nimport \"@openzeppelin/contracts/utils/Strings.sol\";\nimport \"../interfaces/IWETH.sol\";\n\nlibrary Token {\n enum Standard {\n ERC20,\n ERC721\n }\n struct Info {\n Standard erc;\n // For ERC20: the id must be 0 and the quantity is larger than 0.\n // For ERC721: the quantity must be 0.\n uint256 id;\n uint256 quantity;\n }\n\n // keccak256(\"TokenInfo(uint8 erc,uint256 id,uint256 quantity)\");\n bytes32 public constant INFO_TYPE_HASH = 0x1e2b74b2a792d5c0f0b6e59b037fa9d43d84fbb759337f0112fcc15ca414fc8d;\n\n /**\n * @dev Returns token info struct hash.\n */\n function hash(Info memory _info) internal pure returns (bytes32) {\n return keccak256(abi.encode(INFO_TYPE_HASH, _info.erc, _info.id, _info.quantity));\n }\n\n /**\n * @dev Validates the token info.\n */\n function validate(Info memory _info) internal pure {\n require(\n (_info.erc == Standard.ERC20 && _info.quantity > 0 && _info.id == 0) ||\n (_info.erc == Standard.ERC721 && _info.quantity == 0),\n \"Token: invalid info\"\n );\n }\n\n /**\n * @dev Transfer asset from.\n *\n * Requirements:\n * - The `_from` address must approve for the contract using this library.\n *\n */\n function transferFrom(\n Info memory _info,\n address _from,\n address _to,\n address _token\n ) internal {\n bool _success;\n bytes memory _data;\n if (_info.erc == Standard.ERC20) {\n (_success, _data) = _token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, _from, _to, _info.quantity));\n _success = _success && (_data.length == 0 || abi.decode(_data, (bool)));\n } else if (_info.erc == Standard.ERC721) {\n // bytes4(keccak256(\"transferFrom(address,address,uint256)\"))\n (_success, ) = _token.call(abi.encodeWithSelector(0x23b872dd, _from, _to, _info.id));\n } else {\n revert(\"Token: unsupported token standard\");\n }\n\n if (!_success) {\n revert(\n string(\n abi.encodePacked(\n \"Token: could not transfer \",\n toString(_info),\n \" from \",\n Strings.toHexString(uint160(_from), 20),\n \" to \",\n Strings.toHexString(uint160(_to), 20),\n \" token \",\n Strings.toHexString(uint160(_token), 20)\n )\n )\n );\n }\n }\n\n /**\n * @dev Transfers ERC721 token and returns the result.\n */\n function tryTransferERC721(\n address _token,\n address _to,\n uint256 _id\n ) internal returns (bool _success) {\n (_success, ) = _token.call(abi.encodeWithSelector(IERC721.transferFrom.selector, address(this), _to, _id));\n }\n\n /**\n * @dev Transfers ERC20 token and returns the result.\n */\n function tryTransferERC20(\n address _token,\n address _to,\n uint256 _quantity\n ) internal returns (bool _success) {\n bytes memory _data;\n (_success, _data) = _token.call(abi.encodeWithSelector(IERC20.transfer.selector, _to, _quantity));\n _success = _success && (_data.length == 0 || abi.decode(_data, (bool)));\n }\n\n /**\n * @dev Transfer assets from current address to `_to` address.\n */\n function transfer(\n Info memory _info,\n address _to,\n address _token\n ) internal {\n bool _success;\n if (_info.erc == Standard.ERC20) {\n _success = tryTransferERC20(_token, _to, _info.quantity);\n } else if (_info.erc == Standard.ERC721) {\n _success = tryTransferERC721(_token, _to, _info.id);\n } else {\n revert(\"Token: unsupported token standard\");\n }\n\n if (!_success) {\n revert(\n string(\n abi.encodePacked(\n \"Token: could not transfer \",\n toString(_info),\n \" to \",\n Strings.toHexString(uint160(_to), 20),\n \" token \",\n Strings.toHexString(uint160(_token), 20)\n )\n )\n );\n }\n }\n\n /**\n * @dev Tries minting and transfering assets.\n *\n * @notice Prioritizes transfer native token if the token is wrapped.\n *\n */\n function handleAssetTransfer(\n Info memory _info,\n address payable _to,\n address _token,\n IWETH _wrappedNativeToken\n ) internal {\n bool _success;\n if (_token == address(_wrappedNativeToken)) {\n // Try sending the native token before transferring the wrapped token\n if (!_to.send(_info.quantity)) {\n _wrappedNativeToken.deposit{ value: _info.quantity }();\n transfer(_info, _to, _token);\n }\n } else if (_info.erc == Token.Standard.ERC20) {\n uint256 _balance = IERC20(_token).balanceOf(address(this));\n\n if (_balance < _info.quantity) {\n // bytes4(keccak256(\"mint(address,uint256)\"))\n (_success, ) = _token.call(abi.encodeWithSelector(0x40c10f19, address(this), _info.quantity - _balance));\n require(_success, \"Token: ERC20 minting failed\");\n }\n\n transfer(_info, _to, _token);\n } else if (_info.erc == Token.Standard.ERC721) {\n if (!tryTransferERC721(_token, _to, _info.id)) {\n // bytes4(keccak256(\"mint(address,uint256)\"))\n (_success, ) = _token.call(abi.encodeWithSelector(0x40c10f19, _to, _info.id));\n require(_success, \"Token: ERC721 minting failed\");\n }\n } else {\n revert(\"Token: unsupported token standard\");\n }\n }\n\n /**\n * @dev Returns readable string.\n */\n function toString(Info memory _info) internal pure returns (string memory) {\n return\n string(\n abi.encodePacked(\n \"TokenInfo(\",\n Strings.toHexString(uint160(_info.erc), 1),\n \",\",\n Strings.toHexString(_info.id),\n \",\",\n Strings.toHexString(_info.quantity),\n \")\"\n )\n );\n }\n\n struct Owner {\n address addr;\n address tokenAddr;\n uint256 chainId;\n }\n\n // keccak256(\"TokenOwner(address addr,address tokenAddr,uint256 chainId)\");\n bytes32 public constant OWNER_TYPE_HASH = 0x353bdd8d69b9e3185b3972e08b03845c0c14a21a390215302776a7a34b0e8764;\n\n /**\n * @dev Returns ownership struct hash.\n */\n function hash(Owner memory _owner) internal pure returns (bytes32) {\n return keccak256(abi.encode(OWNER_TYPE_HASH, _owner.addr, _owner.tokenAddr, _owner.chainId));\n }\n}\n"},"contracts/v0.8/mainchain/IMainchainGatewayV2.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"../interfaces/IWETH.sol\";\nimport \"../library/Transfer.sol\";\nimport \"../interfaces/SignatureConsumer.sol\";\nimport \"../interfaces/MappedTokenConsumer.sol\";\n\ninterface IMainchainGatewayV2 is SignatureConsumer, MappedTokenConsumer {\n /// @dev Emitted when the deposit is requested\n event DepositRequested(bytes32 receiptHash, Transfer.Receipt receipt);\n /// @dev Emitted when the assets are withdrawn\n event Withdrew(bytes32 receiptHash, Transfer.Receipt receipt);\n /// @dev Emitted when the tokens are mapped\n event TokenMapped(address[] mainchainTokens, address[] roninTokens, Token.Standard[] standards);\n /// @dev Emitted when the wrapped native token contract is updated\n event WrappedNativeTokenContractUpdated(IWETH weth);\n /// @dev Emitted when the withdrawal is locked\n event WithdrawalLocked(bytes32 receiptHash, Transfer.Receipt receipt);\n /// @dev Emitted when the withdrawal is unlocked\n event WithdrawalUnlocked(bytes32 receiptHash, Transfer.Receipt receipt);\n\n /**\n * @dev Returns the domain seperator.\n */\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n /**\n * @dev Returns deposit count.\n */\n function depositCount() external view returns (uint256);\n\n /**\n * @dev Sets the wrapped native token contract.\n *\n * Requirements:\n * - The method caller is admin.\n *\n * Emits the `WrappedNativeTokenContractUpdated` event.\n *\n */\n function setWrappedNativeTokenContract(IWETH _wrappedToken) external;\n\n /**\n * @dev Returns whether the withdrawal is locked.\n */\n function withdrawalLocked(uint256 withdrawalId) external view returns (bool);\n\n /**\n * @dev Returns the withdrawal hash.\n */\n function withdrawalHash(uint256 withdrawalId) external view returns (bytes32);\n\n /**\n * @dev Locks the assets and request deposit.\n */\n function requestDepositFor(Transfer.Request calldata _request) external payable;\n\n /**\n * @dev Withdraws based on the receipt and the validator signatures.\n * Returns whether the withdrawal is locked.\n *\n * Emits the `Withdrew` once the assets are released.\n *\n */\n function submitWithdrawal(Transfer.Receipt memory _receipt, Signature[] memory _signatures)\n external\n returns (bool _locked);\n\n /**\n * @dev Approves a specific withdrawal.\n *\n * Requirements:\n * - The method caller is a validator.\n *\n * Emits the `Withdrew` once the assets are released.\n *\n */\n function unlockWithdrawal(Transfer.Receipt calldata _receipt) external;\n\n /**\n * @dev Maps mainchain tokens to Ronin network.\n *\n * Requirement:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `TokenMapped` event.\n *\n */\n function mapTokens(\n address[] calldata _mainchainTokens,\n address[] calldata _roninTokens,\n Token.Standard[] calldata _standards\n ) external;\n\n /**\n * @dev Maps mainchain tokens to Ronin network and sets thresholds.\n *\n * Requirement:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `TokenMapped` event.\n *\n */\n function mapTokensAndThresholds(\n address[] calldata _mainchainTokens,\n address[] calldata _roninTokens,\n Token.Standard[] calldata _standards,\n uint256[][4] calldata _thresholds\n ) external;\n\n /**\n * @dev Returns token address on Ronin network.\n * @notice Reverts for unsupported token.\n */\n function getRoninToken(address _mainchainToken) external view returns (MappedToken memory _token);\n}\n"},"contracts/v0.8/mainchain/MainchainGatewayV2.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"@openzeppelin/contracts/access/AccessControlEnumerable.sol\";\nimport \"@openzeppelin/contracts/proxy/utils/Initializable.sol\";\nimport \"../extensions/GatewayV2.sol\";\nimport \"../extensions/WithdrawalLimitation.sol\";\nimport \"../library/Transfer.sol\";\nimport \"./IMainchainGatewayV2.sol\";\n\ncontract MainchainGatewayV2 is WithdrawalLimitation, Initializable, AccessControlEnumerable, IMainchainGatewayV2 {\n using Token for Token.Info;\n using Transfer for Transfer.Request;\n using Transfer for Transfer.Receipt;\n\n /// @dev Withdrawal unlocker role hash\n bytes32 public constant WITHDRAWAL_UNLOCKER_ROLE = keccak256(\"WITHDRAWAL_UNLOCKER_ROLE\");\n\n /// @dev Wrapped native token address\n IWETH public wrappedNativeToken;\n /// @dev Ronin network id\n uint256 public roninChainId;\n /// @dev Total deposit\n uint256 public depositCount;\n /// @dev Domain seperator\n bytes32 internal _domainSeparator;\n /// @dev Mapping from mainchain token => token address on Ronin network\n mapping(address => MappedToken) internal _roninToken;\n /// @dev Mapping from withdrawal id => withdrawal hash\n mapping(uint256 => bytes32) public withdrawalHash;\n /// @dev Mapping from withdrawal id => locked\n mapping(uint256 => bool) public withdrawalLocked;\n\n fallback() external payable {\n _fallback();\n }\n\n receive() external payable {\n _fallback();\n }\n\n /**\n * @dev Initializes contract storage.\n */\n function initialize(\n address _roleSetter,\n IWETH _wrappedToken,\n IWeightedValidator _validatorContract,\n uint256 _roninChainId,\n uint256 _numerator,\n uint256 _highTierVWNumerator,\n uint256 _denominator,\n // _addresses[0]: mainchainTokens\n // _addresses[1]: roninTokens\n // _addresses[2]: withdrawalUnlockers\n address[][3] calldata _addresses,\n // _thresholds[0]: highTierThreshold\n // _thresholds[1]: lockedThreshold\n // _thresholds[2]: unlockFeePercentages\n // _thresholds[3]: dailyWithdrawalLimit\n uint256[][4] calldata _thresholds,\n Token.Standard[] calldata _standards\n ) external payable virtual initializer {\n _setupRole(DEFAULT_ADMIN_ROLE, _roleSetter);\n roninChainId = _roninChainId;\n\n _setWrappedNativeTokenContract(_wrappedToken);\n _setValidatorContract(_validatorContract);\n _updateDomainSeparator();\n _setThreshold(_numerator, _denominator);\n _setHighTierVoteWeightThreshold(_highTierVWNumerator, _denominator);\n _verifyThresholds();\n\n if (_addresses[0].length > 0) {\n // Map mainchain tokens to ronin tokens\n _mapTokens(_addresses[0], _addresses[1], _standards);\n // Sets thresholds based on the mainchain tokens\n _setHighTierThresholds(_addresses[0], _thresholds[0]);\n _setLockedThresholds(_addresses[0], _thresholds[1]);\n _setUnlockFeePercentages(_addresses[0], _thresholds[2]);\n _setDailyWithdrawalLimits(_addresses[0], _thresholds[3]);\n }\n\n // Grant role for withdrawal unlocker\n for (uint256 _i; _i < _addresses[2].length; _i++) {\n _grantRole(WITHDRAWAL_UNLOCKER_ROLE, _addresses[2][_i]);\n }\n }\n\n /**\n * @dev Receives ether without doing anything. Use this function to topup native token.\n */\n function receiveEther() external payable {}\n\n /**\n * @dev See {IMainchainGatewayV2-DOMAIN_SEPARATOR}.\n */\n function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {\n return _domainSeparator;\n }\n\n /**\n * @dev See {IMainchainGatewayV2-setWrappedNativeTokenContract}.\n */\n function setWrappedNativeTokenContract(IWETH _wrappedToken) external virtual onlyAdmin {\n _setWrappedNativeTokenContract(_wrappedToken);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-requestDepositFor}.\n */\n function requestDepositFor(Transfer.Request calldata _request) external payable virtual whenNotPaused {\n _requestDepositFor(_request, msg.sender);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-submitWithdrawal}.\n */\n function submitWithdrawal(Transfer.Receipt calldata _receipt, Signature[] calldata _signatures)\n external\n virtual\n whenNotPaused\n returns (bool _locked)\n {\n return _submitWithdrawal(_receipt, _signatures);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-unlockWithdrawal}.\n */\n function unlockWithdrawal(Transfer.Receipt calldata _receipt) external onlyRole(WITHDRAWAL_UNLOCKER_ROLE) {\n bytes32 _receiptHash = _receipt.hash();\n require(withdrawalHash[_receipt.id] == _receipt.hash(), \"MainchainGatewayV2: invalid receipt\");\n require(withdrawalLocked[_receipt.id], \"MainchainGatewayV2: query for approved withdrawal\");\n delete withdrawalLocked[_receipt.id];\n emit WithdrawalUnlocked(_receiptHash, _receipt);\n\n address _token = _receipt.mainchain.tokenAddr;\n if (_receipt.info.erc == Token.Standard.ERC20) {\n Token.Info memory _feeInfo = _receipt.info;\n _feeInfo.quantity = _computeFeePercentage(_receipt.info.quantity, unlockFeePercentages[_token]);\n Token.Info memory _withdrawInfo = _receipt.info;\n _withdrawInfo.quantity = _receipt.info.quantity - _feeInfo.quantity;\n\n _feeInfo.handleAssetTransfer(payable(msg.sender), _token, wrappedNativeToken);\n _withdrawInfo.handleAssetTransfer(payable(_receipt.mainchain.addr), _token, wrappedNativeToken);\n } else {\n _receipt.info.handleAssetTransfer(payable(_receipt.mainchain.addr), _token, wrappedNativeToken);\n }\n\n emit Withdrew(_receiptHash, _receipt);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-mapTokens}.\n */\n function mapTokens(\n address[] calldata _mainchainTokens,\n address[] calldata _roninTokens,\n Token.Standard[] calldata _standards\n ) external virtual onlyAdmin {\n require(_mainchainTokens.length > 0, \"MainchainGatewayV2: query for empty array\");\n _mapTokens(_mainchainTokens, _roninTokens, _standards);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-mapTokensAndThresholds}.\n */\n function mapTokensAndThresholds(\n address[] calldata _mainchainTokens,\n address[] calldata _roninTokens,\n Token.Standard[] calldata _standards,\n // _thresholds[0]: highTierThreshold\n // _thresholds[1]: lockedThreshold\n // _thresholds[2]: unlockFeePercentages\n // _thresholds[3]: dailyWithdrawalLimit\n uint256[][4] calldata _thresholds\n ) external virtual onlyAdmin {\n require(_mainchainTokens.length > 0, \"MainchainGatewayV2: query for empty array\");\n _mapTokens(_mainchainTokens, _roninTokens, _standards);\n _setHighTierThresholds(_mainchainTokens, _thresholds[0]);\n _setLockedThresholds(_mainchainTokens, _thresholds[1]);\n _setUnlockFeePercentages(_mainchainTokens, _thresholds[2]);\n _setDailyWithdrawalLimits(_mainchainTokens, _thresholds[3]);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-getRoninToken}.\n */\n function getRoninToken(address _mainchainToken) public view returns (MappedToken memory _token) {\n _token = _roninToken[_mainchainToken];\n require(_token.tokenAddr != address(0), \"MainchainGatewayV2: unsupported token\");\n }\n\n /**\n * @dev Maps mainchain tokens to Ronin network.\n *\n * Requirement:\n * - The arrays have the same length.\n *\n * Emits the `TokenMapped` event.\n *\n */\n function _mapTokens(\n address[] calldata _mainchainTokens,\n address[] calldata _roninTokens,\n Token.Standard[] calldata _standards\n ) internal virtual {\n require(\n _mainchainTokens.length == _roninTokens.length && _mainchainTokens.length == _standards.length,\n \"MainchainGatewayV2: invalid array length\"\n );\n\n for (uint256 _i; _i < _mainchainTokens.length; _i++) {\n _roninToken[_mainchainTokens[_i]].tokenAddr = _roninTokens[_i];\n _roninToken[_mainchainTokens[_i]].erc = _standards[_i];\n }\n\n emit TokenMapped(_mainchainTokens, _roninTokens, _standards);\n }\n\n /**\n * @dev Submits withdrawal receipt.\n *\n * Requirements:\n * - The receipt kind is withdrawal.\n * - The receipt is to withdraw on this chain.\n * - The receipt is not used to withdraw before.\n * - The withdrawal is not reached the limit threshold.\n * - The signer weight total is larger than or equal to the minimum threshold.\n * - The signature signers are in order.\n *\n * Emits the `Withdrew` once the assets are released.\n *\n */\n function _submitWithdrawal(Transfer.Receipt calldata _receipt, Signature[] memory _signatures)\n internal\n virtual\n returns (bool _locked)\n {\n uint256 _id = _receipt.id;\n uint256 _quantity = _receipt.info.quantity;\n address _tokenAddr = _receipt.mainchain.tokenAddr;\n\n _receipt.info.validate();\n require(_receipt.kind == Transfer.Kind.Withdrawal, \"MainchainGatewayV2: invalid receipt kind\");\n require(_receipt.mainchain.chainId == block.chainid, \"MainchainGatewayV2: invalid chain id\");\n MappedToken memory _token = getRoninToken(_receipt.mainchain.tokenAddr);\n require(\n _token.erc == _receipt.info.erc && _token.tokenAddr == _receipt.ronin.tokenAddr,\n \"MainchainGatewayV2: invalid receipt\"\n );\n require(withdrawalHash[_id] == bytes32(0), \"MainchainGatewayV2: query for processed withdrawal\");\n require(\n _receipt.info.erc == Token.Standard.ERC721 || !_reachedWithdrawalLimit(_tokenAddr, _quantity),\n \"MainchainGatewayV2: reached daily withdrawal limit\"\n );\n\n bytes32 _receiptHash = _receipt.hash();\n bytes32 _receiptDigest = Transfer.receiptDigest(_domainSeparator, _receiptHash);\n IWeightedValidator _validatorContract = validatorContract;\n\n uint256 _minimumVoteWeight;\n (_minimumVoteWeight, _locked) = _computeMinVoteWeight(_receipt.info.erc, _tokenAddr, _quantity, _validatorContract);\n\n {\n bool _passed;\n address _signer;\n address _lastSigner;\n Signature memory _sig;\n uint256 _weight;\n for (uint256 _i; _i < _signatures.length; _i++) {\n _sig = _signatures[_i];\n _signer = ecrecover(_receiptDigest, _sig.v, _sig.r, _sig.s);\n require(_lastSigner < _signer, \"MainchainGatewayV2: invalid order\");\n _lastSigner = _signer;\n\n _weight += _validatorContract.getValidatorWeight(_signer);\n if (_weight >= _minimumVoteWeight) {\n _passed = true;\n break;\n }\n }\n require(_passed, \"MainchainGatewayV2: query for insufficient vote weight\");\n withdrawalHash[_id] = _receiptHash;\n }\n\n if (_locked) {\n withdrawalLocked[_id] = true;\n emit WithdrawalLocked(_receiptHash, _receipt);\n return _locked;\n }\n\n _recordWithdrawal(_tokenAddr, _quantity);\n _receipt.info.handleAssetTransfer(payable(_receipt.mainchain.addr), _tokenAddr, wrappedNativeToken);\n emit Withdrew(_receiptHash, _receipt);\n }\n\n /**\n * @dev Requests deposit made by `_requester` address.\n *\n * Requirements:\n * - The token info is valid.\n * - The `msg.value` is 0 while depositing ERC20 token.\n * - The `msg.value` is equal to deposit quantity while depositing native token.\n *\n * Emits the `DepositRequested` event.\n *\n */\n function _requestDepositFor(Transfer.Request memory _request, address _requester) internal virtual {\n MappedToken memory _token;\n address _weth = address(wrappedNativeToken);\n\n _request.info.validate();\n if (_request.tokenAddr == address(0)) {\n require(_request.info.quantity == msg.value, \"MainchainGatewayV2: invalid request\");\n _token = getRoninToken(_weth);\n require(_token.erc == _request.info.erc, \"MainchainGatewayV2: invalid token standard\");\n _request.tokenAddr = _weth;\n } else {\n require(msg.value == 0, \"MainchainGatewayV2: invalid request\");\n _token = getRoninToken(_request.tokenAddr);\n require(_token.erc == _request.info.erc, \"MainchainGatewayV2: invalid token standard\");\n _request.info.transferFrom(_requester, address(this), _request.tokenAddr);\n // Withdraw if token is WETH\n if (_weth == _request.tokenAddr) {\n IWETH(_weth).withdraw(_request.info.quantity);\n }\n }\n\n uint256 _depositId = depositCount++;\n Transfer.Receipt memory _receipt = _request.into_deposit_receipt(\n _requester,\n _depositId,\n _token.tokenAddr,\n roninChainId\n );\n\n emit DepositRequested(_receipt.hash(), _receipt);\n }\n\n /**\n * @dev Returns the minimum vote weight for the token.\n */\n function _computeMinVoteWeight(\n Token.Standard _erc,\n address _token,\n uint256 _quantity,\n IWeightedValidator _validatorContract\n ) internal virtual returns (uint256 _weight, bool _locked) {\n uint256 _totalWeights = _validatorContract.totalWeights();\n _weight = _minimumVoteWeight(_totalWeights);\n if (_erc == Token.Standard.ERC20) {\n if (highTierThreshold[_token] <= _quantity) {\n _weight = _highTierVoteWeight(_totalWeights);\n }\n _locked = _lockedWithdrawalRequest(_token, _quantity);\n }\n }\n\n /**\n * @dev Update domain seperator.\n */\n function _updateDomainSeparator() internal {\n _domainSeparator = keccak256(\n abi.encode(\n keccak256(\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\"),\n keccak256(\"MainchainGatewayV2\"),\n keccak256(\"2\"),\n block.chainid,\n address(this)\n )\n );\n }\n\n /**\n * @dev Sets the WETH contract.\n *\n * Emits the `WrappedNativeTokenContractUpdated` event.\n *\n */\n function _setWrappedNativeTokenContract(IWETH _wrapedToken) internal {\n wrappedNativeToken = _wrapedToken;\n emit WrappedNativeTokenContractUpdated(_wrapedToken);\n }\n\n /**\n * @dev Receives ETH from WETH or creates deposit request.\n */\n function _fallback() internal virtual whenNotPaused {\n if (msg.sender != address(wrappedNativeToken)) {\n Transfer.Request memory _request;\n _request.recipientAddr = msg.sender;\n _request.info.quantity = msg.value;\n _requestDepositFor(_request, _request.recipientAddr);\n }\n }\n}\n"},"contracts/v0.8/interfaces/IWeightedValidator.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"./IQuorum.sol\";\n\ninterface IWeightedValidator is IQuorum {\n struct WeightedValidator {\n address validator;\n address governor;\n uint256 weight;\n }\n\n /// @dev Emitted when the validators are added\n event ValidatorsAdded(uint256 indexed nonce, WeightedValidator[] validators);\n /// @dev Emitted when the validators are updated\n event ValidatorsUpdated(uint256 indexed nonce, WeightedValidator[] validators);\n /// @dev Emitted when the validators are removed\n event ValidatorsRemoved(uint256 indexed nonce, address[] validators);\n\n /**\n * @dev Returns validator weight of the validator.\n */\n function getValidatorWeight(address _addr) external view returns (uint256);\n\n /**\n * @dev Returns governor weight of the governor.\n */\n function getGovernorWeight(address _addr) external view returns (uint256);\n\n /**\n * @dev Returns total validator weights of the address list.\n */\n function sumValidatorWeights(address[] calldata _addrList) external view returns (uint256 _weight);\n\n /**\n * @dev Returns total governor weights of the address list.\n */\n function sumGovernorWeights(address[] calldata _addrList) external view returns (uint256 _weight);\n\n /**\n * @dev Returns the validator list attached with governor address and weight.\n */\n function getValidatorInfo() external view returns (WeightedValidator[] memory _list);\n\n /**\n * @dev Returns the validator list.\n */\n function getValidators() external view returns (address[] memory _validators);\n\n /**\n * @dev Returns the validator at `_index` position.\n */\n function validators(uint256 _index) external view returns (WeightedValidator memory);\n\n /**\n * @dev Returns total of validators.\n */\n function totalValidators() external view returns (uint256);\n\n /**\n * @dev Returns total weights.\n */\n function totalWeights() external view returns (uint256);\n\n /**\n * @dev Adds validators.\n *\n * Requirements:\n * - The weights are larger than 0.\n * - The validators are not added.\n * - The method caller is admin.\n *\n * Emits the `ValidatorsAdded` event.\n *\n */\n function addValidators(WeightedValidator[] calldata _validators) external;\n\n /**\n * @dev Updates validators.\n *\n * Requirements:\n * - The weights are larger than 0.\n * - The validators are added.\n * - The method caller is admin.\n *\n * Emits the `ValidatorsUpdated` event.\n *\n */\n function updateValidators(WeightedValidator[] calldata _validators) external;\n\n /**\n * @dev Removes validators.\n *\n * Requirements:\n * - The validators are added.\n * - The method caller is admin.\n *\n * Emits the `ValidatorsRemoved` event.\n *\n */\n function removeValidators(address[] calldata _validators) external;\n}\n"},"contracts/v0.8/extensions/HasProxyAdmin.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"@openzeppelin/contracts/utils/StorageSlot.sol\";\n\nabstract contract HasProxyAdmin {\n // bytes32(uint256(keccak256(\"eip1967.proxy.admin\")) - 1));\n bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\n\n modifier onlyAdmin() {\n require(msg.sender == _getAdmin(), \"HasProxyAdmin: unauthorized sender\");\n _;\n }\n\n /**\n * @dev Returns proxy admin.\n */\n function _getAdmin() internal view returns (address) {\n return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;\n }\n}\n"},"@openzeppelin/contracts/utils/introspection/IERC165.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n"},"contracts/v0.8/interfaces/SignatureConsumer.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface SignatureConsumer {\n struct Signature {\n uint8 v;\n bytes32 r;\n bytes32 s;\n }\n}\n"},"@openzeppelin/contracts/utils/Strings.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n bytes16 private constant _HEX_SYMBOLS = \"0123456789abcdef\";\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n // Inspired by OraclizeAPI's implementation - MIT licence\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\n\n if (value == 0) {\n return \"0\";\n }\n uint256 temp = value;\n uint256 digits;\n while (temp != 0) {\n digits++;\n temp /= 10;\n }\n bytes memory buffer = new bytes(digits);\n while (value != 0) {\n digits -= 1;\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\n value /= 10;\n }\n return string(buffer);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n if (value == 0) {\n return \"0x00\";\n }\n uint256 temp = value;\n uint256 length = 0;\n while (temp != 0) {\n length++;\n temp >>= 8;\n }\n return toHexString(value, length);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n}\n"},"@openzeppelin/contracts/utils/introspection/ERC165.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC165.sol\";\n\n/**\n * @dev Implementation of the {IERC165} interface.\n *\n * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check\n * for the additional interface id that will be supported. For example:\n *\n * ```solidity\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\n * }\n * ```\n *\n * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.\n */\nabstract contract ERC165 is IERC165 {\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IERC165).interfaceId;\n }\n}\n"},"@openzeppelin/contracts/access/AccessControlEnumerable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControlEnumerable.sol\";\nimport \"./AccessControl.sol\";\nimport \"../utils/structs/EnumerableSet.sol\";\n\n/**\n * @dev Extension of {AccessControl} that allows enumerating the members of each role.\n */\nabstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {\n using EnumerableSet for EnumerableSet.AddressSet;\n\n mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns one of the accounts that have `role`. `index` must be a\n * value between 0 and {getRoleMemberCount}, non-inclusive.\n *\n * Role bearers are not sorted in any particular way, and their ordering may\n * change at any point.\n *\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\n * you perform all queries on the same block. See the following\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\n * for more information.\n */\n function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) {\n return _roleMembers[role].at(index);\n }\n\n /**\n * @dev Returns the number of accounts that have `role`. Can be used\n * together with {getRoleMember} to enumerate all bearers of a role.\n */\n function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) {\n return _roleMembers[role].length();\n }\n\n /**\n * @dev Overload {_grantRole} to track enumerable memberships\n */\n function _grantRole(bytes32 role, address account) internal virtual override {\n super._grantRole(role, account);\n _roleMembers[role].add(account);\n }\n\n /**\n * @dev Overload {_revokeRole} to track enumerable memberships\n */\n function _revokeRole(bytes32 role, address account) internal virtual override {\n super._revokeRole(role, account);\n _roleMembers[role].remove(account);\n }\n}\n"},"contracts/v0.8/library/Transfer.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/utils/Strings.sol\";\nimport \"./Token.sol\";\n\nlibrary Transfer {\n using ECDSA for bytes32;\n\n enum Kind {\n Deposit,\n Withdrawal\n }\n\n struct Request {\n // For deposit request: Recipient address on Ronin network\n // For withdrawal request: Recipient address on mainchain network\n address recipientAddr;\n // Token address to deposit/withdraw\n // Value 0: native token\n address tokenAddr;\n Token.Info info;\n }\n\n /**\n * @dev Converts the transfer request into the deposit receipt.\n */\n function into_deposit_receipt(\n Request memory _request,\n address _requester,\n uint256 _id,\n address _roninTokenAddr,\n uint256 _roninChainId\n ) internal view returns (Receipt memory _receipt) {\n _receipt.id = _id;\n _receipt.kind = Kind.Deposit;\n _receipt.mainchain.addr = _requester;\n _receipt.mainchain.tokenAddr = _request.tokenAddr;\n _receipt.mainchain.chainId = block.chainid;\n _receipt.ronin.addr = _request.recipientAddr;\n _receipt.ronin.tokenAddr = _roninTokenAddr;\n _receipt.ronin.chainId = _roninChainId;\n _receipt.info = _request.info;\n }\n\n /**\n * @dev Converts the transfer request into the withdrawal receipt.\n */\n function into_withdrawal_receipt(\n Request memory _request,\n address _requester,\n uint256 _id,\n address _mainchainTokenAddr,\n uint256 _mainchainId\n ) internal view returns (Receipt memory _receipt) {\n _receipt.id = _id;\n _receipt.kind = Kind.Withdrawal;\n _receipt.ronin.addr = _requester;\n _receipt.ronin.tokenAddr = _request.tokenAddr;\n _receipt.ronin.chainId = block.chainid;\n _receipt.mainchain.addr = _request.recipientAddr;\n _receipt.mainchain.tokenAddr = _mainchainTokenAddr;\n _receipt.mainchain.chainId = _mainchainId;\n _receipt.info = _request.info;\n }\n\n struct Receipt {\n uint256 id;\n Kind kind;\n Token.Owner mainchain;\n Token.Owner ronin;\n Token.Info info;\n }\n\n // keccak256(\"Receipt(uint256 id,uint8 kind,TokenOwner mainchain,TokenOwner ronin,TokenInfo info)TokenInfo(uint8 erc,uint256 id,uint256 quantity)TokenOwner(address addr,address tokenAddr,uint256 chainId)\");\n bytes32 public constant TYPE_HASH = 0xb9d1fe7c9deeec5dc90a2f47ff1684239519f2545b2228d3d91fb27df3189eea;\n\n /**\n * @dev Returns token info struct hash.\n */\n function hash(Receipt memory _receipt) internal pure returns (bytes32) {\n return\n keccak256(\n abi.encode(\n TYPE_HASH,\n _receipt.id,\n _receipt.kind,\n Token.hash(_receipt.mainchain),\n Token.hash(_receipt.ronin),\n Token.hash(_receipt.info)\n )\n );\n }\n\n /**\n * @dev Returns the receipt digest.\n */\n function receiptDigest(bytes32 _domainSeparator, bytes32 _receiptHash) internal pure returns (bytes32) {\n return _domainSeparator.toTypedDataHash(_receiptHash);\n }\n}\n"},"@openzeppelin/contracts/utils/Context.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n"},"@openzeppelin/contracts/security/Pausable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract Pausable is Context {\n /**\n * @dev Emitted when the pause is triggered by `account`.\n */\n event Paused(address account);\n\n /**\n * @dev Emitted when the pause is lifted by `account`.\n */\n event Unpaused(address account);\n\n bool private _paused;\n\n /**\n * @dev Initializes the contract in unpaused state.\n */\n constructor() {\n _paused = false;\n }\n\n /**\n * @dev Returns true if the contract is paused, and false otherwise.\n */\n function paused() public view virtual returns (bool) {\n return _paused;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is not paused.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n modifier whenNotPaused() {\n require(!paused(), \"Pausable: paused\");\n _;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is paused.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n modifier whenPaused() {\n require(paused(), \"Pausable: not paused\");\n _;\n }\n\n /**\n * @dev Triggers stopped state.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n function _pause() internal virtual whenNotPaused {\n _paused = true;\n emit Paused(_msgSender());\n }\n\n /**\n * @dev Returns to normal state.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n function _unpause() internal virtual whenPaused {\n _paused = false;\n emit Unpaused(_msgSender());\n }\n}\n"},"@openzeppelin/contracts/utils/structs/EnumerableSet.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Library for managing\n * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive\n * types.\n *\n * Sets have the following properties:\n *\n * - Elements are added, removed, and checked for existence in constant time\n * (O(1)).\n * - Elements are enumerated in O(n). No guarantees are made on the ordering.\n *\n * ```\n * contract Example {\n * // Add the library methods\n * using EnumerableSet for EnumerableSet.AddressSet;\n *\n * // Declare a set state variable\n * EnumerableSet.AddressSet private mySet;\n * }\n * ```\n *\n * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)\n * and `uint256` (`UintSet`) are supported.\n */\nlibrary EnumerableSet {\n // To implement this library for multiple types with as little code\n // repetition as possible, we write it in terms of a generic Set type with\n // bytes32 values.\n // The Set implementation uses private functions, and user-facing\n // implementations (such as AddressSet) are just wrappers around the\n // underlying Set.\n // This means that we can only create new EnumerableSets for types that fit\n // in bytes32.\n\n struct Set {\n // Storage of set values\n bytes32[] _values;\n // Position of the value in the `values` array, plus 1 because index 0\n // means a value is not in the set.\n mapping(bytes32 => uint256) _indexes;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function _add(Set storage set, bytes32 value) private returns (bool) {\n if (!_contains(set, value)) {\n set._values.push(value);\n // The value is stored at length-1, but we add 1 to all indexes\n // and use 0 as a sentinel value\n set._indexes[value] = set._values.length;\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function _remove(Set storage set, bytes32 value) private returns (bool) {\n // We read and store the value's index to prevent multiple reads from the same storage slot\n uint256 valueIndex = set._indexes[value];\n\n if (valueIndex != 0) {\n // Equivalent to contains(set, value)\n // To delete an element from the _values array in O(1), we swap the element to delete with the last one in\n // the array, and then remove the last element (sometimes called as 'swap and pop').\n // This modifies the order of the array, as noted in {at}.\n\n uint256 toDeleteIndex = valueIndex - 1;\n uint256 lastIndex = set._values.length - 1;\n\n if (lastIndex != toDeleteIndex) {\n bytes32 lastvalue = set._values[lastIndex];\n\n // Move the last value to the index where the value to delete is\n set._values[toDeleteIndex] = lastvalue;\n // Update the index for the moved value\n set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex\n }\n\n // Delete the slot where the moved value was stored\n set._values.pop();\n\n // Delete the index for the deleted slot\n delete set._indexes[value];\n\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function _contains(Set storage set, bytes32 value) private view returns (bool) {\n return set._indexes[value] != 0;\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function _length(Set storage set) private view returns (uint256) {\n return set._values.length;\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function _at(Set storage set, uint256 index) private view returns (bytes32) {\n return set._values[index];\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function _values(Set storage set) private view returns (bytes32[] memory) {\n return set._values;\n }\n\n // Bytes32Set\n\n struct Bytes32Set {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _add(set._inner, value);\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _remove(set._inner, value);\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {\n return _contains(set._inner, value);\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(Bytes32Set storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {\n return _at(set._inner, index);\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {\n return _values(set._inner);\n }\n\n // AddressSet\n\n struct AddressSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(AddressSet storage set, address value) internal returns (bool) {\n return _add(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(AddressSet storage set, address value) internal returns (bool) {\n return _remove(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(AddressSet storage set, address value) internal view returns (bool) {\n return _contains(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(AddressSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(AddressSet storage set, uint256 index) internal view returns (address) {\n return address(uint160(uint256(_at(set._inner, index))));\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(AddressSet storage set) internal view returns (address[] memory) {\n bytes32[] memory store = _values(set._inner);\n address[] memory result;\n\n assembly {\n result := store\n }\n\n return result;\n }\n\n // UintSet\n\n struct UintSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(UintSet storage set, uint256 value) internal returns (bool) {\n return _add(set._inner, bytes32(value));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(UintSet storage set, uint256 value) internal returns (bool) {\n return _remove(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(UintSet storage set, uint256 value) internal view returns (bool) {\n return _contains(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function length(UintSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(UintSet storage set, uint256 index) internal view returns (uint256) {\n return uint256(_at(set._inner, index));\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(UintSet storage set) internal view returns (uint256[] memory) {\n bytes32[] memory store = _values(set._inner);\n uint256[] memory result;\n\n assembly {\n result := store\n }\n\n return result;\n }\n}\n"},"contracts/v0.8/interfaces/IQuorum.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IQuorum {\n /// @dev Emitted when the threshold is updated\n event ThresholdUpdated(\n uint256 indexed nonce,\n uint256 indexed numerator,\n uint256 indexed denominator,\n uint256 previousNumerator,\n uint256 previousDenominator\n );\n\n /**\n * @dev Returns the threshold.\n */\n function getThreshold() external view returns (uint256 _num, uint256 _denom);\n\n /**\n * @dev Checks whether the `_voteWeight` passes the threshold.\n */\n function checkThreshold(uint256 _voteWeight) external view returns (bool);\n\n /**\n * @dev Returns the minimum vote weight to pass the threshold.\n */\n function minimumVoteWeight() external view returns (uint256);\n\n /**\n * @dev Sets the threshold.\n *\n * Requirements:\n * - The method caller is admin.\n *\n * Emits the `ThresholdUpdated` event.\n *\n */\n function setThreshold(uint256 _numerator, uint256 _denominator)\n external\n returns (uint256 _previousNum, uint256 _previousDenom);\n}\n"},"contracts/v0.8/interfaces/IWETH.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IWETH {\n function deposit() external payable;\n\n function withdraw(uint256 _wad) external;\n\n function balanceOf(address) external view returns (uint256);\n}\n"},"@openzeppelin/contracts/access/AccessControl.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControl.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\nimport \"../utils/Context.sol\";\nimport \"../utils/Strings.sol\";\nimport \"../utils/introspection/ERC165.sol\";\n\n/**\n * @dev Contract module that allows children to implement role-based access\n * control mechanisms. This is a lightweight version that doesn't allow enumerating role\n * members except through off-chain means by accessing the contract event logs. Some\n * applications may benefit from on-chain enumerability, for those cases see\n * {AccessControlEnumerable}.\n *\n * Roles are referred to by their `bytes32` identifier. These should be exposed\n * in the external API and be unique. The best way to achieve this is by\n * using `public constant` hash digests:\n *\n * ```\n * bytes32 public constant MY_ROLE = keccak256(\"MY_ROLE\");\n * ```\n *\n * Roles can be used to represent a set of permissions. To restrict access to a\n * function call, use {hasRole}:\n *\n * ```\n * function foo() public {\n * require(hasRole(MY_ROLE, msg.sender));\n * ...\n * }\n * ```\n *\n * Roles can be granted and revoked dynamically via the {grantRole} and\n * {revokeRole} functions. Each role has an associated admin role, and only\n * accounts that have a role's admin role can call {grantRole} and {revokeRole}.\n *\n * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means\n * that only accounts with this role will be able to grant or revoke other\n * roles. More complex role relationships can be created by using\n * {_setRoleAdmin}.\n *\n * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to\n * grant and revoke this role. Extra precautions should be taken to secure\n * accounts that have been granted it.\n */\nabstract contract AccessControl is Context, IAccessControl, ERC165 {\n struct RoleData {\n mapping(address => bool) members;\n bytes32 adminRole;\n }\n\n mapping(bytes32 => RoleData) private _roles;\n\n bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;\n\n /**\n * @dev Modifier that checks that an account has a specific role. Reverts\n * with a standardized message including the required role.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n *\n * _Available since v4.1._\n */\n modifier onlyRole(bytes32 role) {\n _checkRole(role, _msgSender());\n _;\n }\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) public view virtual override returns (bool) {\n return _roles[role].members[account];\n }\n\n /**\n * @dev Revert with a standard message if `account` is missing `role`.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n */\n function _checkRole(bytes32 role, address account) internal view virtual {\n if (!hasRole(role, account)) {\n revert(\n string(\n abi.encodePacked(\n \"AccessControl: account \",\n Strings.toHexString(uint160(account), 20),\n \" is missing role \",\n Strings.toHexString(uint256(role), 32)\n )\n )\n );\n }\n }\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {\n return _roles[role].adminRole;\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _grantRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _revokeRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been revoked `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) public virtual override {\n require(account == _msgSender(), \"AccessControl: can only renounce roles for self\");\n\n _revokeRole(role, account);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event. Note that unlike {grantRole}, this function doesn't perform any\n * checks on the calling account.\n *\n * [WARNING]\n * ====\n * This function should only be called from the constructor when setting\n * up the initial roles for the system.\n *\n * Using this function in any other way is effectively circumventing the admin\n * system imposed by {AccessControl}.\n * ====\n *\n * NOTE: This function is deprecated in favor of {_grantRole}.\n */\n function _setupRole(bytes32 role, address account) internal virtual {\n _grantRole(role, account);\n }\n\n /**\n * @dev Sets `adminRole` as ``role``'s admin role.\n *\n * Emits a {RoleAdminChanged} event.\n */\n function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {\n bytes32 previousAdminRole = getRoleAdmin(role);\n _roles[role].adminRole = adminRole;\n emit RoleAdminChanged(role, previousAdminRole, adminRole);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * Internal function without access restriction.\n */\n function _grantRole(bytes32 role, address account) internal virtual {\n if (!hasRole(role, account)) {\n _roles[role].members[account] = true;\n emit RoleGranted(role, account, _msgSender());\n }\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * Internal function without access restriction.\n */\n function _revokeRole(bytes32 role, address account) internal virtual {\n if (hasRole(role, account)) {\n _roles[role].members[account] = false;\n emit RoleRevoked(role, account, _msgSender());\n }\n }\n}\n"},"@openzeppelin/contracts/utils/Address.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n"},"@openzeppelin/contracts/token/ERC20/IERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n"}},"settings":{"evmVersion":"london","libraries":{},"metadata":{"bytecodeHash":"ipfs","useLiteralContent":true},"optimizer":{"enabled":true,"runs":1000},"remappings":[],"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}}}},"ABI":"[{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"limits\",\"type\":\"uint256[]\"}],\"name\":\"DailyWithdrawalLimitsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"receiptHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"indexed\":false,\"internalType\":\"struct Transfer.Receipt\",\"name\":\"receipt\",\"type\":\"tuple\"}],\"name\":\"DepositRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"thresholds\",\"type\":\"uint256[]\"}],\"name\":\"HighTierThresholdsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"numerator\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"denominator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousNumerator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousDenominator\",\"type\":\"uint256\"}],\"name\":\"HighTierVoteWeightThresholdUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"thresholds\",\"type\":\"uint256[]\"}],\"name\":\"LockedThresholdsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"Paused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"previousAdminRole\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"newAdminRole\",\"type\":\"bytes32\"}],\"name\":\"RoleAdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleGranted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"numerator\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"denominator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousNumerator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousDenominator\",\"type\":\"uint256\"}],\"name\":\"ThresholdUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"mainchainTokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"roninTokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"enum Token.Standard[]\",\"name\":\"standards\",\"type\":\"uint8[]\"}],\"name\":\"TokenMapped\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"percentages\",\"type\":\"uint256[]\"}],\"name\":\"UnlockFeePercentagesUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"Unpaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IWeightedValidator\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"ValidatorContractUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"receiptHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"indexed\":false,\"internalType\":\"struct Transfer.Receipt\",\"name\":\"receipt\",\"type\":\"tuple\"}],\"name\":\"WithdrawalLocked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"receiptHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"indexed\":false,\"internalType\":\"struct Transfer.Receipt\",\"name\":\"receipt\",\"type\":\"tuple\"}],\"name\":\"WithdrawalUnlocked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"receiptHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"indexed\":false,\"internalType\":\"struct Transfer.Receipt\",\"name\":\"receipt\",\"type\":\"tuple\"}],\"name\":\"Withdrew\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IWETH\",\"name\":\"weth\",\"type\":\"address\"}],\"name\":\"WrappedNativeTokenContractUpdated\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"DEFAULT_ADMIN_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DOMAIN_SEPARATOR\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"WITHDRAWAL_UNLOCKER_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_MAX_PERCENTAGE\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_voteWeight\",\"type\":\"uint256\"}],\"name\":\"checkHighTierVoteWeightThreshold\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_voteWeight\",\"type\":\"uint256\"}],\"name\":\"checkThreshold\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"dailyWithdrawalLimit\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"depositCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getHighTierVoteWeightThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleAdmin\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getRoleMember\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleMemberCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_mainchainToken\",\"type\":\"address\"}],\"name\":\"getRoninToken\",\"outputs\":[{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"}],\"internalType\":\"struct MappedTokenConsumer.MappedToken\",\"name\":\"_token\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"grantRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasRole\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"highTierThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_roleSetter\",\"type\":\"address\"},{\"internalType\":\"contract IWETH\",\"name\":\"_wrappedToken\",\"type\":\"address\"},{\"internalType\":\"contract IWeightedValidator\",\"name\":\"_validatorContract\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_roninChainId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_numerator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_highTierVWNumerator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_denominator\",\"type\":\"uint256\"},{\"internalType\":\"address[][3]\",\"name\":\"_addresses\",\"type\":\"address[][3]\"},{\"internalType\":\"uint256[][4]\",\"name\":\"_thresholds\",\"type\":\"uint256[][4]\"},{\"internalType\":\"enum Token.Standard[]\",\"name\":\"_standards\",\"type\":\"uint8[]\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"lastDateSynced\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"lastSyncedWithdrawal\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"lockedThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_mainchainTokens\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"_roninTokens\",\"type\":\"address[]\"},{\"internalType\":\"enum Token.Standard[]\",\"name\":\"_standards\",\"type\":\"uint8[]\"}],\"name\":\"mapTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_mainchainTokens\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"_roninTokens\",\"type\":\"address[]\"},{\"internalType\":\"enum Token.Standard[]\",\"name\":\"_standards\",\"type\":\"uint8[]\"},{\"internalType\":\"uint256[][4]\",\"name\":\"_thresholds\",\"type\":\"uint256[][4]\"}],\"name\":\"mapTokensAndThresholds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minimumVoteWeight\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"nonce\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_quantity\",\"type\":\"uint256\"}],\"name\":\"reachedWithdrawalLimit\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"receiveEther\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"renounceRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"recipientAddr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"internalType\":\"struct Transfer.Request\",\"name\":\"_request\",\"type\":\"tuple\"}],\"name\":\"requestDepositFor\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"revokeRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"roninChainId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_tokens\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"_limits\",\"type\":\"uint256[]\"}],\"name\":\"setDailyWithdrawalLimits\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_tokens\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"_thresholds\",\"type\":\"uint256[]\"}],\"name\":\"setHighTierThresholds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_numerator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_denominator\",\"type\":\"uint256\"}],\"name\":\"setHighTierVoteWeightThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"_previousNum\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_previousDenom\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_tokens\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"_thresholds\",\"type\":\"uint256[]\"}],\"name\":\"setLockedThresholds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_numerator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_denominator\",\"type\":\"uint256\"}],\"name\":\"setThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"_previousNum\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_previousDenom\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_tokens\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"_percentages\",\"type\":\"uint256[]\"}],\"name\":\"setUnlockFeePercentages\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IWeightedValidator\",\"name\":\"_validatorContract\",\"type\":\"address\"}],\"name\":\"setValidatorContract\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IWETH\",\"name\":\"_wrappedToken\",\"type\":\"address\"}],\"name\":\"setWrappedNativeTokenContract\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"internalType\":\"struct Transfer.Receipt\",\"name\":\"_receipt\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"struct SignatureConsumer.Signature[]\",\"name\":\"_signatures\",\"type\":\"tuple[]\"}],\"name\":\"submitWithdrawal\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"_locked\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"unlockFeePercentages\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"internalType\":\"struct Transfer.Receipt\",\"name\":\"_receipt\",\"type\":\"tuple\"}],\"name\":\"unlockWithdrawal\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unpause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"validatorContract\",\"outputs\":[{\"internalType\":\"contract IWeightedValidator\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"withdrawalHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"withdrawalLocked\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"wrappedNativeToken\",\"outputs\":[{\"internalType\":\"contract IWETH\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]","ContractName":"MainchainGatewayV2","CompilerVersion":"v0.8.9+commit.e5eed63a","OptimizationUsed":1,"Runs":1000,"ConstructorArguments":"0x","EVMVersion":"Default","Library":"","LicenseType":"MIT","Proxy":0,"SwarmSource":""}] \ No newline at end of file diff --git a/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/creation_data.json b/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/creation_data.json new file mode 100644 index 0000000000000..f4dfea9e7de46 --- /dev/null +++ b/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x8b3d32cf2bb4d0d16656f4c0b04fa546274f1545","contractCreator":"0x958892b4a0512b28aaac890fc938868bbd42f064","txHash":"0x79820495643caf5a1e7e96578361c9ddba0e0735cd684ada7450254f6fd58f51"} \ No newline at end of file diff --git a/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/metadata.json b/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/metadata.json new file mode 100644 index 0000000000000..1fd95fa4fe5dd --- /dev/null +++ b/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"contracts/governance/governor/GovernorStorage.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"./IIpt.sol\";\nimport \"./Structs.sol\";\n\ncontract GovernorCharlieDelegatorStorage {\n /// @notice Active brains of Governor\n address public implementation;\n}\n\n/**\n * @title Storage for Governor Charlie Delegate\n * @notice For future upgrades, do not change GovernorCharlieDelegateStorage. Create a new\n * contract which implements GovernorCharlieDelegateStorage and following the naming convention\n * GovernorCharlieDelegateStorageVX.\n */\n//solhint-disable-next-line max-states-count\ncontract GovernorCharlieDelegateStorage is GovernorCharlieDelegatorStorage {\n /// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed\n uint256 public quorumVotes;\n\n /// @notice The number of votes in support of a proposal required in order for an emergency quorum to be reached and for a vote to succeed\n uint256 public emergencyQuorumVotes;\n\n /// @notice The delay before voting on a proposal may take place, once proposed, in blocks\n uint256 public votingDelay;\n\n /// @notice The duration of voting on a proposal, in blocks\n uint256 public votingPeriod;\n\n /// @notice The number of votes required in order for a voter to become a proposer\n uint256 public proposalThreshold;\n\n /// @notice Initial proposal id set at become\n uint256 public initialProposalId;\n\n /// @notice The total number of proposals\n uint256 public proposalCount;\n\n /// @notice The address of the Interest Protocol governance token\n IIpt public ipt;\n\n /// @notice The official record of all proposals ever proposed\n mapping(uint256 => Proposal) public proposals;\n\n /// @notice The latest proposal for each proposer\n mapping(address => uint256) public latestProposalIds;\n\n /// @notice The latest proposal for each proposer\n mapping(bytes32 => bool) public queuedTransactions;\n\n /// @notice The proposal holding period\n uint256 public proposalTimelockDelay;\n\n /// @notice Stores the expiration of account whitelist status as a timestamp\n mapping(address => uint256) public whitelistAccountExpirations;\n\n /// @notice Address which manages whitelisted proposals and whitelist accounts\n address public whitelistGuardian;\n\n /// @notice The duration of the voting on a emergency proposal, in blocks\n uint256 public emergencyVotingPeriod;\n\n /// @notice The emergency proposal holding period\n uint256 public emergencyTimelockDelay;\n\n /// all receipts for proposal\n mapping(uint256 => mapping(address => Receipt)) public proposalReceipts;\n\n /// @notice The emergency proposal holding period\n bool public initialized;\n\n /// @notice The number of votes to reject an optimistic proposal\n uint256 public optimisticQuorumVotes; \n\n /// @notice The delay period before voting begins\n uint256 public optimisticVotingDelay; \n\n /// @notice The maximum number of seconds an address can be whitelisted for\n uint256 public maxWhitelistPeriod; \n}\n"},"hardhat/console.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >= 0.4.22 <0.9.0;\n\nlibrary console {\n\taddress constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67);\n\n\tfunction _sendLogPayload(bytes memory payload) private view {\n\t\tuint256 payloadLength = payload.length;\n\t\taddress consoleAddress = CONSOLE_ADDRESS;\n\t\tassembly {\n\t\t\tlet payloadStart := add(payload, 32)\n\t\t\tlet r := staticcall(gas(), consoleAddress, payloadStart, payloadLength, 0, 0)\n\t\t}\n\t}\n\n\tfunction log() internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log()\"));\n\t}\n\n\tfunction logInt(int p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(int)\", p0));\n\t}\n\n\tfunction logUint(uint p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint)\", p0));\n\t}\n\n\tfunction logString(string memory p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string)\", p0));\n\t}\n\n\tfunction logBool(bool p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool)\", p0));\n\t}\n\n\tfunction logAddress(address p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address)\", p0));\n\t}\n\n\tfunction logBytes(bytes memory p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes)\", p0));\n\t}\n\n\tfunction logBytes1(bytes1 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes1)\", p0));\n\t}\n\n\tfunction logBytes2(bytes2 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes2)\", p0));\n\t}\n\n\tfunction logBytes3(bytes3 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes3)\", p0));\n\t}\n\n\tfunction logBytes4(bytes4 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes4)\", p0));\n\t}\n\n\tfunction logBytes5(bytes5 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes5)\", p0));\n\t}\n\n\tfunction logBytes6(bytes6 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes6)\", p0));\n\t}\n\n\tfunction logBytes7(bytes7 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes7)\", p0));\n\t}\n\n\tfunction logBytes8(bytes8 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes8)\", p0));\n\t}\n\n\tfunction logBytes9(bytes9 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes9)\", p0));\n\t}\n\n\tfunction logBytes10(bytes10 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes10)\", p0));\n\t}\n\n\tfunction logBytes11(bytes11 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes11)\", p0));\n\t}\n\n\tfunction logBytes12(bytes12 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes12)\", p0));\n\t}\n\n\tfunction logBytes13(bytes13 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes13)\", p0));\n\t}\n\n\tfunction logBytes14(bytes14 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes14)\", p0));\n\t}\n\n\tfunction logBytes15(bytes15 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes15)\", p0));\n\t}\n\n\tfunction logBytes16(bytes16 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes16)\", p0));\n\t}\n\n\tfunction logBytes17(bytes17 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes17)\", p0));\n\t}\n\n\tfunction logBytes18(bytes18 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes18)\", p0));\n\t}\n\n\tfunction logBytes19(bytes19 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes19)\", p0));\n\t}\n\n\tfunction logBytes20(bytes20 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes20)\", p0));\n\t}\n\n\tfunction logBytes21(bytes21 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes21)\", p0));\n\t}\n\n\tfunction logBytes22(bytes22 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes22)\", p0));\n\t}\n\n\tfunction logBytes23(bytes23 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes23)\", p0));\n\t}\n\n\tfunction logBytes24(bytes24 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes24)\", p0));\n\t}\n\n\tfunction logBytes25(bytes25 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes25)\", p0));\n\t}\n\n\tfunction logBytes26(bytes26 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes26)\", p0));\n\t}\n\n\tfunction logBytes27(bytes27 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes27)\", p0));\n\t}\n\n\tfunction logBytes28(bytes28 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes28)\", p0));\n\t}\n\n\tfunction logBytes29(bytes29 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes29)\", p0));\n\t}\n\n\tfunction logBytes30(bytes30 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes30)\", p0));\n\t}\n\n\tfunction logBytes31(bytes31 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes31)\", p0));\n\t}\n\n\tfunction logBytes32(bytes32 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes32)\", p0));\n\t}\n\n\tfunction log(uint p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint)\", p0));\n\t}\n\n\tfunction log(string memory p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string)\", p0));\n\t}\n\n\tfunction log(bool p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool)\", p0));\n\t}\n\n\tfunction log(address p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address)\", p0));\n\t}\n\n\tfunction log(uint p0, uint p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint)\", p0, p1));\n\t}\n\n\tfunction log(uint p0, string memory p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string)\", p0, p1));\n\t}\n\n\tfunction log(uint p0, bool p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool)\", p0, p1));\n\t}\n\n\tfunction log(uint p0, address p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address)\", p0, p1));\n\t}\n\n\tfunction log(string memory p0, uint p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint)\", p0, p1));\n\t}\n\n\tfunction log(string memory p0, string memory p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string)\", p0, p1));\n\t}\n\n\tfunction log(string memory p0, bool p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool)\", p0, p1));\n\t}\n\n\tfunction log(string memory p0, address p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address)\", p0, p1));\n\t}\n\n\tfunction log(bool p0, uint p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint)\", p0, p1));\n\t}\n\n\tfunction log(bool p0, string memory p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string)\", p0, p1));\n\t}\n\n\tfunction log(bool p0, bool p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool)\", p0, p1));\n\t}\n\n\tfunction log(bool p0, address p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address)\", p0, p1));\n\t}\n\n\tfunction log(address p0, uint p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint)\", p0, p1));\n\t}\n\n\tfunction log(address p0, string memory p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string)\", p0, p1));\n\t}\n\n\tfunction log(address p0, bool p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool)\", p0, p1));\n\t}\n\n\tfunction log(address p0, address p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address)\", p0, p1));\n\t}\n\n\tfunction log(uint p0, uint p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, uint p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, uint p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, uint p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, string memory p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, string memory p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, string memory p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, string memory p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, bool p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, bool p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, bool p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, bool p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, address p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, address p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, address p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, address p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, uint p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, uint p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, uint p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, uint p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, string memory p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, string memory p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, string memory p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, string memory p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, bool p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, bool p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, bool p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, bool p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, address p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, address p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, address p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, address p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, uint p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, uint p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, uint p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, uint p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, string memory p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, string memory p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, string memory p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, string memory p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, bool p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, bool p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, bool p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, bool p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, address p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, address p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, address p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, address p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, uint p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, uint p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, uint p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, uint p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, string memory p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, string memory p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, string memory p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, string memory p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, bool p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, bool p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, bool p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, bool p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, address p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, address p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, address p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, address p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, uint p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,address,address)\", p0, p1, p2, p3));\n\t}\n\n}\n"},"contracts/governance/governor/IGovernor.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"./Structs.sol\";\n\n/// @title interface to interact with TokenDelgator\ninterface IGovernorCharlieDelegator {\n function _setImplementation(address implementation_) external;\n\n fallback() external payable;\n\n receive() external payable;\n}\n\n/// @title interface to interact with TokenDelgate\ninterface IGovernorCharlieDelegate {\n function initialize(\n address ipt_\n ) external;\n\n function propose(\n address[] memory targets,\n uint256[] memory values,\n string[] memory signatures,\n bytes[] memory calldatas,\n string memory description,\n bool emergency\n ) external returns (uint256);\n\n function queue(uint256 proposalId) external;\n\n function execute(uint256 proposalId) external payable;\n\n function executeTransaction(\n address target,\n uint256 value,\n string memory signature,\n bytes memory data,\n uint256 eta\n ) external payable;\n\n function cancel(uint256 proposalId) external;\n\n function getActions(uint256 proposalId)\n external\n view\n returns (\n address[] memory targets,\n uint256[] memory values,\n string[] memory signatures,\n bytes[] memory calldatas\n );\n\n function getReceipt(uint256 proposalId, address voter) external view returns (Receipt memory);\n\n function state(uint256 proposalId) external view returns (ProposalState);\n\n function castVote(uint256 proposalId, uint8 support) external;\n\n function castVoteWithReason(\n uint256 proposalId,\n uint8 support,\n string calldata reason\n ) external;\n\n function castVoteBySig(\n uint256 proposalId,\n uint8 support,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n function isWhitelisted(address account) external view returns (bool);\n\n function _setDelay(uint256 proposalTimelockDelay_) external;\n\n function _setEmergencyDelay(uint256 emergencyTimelockDelay_) external;\n\n function _setVotingDelay(uint256 newVotingDelay) external;\n\n function _setVotingPeriod(uint256 newVotingPeriod) external;\n\n function _setEmergencyVotingPeriod(uint256 newEmergencyVotingPeriod) external;\n\n function _setProposalThreshold(uint256 newProposalThreshold) external;\n\n function _setQuorumVotes(uint256 newQuorumVotes) external;\n\n function _setEmergencyQuorumVotes(uint256 newEmergencyQuorumVotes) external;\n\n function _setWhitelistAccountExpiration(address account, uint256 expiration) external;\n\n function _setWhitelistGuardian(address account) external;\n\n function _setOptimisticDelay(uint256 newOptimisticVotingDelay) external;\n\n function _setOptimisticQuorumVotes(uint256 newOptimisticQuorumVotes) external;\n}\n\n/// @title interface which contains all events emitted by delegator & delegate\ninterface GovernorCharlieEvents {\n /// @notice An event emitted when a new proposal is created\n event ProposalCreated(\n uint256 indexed id,\n address indexed proposer,\n address[] targets,\n uint256[] values,\n string[] signatures,\n bytes[] calldatas,\n uint256 indexed startBlock,\n uint256 endBlock,\n string description\n );\n\n /// @notice An event emitted when a vote has been cast on a proposal\n /// @param voter The address which casted a vote\n /// @param proposalId The proposal id which was voted on\n /// @param support Support value for the vote. 0=against, 1=for, 2=abstain\n /// @param votes Number of votes which were cast by the voter\n /// @param reason The reason given for the vote by the voter\n event VoteCast(address indexed voter, uint256 indexed proposalId, uint8 support, uint256 votes, string reason);\n\n /// @notice An event emitted when a proposal has been canceled\n event ProposalCanceled(uint256 indexed id);\n\n /// @notice An event emitted when a proposal has been queued in the Timelock\n event ProposalQueued(uint256 indexed id, uint256 eta);\n\n /// @notice An event emitted when a proposal has been executed in the Timelock\n event ProposalExecuted(uint256 indexed id);\n\n /// @notice An event emitted when the voting delay is set\n event VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay);\n\n /// @notice An event emitted when the voting period is set\n event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod);\n\n /// @notice An event emitted when the emergency voting period is set\n event EmergencyVotingPeriodSet(uint256 oldEmergencyVotingPeriod, uint256 emergencyVotingPeriod);\n\n /// @notice Emitted when implementation is changed\n event NewImplementation(address oldImplementation, address newImplementation);\n\n /// @notice Emitted when proposal threshold is set\n event ProposalThresholdSet(uint256 oldProposalThreshold, uint256 newProposalThreshold);\n\n /// @notice Emitted when pendingAdmin is changed\n event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);\n\n /// @notice Emitted when pendingAdmin is accepted, which means admin is updated\n event NewAdmin(address oldAdmin, address newAdmin);\n\n /// @notice Emitted when whitelist account expiration is set\n event WhitelistAccountExpirationSet(address account, uint256 expiration);\n\n /// @notice Emitted when the whitelistGuardian is set\n event WhitelistGuardianSet(address oldGuardian, address newGuardian);\n\n /// @notice Emitted when the a new delay is set\n event NewDelay(uint256 oldTimelockDelay, uint256 proposalTimelockDelay);\n\n /// @notice Emitted when the a new emergency delay is set\n event NewEmergencyDelay(uint256 oldEmergencyTimelockDelay, uint256 emergencyTimelockDelay);\n\n /// @notice Emitted when the quorum is updated\n event NewQuorum(uint256 oldQuorumVotes, uint256 quorumVotes);\n\n /// @notice Emitted when the emergency quorum is updated\n event NewEmergencyQuorum(uint256 oldEmergencyQuorumVotes, uint256 emergencyQuorumVotes);\n\n /// @notice An event emitted when the optimistic voting delay is set\n event OptimisticVotingDelaySet(uint256 oldOptimisticVotingDelay, uint256 optimisticVotingDelay);\n\n /// @notice Emitted when the optimistic quorum is updated\n event OptimisticQuorumVotesSet(uint256 oldOptimisticQuorumVotes, uint256 optimisticQuorumVotes);\n\n /// @notice Emitted when a transaction is canceled\n event CancelTransaction(\n bytes32 indexed txHash,\n address indexed target,\n uint256 value,\n string signature,\n bytes data,\n uint256 eta\n );\n\n /// @notice Emitted when a transaction is executed\n event ExecuteTransaction(\n bytes32 indexed txHash,\n address indexed target,\n uint256 value,\n string signature,\n bytes data,\n uint256 eta\n );\n\n /// @notice Emitted when a transaction is queued\n event QueueTransaction(\n bytes32 indexed txHash,\n address indexed target,\n uint256 value,\n string signature,\n bytes data,\n uint256 eta\n );\n}\n"},"contracts/governance/governor/Structs.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nstruct Proposal {\n /// @notice Unique id for looking up a proposal\n uint256 id;\n /// @notice Creator of the proposal\n address proposer;\n /// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds\n uint256 eta;\n /// @notice the ordered list of target addresses for calls to be made\n address[] targets;\n /// @notice The ordered list of values (i.e. msg.value) to be passed to the calls to be made\n uint256[] values;\n /// @notice The ordered list of function signatures to be called\n string[] signatures;\n /// @notice The ordered list of calldata to be passed to each call\n bytes[] calldatas;\n /// @notice The block at which voting begins: holders must delegate their votes prior to this block\n uint256 startBlock;\n /// @notice The block at which voting ends: votes must be cast prior to this block\n uint256 endBlock;\n /// @notice Current number of votes in favor of this proposal\n uint256 forVotes;\n /// @notice Current number of votes in opposition to this proposal\n uint256 againstVotes;\n /// @notice Current number of votes for abstaining for this proposal\n uint256 abstainVotes;\n /// @notice Flag marking whether the proposal has been canceled\n bool canceled;\n /// @notice Flag marking whether the proposal has been executed\n bool executed;\n /// @notice Whether the proposal is an emergency proposal\n bool emergency;\n /// @notice quorum votes requires\n uint256 quorumVotes;\n /// @notice time delay\n uint256 delay;\n}\n\n/// @notice Ballot receipt record for a voter\nstruct Receipt {\n /// @notice Whether or not a vote has been cast\n bool hasVoted;\n /// @notice Whether or not the voter supports the proposal or abstains\n uint8 support;\n /// @notice The number of votes the voter had, which were cast\n uint96 votes;\n}\n\n/// @notice Possible states that a proposal may be in\nenum ProposalState {\n Pending,\n Active,\n Canceled,\n Defeated,\n Succeeded,\n Queued,\n Expired,\n Executed\n}\n"},"contracts/governance/governor/IIpt.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\ninterface IIpt {\n function getPriorVotes(address account, uint256 blockNumber) external view returns (uint96);\n}\n"},"contracts/governance/governor/GovernorDelegate.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\npragma experimental ABIEncoderV2;\nimport \"hardhat/console.sol\";\n\nimport \"./IGovernor.sol\";\nimport \"./GovernorStorage.sol\";\n\ncontract GovernorCharlieDelegate is GovernorCharlieDelegateStorage, GovernorCharlieEvents, IGovernorCharlieDelegate {\n /// @notice The name of this contract\n string public constant name = \"Interest Protocol Governor\";\n\n /// @notice The maximum number of actions that can be included in a proposal\n uint256 public constant proposalMaxOperations = 10;\n\n /// @notice The EIP-712 typehash for the contract's domain\n bytes32 public constant DOMAIN_TYPEHASH =\n keccak256(\"EIP712Domain(string name,uint256 chainId,address verifyingContract)\");\n\n /// @notice The EIP-712 typehash for the ballot struct used by the contract\n bytes32 public constant BALLOT_TYPEHASH = keccak256(\"Ballot(uint256 proposalId,uint8 support)\");\n\n /// @notice The time for a proposal to be executed after passing\n uint256 public constant GRACE_PERIOD = 14 days;\n\n /**\n * @notice Used to initialize the contract during delegator contructor\n * @param ipt_ The address of the IPT token\n */\n function initialize(\n address ipt_\n ) external override {\n require(!initialized, \"already been initialized\");\n ipt = IIpt(ipt_);\n votingPeriod = 40320;\n votingDelay = 13140;\n proposalThreshold = 1000000000000000000000000;\n proposalTimelockDelay = 172800;\n proposalCount = 0;\n quorumVotes = 10000000000000000000000000;\n emergencyQuorumVotes = 40000000000000000000000000;\n emergencyVotingPeriod = 6570;\n emergencyTimelockDelay = 43200;\n optimisticQuorumVotes = 2000000000000000000000000;\n optimisticVotingDelay = 18000;\n maxWhitelistPeriod = 31536000;\n\n initialized = true;\n }\n\n /// @notice any function with this modifier will call the pay_interest() function before\n modifier onlyGov() {\n require(_msgSender() == address(this), \"must come from the gov.\");\n _;\n }\n\n /**\n * @notice Function used to propose a new proposal. Sender must have delegates above the proposal threshold\n * @param targets Target addresses for proposal calls\n * @param values Eth values for proposal calls\n * @param signatures Function signatures for proposal calls\n * @param calldatas Calldatas for proposal calls\n * @param description String description of the proposal\n * @return Proposal id of new proposal\n */\n function propose(\n address[] memory targets,\n uint256[] memory values,\n string[] memory signatures,\n bytes[] memory calldatas,\n string memory description,\n bool emergency\n ) public override returns (uint256) {\n // Reject proposals before initiating as Governor\n require(quorumVotes != 0, \"Charlie not active\");\n // Allow addresses above proposal threshold and whitelisted addresses to propose\n require(\n ipt.getPriorVotes(_msgSender(), (block.number - 1)) >= proposalThreshold || isWhitelisted(_msgSender()),\n \"votes below proposal threshold\"\n );\n require(\n targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length,\n \"information arity mismatch\"\n );\n require(targets.length != 0, \"must provide actions\");\n require(targets.length <= proposalMaxOperations, \"too many actions\");\n\n uint256 latestProposalId = latestProposalIds[_msgSender()];\n if (latestProposalId != 0) {\n ProposalState proposersLatestProposalState = state(latestProposalId);\n require(proposersLatestProposalState != ProposalState.Active, \"one live proposal per proposer\");\n require(proposersLatestProposalState != ProposalState.Pending, \"one live proposal per proposer\");\n }\n\n proposalCount++;\n Proposal memory newProposal = Proposal({\n id: proposalCount,\n proposer: _msgSender(),\n eta: 0,\n targets: targets,\n values: values,\n signatures: signatures,\n calldatas: calldatas,\n startBlock: block.number + votingDelay,\n endBlock: block.number + votingDelay + votingPeriod,\n forVotes: 0,\n againstVotes: 0,\n abstainVotes: 0,\n canceled: false,\n executed: false,\n emergency: emergency,\n quorumVotes: quorumVotes,\n delay: proposalTimelockDelay\n });\n\n //whitelist can't make emergency\n if (emergency && !isWhitelisted(_msgSender())) {\n newProposal.startBlock = block.number;\n newProposal.endBlock = block.number + emergencyVotingPeriod;\n newProposal.quorumVotes = emergencyQuorumVotes;\n newProposal.delay = emergencyTimelockDelay;\n }\n\n //whitelist can only make optimistic proposals\n if (isWhitelisted(_msgSender())) {\n newProposal.quorumVotes = optimisticQuorumVotes;\n newProposal.startBlock = block.number + optimisticVotingDelay;\n newProposal.endBlock = block.number + optimisticVotingDelay + votingPeriod;\n }\n\n proposals[newProposal.id] = newProposal;\n latestProposalIds[newProposal.proposer] = newProposal.id;\n\n emit ProposalCreated(\n newProposal.id,\n _msgSender(),\n targets,\n values,\n signatures,\n calldatas,\n newProposal.startBlock,\n newProposal.endBlock,\n description\n );\n return newProposal.id;\n }\n\n /**\n * @notice Queues a proposal of state succeeded\n * @param proposalId The id of the proposal to queue\n */\n function queue(uint256 proposalId) external override {\n require(state(proposalId) == ProposalState.Succeeded, \"can only be queued if succeeded\");\n Proposal storage proposal = proposals[proposalId];\n uint256 eta = block.timestamp + proposal.delay;\n for (uint256 i = 0; i < proposal.targets.length; i++) {\n require(\n !queuedTransactions[\n keccak256(\n abi.encode(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], eta)\n )\n ],\n \"proposal already queued\"\n );\n queueTransaction(\n proposal.targets[i],\n proposal.values[i],\n proposal.signatures[i],\n proposal.calldatas[i],\n eta,\n proposal.delay\n );\n }\n proposal.eta = eta;\n emit ProposalQueued(proposalId, eta);\n }\n\n function queueTransaction(\n address target,\n uint256 value,\n string memory signature,\n bytes memory data,\n uint256 eta,\n uint256 delay\n ) internal returns (bytes32) {\n require(eta >= (getBlockTimestamp() + delay), \"must satisfy delay.\");\n\n bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));\n queuedTransactions[txHash] = true;\n\n emit QueueTransaction(txHash, target, value, signature, data, eta);\n return txHash;\n }\n\n /**\n * @notice Executes a queued proposal if eta has passed\n * @param proposalId The id of the proposal to execute\n */\n function execute(uint256 proposalId) external payable override {\n require(state(proposalId) == ProposalState.Queued, \"can only be exec'd if queued\");\n Proposal storage proposal = proposals[proposalId];\n proposal.executed = true;\n for (uint256 i = 0; i < proposal.targets.length; i++) {\n this.executeTransaction{value: proposal.values[i]}(\n proposal.targets[i],\n proposal.values[i],\n proposal.signatures[i],\n proposal.calldatas[i],\n proposal.eta\n );\n }\n emit ProposalExecuted(proposalId);\n }\n\n function executeTransaction(\n address target,\n uint256 value,\n string memory signature,\n bytes memory data,\n uint256 eta\n ) external payable override {\n bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));\n require(queuedTransactions[txHash], \"tx hasn't been queued.\");\n require(getBlockTimestamp() >= eta, \"tx hasn't surpassed timelock.\");\n require(getBlockTimestamp() <= eta + GRACE_PERIOD, \"tx is stale.\");\n\n queuedTransactions[txHash] = false;\n\n bytes memory callData;\n\n if (bytes(signature).length == 0) {\n callData = data;\n } else {\n callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);\n }\n\n // solhint-disable-next-line avoid-low-level-calls\n (\n bool success, /*bytes memory returnData*/\n\n ) = target.call{value: value}(callData);\n require(success, \"tx execution reverted.\");\n\n emit ExecuteTransaction(txHash, target, value, signature, data, eta);\n }\n\n /**\n * @notice Cancels a proposal only if sender is the proposer, or proposer delegates dropped below proposal threshold\n * @param proposalId The id of the proposal to cancel\n */\n function cancel(uint256 proposalId) external override {\n require(state(proposalId) != ProposalState.Executed, \"cant cancel executed proposal\");\n\n Proposal storage proposal = proposals[proposalId];\n\n // Proposer can cancel\n if (_msgSender() != proposal.proposer) {\n // Whitelisted proposers can't be canceled for falling below proposal threshold\n if (isWhitelisted(proposal.proposer)) {\n require(\n (ipt.getPriorVotes(proposal.proposer, (block.number - 1)) < proposalThreshold) &&\n _msgSender() == whitelistGuardian,\n \"cancel: whitelisted proposer\"\n );\n } else {\n require(\n (ipt.getPriorVotes(proposal.proposer, (block.number - 1)) < proposalThreshold),\n \"cancel: proposer above threshold\"\n );\n }\n }\n\n proposal.canceled = true;\n for (uint256 i = 0; i < proposal.targets.length; i++) {\n cancelTransaction(\n proposal.targets[i],\n proposal.values[i],\n proposal.signatures[i],\n proposal.calldatas[i],\n proposal.eta\n );\n }\n\n emit ProposalCanceled(proposalId);\n }\n\n function cancelTransaction(\n address target,\n uint256 value,\n string memory signature,\n bytes memory data,\n uint256 eta\n ) internal {\n bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));\n queuedTransactions[txHash] = false;\n\n emit CancelTransaction(txHash, target, value, signature, data, eta);\n }\n\n /**\n * @notice Gets actions of a proposal\n * @param proposalId the id of the proposal\n * @return targets proposal targets\n * @return values proposal values\n * @return signatures proposal signatures\n * @return calldatas proposal calldatae\n */\n function getActions(uint256 proposalId)\n external\n view\n override\n returns (\n address[] memory targets,\n uint256[] memory values,\n string[] memory signatures,\n bytes[] memory calldatas\n )\n {\n Proposal storage p = proposals[proposalId];\n return (p.targets, p.values, p.signatures, p.calldatas);\n }\n\n /**\n * @notice Gets the receipt for a voter on a given proposal\n * @param proposalId the id of proposal\n * @param voter The address of the voter\n * @return The voting receipt\n */\n function getReceipt(uint256 proposalId, address voter) external view override returns (Receipt memory) {\n return proposalReceipts[proposalId][voter];\n }\n\n /**\n * @notice Gets the state of a proposal\n * @param proposalId The id of the proposal\n * @return Proposal state\n */\n // solhint-disable-next-line code-complexity\n function state(uint256 proposalId) public view override returns (ProposalState) {\n require(proposalCount >= proposalId && proposalId > initialProposalId, \"state: invalid proposal id\");\n Proposal storage proposal = proposals[proposalId];\n bool whitelisted = isWhitelisted(proposal.proposer);\n if (proposal.canceled) {\n return ProposalState.Canceled;\n } else if (block.number <= proposal.startBlock) {\n return ProposalState.Pending;\n } else if (block.number <= proposal.endBlock) {\n return ProposalState.Active;\n } else if (\n (whitelisted && proposal.againstVotes > proposal.quorumVotes) ||\n (!whitelisted && proposal.forVotes <= proposal.againstVotes) ||\n (!whitelisted && proposal.forVotes < proposal.quorumVotes)\n ) {\n return ProposalState.Defeated;\n } else if (proposal.eta == 0) {\n return ProposalState.Succeeded;\n } else if (proposal.executed) {\n return ProposalState.Executed;\n } else if (block.timestamp >= (proposal.eta + GRACE_PERIOD)) {\n return ProposalState.Expired;\n }\n return ProposalState.Queued;\n }\n\n /**\n * @notice Cast a vote for a proposal\n * @param proposalId The id of the proposal to vote on\n * @param support The support value for the vote. 0=against, 1=for, 2=abstain\n */\n function castVote(uint256 proposalId, uint8 support) external override {\n emit VoteCast(_msgSender(), proposalId, support, castVoteInternal(_msgSender(), proposalId, support), \"\");\n }\n\n /**\n * @notice Cast a vote for a proposal with a reason\n * @param proposalId The id of the proposal to vote on\n * @param support The support value for the vote. 0=against, 1=for, 2=abstain\n * @param reason The reason given for the vote by the voter\n */\n function castVoteWithReason(\n uint256 proposalId,\n uint8 support,\n string calldata reason\n ) external override {\n emit VoteCast(_msgSender(), proposalId, support, castVoteInternal(_msgSender(), proposalId, support), reason);\n }\n\n /**\n * @notice Cast a vote for a proposal by signature\n * @dev external override function that accepts EIP-712 signatures for voting on proposals.\n */\n function castVoteBySig(\n uint256 proposalId,\n uint8 support,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external override {\n bytes32 domainSeparator = keccak256(\n abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainIdInternal(), address(this))\n );\n bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support));\n bytes32 digest = keccak256(abi.encodePacked(\"\\x19\\x01\", domainSeparator, structHash));\n address signatory = ecrecover(digest, v, r, s);\n require(signatory != address(0), \"castVoteBySig: invalid signature\");\n emit VoteCast(signatory, proposalId, support, castVoteInternal(signatory, proposalId, support), \"\");\n }\n\n /**\n * @notice Internal function that caries out voting logic\n * @param voter The voter that is casting their vote\n * @param proposalId The id of the proposal to vote on\n * @param support The support value for the vote. 0=against, 1=for, 2=abstain\n * @return The number of votes cast\n */\n function castVoteInternal(\n address voter,\n uint256 proposalId,\n uint8 support\n ) internal returns (uint96) {\n require(state(proposalId) == ProposalState.Active, \"voting is closed\");\n require(support <= 2, \"invalid vote type\");\n Proposal storage proposal = proposals[proposalId];\n Receipt storage receipt = proposalReceipts[proposalId][voter];\n require(receipt.hasVoted == false, \"voter already voted\");\n uint96 votes = ipt.getPriorVotes(voter, proposal.startBlock);\n\n if (support == 0) {\n proposal.againstVotes = proposal.againstVotes + votes;\n } else if (support == 1) {\n proposal.forVotes = proposal.forVotes + votes;\n } else if (support == 2) {\n proposal.abstainVotes = proposal.abstainVotes + votes;\n }\n\n receipt.hasVoted = true;\n receipt.support = support;\n receipt.votes = votes;\n\n return votes;\n }\n\n /**\n * @notice View function which returns if an account is whitelisted\n * @param account Account to check white list status of\n * @return If the account is whitelisted\n */\n function isWhitelisted(address account) public view override returns (bool) {\n return (whitelistAccountExpirations[account] > block.timestamp);\n }\n\n /**\n * @notice Governance function for setting the governance token\n * @param token_ new token addr\n */\n function _setNewToken(address token_) external onlyGov {\n ipt = IIpt(token_);\n }\n\n /**\n * @notice Used to update the timelock period\n * @param proposalTimelockDelay_ The proposal holding period\n */\n function _setDelay(uint256 proposalTimelockDelay_) public override onlyGov {\n uint256 oldTimelockDelay = proposalTimelockDelay;\n proposalTimelockDelay = proposalTimelockDelay_;\n\n emit NewDelay(oldTimelockDelay, proposalTimelockDelay);\n }\n\n /**\n * @notice Used to update the emergency timelock period\n * @param emergencyTimelockDelay_ The proposal holding period\n */\n function _setEmergencyDelay(uint256 emergencyTimelockDelay_) public override onlyGov {\n uint256 oldEmergencyTimelockDelay = emergencyTimelockDelay;\n emergencyTimelockDelay = emergencyTimelockDelay_;\n\n emit NewEmergencyDelay(oldEmergencyTimelockDelay, emergencyTimelockDelay);\n }\n\n /**\n * @notice Governance function for setting the voting delay\n * @param newVotingDelay new voting delay, in blocks\n */\n function _setVotingDelay(uint256 newVotingDelay) external override onlyGov {\n uint256 oldVotingDelay = votingDelay;\n votingDelay = newVotingDelay;\n\n emit VotingDelaySet(oldVotingDelay, votingDelay);\n }\n\n /**\n * @notice Governance function for setting the voting period\n * @param newVotingPeriod new voting period, in blocks\n */\n function _setVotingPeriod(uint256 newVotingPeriod) external override onlyGov {\n uint256 oldVotingPeriod = votingPeriod;\n votingPeriod = newVotingPeriod;\n\n emit VotingPeriodSet(oldVotingPeriod, votingPeriod);\n }\n\n /**\n * @notice Governance function for setting the emergency voting period\n * @param newEmergencyVotingPeriod new voting period, in blocks\n */\n function _setEmergencyVotingPeriod(uint256 newEmergencyVotingPeriod) external override onlyGov {\n uint256 oldEmergencyVotingPeriod = emergencyVotingPeriod;\n emergencyVotingPeriod = newEmergencyVotingPeriod;\n\n emit EmergencyVotingPeriodSet(oldEmergencyVotingPeriod, emergencyVotingPeriod);\n }\n\n /**\n * @notice Governance function for setting the proposal threshold\n * @param newProposalThreshold new proposal threshold\n */\n function _setProposalThreshold(uint256 newProposalThreshold) external override onlyGov {\n uint256 oldProposalThreshold = proposalThreshold;\n proposalThreshold = newProposalThreshold;\n\n emit ProposalThresholdSet(oldProposalThreshold, proposalThreshold);\n }\n\n /**\n * @notice Governance function for setting the quorum\n * @param newQuorumVotes new proposal quorum\n */\n function _setQuorumVotes(uint256 newQuorumVotes) external override onlyGov {\n uint256 oldQuorumVotes = quorumVotes;\n quorumVotes = newQuorumVotes;\n\n emit NewQuorum(oldQuorumVotes, quorumVotes);\n }\n\n /**\n * @notice Governance function for setting the emergency quorum\n * @param newEmergencyQuorumVotes new proposal quorum\n */\n function _setEmergencyQuorumVotes(uint256 newEmergencyQuorumVotes) external override onlyGov {\n uint256 oldEmergencyQuorumVotes = emergencyQuorumVotes;\n emergencyQuorumVotes = newEmergencyQuorumVotes;\n\n emit NewEmergencyQuorum(oldEmergencyQuorumVotes, emergencyQuorumVotes);\n }\n\n /**\n * @notice Governance function for setting the whitelist expiration as a timestamp\n * for an account. Whitelist status allows accounts to propose without meeting threshold\n * @param account Account address to set whitelist expiration for\n * @param expiration Expiration for account whitelist status as timestamp (if now < expiration, whitelisted)\n */\n function _setWhitelistAccountExpiration(address account, uint256 expiration) external override onlyGov {\n require (expiration < (maxWhitelistPeriod + block.timestamp), \"expiration exceeds max\");\n whitelistAccountExpirations[account] = expiration;\n\n emit WhitelistAccountExpirationSet(account, expiration);\n }\n\n /**\n * @notice Governance function for setting the whitelistGuardian. WhitelistGuardian can cancel proposals from whitelisted addresses\n * @param account Account to set whitelistGuardian to (0x0 to remove whitelistGuardian)\n */\n function _setWhitelistGuardian(address account) external override onlyGov {\n address oldGuardian = whitelistGuardian;\n whitelistGuardian = account;\n\n emit WhitelistGuardianSet(oldGuardian, whitelistGuardian);\n }\n\n /**\n * @notice Governance function for setting the optimistic voting delay\n * @param newOptimisticVotingDelay new optimistic voting delay, in blocks\n */\n function _setOptimisticDelay(uint256 newOptimisticVotingDelay) external override onlyGov {\n uint256 oldOptimisticVotingDelay = optimisticVotingDelay;\n optimisticVotingDelay = newOptimisticVotingDelay;\n\n emit OptimisticVotingDelaySet(oldOptimisticVotingDelay, optimisticVotingDelay);\n }\n\n /**\n * @notice Governance function for setting the optimistic quorum\n * @param newOptimisticQuorumVotes new optimistic quorum votes, in blocks\n */\n function _setOptimisticQuorumVotes(uint256 newOptimisticQuorumVotes) external override onlyGov {\n uint256 oldOptimisticQuorumVotes = optimisticQuorumVotes;\n optimisticQuorumVotes = newOptimisticQuorumVotes;\n\n emit OptimisticQuorumVotesSet(oldOptimisticQuorumVotes, optimisticQuorumVotes);\n }\n\n function getChainIdInternal() internal view returns (uint256) {\n return block.chainid;\n }\n\n function getBlockTimestamp() internal view returns (uint256) {\n // solium-disable-next-line security/no-block-members\n return block.timestamp;\n }\n\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n}\n"}},"settings":{"optimizer":{"enabled":true,"runs":200,"details":{"orderLiterals":true,"deduplicate":true,"cse":true,"yul":true}},"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}},"libraries":{}}},"ABI":"[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"txHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"signature\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"}],\"name\":\"CancelTransaction\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldEmergencyVotingPeriod\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"emergencyVotingPeriod\",\"type\":\"uint256\"}],\"name\":\"EmergencyVotingPeriodSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"txHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"signature\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"}],\"name\":\"ExecuteTransaction\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldAdmin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"NewAdmin\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldTimelockDelay\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"proposalTimelockDelay\",\"type\":\"uint256\"}],\"name\":\"NewDelay\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldEmergencyTimelockDelay\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"emergencyTimelockDelay\",\"type\":\"uint256\"}],\"name\":\"NewEmergencyDelay\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldEmergencyQuorumVotes\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"emergencyQuorumVotes\",\"type\":\"uint256\"}],\"name\":\"NewEmergencyQuorum\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldImplementation\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"}],\"name\":\"NewImplementation\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldPendingAdmin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newPendingAdmin\",\"type\":\"address\"}],\"name\":\"NewPendingAdmin\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldQuorumVotes\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"quorumVotes\",\"type\":\"uint256\"}],\"name\":\"NewQuorum\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldOptimisticQuorumVotes\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"optimisticQuorumVotes\",\"type\":\"uint256\"}],\"name\":\"OptimisticQuorumVotesSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldOptimisticVotingDelay\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"optimisticVotingDelay\",\"type\":\"uint256\"}],\"name\":\"OptimisticVotingDelaySet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"ProposalCanceled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"proposer\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"targets\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"values\",\"type\":\"uint256[]\"},{\"indexed\":false,\"internalType\":\"string[]\",\"name\":\"signatures\",\"type\":\"string[]\"},{\"indexed\":false,\"internalType\":\"bytes[]\",\"name\":\"calldatas\",\"type\":\"bytes[]\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"startBlock\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"endBlock\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"description\",\"type\":\"string\"}],\"name\":\"ProposalCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"ProposalExecuted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"}],\"name\":\"ProposalQueued\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldProposalThreshold\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newProposalThreshold\",\"type\":\"uint256\"}],\"name\":\"ProposalThresholdSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"txHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"signature\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"}],\"name\":\"QueueTransaction\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"voter\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"votes\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"reason\",\"type\":\"string\"}],\"name\":\"VoteCast\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldVotingDelay\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newVotingDelay\",\"type\":\"uint256\"}],\"name\":\"VotingDelaySet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldVotingPeriod\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newVotingPeriod\",\"type\":\"uint256\"}],\"name\":\"VotingPeriodSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"}],\"name\":\"WhitelistAccountExpirationSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldGuardian\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newGuardian\",\"type\":\"address\"}],\"name\":\"WhitelistGuardianSet\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BALLOT_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DOMAIN_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"GRACE_PERIOD\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalTimelockDelay_\",\"type\":\"uint256\"}],\"name\":\"_setDelay\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"emergencyTimelockDelay_\",\"type\":\"uint256\"}],\"name\":\"_setEmergencyDelay\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newEmergencyQuorumVotes\",\"type\":\"uint256\"}],\"name\":\"_setEmergencyQuorumVotes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newEmergencyVotingPeriod\",\"type\":\"uint256\"}],\"name\":\"_setEmergencyVotingPeriod\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token_\",\"type\":\"address\"}],\"name\":\"_setNewToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newOptimisticVotingDelay\",\"type\":\"uint256\"}],\"name\":\"_setOptimisticDelay\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newOptimisticQuorumVotes\",\"type\":\"uint256\"}],\"name\":\"_setOptimisticQuorumVotes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newProposalThreshold\",\"type\":\"uint256\"}],\"name\":\"_setProposalThreshold\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newQuorumVotes\",\"type\":\"uint256\"}],\"name\":\"_setQuorumVotes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newVotingDelay\",\"type\":\"uint256\"}],\"name\":\"_setVotingDelay\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newVotingPeriod\",\"type\":\"uint256\"}],\"name\":\"_setVotingPeriod\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"}],\"name\":\"_setWhitelistAccountExpiration\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"_setWhitelistGuardian\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"}],\"name\":\"cancel\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"}],\"name\":\"castVote\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"castVoteBySig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"},{\"internalType\":\"string\",\"name\":\"reason\",\"type\":\"string\"}],\"name\":\"castVoteWithReason\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"emergencyQuorumVotes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"emergencyTimelockDelay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"emergencyVotingPeriod\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"}],\"name\":\"execute\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"signature\",\"type\":\"string\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"}],\"name\":\"executeTransaction\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"}],\"name\":\"getActions\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"targets\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"values\",\"type\":\"uint256[]\"},{\"internalType\":\"string[]\",\"name\":\"signatures\",\"type\":\"string[]\"},{\"internalType\":\"bytes[]\",\"name\":\"calldatas\",\"type\":\"bytes[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"voter\",\"type\":\"address\"}],\"name\":\"getReceipt\",\"outputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"hasVoted\",\"type\":\"bool\"},{\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"},{\"internalType\":\"uint96\",\"name\":\"votes\",\"type\":\"uint96\"}],\"internalType\":\"struct Receipt\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"implementation\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initialProposalId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"ipt_\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initialized\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ipt\",\"outputs\":[{\"internalType\":\"contract IIpt\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"isWhitelisted\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"latestProposalIds\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxWhitelistPeriod\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimisticQuorumVotes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimisticVotingDelay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proposalCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proposalMaxOperations\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"proposalReceipts\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"hasVoted\",\"type\":\"bool\"},{\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"},{\"internalType\":\"uint96\",\"name\":\"votes\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proposalThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proposalTimelockDelay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"proposals\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"proposer\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"startBlock\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"endBlock\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"forVotes\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"againstVotes\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"abstainVotes\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"canceled\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"executed\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"emergency\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"quorumVotes\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"delay\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"targets\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"values\",\"type\":\"uint256[]\"},{\"internalType\":\"string[]\",\"name\":\"signatures\",\"type\":\"string[]\"},{\"internalType\":\"bytes[]\",\"name\":\"calldatas\",\"type\":\"bytes[]\"},{\"internalType\":\"string\",\"name\":\"description\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"emergency\",\"type\":\"bool\"}],\"name\":\"propose\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"}],\"name\":\"queue\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"queuedTransactions\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"quorumVotes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"}],\"name\":\"state\",\"outputs\":[{\"internalType\":\"enum ProposalState\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"votingDelay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"votingPeriod\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"whitelistAccountExpirations\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"whitelistGuardian\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]","ContractName":"GovernorCharlieDelegate","CompilerVersion":"v0.8.9+commit.e5eed63a","OptimizationUsed":1,"Runs":200,"ConstructorArguments":"0x","EVMVersion":"Default","Library":"","LicenseType":"","Proxy":0,"SwarmSource":""}] \ No newline at end of file diff --git a/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/creation_data.json b/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/creation_data.json new file mode 100644 index 0000000000000..2378ed6aa2a8d --- /dev/null +++ b/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x9ab6b21cdf116f611110b048987e58894786c244","contractCreator":"0x603d50bad151da8becf405e51a8c4abc8ba1c95e","txHash":"0x72be611ae1ade09242d9fc9c950a73d076f6c23514564a7b9ac730400dbaf2c0"} \ No newline at end of file diff --git a/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/metadata.json b/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/metadata.json new file mode 100644 index 0000000000000..b0f893895eb0f --- /dev/null +++ b/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"contracts/InterestRates/InterestRatePositionManager.f.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\npragma solidity 0.8.19;\n\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) external returns (bool);\n}\n\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)\n\n/**\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n */\ninterface IERC20Permit {\n /**\n * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\n * given ``owner``'s signed approval.\n *\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\n * ordering also apply here.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `deadline` must be a timestamp in the future.\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\n * over the EIP712-formatted function arguments.\n * - the signature must use ``owner``'s current nonce (see {nonces}).\n *\n * For more information on the signature format, see the\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\n * section].\n */\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n /**\n * @dev Returns the current nonce for `owner`. This value must be\n * included whenever a signature is generated for {permit}.\n *\n * Every successful call to {permit} increases ``owner``'s nonce by one. This\n * prevents a signature from being used multiple times.\n */\n function nonces(address owner) external view returns (uint256);\n\n /**\n * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\n */\n // solhint-disable-next-line func-name-mixedcase\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n}\n\n/// Parameters for ERC20Permit.permit call\nstruct ERC20PermitSignature {\n IERC20Permit token;\n uint256 value;\n uint256 deadline;\n uint8 v;\n bytes32 r;\n bytes32 s;\n}\n\nlibrary PermitHelper {\n function applyPermit(\n ERC20PermitSignature calldata p,\n address owner,\n address spender\n ) internal {\n p.token.permit(owner, spender, p.value, p.deadline, p.v, p.r, p.s);\n }\n\n function applyPermits(\n ERC20PermitSignature[] calldata permits,\n address owner,\n address spender\n ) internal {\n for (uint256 i = 0; i < permits.length; i++) {\n applyPermit(permits[i], owner, spender);\n }\n }\n}\n\n// OpenZeppelin Contracts v4.4.1 (interfaces/IERC3156FlashLender.sol)\n\n// OpenZeppelin Contracts (last updated v4.7.0) (interfaces/IERC3156FlashBorrower.sol)\n\n/**\n * @dev Interface of the ERC3156 FlashBorrower, as defined in\n * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].\n *\n * _Available since v4.1._\n */\ninterface IERC3156FlashBorrower {\n /**\n * @dev Receive a flash loan.\n * @param initiator The initiator of the loan.\n * @param token The loan currency.\n * @param amount The amount of tokens lent.\n * @param fee The additional amount of tokens to repay.\n * @param data Arbitrary data structure, intended to contain user-defined parameters.\n * @return The keccak256 hash of \"IERC3156FlashBorrower.onFlashLoan\"\n */\n function onFlashLoan(\n address initiator,\n address token,\n uint256 amount,\n uint256 fee,\n bytes calldata data\n ) external returns (bytes32);\n}\n\n/**\n * @dev Interface of the ERC3156 FlashLender, as defined in\n * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].\n *\n * _Available since v4.1._\n */\ninterface IERC3156FlashLender {\n /**\n * @dev The amount of currency available to be lended.\n * @param token The loan currency.\n * @return The amount of `token` that can be borrowed.\n */\n function maxFlashLoan(address token) external view returns (uint256);\n\n /**\n * @dev The fee to be charged for a given loan.\n * @param token The loan currency.\n * @param amount The amount of tokens lent.\n * @return The amount of `token` to be charged for the loan, on top of the returned principal.\n */\n function flashFee(address token, uint256 amount) external view returns (uint256);\n\n /**\n * @dev Initiate a flash loan.\n * @param receiver The receiver of the tokens in the loan, and the receiver of the callback.\n * @param token The loan currency.\n * @param amount The amount of tokens lent.\n * @param data Arbitrary data structure, intended to contain user-defined parameters.\n */\n function flashLoan(\n IERC3156FlashBorrower receiver,\n address token,\n uint256 amount,\n bytes calldata data\n ) external returns (bool);\n}\n\n/// @dev Interface to be used by contracts that collect fees. Contains fee recipient that can be changed by owner.\ninterface IFeeCollector {\n // --- Events ---\n\n /// @dev Fee Recipient is changed to @param feeRecipient address.\n /// @param feeRecipient New fee recipient address.\n event FeeRecipientChanged(address feeRecipient);\n\n // --- Errors ---\n\n /// @dev Invalid fee recipient.\n error InvalidFeeRecipient();\n\n // --- Functions ---\n\n /// @return Address of the current fee recipient.\n function feeRecipient() external view returns (address);\n\n /// @dev Sets new fee recipient address\n /// @param newFeeRecipient Address of the new fee recipient.\n function setFeeRecipient(address newFeeRecipient) external;\n}\n\ninterface IPositionManagerDependent {\n // --- Errors ---\n\n /// @dev Position Manager cannot be zero.\n error PositionManagerCannotBeZero();\n\n /// @dev Caller is not Position Manager.\n error CallerIsNotPositionManager(address caller);\n\n // --- Functions ---\n\n /// @dev Returns address of the PositionManager contract.\n function positionManager() external view returns (address);\n}\n\n/// @dev Interface of R stablecoin token. Implements some standards like IERC20, IERC20Permit, and IERC3156FlashLender.\n/// Raft's specific implementation contains IFeeCollector and IPositionManagerDependent.\n/// PositionManager can mint and burn R when particular actions happen with user's position.\ninterface IRToken is IERC20, IERC20Permit, IERC3156FlashLender, IFeeCollector, IPositionManagerDependent {\n // --- Events ---\n\n /// @dev New R token is deployed\n /// @param positionManager Address of the PositionManager contract that is authorized to mint and burn new tokens.\n /// @param flashMintFeeRecipient Address of flash mint fee recipient.\n event RDeployed(address positionManager, address flashMintFeeRecipient);\n\n /// @dev The Flash Mint Fee Percentage has been changed.\n /// @param flashMintFeePercentage The new Flash Mint Fee Percentage value.\n event FlashMintFeePercentageChanged(uint256 flashMintFeePercentage);\n\n /// --- Errors ---\n\n /// @dev Proposed flash mint fee percentage is too big.\n /// @param feePercentage Proposed flash mint fee percentage.\n error FlashFeePercentageTooBig(uint256 feePercentage);\n\n // --- Functions ---\n\n /// @return Number representing 100 percentage.\n function PERCENTAGE_BASE() external view returns (uint256);\n\n /// @dev Mints new tokens. Callable only by PositionManager contract.\n /// @param to Address that will receive newly minted tokens.\n /// @param amount Amount of tokens to mint.\n function mint(address to, uint256 amount) external;\n\n /// @dev Mints new tokens. Callable only by PositionManager contract.\n /// @param from Address of user whose tokens are burnt.\n /// @param amount Amount of tokens to burn.\n function burn(address from, uint256 amount) external;\n\n /// @return Maximum flash mint fee percentage that can be set by owner.\n function MAX_FLASH_MINT_FEE_PERCENTAGE() external view returns (uint256);\n\n /// @return Current flash mint fee percentage.\n function flashMintFeePercentage() external view returns (uint256);\n\n /// @dev Sets new flash mint fee percentage. Callable only by owner.\n /// @notice The proposed flash mint fee percentage cannot exceed `MAX_FLASH_MINT_FEE_PERCENTAGE`.\n /// @param feePercentage New flash fee percentage.\n function setFlashMintFeePercentage(uint256 feePercentage) external;\n}\n\ninterface IPriceOracle {\n // --- Errors ---\n\n /// @dev Contract initialized with an invalid deviation parameter.\n error InvalidDeviation();\n\n // --- Types ---\n\n struct PriceOracleResponse {\n bool isBrokenOrFrozen;\n bool priceChangeAboveMax;\n uint256 price;\n }\n\n // --- Functions ---\n\n /// @dev Return price oracle response which consists the following information: oracle is broken or frozen, the\n /// price change between two rounds is more than max, and the price.\n function getPriceOracleResponse() external returns (PriceOracleResponse memory);\n\n /// @dev Maximum time period allowed since oracle latest round data timestamp, beyond which oracle is considered\n /// frozen.\n function timeout() external view returns (uint256);\n\n /// @dev Used to convert a price answer to an 18-digit precision uint.\n function TARGET_DIGITS() external view returns (uint256);\n\n /// @dev price deviation for the oracle in percentage.\n function DEVIATION() external view returns (uint256);\n}\n\ninterface IPriceFeed {\n // --- Events ---\n\n /// @dev Last good price has been updated.\n event LastGoodPriceUpdated(uint256 lastGoodPrice);\n\n /// @dev Price difference between oracles has been updated.\n /// @param priceDifferenceBetweenOracles New price difference between oracles.\n event PriceDifferenceBetweenOraclesUpdated(uint256 priceDifferenceBetweenOracles);\n\n /// @dev Primary oracle has been updated.\n /// @param primaryOracle New primary oracle.\n event PrimaryOracleUpdated(IPriceOracle primaryOracle);\n\n /// @dev Secondary oracle has been updated.\n /// @param secondaryOracle New secondary oracle.\n event SecondaryOracleUpdated(IPriceOracle secondaryOracle);\n\n // --- Errors ---\n\n /// @dev Invalid primary oracle.\n error InvalidPrimaryOracle();\n\n /// @dev Invalid secondary oracle.\n error InvalidSecondaryOracle();\n\n /// @dev Primary oracle is broken or frozen or has bad result.\n error PrimaryOracleBrokenOrFrozenOrBadResult();\n\n /// @dev Invalid price difference between oracles.\n error InvalidPriceDifferenceBetweenOracles();\n\n // --- Functions ---\n\n /// @dev Return primary oracle address.\n function primaryOracle() external returns (IPriceOracle);\n\n /// @dev Return secondary oracle address\n function secondaryOracle() external returns (IPriceOracle);\n\n /// @dev The last good price seen from an oracle by Raft.\n function lastGoodPrice() external returns (uint256);\n\n /// @dev The maximum relative price difference between two oracle responses.\n function priceDifferenceBetweenOracles() external returns (uint256);\n\n /// @dev Set primary oracle address.\n /// @param newPrimaryOracle Primary oracle address.\n function setPrimaryOracle(IPriceOracle newPrimaryOracle) external;\n\n /// @dev Set secondary oracle address.\n /// @param newSecondaryOracle Secondary oracle address.\n function setSecondaryOracle(IPriceOracle newSecondaryOracle) external;\n\n /// @dev Set the maximum relative price difference between two oracle responses.\n /// @param newPriceDifferenceBetweenOracles The maximum relative price difference between two oracle responses.\n function setPriceDifferenceBetweenOracles(uint256 newPriceDifferenceBetweenOracles) external;\n\n /// @dev Returns the latest price obtained from the Oracle. Called by Raft functions that require a current price.\n ///\n /// Also callable by anyone externally.\n /// Non-view function - it stores the last good price seen by Raft.\n ///\n /// Uses a primary oracle and a fallback oracle in case primary fails. If both fail,\n /// it uses the last good price seen by Raft.\n ///\n /// @return currentPrice Returned price.\n /// @return deviation Deviation of the reported price in percentage.\n /// @notice Actual returned price is in range `currentPrice` +/- `currentPrice * deviation / ONE`\n function fetchPrice() external returns (uint256 currentPrice, uint256 deviation);\n}\n\ninterface IERC20Indexable is IERC20, IPositionManagerDependent {\n // --- Events ---\n\n /// @dev New token is deployed.\n /// @param positionManager Address of the PositionManager contract that is authorized to mint and burn new tokens.\n event ERC20IndexableDeployed(address positionManager);\n\n /// @dev New index has been set.\n /// @param newIndex Value of the new index.\n event IndexUpdated(uint256 newIndex);\n\n // --- Errors ---\n\n /// @dev Unsupported action for ERC20Indexable contract.\n error NotSupported();\n\n // --- Functions ---\n\n /// @return Precision for token index. Represents index that is equal to 1.\n function INDEX_PRECISION() external view returns (uint256);\n\n /// @return Current index value.\n function currentIndex() external view returns (uint256);\n\n /// @dev Sets new token index. Callable only by PositionManager contract.\n /// @param backingAmount Amount of backing token that is covered by total supply.\n function setIndex(uint256 backingAmount) external;\n\n /// @dev Mints new tokens. Callable only by PositionManager contract.\n /// @param to Address that will receive newly minted tokens.\n /// @param amount Amount of tokens to mint.\n function mint(address to, uint256 amount) external;\n\n /// @dev Mints new tokens. Callable only by PositionManager contract.\n /// @param from Address of user whose tokens are burnt.\n /// @param amount Amount of tokens to burn.\n function burn(address from, uint256 amount) external;\n}\n\ninterface ISplitLiquidationCollateral {\n // --- Functions ---\n\n /// @dev Returns lowest total debt that will be split.\n function LOW_TOTAL_DEBT() external view returns (uint256);\n\n /// @dev Minimum collateralization ratio for position\n function MCR() external view returns (uint256);\n\n /// @dev Splits collateral between protocol and liquidator.\n /// @param totalCollateral Amount of collateral to split.\n /// @param totalDebt Amount of debt to split.\n /// @param price Price of collateral.\n /// @param isRedistribution True if this is a redistribution.\n /// @return collateralToSendToProtocol Amount of collateral to send to protocol.\n /// @return collateralToSentToLiquidator Amount of collateral to send to liquidator.\n function split(\n uint256 totalCollateral,\n uint256 totalDebt,\n uint256 price,\n bool isRedistribution\n )\n external\n view\n returns (uint256 collateralToSendToProtocol, uint256 collateralToSentToLiquidator);\n}\n\n/// @dev Common interface for the Position Manager.\ninterface IPositionManager is IFeeCollector {\n // --- Types ---\n\n /// @dev Information for a Raft indexable collateral token.\n /// @param collateralToken The Raft indexable collateral token.\n /// @param debtToken Corresponding Raft indexable debt token.\n /// @param priceFeed The contract that provides a price for the collateral token.\n /// @param splitLiquidation The contract that calculates collateral split in case of liquidation.\n /// @param isEnabled Whether the token can be used as collateral or not.\n /// @param lastFeeOperationTime Timestamp of the last operation for the collateral token.\n /// @param borrowingSpread The current borrowing spread.\n /// @param baseRate The current base rate.\n /// @param redemptionSpread The current redemption spread.\n /// @param redemptionRebate Percentage of the redemption fee returned to redeemed positions.\n struct CollateralTokenInfo {\n IERC20Indexable collateralToken;\n IERC20Indexable debtToken;\n IPriceFeed priceFeed;\n ISplitLiquidationCollateral splitLiquidation;\n bool isEnabled;\n uint256 lastFeeOperationTime;\n uint256 borrowingSpread;\n uint256 baseRate;\n uint256 redemptionSpread;\n uint256 redemptionRebate;\n }\n\n // --- Events ---\n\n /// @dev New position manager has been token deployed.\n /// @param rToken The R token used by the position manager.\n /// @param feeRecipient The address of fee recipient.\n event PositionManagerDeployed(IRToken rToken, address feeRecipient);\n\n /// @dev New collateral token has been added added to the system.\n /// @param collateralToken The token used as collateral.\n /// @param raftCollateralToken The Raft indexable collateral token for the given collateral token.\n /// @param raftDebtToken The Raft indexable debt token for given collateral token.\n /// @param priceFeed The contract that provides price for the collateral token.\n event CollateralTokenAdded(\n IERC20 collateralToken,\n IERC20Indexable raftCollateralToken,\n IERC20Indexable raftDebtToken,\n IPriceFeed priceFeed\n );\n\n /// @dev Collateral token has been enabled or disabled.\n /// @param collateralToken The token used as collateral.\n /// @param isEnabled True if the token is enabled, false otherwise.\n event CollateralTokenModified(IERC20 collateralToken, bool isEnabled);\n\n /// @dev A delegate has been whitelisted for a certain position.\n /// @param position The position for which the delegate was whitelisted.\n /// @param delegate The delegate which was whitelisted.\n /// @param whitelisted Specifies whether the delegate whitelisting has been enabled (true) or disabled (false).\n event DelegateWhitelisted(address indexed position, address indexed delegate, bool whitelisted);\n\n /// @dev New position has been created.\n /// @param position The address of the user opening new position.\n /// @param collateralToken The token used as collateral for the created position.\n event PositionCreated(address indexed position, IERC20 indexed collateralToken);\n\n /// @dev The position has been closed by either repayment, liquidation, or redemption.\n /// @param position The address of the user whose position is closed.\n event PositionClosed(address indexed position);\n\n /// @dev Collateral amount for the position has been changed.\n /// @param position The address of the user that has opened the position.\n /// @param collateralToken The address of the collateral token being added to position.\n /// @param collateralAmount The amount of collateral added or removed.\n /// @param isCollateralIncrease Whether the collateral is added to the position or removed from it.\n event CollateralChanged(\n address indexed position, IERC20 indexed collateralToken, uint256 collateralAmount, bool isCollateralIncrease\n );\n\n /// @dev Debt amount for position has been changed.\n /// @param position The address of the user that has opened the position.\n /// @param collateralToken The address of the collateral token backing the debt.\n /// @param debtAmount The amount of debt added or removed.\n /// @param isDebtIncrease Whether the debt is added to the position or removed from it.\n event DebtChanged(\n address indexed position, IERC20 indexed collateralToken, uint256 debtAmount, bool isDebtIncrease\n );\n\n /// @dev Borrowing fee has been paid. Emitted only if the actual fee was paid - doesn't happen with no fees are\n /// paid.\n /// @param collateralToken Collateral token used to mint R.\n /// @param position The address of position's owner that triggered the fee payment.\n /// @param feeAmount The amount of tokens paid as the borrowing fee.\n event RBorrowingFeePaid(IERC20 collateralToken, address indexed position, uint256 feeAmount);\n\n /// @dev Liquidation has been executed.\n /// @param liquidator The liquidator that executed the liquidation.\n /// @param position The address of position's owner whose position was liquidated.\n /// @param collateralToken The collateral token used for the liquidation.\n /// @param debtLiquidated The total debt that was liquidated or redistributed.\n /// @param collateralLiquidated The total collateral liquidated.\n /// @param collateralSentToLiquidator The collateral amount sent to the liquidator.\n /// @param collateralLiquidationFeePaid The total collateral paid as the liquidation fee to the fee recipient.\n /// @param isRedistribution Whether the executed liquidation was redistribution or not.\n event Liquidation(\n address indexed liquidator,\n address indexed position,\n IERC20 indexed collateralToken,\n uint256 debtLiquidated,\n uint256 collateralLiquidated,\n uint256 collateralSentToLiquidator,\n uint256 collateralLiquidationFeePaid,\n bool isRedistribution\n );\n\n /// @dev Redemption has been executed.\n /// @param redeemer User that redeemed R.\n /// @param amount Amount of R that was redeemed.\n /// @param collateralSent The amount of collateral sent to the redeemer.\n /// @param fee The amount of fee paid to the fee recipient.\n /// @param rebate Redemption rebate amount.\n event Redemption(address indexed redeemer, uint256 amount, uint256 collateralSent, uint256 fee, uint256 rebate);\n\n /// @dev Borrowing spread has been updated.\n /// @param borrowingSpread The new borrowing spread.\n event BorrowingSpreadUpdated(uint256 borrowingSpread);\n\n /// @dev Redemption rebate has been updated.\n /// @param redemptionRebate The new redemption rebate.\n event RedemptionRebateUpdated(uint256 redemptionRebate);\n\n /// @dev Redemption spread has been updated.\n /// @param collateralToken Collateral token that the spread was set for.\n /// @param redemptionSpread The new redemption spread.\n event RedemptionSpreadUpdated(IERC20 collateralToken, uint256 redemptionSpread);\n\n /// @dev Base rate has been updated.\n /// @param collateralToken Collateral token that the baser rate was updated for.\n /// @param baseRate The new base rate.\n event BaseRateUpdated(IERC20 collateralToken, uint256 baseRate);\n\n /// @dev Last fee operation time has been updated.\n /// @param collateralToken Collateral token that the baser rate was updated for.\n /// @param lastFeeOpTime The new operation time.\n event LastFeeOpTimeUpdated(IERC20 collateralToken, uint256 lastFeeOpTime);\n\n /// @dev Split liquidation collateral has been changed.\n /// @param collateralToken Collateral token whose split liquidation collateral contract is set.\n /// @param newSplitLiquidationCollateral New value that was set to be split liquidation collateral.\n event SplitLiquidationCollateralChanged(\n IERC20 collateralToken, ISplitLiquidationCollateral indexed newSplitLiquidationCollateral\n );\n\n // --- Errors ---\n\n /// @dev Max fee percentage must be between borrowing spread and 100%.\n error InvalidMaxFeePercentage();\n\n /// @dev Max fee percentage must be between 0.5% and 100%.\n error MaxFeePercentageOutOfRange();\n\n /// @dev Amount is zero.\n error AmountIsZero();\n\n /// @dev Nothing to liquidate.\n error NothingToLiquidate();\n\n /// @dev Cannot liquidate last position.\n error CannotLiquidateLastPosition();\n\n /// @dev Cannot redeem collateral below minimum debt threshold.\n /// @param collateralToken Collateral token used to redeem.\n /// @param newTotalDebt New total debt backed by collateral, which is lower than minimum debt.\n error TotalDebtCannotBeLowerThanMinDebt(IERC20 collateralToken, uint256 newTotalDebt);\n\n /// @dev Cannot redeem collateral\n /// @param collateralToken Collateral token used to redeem.\n /// @param newTotalCollateral New total collateral, which is lower than minimum collateral.\n /// @param minimumCollateral Minimum collateral required to complete redeem\n error TotalCollateralCannotBeLowerThanMinCollateral(\n IERC20 collateralToken, uint256 newTotalCollateral, uint256 minimumCollateral\n );\n\n /// @dev Fee would eat up all returned collateral.\n error FeeEatsUpAllReturnedCollateral();\n\n /// @dev Borrowing spread exceeds maximum.\n error BorrowingSpreadExceedsMaximum();\n\n /// @dev Redemption rebate exceeds maximum.\n error RedemptionRebateExceedsMaximum();\n\n /// @dev Redemption spread is out of allowed range.\n error RedemptionSpreadOutOfRange();\n\n /// @dev There must be either a collateral change or a debt change.\n error NoCollateralOrDebtChange();\n\n /// @dev There is some collateral for position that doesn't have debt.\n error InvalidPosition();\n\n /// @dev An operation that would result in ICR < MCR is not permitted.\n /// @param newICR Resulting ICR that is below MCR.\n error NewICRLowerThanMCR(uint256 newICR);\n\n /// @dev Position's net debt must be greater than minimum.\n /// @param netDebt Net debt amount that is below minimum.\n error NetDebtBelowMinimum(uint256 netDebt);\n\n /// @dev The provided delegate address is invalid.\n error InvalidDelegateAddress();\n\n /// @dev A non-whitelisted delegate cannot adjust positions.\n error DelegateNotWhitelisted();\n\n /// @dev Fee exceeded provided maximum fee percentage.\n /// @param fee The fee amount.\n /// @param amount The amount of debt or collateral.\n /// @param maxFeePercentage The maximum fee percentage.\n error FeeExceedsMaxFee(uint256 fee, uint256 amount, uint256 maxFeePercentage);\n\n /// @dev Borrower uses a different collateral token already.\n error PositionCollateralTokenMismatch();\n\n /// @dev Collateral token address cannot be zero.\n error CollateralTokenAddressCannotBeZero();\n\n /// @dev Price feed address cannot be zero.\n error PriceFeedAddressCannotBeZero();\n\n /// @dev Collateral token already added.\n error CollateralTokenAlreadyAdded();\n\n /// @dev Collateral token is not added.\n error CollateralTokenNotAdded();\n\n /// @dev Collateral token is not enabled.\n error CollateralTokenDisabled();\n\n /// @dev Split liquidation collateral cannot be zero.\n error SplitLiquidationCollateralCannotBeZero();\n\n /// @dev Cannot change collateral in case of repaying the whole debt.\n error WrongCollateralParamsForFullRepayment();\n\n // --- Functions ---\n\n /// @return The R token used by position manager.\n function rToken() external view returns (IRToken);\n\n /// @dev Retrieves information about certain collateral type.\n /// @param collateralToken The token used as collateral.\n /// @return raftCollateralToken The Raft indexable collateral token.\n /// @return raftDebtToken The Raft indexable debt token.\n /// @return priceFeed The contract that provides a price for the collateral token.\n /// @return splitLiquidation The contract that calculates collateral split in case of liquidation.\n /// @return isEnabled Whether the collateral token can be used as collateral or not.\n /// @return lastFeeOperationTime Timestamp of the last operation for the collateral token.\n /// @return borrowingSpread The current borrowing spread.\n /// @return baseRate The current base rate.\n /// @return redemptionSpread The current redemption spread.\n /// @return redemptionRebate Percentage of the redemption fee returned to redeemed positions.\n function collateralInfo(IERC20 collateralToken)\n external\n view\n returns (\n IERC20Indexable raftCollateralToken,\n IERC20Indexable raftDebtToken,\n IPriceFeed priceFeed,\n ISplitLiquidationCollateral splitLiquidation,\n bool isEnabled,\n uint256 lastFeeOperationTime,\n uint256 borrowingSpread,\n uint256 baseRate,\n uint256 redemptionSpread,\n uint256 redemptionRebate\n );\n\n /// @param collateralToken Collateral token whose raft collateral indexable token is being queried.\n /// @return Raft collateral token address for given collateral token.\n function raftCollateralToken(IERC20 collateralToken) external view returns (IERC20Indexable);\n\n /// @param collateralToken Collateral token whose raft collateral indexable token is being queried.\n /// @return Raft debt token address for given collateral token.\n function raftDebtToken(IERC20 collateralToken) external view returns (IERC20Indexable);\n\n /// @param collateralToken Collateral token whose price feed contract is being queried.\n /// @return Price feed contract address for given collateral token.\n function priceFeed(IERC20 collateralToken) external view returns (IPriceFeed);\n\n /// @param collateralToken Collateral token whose split liquidation collateral is being queried.\n /// @return Returns address of the split liquidation collateral contract.\n function splitLiquidationCollateral(IERC20 collateralToken) external view returns (ISplitLiquidationCollateral);\n\n /// @param collateralToken Collateral token whose split liquidation collateral is being queried.\n /// @return Returns whether collateral is enabled or nor.\n function collateralEnabled(IERC20 collateralToken) external view returns (bool);\n\n /// @param collateralToken Collateral token we query last operation time fee for.\n /// @return The timestamp of the latest fee operation (redemption or new R issuance).\n function lastFeeOperationTime(IERC20 collateralToken) external view returns (uint256);\n\n /// @param collateralToken Collateral token we query borrowing spread for.\n /// @return The current borrowing spread.\n function borrowingSpread(IERC20 collateralToken) external view returns (uint256);\n\n /// @param collateralToken Collateral token we query base rate for.\n /// @return rate The base rate.\n function baseRate(IERC20 collateralToken) external view returns (uint256 rate);\n\n /// @param collateralToken Collateral token we query redemption spread for.\n /// @return The current redemption spread for collateral token.\n function redemptionSpread(IERC20 collateralToken) external view returns (uint256);\n\n /// @param collateralToken Collateral token we query redemption rebate for.\n /// @return rebate Percentage of the redemption fee returned to redeemed positions.\n function redemptionRebate(IERC20 collateralToken) external view returns (uint256);\n\n /// @param collateralToken Collateral token we query redemption rate for.\n /// @return rate The current redemption rate for collateral token.\n function getRedemptionRate(IERC20 collateralToken) external view returns (uint256 rate);\n\n /// @dev Returns the collateral token that a given position used for their position.\n /// @param position The address of the borrower.\n /// @return collateralToken The collateral token of the borrower's position.\n function collateralTokenForPosition(address position) external view returns (IERC20 collateralToken);\n\n /// @dev Adds a new collateral token to the protocol.\n /// @param collateralToken The new collateral token.\n /// @param priceFeed The price feed for the collateral token.\n /// @param newSplitLiquidationCollateral split liquidation collateral contract address.\n function addCollateralToken(\n IERC20 collateralToken,\n IPriceFeed priceFeed,\n ISplitLiquidationCollateral newSplitLiquidationCollateral\n )\n external;\n\n /// @dev Adds a new collateral token to the protocol.\n /// @param collateralToken The new collateral token.\n /// @param priceFeed The price feed for the collateral token.\n /// @param newSplitLiquidationCollateral split liquidation collateral contract address.\n /// @param raftCollateralToken_ Address of raft collateral token.\n /// @param raftDebtToken_ Address of raft debt token.\n function addCollateralToken(\n IERC20 collateralToken,\n IPriceFeed priceFeed,\n ISplitLiquidationCollateral newSplitLiquidationCollateral,\n IERC20Indexable raftCollateralToken_,\n IERC20Indexable raftDebtToken_\n )\n external;\n\n /// @dev Enables or disables a collateral token. Reverts if the collateral token has not been added.\n /// @param collateralToken The collateral token.\n /// @param isEnabled Whether the collateral token can be used as collateral or not.\n function setCollateralEnabled(IERC20 collateralToken, bool isEnabled) external;\n\n /// @dev Sets the new split liquidation collateral contract.\n /// @param collateralToken Collateral token whose split liquidation collateral is being set.\n /// @param newSplitLiquidationCollateral New split liquidation collateral contract address.\n function setSplitLiquidationCollateral(\n IERC20 collateralToken,\n ISplitLiquidationCollateral newSplitLiquidationCollateral\n )\n external;\n\n /// @dev Liquidates the borrower if its position's ICR is lower than the minimum collateral ratio.\n /// @param position The address of the borrower.\n function liquidate(address position) external;\n\n /// @dev Redeems the collateral token for a given debt amount. It sends @param debtAmount R to the system and\n /// redeems the corresponding amount of collateral from as many positions as are needed to fill the redemption\n /// request.\n /// @param collateralToken The token used as collateral.\n /// @param debtAmount The amount of debt to be redeemed. Must be greater than zero.\n /// @param maxFeePercentage The maximum fee percentage to pay for the redemption.\n function redeemCollateral(IERC20 collateralToken, uint256 debtAmount, uint256 maxFeePercentage) external;\n\n /// @dev Manages the position on behalf of a given borrower.\n /// @param collateralToken The token the borrower used as collateral.\n /// @param position The address of the borrower.\n /// @param collateralChange The amount of collateral to add or remove.\n /// @param isCollateralIncrease True if the collateral is being increased, false otherwise.\n /// @param debtChange The amount of R to add or remove. In case of repayment (isDebtIncrease = false)\n /// `type(uint256).max` value can be used to repay the whole outstanding loan.\n /// @param isDebtIncrease True if the debt is being increased, false otherwise.\n /// @param maxFeePercentage The maximum fee percentage to pay for the position management.\n /// @param permitSignature Optional permit signature for tokens that support IERC20Permit interface.\n /// @notice `permitSignature` it is ignored if permit signature is not for `collateralToken`.\n /// @notice In case of full debt repayment, `isCollateralIncrease` is ignored and `collateralChange` must be 0.\n /// These values are set to `false`(collateral decrease), and the whole collateral balance of the user.\n /// @return actualCollateralChange Actual amount of collateral added/removed.\n /// Can be different to `collateralChange` in case of full repayment.\n /// @return actualDebtChange Actual amount of debt added/removed.\n /// Can be different to `debtChange` in case of passing type(uint256).max as `debtChange`.\n function managePosition(\n IERC20 collateralToken,\n address position,\n uint256 collateralChange,\n bool isCollateralIncrease,\n uint256 debtChange,\n bool isDebtIncrease,\n uint256 maxFeePercentage,\n ERC20PermitSignature calldata permitSignature\n )\n external\n returns (uint256 actualCollateralChange, uint256 actualDebtChange);\n\n /// @return The max borrowing spread.\n function MAX_BORROWING_SPREAD() external view returns (uint256);\n\n /// @return The max borrowing rate.\n function MAX_BORROWING_RATE() external view returns (uint256);\n\n /// @dev Sets the new borrowing spread.\n /// @param collateralToken Collateral token we set borrowing spread for.\n /// @param newBorrowingSpread New borrowing spread to be used.\n function setBorrowingSpread(IERC20 collateralToken, uint256 newBorrowingSpread) external;\n\n /// @param collateralToken Collateral token we query borrowing rate for.\n /// @return The current borrowing rate.\n function getBorrowingRate(IERC20 collateralToken) external view returns (uint256);\n\n /// @param collateralToken Collateral token we query borrowing rate with decay for.\n /// @return The current borrowing rate with decay.\n function getBorrowingRateWithDecay(IERC20 collateralToken) external view returns (uint256);\n\n /// @dev Returns the borrowing fee for a given debt amount.\n /// @param collateralToken Collateral token we query borrowing fee for.\n /// @param debtAmount The amount of debt.\n /// @return The borrowing fee.\n function getBorrowingFee(IERC20 collateralToken, uint256 debtAmount) external view returns (uint256);\n\n /// @dev Sets the new redemption spread.\n /// @param newRedemptionSpread New redemption spread to be used.\n function setRedemptionSpread(IERC20 collateralToken, uint256 newRedemptionSpread) external;\n\n /// @dev Sets new redemption rebate percentage.\n /// @param newRedemptionRebate Value that is being set as a redemption rebate percentage.\n function setRedemptionRebate(IERC20 collateralToken, uint256 newRedemptionRebate) external;\n\n /// @param collateralToken Collateral token we query redemption rate with decay for.\n /// @return The current redemption rate with decay.\n function getRedemptionRateWithDecay(IERC20 collateralToken) external view returns (uint256);\n\n /// @dev Returns the redemption fee for a given collateral amount.\n /// @param collateralToken Collateral token we query redemption fee for.\n /// @param collateralAmount The amount of collateral.\n /// @param priceDeviation Deviation for the reported price by oracle in percentage.\n /// @return The redemption fee.\n function getRedemptionFee(\n IERC20 collateralToken,\n uint256 collateralAmount,\n uint256 priceDeviation\n )\n external\n view\n returns (uint256);\n\n /// @dev Returns the redemption fee with decay for a given collateral amount.\n /// @param collateralToken Collateral token we query redemption fee with decay for.\n /// @param collateralAmount The amount of collateral.\n /// @return The redemption fee with decay.\n function getRedemptionFeeWithDecay(\n IERC20 collateralToken,\n uint256 collateralAmount\n )\n external\n view\n returns (uint256);\n\n /// @return Half-life of 12h (720 min).\n /// @dev (1/2) = d^720 => d = (1/2)^(1/720)\n function MINUTE_DECAY_FACTOR() external view returns (uint256);\n\n /// @dev Returns if a given delegate is whitelisted for a given borrower.\n /// @param position The address of the borrower.\n /// @param delegate The address of the delegate.\n /// @return isWhitelisted True if the delegate is whitelisted for a given borrower, false otherwise.\n function isDelegateWhitelisted(address position, address delegate) external view returns (bool isWhitelisted);\n\n /// @dev Whitelists a delegate.\n /// @param delegate The address of the delegate.\n /// @param whitelisted True if delegate is being whitelisted, false otherwise.\n function whitelistDelegate(address delegate, bool whitelisted) external;\n\n /// @return Parameter by which to divide the redeemed fraction, in order to calc the new base rate from a\n /// redemption. Corresponds to (1 / ALPHA) in the white paper.\n function BETA() external view returns (uint256);\n}\n\n/// @dev Common interface for the Position Manager.\ninterface IInterestRatePositionManager is IPositionManager {\n // --- Events ---\n\n /// @dev Fees coming from accrued interest are minted.\n /// @param collateralToken Collateral token that fees are paid for.\n /// @param amount Amount of R minted.\n event MintedFees(IERC20 collateralToken, uint256 amount);\n\n // --- Errors ---\n\n /// @dev Only registered debt token can be caller.\n /// @param sender Actual caller.\n error InvalidDebtToken(address sender);\n\n // --- Functions ---\n\n /// @dev Mints fees coming from accrued interest. Can be called only from matching debt token.\n /// @param collateralToken Collateral token to mint fees for.\n /// @param amount Amount of R to mint.\n function mintFees(IERC20 collateralToken, uint256 amount) external;\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n enum Rounding {\n Down, // Toward negative infinity\n Up, // Toward infinity\n Zero // Toward zero\n }\n\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return a > b ? a : b;\n }\n\n /**\n * @dev Returns the smallest of two numbers.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return a < b ? a : b;\n }\n\n /**\n * @dev Returns the average of two numbers. The result is rounded towards\n * zero.\n */\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b) / 2 can overflow.\n return (a & b) + (a ^ b) / 2;\n }\n\n /**\n * @dev Returns the ceiling of the division of two numbers.\n *\n * This differs from standard division with `/` in that it rounds up instead\n * of rounding down.\n */\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b - 1) / b can overflow on addition, so we distribute.\n return a == 0 ? 0 : (a - 1) / b + 1;\n }\n\n /**\n * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0\n * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)\n * with further edits by Uniswap Labs also under MIT license.\n */\n function mulDiv(\n uint256 x,\n uint256 y,\n uint256 denominator\n ) internal pure returns (uint256 result) {\n unchecked {\n // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use\n // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256\n // variables such that product = prod1 * 2^256 + prod0.\n uint256 prod0; // Least significant 256 bits of the product\n uint256 prod1; // Most significant 256 bits of the product\n assembly {\n let mm := mulmod(x, y, not(0))\n prod0 := mul(x, y)\n prod1 := sub(sub(mm, prod0), lt(mm, prod0))\n }\n\n // Handle non-overflow cases, 256 by 256 division.\n if (prod1 == 0) {\n return prod0 / denominator;\n }\n\n // Make sure the result is less than 2^256. Also prevents denominator == 0.\n require(denominator > prod1);\n\n ///////////////////////////////////////////////\n // 512 by 256 division.\n ///////////////////////////////////////////////\n\n // Make division exact by subtracting the remainder from [prod1 prod0].\n uint256 remainder;\n assembly {\n // Compute remainder using mulmod.\n remainder := mulmod(x, y, denominator)\n\n // Subtract 256 bit number from 512 bit number.\n prod1 := sub(prod1, gt(remainder, prod0))\n prod0 := sub(prod0, remainder)\n }\n\n // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.\n // See https://cs.stackexchange.com/q/138556/92363.\n\n // Does not overflow because the denominator cannot be zero at this stage in the function.\n uint256 twos = denominator & (~denominator + 1);\n assembly {\n // Divide denominator by twos.\n denominator := div(denominator, twos)\n\n // Divide [prod1 prod0] by twos.\n prod0 := div(prod0, twos)\n\n // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.\n twos := add(div(sub(0, twos), twos), 1)\n }\n\n // Shift in bits from prod1 into prod0.\n prod0 |= prod1 * twos;\n\n // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such\n // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for\n // four bits. That is, denominator * inv = 1 mod 2^4.\n uint256 inverse = (3 * denominator) ^ 2;\n\n // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works\n // in modular arithmetic, doubling the correct bits in each step.\n inverse *= 2 - denominator * inverse; // inverse mod 2^8\n inverse *= 2 - denominator * inverse; // inverse mod 2^16\n inverse *= 2 - denominator * inverse; // inverse mod 2^32\n inverse *= 2 - denominator * inverse; // inverse mod 2^64\n inverse *= 2 - denominator * inverse; // inverse mod 2^128\n inverse *= 2 - denominator * inverse; // inverse mod 2^256\n\n // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.\n // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is\n // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1\n // is no longer required.\n result = prod0 * inverse;\n return result;\n }\n }\n\n /**\n * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.\n */\n function mulDiv(\n uint256 x,\n uint256 y,\n uint256 denominator,\n Rounding rounding\n ) internal pure returns (uint256) {\n uint256 result = mulDiv(x, y, denominator);\n if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {\n result += 1;\n }\n return result;\n }\n\n /**\n * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.\n *\n * Inspired by Henry S. Warren, Jr.'s \"Hacker's Delight\" (Chapter 11).\n */\n function sqrt(uint256 a) internal pure returns (uint256) {\n if (a == 0) {\n return 0;\n }\n\n // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.\n //\n // We know that the \"msb\" (most significant bit) of our target number `a` is a power of 2 such that we have\n // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.\n //\n // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`\n // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`\n // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`\n //\n // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.\n uint256 result = 1 << (log2(a) >> 1);\n\n // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,\n // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at\n // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision\n // into the expected uint128 result.\n unchecked {\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n return min(result, a / result);\n }\n }\n\n /**\n * @notice Calculates sqrt(a), following the selected rounding direction.\n */\n function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = sqrt(a);\n return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);\n }\n }\n\n /**\n * @dev Return the log in base 2, rounded down, of a positive value.\n * Returns 0 if given 0.\n */\n function log2(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >> 128 > 0) {\n value >>= 128;\n result += 128;\n }\n if (value >> 64 > 0) {\n value >>= 64;\n result += 64;\n }\n if (value >> 32 > 0) {\n value >>= 32;\n result += 32;\n }\n if (value >> 16 > 0) {\n value >>= 16;\n result += 16;\n }\n if (value >> 8 > 0) {\n value >>= 8;\n result += 8;\n }\n if (value >> 4 > 0) {\n value >>= 4;\n result += 4;\n }\n if (value >> 2 > 0) {\n value >>= 2;\n result += 2;\n }\n if (value >> 1 > 0) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 2, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log2(value);\n return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);\n }\n }\n\n /**\n * @dev Return the log in base 10, rounded down, of a positive value.\n * Returns 0 if given 0.\n */\n function log10(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >= 10**64) {\n value /= 10**64;\n result += 64;\n }\n if (value >= 10**32) {\n value /= 10**32;\n result += 32;\n }\n if (value >= 10**16) {\n value /= 10**16;\n result += 16;\n }\n if (value >= 10**8) {\n value /= 10**8;\n result += 8;\n }\n if (value >= 10**4) {\n value /= 10**4;\n result += 4;\n }\n if (value >= 10**2) {\n value /= 10**2;\n result += 2;\n }\n if (value >= 10**1) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log10(value);\n return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);\n }\n }\n\n /**\n * @dev Return the log in base 256, rounded down, of a positive value.\n * Returns 0 if given 0.\n *\n * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.\n */\n function log256(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >> 128 > 0) {\n value >>= 128;\n result += 16;\n }\n if (value >> 64 > 0) {\n value >>= 64;\n result += 8;\n }\n if (value >> 32 > 0) {\n value >>= 32;\n result += 4;\n }\n if (value >> 16 > 0) {\n value >>= 16;\n result += 2;\n }\n if (value >> 8 > 0) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log256(value);\n return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);\n }\n }\n}\n\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\n\n/**\n * @dev Interface for the optional metadata functions from the ERC20 standard.\n *\n * _Available since v4.1._\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)\n\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\n *\n * _Available since v4.8._\n */\n function verifyCallResultFromTarget(\n address target,\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n if (success) {\n if (returndata.length == 0) {\n // only check isContract if the call was successful and the return data is empty\n // otherwise we already know that it was a contract\n require(isContract(target), \"Address: call to non-contract\");\n }\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n /**\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason or using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n /// @solidity memory-safe-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n}\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n using Address for address;\n\n function safeTransfer(\n IERC20 token,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n function safeTransferFrom(\n IERC20 token,\n address from,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n require(\n (value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n function safeIncreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n uint256 newAllowance = token.allowance(address(this), spender) + value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n\n function safeDecreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n unchecked {\n uint256 oldAllowance = token.allowance(address(this), spender);\n require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\n uint256 newAllowance = oldAllowance - value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n }\n\n function safePermit(\n IERC20Permit token,\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal {\n uint256 nonceBefore = token.nonces(owner);\n token.permit(owner, spender, value, deadline, v, r, s);\n uint256 nonceAfter = token.nonces(owner);\n require(nonceAfter == nonceBefore + 1, \"SafeERC20: permit did not succeed\");\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n if (returndata.length > 0) {\n // Return data is optional\n require(abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n }\n}\n\nlibrary Fixed256x18 {\n uint256 internal constant ONE = 1e18; // 18 decimal places\n\n function mulDown(uint256 a, uint256 b) internal pure returns (uint256) {\n return (a * b) / ONE;\n }\n\n function mulUp(uint256 a, uint256 b) internal pure returns (uint256) {\n uint256 product = a * b;\n\n if (product == 0) {\n return 0;\n } else {\n return ((product - 1) / ONE) + 1;\n }\n }\n\n function divDown(uint256 a, uint256 b) internal pure returns (uint256) {\n return (a * ONE) / b;\n }\n\n function divUp(uint256 a, uint256 b) internal pure returns (uint256) {\n if (a == 0) {\n return 0;\n } else {\n return (((a * ONE) - 1) / b) + 1;\n }\n }\n\n function complement(uint256 x) internal pure returns (uint256) {\n return (x < ONE) ? (ONE - x) : 0;\n }\n}\n\nlibrary MathUtils {\n // --- Constants ---\n\n /// @notice Represents 100%.\n /// @dev 1e18 is the scaling factor (100% == 1e18).\n uint256 public constant _100_PERCENT = Fixed256x18.ONE;\n\n /// @notice Precision for Nominal ICR (independent of price).\n /// @dev Rationale for the value:\n /// - Making it “too high” could lead to overflows.\n /// - Making it “too low” could lead to an ICR equal to zero, due to truncation from floor division.\n ///\n /// This value of 1e20 is chosen for safety: the NICR will only overflow for numerator > ~1e39 collateralToken,\n /// and will only truncate to 0 if the denominator is at least 1e20 times greater than the numerator.\n uint256 internal constant _NICR_PRECISION = 1e20;\n\n /// @notice Number of minutes in 1000 years.\n uint256 internal constant _MINUTES_IN_1000_YEARS = 1000 * 365 days / 1 minutes;\n\n // --- Functions ---\n\n /// @notice Multiplies two decimal numbers and use normal rounding rules:\n /// - round product up if 19'th mantissa digit >= 5\n /// - round product down if 19'th mantissa digit < 5.\n /// @param x First number.\n /// @param y Second number.\n function _decMul(uint256 x, uint256 y) internal pure returns (uint256 decProd) {\n decProd = (x * y + Fixed256x18.ONE / 2) / Fixed256x18.ONE;\n }\n\n /// @notice Exponentiation function for 18-digit decimal base, and integer exponent n.\n ///\n /// @dev Uses the efficient \"exponentiation by squaring\" algorithm. O(log(n)) complexity. The exponent is capped to\n /// avoid reverting due to overflow.\n ///\n /// If a period of > 1000 years is ever used as an exponent in either of the above functions, the result will be\n /// negligibly different from just passing the cap, since the decayed base rate will be 0 for 1000 years or > 1000\n /// years.\n /// @param base The decimal base.\n /// @param exponent The exponent.\n /// @return The result of the exponentiation.\n function _decPow(uint256 base, uint256 exponent) internal pure returns (uint256) {\n if (exponent == 0) {\n return Fixed256x18.ONE;\n }\n\n uint256 y = Fixed256x18.ONE;\n uint256 x = base;\n uint256 n = Math.min(exponent, _MINUTES_IN_1000_YEARS); // cap to avoid overflow\n\n // Exponentiation-by-squaring\n while (n > 1) {\n if (n % 2 != 0) {\n y = _decMul(x, y);\n }\n x = _decMul(x, x);\n n /= 2;\n }\n\n return _decMul(x, y);\n }\n\n /// @notice Computes the Nominal Individual Collateral Ratio (NICR) for given collateral and debt. If debt is zero,\n /// it returns the maximal value for uint256 (represents \"infinite\" CR).\n /// @param collateral Collateral amount.\n /// @param debt Debt amount.\n /// @return NICR.\n function _computeNominalCR(uint256 collateral, uint256 debt) internal pure returns (uint256) {\n return debt > 0 ? collateral * _NICR_PRECISION / debt : type(uint256).max;\n }\n\n /// @notice Computes the Collateral Ratio for given collateral, debt and price. If debt is zero, it returns the\n /// maximal value for uint256 (represents \"infinite\" CR).\n /// @param collateral Collateral amount.\n /// @param debt Debt amount.\n /// @param price Collateral price.\n /// @return Collateral ratio.\n function _computeCR(uint256 collateral, uint256 debt, uint256 price) internal pure returns (uint256) {\n return debt > 0 ? collateral * price / debt : type(uint256).max;\n }\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol)\n\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n * For a generic mechanism see {ERC20PresetMinterPauser}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC20\n * applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn't required by the specification.\n *\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\n * functions have been added to mitigate the well-known issues around setting\n * allowances. See {IERC20-approve}.\n */\ncontract ERC20 is Context, IERC20, IERC20Metadata {\n mapping(address => uint256) private _balances;\n\n mapping(address => mapping(address => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * The default value of {decimals} is 18. To select a different value for\n * {decimals} you should overload it.\n *\n * All two of these values are immutable: they can only be set once during\n * construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual override returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual override returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the value {ERC20} uses, unless this function is\n * overridden;\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual override returns (uint8) {\n return 18;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual override returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual override returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address to, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20}.\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `amount`.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) public virtual override returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, amount);\n _transfer(from, to, amount);\n return true;\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, allowance(owner, spender) + addedValue);\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\n address owner = _msgSender();\n uint256 currentAllowance = allowance(owner, spender);\n require(currentAllowance >= subtractedValue, \"ERC20: decreased allowance below zero\");\n unchecked {\n _approve(owner, spender, currentAllowance - subtractedValue);\n }\n\n return true;\n }\n\n /**\n * @dev Moves `amount` of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n */\n function _transfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {\n require(from != address(0), \"ERC20: transfer from the zero address\");\n require(to != address(0), \"ERC20: transfer to the zero address\");\n\n _beforeTokenTransfer(from, to, amount);\n\n uint256 fromBalance = _balances[from];\n require(fromBalance >= amount, \"ERC20: transfer amount exceeds balance\");\n unchecked {\n _balances[from] = fromBalance - amount;\n // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by\n // decrementing then incrementing.\n _balances[to] += amount;\n }\n\n emit Transfer(from, to, amount);\n\n _afterTokenTransfer(from, to, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: mint to the zero address\");\n\n _beforeTokenTransfer(address(0), account, amount);\n\n _totalSupply += amount;\n unchecked {\n // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.\n _balances[account] += amount;\n }\n emit Transfer(address(0), account, amount);\n\n _afterTokenTransfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: burn from the zero address\");\n\n _beforeTokenTransfer(account, address(0), amount);\n\n uint256 accountBalance = _balances[account];\n require(accountBalance >= amount, \"ERC20: burn amount exceeds balance\");\n unchecked {\n _balances[account] = accountBalance - amount;\n // Overflow not possible: amount <= accountBalance <= totalSupply.\n _totalSupply -= amount;\n }\n\n emit Transfer(account, address(0), amount);\n\n _afterTokenTransfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(\n address owner,\n address spender,\n uint256 amount\n ) internal virtual {\n require(owner != address(0), \"ERC20: approve from the zero address\");\n require(spender != address(0), \"ERC20: approve to the zero address\");\n\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Updates `owner` s allowance for `spender` based on spent `amount`.\n *\n * Does not update the allowance amount in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Might emit an {Approval} event.\n */\n function _spendAllowance(\n address owner,\n address spender,\n uint256 amount\n ) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance != type(uint256).max) {\n require(currentAllowance >= amount, \"ERC20: insufficient allowance\");\n unchecked {\n _approve(owner, spender, currentAllowance - amount);\n }\n }\n }\n\n /**\n * @dev Hook that is called before any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * will be transferred to `to`.\n * - when `from` is zero, `amount` tokens will be minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {}\n\n /**\n * @dev Hook that is called after any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * has been transferred to `to`.\n * - when `from` is zero, `amount` tokens have been minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens have been burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _afterTokenTransfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {}\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (access/Ownable2Step.sol)\n\n// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor() {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions anymore. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby removing any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n\n/**\n * @dev Contract module which provides access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership} and {acceptOwnership}.\n *\n * This module is used through inheritance. It will make available all functions\n * from parent (Ownable).\n */\nabstract contract Ownable2Step is Ownable {\n address private _pendingOwner;\n\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Returns the address of the pending owner.\n */\n function pendingOwner() public view virtual returns (address) {\n return _pendingOwner;\n }\n\n /**\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual override onlyOwner {\n _pendingOwner = newOwner;\n emit OwnershipTransferStarted(owner(), newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual override {\n delete _pendingOwner;\n super._transferOwnership(newOwner);\n }\n\n /**\n * @dev The new owner accepts the ownership transfer.\n */\n function acceptOwnership() external {\n address sender = _msgSender();\n require(pendingOwner() == sender, \"Ownable2Step: caller is not the new owner\");\n _transferOwnership(sender);\n }\n}\n\n/**\n * @dev Extension of {ERC20} that adds a cap to the supply of tokens.\n */\nabstract contract ERC20Capped is ERC20, Ownable2Step {\n uint256 public cap;\n\n /**\n * @dev Total supply cap has been exceeded.\n */\n error ERC20ExceededCap();\n\n /**\n * @dev The supplied cap is not a valid cap.\n */\n error ERC20InvalidCap(uint256 cap);\n\n constructor(uint256 cap_) {\n setCap(cap_);\n }\n\n /**\n * @dev Sets the value of the `cap`.\n */\n function setCap(uint256 cap_) public onlyOwner {\n if (cap_ == 0) {\n revert ERC20InvalidCap(0);\n }\n cap = cap_;\n }\n\n /**\n * @dev See {ERC20-_mint}.\n */\n function _mint(address account, uint256 amount) internal virtual override {\n if (totalSupply() + amount > cap) {\n revert ERC20ExceededCap();\n }\n super._mint(account, amount);\n }\n}\n\nabstract contract PositionManagerDependent is IPositionManagerDependent {\n // --- Immutable variables ---\n\n address public immutable override positionManager;\n\n // --- Modifiers ---\n\n modifier onlyPositionManager() {\n if (msg.sender != positionManager) {\n revert CallerIsNotPositionManager(msg.sender);\n }\n _;\n }\n\n // --- Constructor ---\n\n constructor(address positionManager_) {\n if (positionManager_ == address(0)) {\n revert PositionManagerCannotBeZero();\n }\n positionManager = positionManager_;\n }\n}\n\ncontract ERC20Indexable is IERC20Indexable, ERC20Capped, PositionManagerDependent {\n // --- Types ---\n\n using Fixed256x18 for uint256;\n\n // --- Constants ---\n\n uint256 public constant override INDEX_PRECISION = Fixed256x18.ONE;\n\n // --- Variables ---\n\n uint256 internal storedIndex;\n\n // --- Constructor ---\n\n constructor(\n address positionManager_,\n string memory name_,\n string memory symbol_,\n uint256 cap_\n )\n ERC20(name_, symbol_)\n ERC20Capped(cap_)\n PositionManagerDependent(positionManager_)\n {\n storedIndex = INDEX_PRECISION;\n emit ERC20IndexableDeployed(positionManager_);\n }\n\n // --- Functions ---\n\n function mint(address to, uint256 amount) public virtual override onlyPositionManager {\n _mint(to, amount.divUp(storedIndex));\n }\n\n function burn(address from, uint256 amount) public virtual override onlyPositionManager {\n _burn(from, amount == type(uint256).max ? ERC20.balanceOf(from) : amount.divUp(storedIndex));\n }\n\n function setIndex(uint256 backingAmount) external override onlyPositionManager {\n uint256 supply = ERC20.totalSupply();\n uint256 newIndex = (backingAmount == 0 && supply == 0) ? INDEX_PRECISION : backingAmount.divUp(supply);\n storedIndex = newIndex;\n emit IndexUpdated(newIndex);\n }\n\n function currentIndex() public view virtual override returns (uint256) {\n return storedIndex;\n }\n\n function totalSupply() public view virtual override(IERC20, ERC20) returns (uint256) {\n return ERC20.totalSupply().mulDown(currentIndex());\n }\n\n function balanceOf(address account) public view virtual override(IERC20, ERC20) returns (uint256) {\n return ERC20.balanceOf(account).mulDown(currentIndex());\n }\n\n function transfer(address, uint256) public virtual override(IERC20, ERC20) returns (bool) {\n revert NotSupported();\n }\n\n function allowance(address, address) public view virtual override(IERC20, ERC20) returns (uint256) {\n revert NotSupported();\n }\n\n function approve(address, uint256) public virtual override(IERC20, ERC20) returns (bool) {\n revert NotSupported();\n }\n\n function transferFrom(address, address, uint256) public virtual override(IERC20, ERC20) returns (bool) {\n revert NotSupported();\n }\n\n function increaseAllowance(address, uint256) public virtual override returns (bool) {\n revert NotSupported();\n }\n\n function decreaseAllowance(address, uint256) public virtual override returns (bool) {\n revert NotSupported();\n }\n}\n\nabstract contract FeeCollector is Ownable2Step, IFeeCollector {\n // --- Variables ---\n\n address public override feeRecipient;\n\n // --- Constructor ---\n\n /// @param feeRecipient_ Address of the fee recipient to initialize contract with.\n constructor(address feeRecipient_) {\n if (feeRecipient_ == address(0)) {\n revert InvalidFeeRecipient();\n }\n\n feeRecipient = feeRecipient_;\n }\n\n // --- Functions ---\n\n function setFeeRecipient(address newFeeRecipient) external onlyOwner {\n if (newFeeRecipient == address(0)) {\n revert InvalidFeeRecipient();\n }\n\n feeRecipient = newFeeRecipient;\n emit FeeRecipientChanged(newFeeRecipient);\n }\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol)\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuard {\n // Booleans are more expensive than uint256 or any type that takes up a full\n // word because each write operation emits an extra SLOAD to first read the\n // slot's contents, replace the bits taken up by the boolean, and then write\n // back. This is the compiler's defense against contract upgrades and\n // pointer aliasing, and it cannot be disabled.\n\n // The values being non-zero value makes deployment a bit more expensive,\n // but in exchange the refund on every call to nonReentrant will be lower in\n // amount. Since refunds are capped to a percentage of the total\n // transaction's gas, it is best to keep them low in cases like this one, to\n // increase the likelihood of the full refund coming into effect.\n uint256 private constant _NOT_ENTERED = 1;\n uint256 private constant _ENTERED = 2;\n\n uint256 private _status;\n\n constructor() {\n _status = _NOT_ENTERED;\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and making it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n _nonReentrantBefore();\n _;\n _nonReentrantAfter();\n }\n\n function _nonReentrantBefore() private {\n // On the first call to nonReentrant, _status will be _NOT_ENTERED\n require(_status != _ENTERED, \"ReentrancyGuard: reentrant call\");\n\n // Any calls to nonReentrant after this point will fail\n _status = _ENTERED;\n }\n\n function _nonReentrantAfter() private {\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n _status = _NOT_ENTERED;\n }\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/draft-ERC20Permit.sol)\n\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/ECDSA.sol)\n\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n bytes16 private constant _SYMBOLS = \"0123456789abcdef\";\n uint8 private constant _ADDRESS_LENGTH = 20;\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n unchecked {\n uint256 length = Math.log10(value) + 1;\n string memory buffer = new string(length);\n uint256 ptr;\n /// @solidity memory-safe-assembly\n assembly {\n ptr := add(buffer, add(32, length))\n }\n while (true) {\n ptr--;\n /// @solidity memory-safe-assembly\n assembly {\n mstore8(ptr, byte(mod(value, 10), _SYMBOLS))\n }\n value /= 10;\n if (value == 0) break;\n }\n return buffer;\n }\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n unchecked {\n return toHexString(value, Math.log256(value) + 1);\n }\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\n */\n function toHexString(address addr) internal pure returns (string memory) {\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\n }\n}\n\n/**\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\n *\n * These functions can be used to verify that a message was signed by the holder\n * of the private keys of a given address.\n */\nlibrary ECDSA {\n enum RecoverError {\n NoError,\n InvalidSignature,\n InvalidSignatureLength,\n InvalidSignatureS,\n InvalidSignatureV // Deprecated in v4.8\n }\n\n function _throwError(RecoverError error) private pure {\n if (error == RecoverError.NoError) {\n return; // no error: do nothing\n } else if (error == RecoverError.InvalidSignature) {\n revert(\"ECDSA: invalid signature\");\n } else if (error == RecoverError.InvalidSignatureLength) {\n revert(\"ECDSA: invalid signature length\");\n } else if (error == RecoverError.InvalidSignatureS) {\n revert(\"ECDSA: invalid signature 's' value\");\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature` or error string. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n *\n * Documentation for signature generation:\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\n *\n * _Available since v4.3._\n */\n function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {\n if (signature.length == 65) {\n bytes32 r;\n bytes32 s;\n uint8 v;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n /// @solidity memory-safe-assembly\n assembly {\n r := mload(add(signature, 0x20))\n s := mload(add(signature, 0x40))\n v := byte(0, mload(add(signature, 0x60)))\n }\n return tryRecover(hash, v, r, s);\n } else {\n return (address(0), RecoverError.InvalidSignatureLength);\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature`. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n */\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, signature);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\n *\n * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address, RecoverError) {\n bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);\n uint8 v = uint8((uint256(vs) >> 255) + 27);\n return tryRecover(hash, v, r, s);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\n *\n * _Available since v4.2._\n */\n function recover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, r, vs);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\n * `r` and `s` signature fields separately.\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address, RecoverError) {\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\n // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\n //\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\n // these malleable signatures as well.\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\n return (address(0), RecoverError.InvalidSignatureS);\n }\n\n // If the signature is valid (and not malleable), return the signer address\n address signer = ecrecover(hash, v, r, s);\n if (signer == address(0)) {\n return (address(0), RecoverError.InvalidSignature);\n }\n\n return (signer, RecoverError.NoError);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function recover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, v, r, s);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\n // 32 is the length in bytes of hash,\n // enforced by the type signature above\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", hash));\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from `s`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n\", Strings.toString(s.length), s));\n }\n\n /**\n * @dev Returns an Ethereum Signed Typed Data, created from a\n * `domainSeparator` and a `structHash`. This produces hash corresponding\n * to the one signed with the\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]\n * JSON-RPC method as part of EIP-712.\n *\n * See {recover}.\n */\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19\\x01\", domainSeparator, structHash));\n }\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/EIP712.sol)\n\n/**\n * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.\n *\n * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,\n * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding\n * they need in their contracts using a combination of `abi.encode` and `keccak256`.\n *\n * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding\n * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA\n * ({_hashTypedDataV4}).\n *\n * The implementation of the domain separator was designed to be as efficient as possible while still properly updating\n * the chain id to protect against replay attacks on an eventual fork of the chain.\n *\n * NOTE: This contract implements the version of the encoding known as \"v4\", as implemented by the JSON RPC method\n * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].\n *\n * _Available since v3.4._\n */\nabstract contract EIP712 {\n /* solhint-disable var-name-mixedcase */\n // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to\n // invalidate the cached domain separator if the chain id changes.\n bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;\n uint256 private immutable _CACHED_CHAIN_ID;\n address private immutable _CACHED_THIS;\n\n bytes32 private immutable _HASHED_NAME;\n bytes32 private immutable _HASHED_VERSION;\n bytes32 private immutable _TYPE_HASH;\n\n /* solhint-enable var-name-mixedcase */\n\n /**\n * @dev Initializes the domain separator and parameter caches.\n *\n * The meaning of `name` and `version` is specified in\n * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:\n *\n * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.\n * - `version`: the current major version of the signing domain.\n *\n * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart\n * contract upgrade].\n */\n constructor(string memory name, string memory version) {\n bytes32 hashedName = keccak256(bytes(name));\n bytes32 hashedVersion = keccak256(bytes(version));\n bytes32 typeHash = keccak256(\n \"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\"\n );\n _HASHED_NAME = hashedName;\n _HASHED_VERSION = hashedVersion;\n _CACHED_CHAIN_ID = block.chainid;\n _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);\n _CACHED_THIS = address(this);\n _TYPE_HASH = typeHash;\n }\n\n /**\n * @dev Returns the domain separator for the current chain.\n */\n function _domainSeparatorV4() internal view returns (bytes32) {\n if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) {\n return _CACHED_DOMAIN_SEPARATOR;\n } else {\n return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);\n }\n }\n\n function _buildDomainSeparator(\n bytes32 typeHash,\n bytes32 nameHash,\n bytes32 versionHash\n ) private view returns (bytes32) {\n return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this)));\n }\n\n /**\n * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this\n * function returns the hash of the fully encoded EIP712 message for this domain.\n *\n * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:\n *\n * ```solidity\n * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(\n * keccak256(\"Mail(address to,string contents)\"),\n * mailTo,\n * keccak256(bytes(mailContents))\n * )));\n * address signer = ECDSA.recover(digest, signature);\n * ```\n */\n function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {\n return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);\n }\n}\n\n// OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)\n\n/**\n * @title Counters\n * @author Matt Condon (@shrugs)\n * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number\n * of elements in a mapping, issuing ERC721 ids, or counting request ids.\n *\n * Include with `using Counters for Counters.Counter;`\n */\nlibrary Counters {\n struct Counter {\n // This variable should never be directly accessed by users of the library: interactions must be restricted to\n // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add\n // this feature: see https://github.com/ethereum/solidity/issues/4637\n uint256 _value; // default: 0\n }\n\n function current(Counter storage counter) internal view returns (uint256) {\n return counter._value;\n }\n\n function increment(Counter storage counter) internal {\n unchecked {\n counter._value += 1;\n }\n }\n\n function decrement(Counter storage counter) internal {\n uint256 value = counter._value;\n require(value > 0, \"Counter: decrement overflow\");\n unchecked {\n counter._value = value - 1;\n }\n }\n\n function reset(Counter storage counter) internal {\n counter._value = 0;\n }\n}\n\n/**\n * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n *\n * _Available since v3.4._\n */\nabstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {\n using Counters for Counters.Counter;\n\n mapping(address => Counters.Counter) private _nonces;\n\n // solhint-disable-next-line var-name-mixedcase\n bytes32 private constant _PERMIT_TYPEHASH =\n keccak256(\"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\");\n /**\n * @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`.\n * However, to ensure consistency with the upgradeable transpiler, we will continue\n * to reserve a slot.\n * @custom:oz-renamed-from _PERMIT_TYPEHASH\n */\n // solhint-disable-next-line var-name-mixedcase\n bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;\n\n /**\n * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `\"1\"`.\n *\n * It's a good idea to use the same `name` that is defined as the ERC20 token name.\n */\n constructor(string memory name) EIP712(name, \"1\") {}\n\n /**\n * @dev See {IERC20Permit-permit}.\n */\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) public virtual override {\n require(block.timestamp <= deadline, \"ERC20Permit: expired deadline\");\n\n bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));\n\n bytes32 hash = _hashTypedDataV4(structHash);\n\n address signer = ECDSA.recover(hash, v, r, s);\n require(signer == owner, \"ERC20Permit: invalid signature\");\n\n _approve(owner, spender, value);\n }\n\n /**\n * @dev See {IERC20Permit-nonces}.\n */\n function nonces(address owner) public view virtual override returns (uint256) {\n return _nonces[owner].current();\n }\n\n /**\n * @dev See {IERC20Permit-DOMAIN_SEPARATOR}.\n */\n // solhint-disable-next-line func-name-mixedcase\n function DOMAIN_SEPARATOR() external view override returns (bytes32) {\n return _domainSeparatorV4();\n }\n\n /**\n * @dev \"Consume a nonce\": return the current value and increment.\n *\n * _Available since v4.1._\n */\n function _useNonce(address owner) internal virtual returns (uint256 current) {\n Counters.Counter storage nonce = _nonces[owner];\n current = nonce.current();\n nonce.increment();\n }\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/ERC20FlashMint.sol)\n\n/**\n * @dev Implementation of the ERC3156 Flash loans extension, as defined in\n * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].\n *\n * Adds the {flashLoan} method, which provides flash loan support at the token\n * level. By default there is no fee, but this can be changed by overriding {flashFee}.\n *\n * _Available since v4.1._\n */\nabstract contract ERC20FlashMint is ERC20, IERC3156FlashLender {\n bytes32 private constant _RETURN_VALUE = keccak256(\"ERC3156FlashBorrower.onFlashLoan\");\n\n /**\n * @dev Returns the maximum amount of tokens available for loan.\n * @param token The address of the token that is requested.\n * @return The amount of token that can be loaned.\n */\n function maxFlashLoan(address token) public view virtual override returns (uint256) {\n return token == address(this) ? type(uint256).max - ERC20.totalSupply() : 0;\n }\n\n /**\n * @dev Returns the fee applied when doing flash loans. This function calls\n * the {_flashFee} function which returns the fee applied when doing flash\n * loans.\n * @param token The token to be flash loaned.\n * @param amount The amount of tokens to be loaned.\n * @return The fees applied to the corresponding flash loan.\n */\n function flashFee(address token, uint256 amount) public view virtual override returns (uint256) {\n require(token == address(this), \"ERC20FlashMint: wrong token\");\n return _flashFee(token, amount);\n }\n\n /**\n * @dev Returns the fee applied when doing flash loans. By default this\n * implementation has 0 fees. This function can be overloaded to make\n * the flash loan mechanism deflationary.\n * @param token The token to be flash loaned.\n * @param amount The amount of tokens to be loaned.\n * @return The fees applied to the corresponding flash loan.\n */\n function _flashFee(address token, uint256 amount) internal view virtual returns (uint256) {\n // silence warning about unused variable without the addition of bytecode.\n token;\n amount;\n return 0;\n }\n\n /**\n * @dev Returns the receiver address of the flash fee. By default this\n * implementation returns the address(0) which means the fee amount will be burnt.\n * This function can be overloaded to change the fee receiver.\n * @return The address for which the flash fee will be sent to.\n */\n function _flashFeeReceiver() internal view virtual returns (address) {\n return address(0);\n }\n\n /**\n * @dev Performs a flash loan. New tokens are minted and sent to the\n * `receiver`, who is required to implement the {IERC3156FlashBorrower}\n * interface. By the end of the flash loan, the receiver is expected to own\n * amount + fee tokens and have them approved back to the token contract itself so\n * they can be burned.\n * @param receiver The receiver of the flash loan. Should implement the\n * {IERC3156FlashBorrower-onFlashLoan} interface.\n * @param token The token to be flash loaned. Only `address(this)` is\n * supported.\n * @param amount The amount of tokens to be loaned.\n * @param data An arbitrary datafield that is passed to the receiver.\n * @return `true` if the flash loan was successful.\n */\n // This function can reenter, but it doesn't pose a risk because it always preserves the property that the amount\n // minted at the beginning is always recovered and burned at the end, or else the entire function will revert.\n // slither-disable-next-line reentrancy-no-eth\n function flashLoan(\n IERC3156FlashBorrower receiver,\n address token,\n uint256 amount,\n bytes calldata data\n ) public virtual override returns (bool) {\n require(amount <= maxFlashLoan(token), \"ERC20FlashMint: amount exceeds maxFlashLoan\");\n uint256 fee = flashFee(token, amount);\n _mint(address(receiver), amount);\n require(\n receiver.onFlashLoan(msg.sender, token, amount, fee, data) == _RETURN_VALUE,\n \"ERC20FlashMint: invalid return value\"\n );\n address flashFeeReceiver = _flashFeeReceiver();\n _spendAllowance(address(receiver), address(this), amount + fee);\n if (fee == 0 || flashFeeReceiver == address(0)) {\n _burn(address(receiver), amount + fee);\n } else {\n _burn(address(receiver), amount);\n _transfer(address(receiver), flashFeeReceiver, fee);\n }\n return true;\n }\n}\n\ncontract RToken is ReentrancyGuard, ERC20Permit, ERC20FlashMint, PositionManagerDependent, FeeCollector, IRToken {\n // --- Constants ---\n\n uint256 public constant override PERCENTAGE_BASE = 10_000;\n uint256 public constant override MAX_FLASH_MINT_FEE_PERCENTAGE = 500;\n\n // --- Variables ---\n\n uint256 public override flashMintFeePercentage;\n\n // --- Constructor ---\n\n /// @dev Deploys new R token. Sets flash mint fee percentage to 0. Transfers ownership to @param feeRecipient_.\n /// @param positionManager_ Address of the PositionManager contract that is authorized to mint and burn new tokens.\n /// @param feeRecipient_ Address of flash mint fee recipient.\n constructor(\n address positionManager_,\n address feeRecipient_\n )\n ERC20Permit(\"R Stablecoin\")\n ERC20(\"R Stablecoin\", \"R\")\n PositionManagerDependent(positionManager_)\n FeeCollector(feeRecipient_)\n {\n setFlashMintFeePercentage(PERCENTAGE_BASE / 200); // 0.5%\n\n transferOwnership(feeRecipient_);\n\n emit RDeployed(positionManager_, feeRecipient_);\n }\n\n // --- Functions ---\n\n function mint(address to, uint256 amount) external override onlyPositionManager {\n _mint(to, amount);\n }\n\n function burn(address from, uint256 amount) external override onlyPositionManager {\n _burn(from, amount);\n }\n\n function setFlashMintFeePercentage(uint256 feePercentage) public override onlyOwner {\n if (feePercentage > MAX_FLASH_MINT_FEE_PERCENTAGE) {\n revert FlashFeePercentageTooBig(feePercentage);\n }\n\n flashMintFeePercentage = feePercentage;\n emit FlashMintFeePercentageChanged(flashMintFeePercentage);\n }\n\n function flashLoan(\n IERC3156FlashBorrower receiver,\n address token,\n uint256 amount,\n bytes calldata data\n )\n public\n override(ERC20FlashMint, IERC3156FlashLender)\n nonReentrant\n returns (bool)\n {\n return super.flashLoan(receiver, token, amount, data);\n }\n\n /// @dev Inherited from ERC20FlashMint. Defines maximum size of the flash mint.\n /// @param token Token to be flash minted. Returns 0 amount in case of token != address(this).\n function maxFlashLoan(address token)\n public\n view\n virtual\n override(ERC20FlashMint, IERC3156FlashLender)\n returns (uint256)\n {\n return token == address(this) ? Math.min(totalSupply() / 10, ERC20FlashMint.maxFlashLoan(address(this))) : 0;\n }\n\n /// @dev Inherited from ERC20FlashMint. Defines flash mint fee for the flash mint of @param amount tokens.\n /// @param token Token to be flash minted. Returns 0 fee in case of token != address(this).\n /// @param amount Size of the flash mint.\n function _flashFee(address token, uint256 amount) internal view virtual override returns (uint256) {\n return token == address(this) ? amount * flashMintFeePercentage / PERCENTAGE_BASE : 0;\n }\n\n /// @dev Inherited from ERC20FlashMint. Defines flash mint fee receiver.\n /// @return Address that will receive flash mint fees.\n function _flashFeeReceiver() internal view virtual override returns (address) {\n return feeRecipient;\n }\n}\n\n/// @dev Implementation of Position Manager. Current implementation does not support rebasing tokens as collateral.\ncontract PositionManager is FeeCollector, IPositionManager {\n // --- Types ---\n\n using SafeERC20 for IERC20;\n using Fixed256x18 for uint256;\n\n // --- Constants ---\n\n uint256 public constant override MINUTE_DECAY_FACTOR = 999_037_758_833_783_000;\n\n uint256 public constant override MAX_BORROWING_SPREAD = MathUtils._100_PERCENT / 100; // 1%\n uint256 public constant override MAX_BORROWING_RATE = MathUtils._100_PERCENT / 100 * 5; // 5%\n\n uint256 public constant override BETA = 2;\n\n // --- Immutables ---\n\n IRToken public immutable override rToken;\n\n // --- Variables ---\n\n mapping(address position => IERC20 collateralToken) public override collateralTokenForPosition;\n\n mapping(address position => mapping(address delegate => bool isWhitelisted)) public override isDelegateWhitelisted;\n\n mapping(IERC20 collateralToken => CollateralTokenInfo collateralTokenInfo) public override collateralInfo;\n\n // --- Modifiers ---\n\n /// @dev Checks if the collateral token has been added to the position manager, or reverts otherwise.\n /// @param collateralToken The collateral token to check.\n modifier collateralTokenExists(IERC20 collateralToken) {\n if (address(collateralInfo[collateralToken].collateralToken) == address(0)) {\n revert CollateralTokenNotAdded();\n }\n _;\n }\n\n /// @dev Checks if the collateral token has enabled, or reverts otherwise. When the condition is false, the check\n /// is skipped.\n /// @param collateralToken The collateral token to check.\n /// @param condition If true, the check will be performed.\n modifier onlyEnabledCollateralTokenWhen(IERC20 collateralToken, bool condition) {\n if (condition && !collateralInfo[collateralToken].isEnabled) {\n revert CollateralTokenDisabled();\n }\n _;\n }\n\n /// @dev Checks if the borrower has a position with the collateral token or doesn't have a position at all, or\n /// reverts otherwise.\n /// @param position The borrower to check.\n /// @param collateralToken The collateral token to check.\n modifier onlyDepositedCollateralTokenOrNew(address position, IERC20 collateralToken) {\n if (\n collateralTokenForPosition[position] != IERC20(address(0))\n && collateralTokenForPosition[position] != collateralToken\n ) {\n revert PositionCollateralTokenMismatch();\n }\n _;\n }\n\n /// @dev Checks if the max fee percentage is between the borrowing spread and 100%, or reverts otherwise. When the\n /// condition is false, the check is skipped.\n /// @param maxFeePercentage The max fee percentage to check.\n /// @param condition If true, the check will be performed.\n modifier validMaxFeePercentageWhen(uint256 maxFeePercentage, bool condition) {\n if (condition && maxFeePercentage > MathUtils._100_PERCENT) {\n revert InvalidMaxFeePercentage();\n }\n _;\n }\n\n // --- Constructor ---\n\n /// @dev Initializes the position manager.\n constructor(address rToken_) FeeCollector(msg.sender) {\n rToken = rToken_ == address(0) ? new RToken(address(this), msg.sender) : IRToken(rToken_);\n emit PositionManagerDeployed(rToken, msg.sender);\n }\n\n // --- External functions ---\n\n function managePosition(\n IERC20 collateralToken,\n address position,\n uint256 collateralChange,\n bool isCollateralIncrease,\n uint256 debtChange,\n bool isDebtIncrease,\n uint256 maxFeePercentage,\n ERC20PermitSignature calldata permitSignature\n )\n public\n virtual\n override\n collateralTokenExists(collateralToken)\n validMaxFeePercentageWhen(maxFeePercentage, isDebtIncrease)\n onlyDepositedCollateralTokenOrNew(position, collateralToken)\n onlyEnabledCollateralTokenWhen(collateralToken, isDebtIncrease && debtChange > 0)\n returns (uint256 actualCollateralChange, uint256 actualDebtChange)\n {\n if (position != msg.sender && !isDelegateWhitelisted[position][msg.sender]) {\n revert DelegateNotWhitelisted();\n }\n if (collateralChange == 0 && debtChange == 0) {\n revert NoCollateralOrDebtChange();\n }\n if (address(permitSignature.token) == address(collateralToken)) {\n PermitHelper.applyPermit(permitSignature, msg.sender, address(this));\n }\n\n CollateralTokenInfo storage collateralTokenInfo = collateralInfo[collateralToken];\n IERC20Indexable raftCollateralToken = collateralTokenInfo.collateralToken;\n IERC20Indexable raftDebtToken = collateralTokenInfo.debtToken;\n\n uint256 debtBefore = raftDebtToken.balanceOf(position);\n if (!isDebtIncrease && (debtChange == type(uint256).max || (debtBefore != 0 && debtChange == debtBefore))) {\n if (collateralChange != 0 || isCollateralIncrease) {\n revert WrongCollateralParamsForFullRepayment();\n }\n collateralChange = raftCollateralToken.balanceOf(position);\n debtChange = debtBefore;\n }\n\n _adjustDebt(position, collateralToken, raftDebtToken, debtChange, isDebtIncrease, maxFeePercentage);\n _adjustCollateral(collateralToken, raftCollateralToken, position, collateralChange, isCollateralIncrease);\n\n uint256 positionDebt = raftDebtToken.balanceOf(position);\n uint256 positionCollateral = raftCollateralToken.balanceOf(position);\n\n if (positionDebt == 0) {\n if (positionCollateral != 0) {\n revert InvalidPosition();\n }\n // position was closed, remove it\n _closePosition(raftCollateralToken, raftDebtToken, position, false);\n } else {\n _checkValidPosition(collateralToken, positionDebt, positionCollateral);\n\n if (debtBefore == 0) {\n collateralTokenForPosition[position] = collateralToken;\n emit PositionCreated(position, collateralToken);\n }\n }\n return (collateralChange, debtChange);\n }\n\n function liquidate(address position) external override {\n IERC20 collateralToken = collateralTokenForPosition[position];\n CollateralTokenInfo storage collateralTokenInfo = collateralInfo[collateralToken];\n IERC20Indexable raftCollateralToken = collateralTokenInfo.collateralToken;\n IERC20Indexable raftDebtToken = collateralTokenInfo.debtToken;\n ISplitLiquidationCollateral splitLiquidation = collateralTokenInfo.splitLiquidation;\n\n if (address(collateralToken) == address(0)) {\n revert NothingToLiquidate();\n }\n (uint256 price,) = collateralTokenInfo.priceFeed.fetchPrice();\n uint256 entireCollateral = raftCollateralToken.balanceOf(position);\n uint256 entireDebt = raftDebtToken.balanceOf(position);\n uint256 icr = MathUtils._computeCR(entireCollateral, entireDebt, price);\n\n if (icr >= splitLiquidation.MCR()) {\n revert NothingToLiquidate();\n }\n\n uint256 totalDebt = raftDebtToken.totalSupply();\n if (entireDebt == totalDebt) {\n revert CannotLiquidateLastPosition();\n }\n bool isRedistribution = icr <= MathUtils._100_PERCENT;\n\n // prettier: ignore\n (uint256 collateralLiquidationFee, uint256 collateralToSendToLiquidator) =\n splitLiquidation.split(entireCollateral, entireDebt, price, isRedistribution);\n\n if (!isRedistribution) {\n _burnRTokens(msg.sender, entireDebt);\n totalDebt -= entireDebt;\n\n // Collateral is sent to protocol as a fee only in case of liquidation\n collateralToken.safeTransfer(feeRecipient, collateralLiquidationFee);\n }\n\n collateralToken.safeTransfer(msg.sender, collateralToSendToLiquidator);\n\n _closePosition(raftCollateralToken, raftDebtToken, position, true);\n\n _updateDebtAndCollateralIndex(collateralToken, raftCollateralToken, raftDebtToken, totalDebt);\n\n emit Liquidation(\n msg.sender,\n position,\n collateralToken,\n entireDebt,\n entireCollateral,\n collateralToSendToLiquidator,\n collateralLiquidationFee,\n isRedistribution\n );\n }\n\n function redeemCollateral(\n IERC20 collateralToken,\n uint256 debtAmount,\n uint256 maxFeePercentage\n )\n public\n virtual\n override\n {\n if (maxFeePercentage > MathUtils._100_PERCENT) {\n revert MaxFeePercentageOutOfRange();\n }\n if (debtAmount == 0) {\n revert AmountIsZero();\n }\n IERC20Indexable raftDebtToken = collateralInfo[collateralToken].debtToken;\n\n uint256 newTotalDebt = raftDebtToken.totalSupply() - debtAmount;\n uint256 lowTotalDebt = collateralInfo[collateralToken].splitLiquidation.LOW_TOTAL_DEBT();\n if (newTotalDebt < lowTotalDebt) {\n revert TotalDebtCannotBeLowerThanMinDebt(collateralToken, newTotalDebt);\n }\n\n (uint256 price, uint256 deviation) = collateralInfo[collateralToken].priceFeed.fetchPrice();\n uint256 collateralToRedeem = debtAmount.divDown(price);\n uint256 totalCollateral = collateralToken.balanceOf(address(this));\n if (\n totalCollateral - collateralToRedeem == 0\n || totalCollateral - collateralToRedeem < lowTotalDebt.divDown(price)\n ) {\n revert TotalCollateralCannotBeLowerThanMinCollateral(\n collateralToken, totalCollateral - collateralToRedeem, lowTotalDebt.divDown(price)\n );\n }\n\n // Decay the baseRate due to time passed, and then increase it according to the size of this redemption.\n // Use the saved total R supply value, from before it was reduced by the redemption.\n _updateBaseRateFromRedemption(collateralToken, collateralToRedeem, price, rToken.totalSupply());\n\n // Calculate the redemption fee\n uint256 redemptionFee = getRedemptionFee(collateralToken, collateralToRedeem, deviation);\n uint256 rebate = redemptionFee.mulDown(collateralInfo[collateralToken].redemptionRebate);\n\n _checkValidFee(redemptionFee, collateralToRedeem, maxFeePercentage);\n\n // Send the redemption fee to the recipient\n collateralToken.safeTransfer(feeRecipient, redemptionFee - rebate);\n\n // Burn the total R that is cancelled with debt, and send the redeemed collateral to msg.sender\n _burnRTokens(msg.sender, debtAmount);\n\n // Send collateral to account\n collateralToken.safeTransfer(msg.sender, collateralToRedeem - redemptionFee);\n\n _updateDebtAndCollateralIndex(\n collateralToken, collateralInfo[collateralToken].collateralToken, raftDebtToken, newTotalDebt\n );\n\n emit Redemption(msg.sender, debtAmount, collateralToRedeem, redemptionFee, rebate);\n }\n\n function whitelistDelegate(address delegate, bool whitelisted) external override {\n if (delegate == address(0)) {\n revert InvalidDelegateAddress();\n }\n isDelegateWhitelisted[msg.sender][delegate] = whitelisted;\n\n emit DelegateWhitelisted(msg.sender, delegate, whitelisted);\n }\n\n function setBorrowingSpread(IERC20 collateralToken, uint256 newBorrowingSpread) external override onlyOwner {\n if (newBorrowingSpread > MAX_BORROWING_SPREAD) {\n revert BorrowingSpreadExceedsMaximum();\n }\n collateralInfo[collateralToken].borrowingSpread = newBorrowingSpread;\n emit BorrowingSpreadUpdated(newBorrowingSpread);\n }\n\n function setRedemptionRebate(IERC20 collateralToken, uint256 newRedemptionRebate) public override onlyOwner {\n if (newRedemptionRebate > MathUtils._100_PERCENT) {\n revert RedemptionRebateExceedsMaximum();\n }\n collateralInfo[collateralToken].redemptionRebate = newRedemptionRebate;\n emit RedemptionRebateUpdated(newRedemptionRebate);\n }\n\n function getRedemptionFeeWithDecay(\n IERC20 collateralToken,\n uint256 collateralAmount\n )\n external\n view\n override\n returns (uint256 redemptionFee)\n {\n redemptionFee = getRedemptionRateWithDecay(collateralToken).mulDown(collateralAmount);\n if (redemptionFee >= collateralAmount) {\n revert FeeEatsUpAllReturnedCollateral();\n }\n }\n\n // --- Public functions ---\n\n function addCollateralToken(\n IERC20 collateralToken,\n IPriceFeed priceFeed,\n ISplitLiquidationCollateral newSplitLiquidationCollateral\n )\n public\n virtual\n override\n {\n addCollateralToken(\n collateralToken,\n priceFeed,\n newSplitLiquidationCollateral,\n new ERC20Indexable(\n address(this),\n string(bytes.concat(\"Raft \", bytes(IERC20Metadata(address(collateralToken)).name()), \" collateral\")),\n string(bytes.concat(\"r\", bytes(IERC20Metadata(address(collateralToken)).symbol()), \"-c\")),\n type(uint256).max\n ),\n new ERC20Indexable(\n address(this),\n string(bytes.concat(\"Raft \", bytes(IERC20Metadata(address(collateralToken)).name()), \" debt\")),\n string(bytes.concat(\"r\", bytes(IERC20Metadata(address(collateralToken)).symbol()), \"-d\")),\n type(uint256).max\n )\n );\n }\n\n function addCollateralToken(\n IERC20 collateralToken,\n IPriceFeed priceFeed,\n ISplitLiquidationCollateral newSplitLiquidationCollateral,\n IERC20Indexable raftCollateralToken_,\n IERC20Indexable raftDebtToken_\n )\n public\n override\n onlyOwner\n {\n if (address(collateralToken) == address(0)) {\n revert CollateralTokenAddressCannotBeZero();\n }\n if (address(priceFeed) == address(0)) {\n revert PriceFeedAddressCannotBeZero();\n }\n if (address(collateralInfo[collateralToken].collateralToken) != address(0)) {\n revert CollateralTokenAlreadyAdded();\n }\n\n CollateralTokenInfo memory raftCollateralTokenInfo;\n raftCollateralTokenInfo.collateralToken = raftCollateralToken_;\n raftCollateralTokenInfo.debtToken = raftDebtToken_;\n raftCollateralTokenInfo.isEnabled = true;\n raftCollateralTokenInfo.priceFeed = priceFeed;\n\n collateralInfo[collateralToken] = raftCollateralTokenInfo;\n\n setRedemptionSpread(collateralToken, MathUtils._100_PERCENT);\n setRedemptionRebate(collateralToken, MathUtils._100_PERCENT);\n\n setSplitLiquidationCollateral(collateralToken, newSplitLiquidationCollateral);\n\n emit CollateralTokenAdded(\n collateralToken, raftCollateralTokenInfo.collateralToken, raftCollateralTokenInfo.debtToken, priceFeed\n );\n }\n\n function setCollateralEnabled(\n IERC20 collateralToken,\n bool isEnabled\n )\n public\n override\n onlyOwner\n collateralTokenExists(collateralToken)\n {\n bool previousIsEnabled = collateralInfo[collateralToken].isEnabled;\n collateralInfo[collateralToken].isEnabled = isEnabled;\n\n if (previousIsEnabled != isEnabled) {\n emit CollateralTokenModified(collateralToken, isEnabled);\n }\n }\n\n function setSplitLiquidationCollateral(\n IERC20 collateralToken,\n ISplitLiquidationCollateral newSplitLiquidationCollateral\n )\n public\n override\n onlyOwner\n {\n if (address(newSplitLiquidationCollateral) == address(0)) {\n revert SplitLiquidationCollateralCannotBeZero();\n }\n collateralInfo[collateralToken].splitLiquidation = newSplitLiquidationCollateral;\n emit SplitLiquidationCollateralChanged(collateralToken, newSplitLiquidationCollateral);\n }\n\n function setRedemptionSpread(IERC20 collateralToken, uint256 newRedemptionSpread) public override onlyOwner {\n if (newRedemptionSpread > MathUtils._100_PERCENT) {\n revert RedemptionSpreadOutOfRange();\n }\n collateralInfo[collateralToken].redemptionSpread = newRedemptionSpread;\n emit RedemptionSpreadUpdated(collateralToken, newRedemptionSpread);\n }\n\n function getRedemptionRateWithDecay(IERC20 collateralToken) public view override returns (uint256) {\n return _calcRedemptionRate(collateralToken, _calcDecayedBaseRate(collateralToken));\n }\n\n function raftCollateralToken(IERC20 collateralToken) external view override returns (IERC20Indexable) {\n return collateralInfo[collateralToken].collateralToken;\n }\n\n function raftDebtToken(IERC20 collateralToken) external view override returns (IERC20Indexable) {\n return collateralInfo[collateralToken].debtToken;\n }\n\n function priceFeed(IERC20 collateralToken) external view override returns (IPriceFeed) {\n return collateralInfo[collateralToken].priceFeed;\n }\n\n function splitLiquidationCollateral(IERC20 collateralToken) external view returns (ISplitLiquidationCollateral) {\n return collateralInfo[collateralToken].splitLiquidation;\n }\n\n function collateralEnabled(IERC20 collateralToken) external view override returns (bool) {\n return collateralInfo[collateralToken].isEnabled;\n }\n\n function lastFeeOperationTime(IERC20 collateralToken) external view override returns (uint256) {\n return collateralInfo[collateralToken].lastFeeOperationTime;\n }\n\n function borrowingSpread(IERC20 collateralToken) external view override returns (uint256) {\n return collateralInfo[collateralToken].borrowingSpread;\n }\n\n function baseRate(IERC20 collateralToken) external view override returns (uint256) {\n return collateralInfo[collateralToken].baseRate;\n }\n\n function redemptionSpread(IERC20 collateralToken) external view override returns (uint256) {\n return collateralInfo[collateralToken].redemptionSpread;\n }\n\n function redemptionRebate(IERC20 collateralToken) external view override returns (uint256) {\n return collateralInfo[collateralToken].redemptionRebate;\n }\n\n function getRedemptionRate(IERC20 collateralToken) public view override returns (uint256) {\n return _calcRedemptionRate(collateralToken, collateralInfo[collateralToken].baseRate);\n }\n\n function getRedemptionFee(\n IERC20 collateralToken,\n uint256 collateralAmount,\n uint256 priceDeviation\n )\n public\n view\n override\n returns (uint256)\n {\n return Math.min(getRedemptionRate(collateralToken) + priceDeviation, MathUtils._100_PERCENT).mulDown(\n collateralAmount\n );\n }\n\n function getBorrowingRate(IERC20 collateralToken) public view override returns (uint256) {\n return _calcBorrowingRate(collateralToken, collateralInfo[collateralToken].baseRate);\n }\n\n function getBorrowingRateWithDecay(IERC20 collateralToken) public view override returns (uint256) {\n return _calcBorrowingRate(collateralToken, _calcDecayedBaseRate(collateralToken));\n }\n\n function getBorrowingFee(IERC20 collateralToken, uint256 debtAmount) public view override returns (uint256) {\n return getBorrowingRate(collateralToken).mulDown(debtAmount);\n }\n\n // --- Helper functions ---\n\n /// @dev Adjusts the debt of a given borrower by burning or minting the corresponding amount of R and the Raft\n /// debt token. If the debt is being increased, the borrowing fee is also triggered.\n /// @param position The address of the borrower.\n /// @param debtChange The amount of R to add or remove. Must be positive.\n /// @param isDebtIncrease True if the debt is being increased, false otherwise.\n /// @param maxFeePercentage The maximum fee percentage.\n function _adjustDebt(\n address position,\n IERC20 collateralToken,\n IERC20Indexable raftDebtToken,\n uint256 debtChange,\n bool isDebtIncrease,\n uint256 maxFeePercentage\n )\n internal\n {\n if (debtChange == 0) {\n return;\n }\n\n if (isDebtIncrease) {\n uint256 totalDebtChange =\n debtChange + _triggerBorrowingFee(collateralToken, position, debtChange, maxFeePercentage);\n raftDebtToken.mint(position, totalDebtChange);\n _mintRTokens(msg.sender, debtChange);\n } else {\n raftDebtToken.burn(position, debtChange);\n _burnRTokens(msg.sender, debtChange);\n }\n\n emit DebtChanged(position, collateralToken, debtChange, isDebtIncrease);\n }\n\n /// @dev Mints R tokens\n function _mintRTokens(address to, uint256 amount) internal virtual {\n rToken.mint(to, amount);\n }\n\n /// @dev Burns R tokens\n function _burnRTokens(address from, uint256 amount) internal virtual {\n rToken.burn(from, amount);\n }\n\n /// @dev Adjusts the collateral of a given borrower by burning or minting the corresponding amount of Raft\n /// collateral token and transferring the corresponding amount of collateral token.\n /// @param collateralToken The token the borrower used as collateral.\n /// @param position The address of the borrower.\n /// @param collateralChange The amount of collateral to add or remove. Must be positive.\n /// @param isCollateralIncrease True if the collateral is being increased, false otherwise.\n function _adjustCollateral(\n IERC20 collateralToken,\n IERC20Indexable raftCollateralToken,\n address position,\n uint256 collateralChange,\n bool isCollateralIncrease\n )\n internal\n {\n if (collateralChange == 0) {\n return;\n }\n\n if (isCollateralIncrease) {\n raftCollateralToken.mint(position, collateralChange);\n collateralToken.safeTransferFrom(msg.sender, address(this), collateralChange);\n } else {\n raftCollateralToken.burn(position, collateralChange);\n collateralToken.safeTransfer(msg.sender, collateralChange);\n }\n\n emit CollateralChanged(position, collateralToken, collateralChange, isCollateralIncrease);\n }\n\n /// @dev Updates debt and collateral indexes for a given collateral token.\n /// @param collateralToken The collateral token for which to update the indexes.\n /// @param raftCollateralToken The raft collateral indexable token.\n /// @param raftDebtToken The raft debt indexable token.\n /// @param totalDebtForCollateral Totam amount of debt backed by collateral token.\n function _updateDebtAndCollateralIndex(\n IERC20 collateralToken,\n IERC20Indexable raftCollateralToken,\n IERC20Indexable raftDebtToken,\n uint256 totalDebtForCollateral\n )\n internal\n {\n raftDebtToken.setIndex(totalDebtForCollateral);\n raftCollateralToken.setIndex(collateralToken.balanceOf(address(this)));\n }\n\n function _closePosition(\n IERC20Indexable raftCollateralToken,\n IERC20Indexable raftDebtToken,\n address position,\n bool burnTokens\n )\n internal\n {\n collateralTokenForPosition[position] = IERC20(address(0));\n\n if (burnTokens) {\n raftDebtToken.burn(position, type(uint256).max);\n raftCollateralToken.burn(position, type(uint256).max);\n }\n emit PositionClosed(position);\n }\n\n // --- Borrowing & redemption fee helper functions ---\n\n /// @dev Updates the base rate from a redemption operation. Impacts on the base rate:\n /// 1. decays the base rate based on time passed since last redemption or R borrowing operation,\n /// 2. increases the base rate based on the amount redeemed, as a proportion of total supply.\n function _updateBaseRateFromRedemption(\n IERC20 collateralToken,\n uint256 collateralDrawn,\n uint256 price,\n uint256 totalDebtSupply\n )\n internal\n returns (uint256)\n {\n uint256 decayedBaseRate = _calcDecayedBaseRate(collateralToken);\n\n /* Convert the drawn collateral back to R at face value rate (1 R:1 USD), in order to get\n * the fraction of total supply that was redeemed at face value. */\n uint256 redeemedFraction = collateralDrawn * price / totalDebtSupply;\n\n uint256 newBaseRate = decayedBaseRate + redeemedFraction / BETA;\n newBaseRate = Math.min(newBaseRate, MathUtils._100_PERCENT); // cap baseRate at a maximum of 100%\n assert(newBaseRate > 0); // Base rate is always non-zero after redemption\n\n // Update the baseRate state variable\n collateralInfo[collateralToken].baseRate = newBaseRate;\n emit BaseRateUpdated(collateralToken, newBaseRate);\n\n _updateLastFeeOpTime(collateralToken);\n\n return newBaseRate;\n }\n\n function _calcRedemptionRate(IERC20 collateralToken, uint256 baseRate_) internal view returns (uint256) {\n return baseRate_ + collateralInfo[collateralToken].redemptionSpread;\n }\n\n function _calcBorrowingRate(IERC20 collateralToken, uint256 baseRate_) internal view returns (uint256) {\n return Math.min(collateralInfo[collateralToken].borrowingSpread + baseRate_, MAX_BORROWING_RATE);\n }\n\n /// @dev Updates the base rate based on time elapsed since the last redemption or R borrowing operation.\n function _decayBaseRateFromBorrowing(IERC20 collateralToken) internal {\n uint256 decayedBaseRate = _calcDecayedBaseRate(collateralToken);\n assert(decayedBaseRate <= MathUtils._100_PERCENT); // The baseRate can decay to 0\n\n collateralInfo[collateralToken].baseRate = decayedBaseRate;\n emit BaseRateUpdated(collateralToken, decayedBaseRate);\n\n _updateLastFeeOpTime(collateralToken);\n }\n\n /// @dev Update the last fee operation time only if time passed >= decay interval. This prevents base rate\n /// griefing.\n function _updateLastFeeOpTime(IERC20 collateralToken) internal {\n uint256 timePassed = block.timestamp - collateralInfo[collateralToken].lastFeeOperationTime;\n\n if (timePassed >= 1 minutes) {\n collateralInfo[collateralToken].lastFeeOperationTime = block.timestamp;\n emit LastFeeOpTimeUpdated(collateralToken, block.timestamp);\n }\n }\n\n function _calcDecayedBaseRate(IERC20 collateralToken) internal view returns (uint256) {\n uint256 minutesPassed = (block.timestamp - collateralInfo[collateralToken].lastFeeOperationTime) / 1 minutes;\n uint256 decayFactor = MathUtils._decPow(MINUTE_DECAY_FACTOR, minutesPassed);\n\n return collateralInfo[collateralToken].baseRate.mulDown(decayFactor);\n }\n\n function _triggerBorrowingFee(\n IERC20 collateralToken,\n address position,\n uint256 debtAmount,\n uint256 maxFeePercentage\n )\n internal\n virtual\n returns (uint256 borrowingFee)\n {\n _decayBaseRateFromBorrowing(collateralToken); // decay the baseRate state variable\n borrowingFee = getBorrowingFee(collateralToken, debtAmount);\n\n _checkValidFee(borrowingFee, debtAmount, maxFeePercentage);\n\n if (borrowingFee > 0) {\n _mintRTokens(feeRecipient, borrowingFee);\n emit RBorrowingFeePaid(collateralToken, position, borrowingFee);\n }\n }\n\n // --- Validation check helper functions ---\n\n function _checkValidPosition(IERC20 collateralToken, uint256 positionDebt, uint256 positionCollateral) internal {\n ISplitLiquidationCollateral splitCollateral = collateralInfo[collateralToken].splitLiquidation;\n if (positionDebt < splitCollateral.LOW_TOTAL_DEBT()) {\n revert NetDebtBelowMinimum(positionDebt);\n }\n\n (uint256 price,) = collateralInfo[collateralToken].priceFeed.fetchPrice();\n uint256 newICR = MathUtils._computeCR(positionCollateral, positionDebt, price);\n if (newICR < splitCollateral.MCR()) {\n revert NewICRLowerThanMCR(newICR);\n }\n }\n\n function _checkValidFee(uint256 fee, uint256 amount, uint256 maxFeePercentage) internal pure {\n uint256 feePercentage = fee.divDown(amount);\n\n if (feePercentage > maxFeePercentage) {\n revert FeeExceedsMaxFee(fee, amount, maxFeePercentage);\n }\n }\n}\n\ninterface IRMinter {\n /// @dev Emitted when tokens are recovered from the contract.\n /// @param token The address of the token being recovered.\n /// @param to The address receiving the recovered tokens.\n /// @param amount The amount of tokens recovered.\n event TokensRecovered(IERC20 token, address to, uint256 amount);\n\n /// @return Address of the R token.\n function r() external view returns (IRToken);\n\n /// @return Address of the Position manager contract responsible for minting R.\n function positionManager() external view returns (IPositionManager);\n\n /// @dev Recover accidentally sent tokens to the contract\n /// @param token Address of the token contract.\n /// @param to Address of the receiver of the tokens.\n /// @param amount Number of tokens to recover.\n function recoverTokens(IERC20 token, address to, uint256 amount) external;\n}\n\ninterface ILock {\n /// @dev Thrown when contract usage is locked.\n error ContractLocked();\n\n /// @dev Unauthorized call to lock/unlock.\n error Unauthorized();\n\n /// @dev Retrieves if contract is currently locked or not.\n function locked() external view returns (bool);\n\n /// @dev Retrieves address of the locker who can unlock contract.\n function locker() external view returns (address);\n\n /// @dev Unlcoks the usage of the contract.\n function unlock() external;\n\n /// @dev Locks the usage of the contract.\n function lock() external;\n}\n\nabstract contract ERC20RMinter is IRMinter, ERC20, Ownable2Step {\n using SafeERC20 for IERC20;\n\n IRToken public immutable override r;\n IPositionManager public immutable override positionManager;\n\n constructor(IRToken rToken_, string memory name_, string memory symbol_) ERC20(name_, symbol_) {\n r = rToken_;\n positionManager = IPositionManager(rToken_.positionManager());\n\n _approve(address(this), address(positionManager), type(uint256).max);\n }\n\n modifier unlockCall() {\n ILock lockContract = ILock(address(positionManager.priceFeed(IERC20(this))));\n lockContract.unlock();\n _;\n lockContract.lock();\n }\n\n function recoverTokens(IERC20 token, address to, uint256 amount) external override onlyOwner {\n token.safeTransfer(to, amount);\n emit TokensRecovered(token, to, amount);\n }\n\n function _mintR(address to, uint256 amount) internal unlockCall {\n _mint(address(this), amount);\n ERC20PermitSignature memory emptySignature;\n positionManager.managePosition(\n IERC20(address(this)),\n address(this),\n amount,\n true, // collateral increase\n amount,\n true, // debt increase\n 1e18, // 100%\n emptySignature\n );\n r.transfer(to, amount);\n }\n\n function _burnR(address from, uint256 amount) internal unlockCall {\n r.transferFrom(from, address(this), amount);\n ERC20PermitSignature memory emptySignature;\n positionManager.managePosition(\n IERC20(address(this)),\n address(this),\n amount,\n false, // collateral decrease\n amount,\n false, // debt decrease\n 1e18, // 100%\n emptySignature\n );\n _burn(address(this), amount);\n }\n}\n\ncontract InterestRateDebtToken is ERC20Indexable {\n // --- Types ---\n\n using Fixed256x18 for uint256;\n\n // --- Events ---\n\n event IndexIncreasePerSecondSet(uint256 indexIncreasePerSecond);\n\n // --- Immutables ---\n\n IERC20 immutable collateralToken;\n\n // --- Variables ---\n\n uint256 internal storedIndexUpdatedAt;\n\n uint256 public indexIncreasePerSecond;\n\n // --- Constructor ---\n\n constructor(\n address positionManager_,\n string memory name_,\n string memory symbol_,\n IERC20 collateralToken_,\n uint256 cap_,\n uint256 indexIncreasePerSecond_\n )\n ERC20Indexable(positionManager_, name_, symbol_, cap_)\n {\n storedIndexUpdatedAt = block.timestamp;\n collateralToken = collateralToken_;\n setIndexIncreasePerSecond(indexIncreasePerSecond_);\n }\n\n // --- Functions ---\n\n function mint(address to, uint256 amount) public virtual override {\n updateIndexAndPayFees();\n super.mint(to, amount);\n }\n\n function burn(address from, uint256 amount) public virtual override {\n updateIndexAndPayFees();\n super.burn(from, amount);\n }\n\n function currentIndex() public view virtual override returns (uint256) {\n return storedIndex.mulUp(INDEX_PRECISION + indexIncreasePerSecond * (block.timestamp - storedIndexUpdatedAt));\n }\n\n function updateIndexAndPayFees() public {\n uint256 currentIndex_ = currentIndex();\n _payFees(currentIndex_);\n storedIndexUpdatedAt = block.timestamp;\n storedIndex = currentIndex_;\n emit IndexUpdated(currentIndex_);\n }\n\n function setIndexIncreasePerSecond(uint256 indexIncreasePerSecond_) public onlyOwner {\n updateIndexAndPayFees();\n indexIncreasePerSecond = indexIncreasePerSecond_;\n emit IndexIncreasePerSecondSet(indexIncreasePerSecond_);\n }\n\n function unpaidFees() external view returns (uint256) {\n return _unpaidFees(currentIndex());\n }\n\n function _unpaidFees(uint256 currentIndex_) private view returns (uint256) {\n return totalSupply().mulDown(currentIndex_ - storedIndex);\n }\n\n function _payFees(uint256 currentIndex_) private {\n uint256 unpaidFees = _unpaidFees(currentIndex_);\n if (unpaidFees > 0) {\n IInterestRatePositionManager(positionManager).mintFees(collateralToken, unpaidFees);\n }\n }\n}\n\n/// @dev Implementation of Position Manager. Current implementation does not support rebasing tokens as collateral.\ncontract InterestRatePositionManager is ERC20RMinter, PositionManager, IInterestRatePositionManager {\n // --- Errors ---\n\n error Unsupported();\n\n // --- Constructor ---\n\n /// @dev Initializes the position manager.\n constructor(IRToken rToken_)\n PositionManager(address(rToken_))\n ERC20RMinter(rToken_, \"Interest Rate Posman\", \"IRPM\")\n { }\n\n // --- External functions ---\n\n function managePosition(\n IERC20 collateralToken,\n address position,\n uint256 collateralChange,\n bool isCollateralIncrease,\n uint256 debtChange,\n bool isDebtIncrease,\n uint256 maxFeePercentage,\n ERC20PermitSignature calldata permitSignature\n )\n public\n virtual\n override(IPositionManager, PositionManager)\n returns (uint256 actualCollateralChange, uint256 actualDebtChange)\n {\n if (address(permitSignature.token) == address(r)) {\n PermitHelper.applyPermit(permitSignature, msg.sender, address(this));\n }\n return super.managePosition(\n collateralToken,\n position,\n collateralChange,\n isCollateralIncrease,\n debtChange,\n isDebtIncrease,\n maxFeePercentage,\n permitSignature\n );\n }\n\n function mintFees(IERC20 collateralToken, uint256 amount) external {\n if (msg.sender != address(collateralInfo[collateralToken].debtToken)) {\n revert InvalidDebtToken(msg.sender);\n }\n _mintR(feeRecipient, amount);\n\n emit MintedFees(collateralToken, amount);\n }\n\n function redeemCollateral(IERC20, uint256, uint256) public virtual override(IPositionManager, PositionManager) {\n revert Unsupported();\n }\n\n function addCollateralToken(\n IERC20,\n IPriceFeed,\n ISplitLiquidationCollateral\n )\n public\n override(IPositionManager, PositionManager)\n {\n revert Unsupported();\n }\n\n // --- Helper functions ---\n\n function _mintRTokens(address to, uint256 amount) internal virtual override {\n _mintR(to, amount);\n }\n\n function _burnRTokens(address from, uint256 amount) internal virtual override {\n _burnR(from, amount);\n }\n\n function _triggerBorrowingFee(\n IERC20,\n address,\n uint256,\n uint256\n )\n internal\n pure\n virtual\n override\n returns (uint256)\n {\n return 0;\n }\n}\n"}},"settings":{"remappings":["@balancer-labs/=node_modules/@balancer-labs/","@balancer-labs/v2-interfaces/contracts/=lib/balancer-v2-monorepo/pkg/interfaces/contracts/","@chainlink/=node_modules/@chainlink/","@eth-optimism/=node_modules/@eth-optimism/","@openzeppelin/=node_modules/@openzeppelin/","@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","@redstone-finance/=node_modules/@redstone-finance/","@smartcontractkit/chainlink/=lib/chainlink/contracts/src/v0.8/","@tempusfinance/=node_modules/@tempusfinance/","@tempusfinance/tempus-utils/contracts/=lib/tempus-utils/contracts/","balancer-v2-monorepo/=lib/balancer-v2-monorepo/","chainlink/=lib/chainlink/","ds-test/=lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/chainlink/contracts/foundry-lib/openzeppelin-contracts/lib/erc4626-tests/","eth-gas-reporter/=node_modules/eth-gas-reporter/","forge-std/=lib/forge-std/src/","hardhat/=node_modules/hardhat/","openzeppelin-contracts/=lib/openzeppelin-contracts/","tempus-utils/=lib/tempus-utils/contracts/"],"optimizer":{"enabled":true,"runs":200000},"metadata":{"bytecodeHash":"ipfs","appendCBOR":true},"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}},"evmVersion":"london","viaIR":true,"libraries":{}}},"ABI":"[{\"inputs\":[{\"internalType\":\"contract IRToken\",\"name\":\"rToken_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"AmountIsZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BorrowingSpreadExceedsMaximum\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CannotLiquidateLastPosition\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CollateralTokenAddressCannotBeZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CollateralTokenAlreadyAdded\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CollateralTokenDisabled\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CollateralTokenNotAdded\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"DelegateNotWhitelisted\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FeeEatsUpAllReturnedCollateral\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxFeePercentage\",\"type\":\"uint256\"}],\"name\":\"FeeExceedsMaxFee\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"InvalidDebtToken\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidDelegateAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidFeeRecipient\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidMaxFeePercentage\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidPosition\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MaxFeePercentageOutOfRange\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"netDebt\",\"type\":\"uint256\"}],\"name\":\"NetDebtBelowMinimum\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newICR\",\"type\":\"uint256\"}],\"name\":\"NewICRLowerThanMCR\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoCollateralOrDebtChange\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NothingToLiquidate\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PositionCollateralTokenMismatch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PriceFeedAddressCannotBeZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RedemptionRebateExceedsMaximum\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RedemptionSpreadOutOfRange\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SplitLiquidationCollateralCannotBeZero\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newTotalCollateral\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minimumCollateral\",\"type\":\"uint256\"}],\"name\":\"TotalCollateralCannotBeLowerThanMinCollateral\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newTotalDebt\",\"type\":\"uint256\"}],\"name\":\"TotalDebtCannotBeLowerThanMinDebt\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"Unsupported\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WrongCollateralParamsForFullRepayment\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"baseRate\",\"type\":\"uint256\"}],\"name\":\"BaseRateUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"borrowingSpread\",\"type\":\"uint256\"}],\"name\":\"BorrowingSpreadUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"collateralAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isCollateralIncrease\",\"type\":\"bool\"}],\"name\":\"CollateralChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"contract IERC20Indexable\",\"name\":\"raftCollateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"contract IERC20Indexable\",\"name\":\"raftDebtToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"contract IPriceFeed\",\"name\":\"priceFeed\",\"type\":\"address\"}],\"name\":\"CollateralTokenAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"}],\"name\":\"CollateralTokenModified\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"debtAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isDebtIncrease\",\"type\":\"bool\"}],\"name\":\"DebtChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"delegate\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"whitelisted\",\"type\":\"bool\"}],\"name\":\"DelegateWhitelisted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"feeRecipient\",\"type\":\"address\"}],\"name\":\"FeeRecipientChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"lastFeeOpTime\",\"type\":\"uint256\"}],\"name\":\"LastFeeOpTimeUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"liquidator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"debtLiquidated\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"collateralLiquidated\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"collateralSentToLiquidator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"collateralLiquidationFeePaid\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isRedistribution\",\"type\":\"bool\"}],\"name\":\"Liquidation\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"MintedFees\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"}],\"name\":\"PositionClosed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"PositionCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IRToken\",\"name\":\"rToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"feeRecipient\",\"type\":\"address\"}],\"name\":\"PositionManagerDeployed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"feeAmount\",\"type\":\"uint256\"}],\"name\":\"RBorrowingFeePaid\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"redeemer\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"collateralSent\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"rebate\",\"type\":\"uint256\"}],\"name\":\"Redemption\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"redemptionRebate\",\"type\":\"uint256\"}],\"name\":\"RedemptionRebateUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"redemptionSpread\",\"type\":\"uint256\"}],\"name\":\"RedemptionSpreadUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"newSplitLiquidationCollateral\",\"type\":\"address\"}],\"name\":\"SplitLiquidationCollateralChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"TokensRecovered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BETA\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_BORROWING_RATE\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_BORROWING_SPREAD\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MINUTE_DECAY_FACTOR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"contract IPriceFeed\",\"name\":\"priceFeed\",\"type\":\"address\"},{\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"newSplitLiquidationCollateral\",\"type\":\"address\"},{\"internalType\":\"contract IERC20Indexable\",\"name\":\"raftCollateralToken_\",\"type\":\"address\"},{\"internalType\":\"contract IERC20Indexable\",\"name\":\"raftDebtToken_\",\"type\":\"address\"}],\"name\":\"addCollateralToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"contract IPriceFeed\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"addCollateralToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"baseRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"borrowingSpread\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"collateralEnabled\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"collateralInfo\",\"outputs\":[{\"internalType\":\"contract IERC20Indexable\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"contract IERC20Indexable\",\"name\":\"debtToken\",\"type\":\"address\"},{\"internalType\":\"contract IPriceFeed\",\"name\":\"priceFeed\",\"type\":\"address\"},{\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"splitLiquidation\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"lastFeeOperationTime\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"borrowingSpread\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"baseRate\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"redemptionSpread\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"redemptionRebate\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"}],\"name\":\"collateralTokenForPosition\",\"outputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"subtractedValue\",\"type\":\"uint256\"}],\"name\":\"decreaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeRecipient\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"debtAmount\",\"type\":\"uint256\"}],\"name\":\"getBorrowingFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"getBorrowingRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"getBorrowingRateWithDecay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"collateralAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"priceDeviation\",\"type\":\"uint256\"}],\"name\":\"getRedemptionFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"collateralAmount\",\"type\":\"uint256\"}],\"name\":\"getRedemptionFeeWithDecay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"redemptionFee\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"getRedemptionRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"getRedemptionRateWithDecay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"addedValue\",\"type\":\"uint256\"}],\"name\":\"increaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"delegate\",\"type\":\"address\"}],\"name\":\"isDelegateWhitelisted\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isWhitelisted\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"lastFeeOperationTime\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"}],\"name\":\"liquidate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"collateralChange\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isCollateralIncrease\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"debtChange\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isDebtIncrease\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"maxFeePercentage\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"contract IERC20Permit\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"struct ERC20PermitSignature\",\"name\":\"permitSignature\",\"type\":\"tuple\"}],\"name\":\"managePosition\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"actualCollateralChange\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"actualDebtChange\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"mintFees\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"positionManager\",\"outputs\":[{\"internalType\":\"contract IPositionManager\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"priceFeed\",\"outputs\":[{\"internalType\":\"contract IPriceFeed\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"r\",\"outputs\":[{\"internalType\":\"contract IRToken\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"rToken\",\"outputs\":[{\"internalType\":\"contract IRToken\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"raftCollateralToken\",\"outputs\":[{\"internalType\":\"contract IERC20Indexable\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"raftDebtToken\",\"outputs\":[{\"internalType\":\"contract IERC20Indexable\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"recoverTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"redeemCollateral\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"redemptionRebate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"redemptionSpread\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newBorrowingSpread\",\"type\":\"uint256\"}],\"name\":\"setBorrowingSpread\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"}],\"name\":\"setCollateralEnabled\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newFeeRecipient\",\"type\":\"address\"}],\"name\":\"setFeeRecipient\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newRedemptionRebate\",\"type\":\"uint256\"}],\"name\":\"setRedemptionRebate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newRedemptionSpread\",\"type\":\"uint256\"}],\"name\":\"setRedemptionSpread\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"newSplitLiquidationCollateral\",\"type\":\"address\"}],\"name\":\"setSplitLiquidationCollateral\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"splitLiquidationCollateral\",\"outputs\":[{\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegate\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"whitelisted\",\"type\":\"bool\"}],\"name\":\"whitelistDelegate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]","ContractName":"InterestRatePositionManager","CompilerVersion":"v0.8.19+commit.7dd6d404","OptimizationUsed":1,"Runs":200000,"ConstructorArguments":"0x000000000000000000000000183015a9ba6ff60230fdeadc3f43b3d788b13e21","EVMVersion":"london","Library":"","LicenseType":"","Proxy":0,"SwarmSource":""}] \ No newline at end of file diff --git a/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/creation_data.json b/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/creation_data.json new file mode 100644 index 0000000000000..7896ef85659c8 --- /dev/null +++ b/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/creation_data.json @@ -0,0 +1,5 @@ +{ + "contractAddress": "0x9d27527ada2cf29fbdab2973cfa243845a08bd3f", + "contractCreator": "0x7773ae67403d2e30102a84c48cc939919c4c881c", + "txHash": "0xc757719b7ae11ea651b1b23249978a3e8fca94d358610f5809a5dc19fbf850ce" +} \ No newline at end of file diff --git a/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/metadata.json b/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/metadata.json new file mode 100644 index 0000000000000..514b2571c4ecf --- /dev/null +++ b/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/metadata.json @@ -0,0 +1,96 @@ +[ + { + "SourceCode": { + "language": "Solidity", + "sources": { + "@openzeppelin/contracts/utils/Address.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize, which returns 0 for contracts in\n // construction, since the code is only stored at the end of the\n // constructor execution.\n\n uint256 size;\n assembly {\n size := extcodesize(account)\n }\n return size > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n" + }, + "src/base/ERC721Checkpointable.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\n/// @title Vote checkpointing for an ERC-721 token\n\n/*********************************\n * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *\n * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *\n * ░░░░░░█████████░░█████████░░░ *\n * ░░░░░░██░░░████░░██░░░████░░░ *\n * ░░██████░░░████████░░░████░░░ *\n * ░░██░░██░░░████░░██░░░████░░░ *\n * ░░██░░██░░░████░░██░░░████░░░ *\n * ░░░░░░█████████░░█████████░░░ *\n * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *\n * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *\n *********************************/\n\n// LICENSE\n// ERC721Checkpointable.sol uses and modifies part of Compound Lab's Comp.sol:\n// https://github.com/compound-finance/compound-protocol/blob/ae4388e780a8d596d97619d9704a931a2752c2bc/contracts/Governance/Comp.sol\n//\n// Comp.sol source code Copyright 2020 Compound Labs, Inc. licensed under the BSD-3-Clause license.\n// With modifications by Nounders DAO.\n//\n// Additional conditions of BSD-3-Clause can be found here: https://opensource.org/licenses/BSD-3-Clause\n//\n// MODIFICATIONS\n// Checkpointing logic from Comp.sol has been used with the following modifications:\n// - `delegates` is renamed to `_delegates` and is set to private\n// - `delegates` is a public function that uses the `_delegates` mapping look-up, but unlike\n// Comp.sol, returns the delegator's own address if there is no delegate.\n// This avoids the delegator needing to \"delegate to self\" with an additional transaction\n// - `_transferTokens()` is renamed `_beforeTokenTransfer()` and adapted to hook into OpenZeppelin's ERC721 hooks.\n\npragma solidity 0.8.9;\n\nimport \"./ERC721BaseWithERC4494Permit.sol\";\n\nabstract contract ERC721Checkpointable is ERC721BaseWithERC4494Permit {\n bool internal _useCheckpoints = true; // can only be disabled and never re-enabled\n\n /// @notice Defines decimals as per ERC-20 convention to make integrations with 3rd party governance platforms easier\n uint8 public constant decimals = 0;\n\n /// @notice A record of each accounts delegate\n mapping(address => address) private _delegates;\n\n /// @notice A checkpoint for marking number of votes from a given block\n struct Checkpoint {\n uint32 fromBlock;\n uint96 votes;\n }\n\n /// @notice A record of votes checkpoints for each account, by index\n mapping(address => mapping(uint32 => Checkpoint)) public checkpoints;\n\n /// @notice The number of checkpoints for each account\n mapping(address => uint32) public numCheckpoints;\n\n /// @notice The EIP-712 typehash for the delegation struct used by the contract\n bytes32 public constant DELEGATION_TYPEHASH =\n keccak256(\"Delegation(address delegatee,uint256 nonce,uint256 expiry)\");\n\n /// @notice An event thats emitted when an account changes its delegate\n event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);\n\n /// @notice An event thats emitted when a delegate account's vote balance changes\n event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);\n\n /**\n * @notice The votes a delegator can delegate, which is the current balance of the delegator.\n * @dev Used when calling `_delegate()`\n */\n function votesToDelegate(address delegator) public view returns (uint96) {\n return safe96(balanceOf(delegator), \"ERC721Checkpointable::votesToDelegate: amount exceeds 96 bits\");\n }\n\n /**\n * @notice Overrides the standard `Comp.sol` delegates mapping to return\n * the delegator's own address if they haven't delegated.\n * This avoids having to delegate to oneself.\n */\n function delegates(address delegator) public view returns (address) {\n address current = _delegates[delegator];\n return current == address(0) ? delegator : current;\n }\n\n /**\n * @notice Adapted from `_transferTokens()` in `Comp.sol` to update delegate votes.\n * @dev hooks into ERC721Base's `ERC721._transfer`\n */\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 tokenId\n ) internal override {\n super._beforeTokenTransfer(from, to, tokenId);\n if (_useCheckpoints) {\n /// @notice Differs from `_transferTokens()` to use `delegates` override method to simulate auto-delegation\n _moveDelegates(delegates(from), delegates(to), 1);\n }\n }\n\n /**\n * @notice Delegate votes from `msg.sender` to `delegatee`\n * @param delegatee The address to delegate votes to\n */\n function delegate(address delegatee) public {\n if (delegatee == address(0)) delegatee = msg.sender;\n return _delegate(msg.sender, delegatee);\n }\n\n /**\n * @notice Delegates votes from signatory to `delegatee`\n * @param delegatee The address to delegate votes to\n * @param nonce The contract state required to match the signature\n * @param expiry The time at which to expire the signature\n * @param v The recovery byte of the signature\n * @param r Half of the ECDSA signature pair\n * @param s Half of the ECDSA signature pair\n */\n function delegateBySig(\n address delegatee,\n uint256 nonce,\n uint256 expiry,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) public {\n bytes32 digest = keccak256(\n abi.encodePacked(\n \"\\x19\\x01\",\n _DOMAIN_SEPARATOR(),\n keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry))\n )\n );\n // TODO support smart contract wallet via IERC721, require change in function signature to know which signer to call first\n address signatory = ecrecover(digest, v, r, s);\n require(signatory != address(0), \"ERC721Checkpointable::delegateBySig: invalid signature\");\n require(nonce == _userNonces[signatory]++, \"ERC721Checkpointable::delegateBySig: invalid nonce\");\n require(block.timestamp <= expiry, \"ERC721Checkpointable::delegateBySig: signature expired\");\n return _delegate(signatory, delegatee);\n }\n\n /**\n * @notice Gets the current votes balance for `account`\n * @param account The address to get votes balance\n * @return The number of current votes for `account`\n */\n function getCurrentVotes(address account) public view returns (uint96) {\n uint32 nCheckpoints = numCheckpoints[account];\n return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;\n }\n\n /**\n * @notice Gets the current votes balance for `account`\n * @param account The address to get votes balance\n * @return The number of current votes for `account`\n */\n function getVotes(address account) external view returns (uint96) {\n return getCurrentVotes(account);\n }\n\n /**\n * @notice Determine the prior number of votes for an account as of a block number\n * @dev Block number must be a finalized block or else this function will revert to prevent misinformation.\n * @param account The address of the account to check\n * @param blockNumber The block number to get the vote balance at\n * @return The number of votes the account had as of the given block\n */\n function getPriorVotes(address account, uint256 blockNumber) public view returns (uint96) {\n require(blockNumber < block.number, \"ERC721Checkpointable::getPriorVotes: not yet determined\");\n\n uint32 nCheckpoints = numCheckpoints[account];\n if (nCheckpoints == 0) {\n return 0;\n }\n\n // First check most recent balance\n if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {\n return checkpoints[account][nCheckpoints - 1].votes;\n }\n\n // Next check implicit zero balance\n if (checkpoints[account][0].fromBlock > blockNumber) {\n return 0;\n }\n\n uint32 lower = 0;\n uint32 upper = nCheckpoints - 1;\n while (upper > lower) {\n uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow\n Checkpoint memory cp = checkpoints[account][center];\n if (cp.fromBlock == blockNumber) {\n return cp.votes;\n } else if (cp.fromBlock < blockNumber) {\n lower = center;\n } else {\n upper = center - 1;\n }\n }\n return checkpoints[account][lower].votes;\n }\n\n /**\n * @notice Determine the prior number of votes for an account as of a block number\n * @dev Block number must be a finalized block or else this function will revert to prevent misinformation.\n * @param account The address of the account to check\n * @param blockNumber The block number to get the vote balance at\n * @return The number of votes the account had as of the given block\n */\n function getPastVotes(address account, uint256 blockNumber) external view returns (uint96) {\n return this.getPriorVotes(account, blockNumber);\n }\n\n function _delegate(address delegator, address delegatee) internal {\n /// @notice differs from `_delegate()` in `Comp.sol` to use `delegates` override method to simulate auto-delegation\n address currentDelegate = delegates(delegator);\n\n _delegates[delegator] = delegatee;\n\n emit DelegateChanged(delegator, currentDelegate, delegatee);\n\n uint96 amount = votesToDelegate(delegator);\n\n _moveDelegates(currentDelegate, delegatee, amount);\n }\n\n function _moveDelegates(\n address srcRep,\n address dstRep,\n uint96 amount\n ) internal {\n if (srcRep != dstRep && amount > 0) {\n if (srcRep != address(0)) {\n uint32 srcRepNum = numCheckpoints[srcRep];\n uint96 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;\n uint96 srcRepNew = sub96(srcRepOld, amount, \"ERC721Checkpointable::_moveDelegates: amount underflows\");\n _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);\n }\n\n if (dstRep != address(0)) {\n uint32 dstRepNum = numCheckpoints[dstRep];\n uint96 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;\n uint96 dstRepNew = add96(dstRepOld, amount, \"ERC721Checkpointable::_moveDelegates: amount overflows\");\n _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);\n }\n }\n }\n\n function _writeCheckpoint(\n address delegatee,\n uint32 nCheckpoints,\n uint96 oldVotes,\n uint96 newVotes\n ) internal {\n uint32 blockNumber = safe32(\n block.number,\n \"ERC721Checkpointable::_writeCheckpoint: block number exceeds 32 bits\"\n );\n\n if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) {\n checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;\n } else {\n checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes);\n numCheckpoints[delegatee] = nCheckpoints + 1;\n }\n\n emit DelegateVotesChanged(delegatee, oldVotes, newVotes);\n }\n\n function safe32(uint256 n, string memory errorMessage) internal pure returns (uint32) {\n require(n < 2**32, errorMessage);\n return uint32(n);\n }\n\n function safe96(uint256 n, string memory errorMessage) internal pure returns (uint96) {\n require(n < 2**96, errorMessage);\n return uint96(n);\n }\n\n function add96(\n uint96 a,\n uint96 b,\n string memory errorMessage\n ) internal pure returns (uint96) {\n uint96 c = a + b;\n require(c >= a, errorMessage);\n return c;\n }\n\n function sub96(\n uint96 a,\n uint96 b,\n string memory errorMessage\n ) internal pure returns (uint96) {\n require(b <= a, errorMessage);\n return a - b;\n }\n}\n" + }, + "src/base/ERC721Base.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"@openzeppelin/contracts/utils/Address.sol\";\nimport \"@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol\";\nimport \"@openzeppelin/contracts/token/ERC721/IERC721.sol\";\nimport \"@openzeppelin/contracts/interfaces/IERC165.sol\";\n\nabstract contract ERC721Base is IERC165, IERC721 {\n using Address for address;\n\n bytes4 internal constant ERC721_RECEIVED = 0x150b7a02;\n bytes4 internal constant ERC165ID = 0x01ffc9a7;\n\n uint256 internal constant OPERATOR_FLAG = 0x8000000000000000000000000000000000000000000000000000000000000000;\n uint256 internal constant NOT_OPERATOR_FLAG = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;\n\n mapping(uint256 => uint256) internal _owners;\n mapping(address => uint256) internal _balances;\n mapping(address => mapping(address => bool)) internal _operatorsForAll;\n mapping(uint256 => address) internal _operators;\n\n function name() public pure virtual returns (string memory) {\n revert(\"NOT_IMPLEMENTED\");\n }\n\n /// @notice Approve an operator to transfer a specific token on the senders behalf.\n /// @param operator The address receiving the approval.\n /// @param id The id of the token.\n function approve(address operator, uint256 id) external override {\n (address owner, uint256 blockNumber) = _ownerAndBlockNumberOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n require(msg.sender == owner || isApprovedForAll(owner, msg.sender), \"UNAUTHORIZED_APPROVAL\");\n _approveFor(owner, blockNumber, operator, id);\n }\n\n /// @notice Transfer a token between 2 addresses.\n /// @param from The sender of the token.\n /// @param to The recipient of the token.\n /// @param id The id of the token.\n function transferFrom(\n address from,\n address to,\n uint256 id\n ) external override {\n (address owner, bool operatorEnabled) = _ownerAndOperatorEnabledOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n require(owner == from, \"NOT_OWNER\");\n require(to != address(0), \"NOT_TO_ZEROADDRESS\");\n require(to != address(this), \"NOT_TO_THIS\");\n if (msg.sender != from) {\n require(\n (operatorEnabled && _operators[id] == msg.sender) || isApprovedForAll(from, msg.sender),\n \"UNAUTHORIZED_TRANSFER\"\n );\n }\n _transferFrom(from, to, id);\n }\n\n /// @notice Transfer a token between 2 addresses letting the receiver know of the transfer.\n /// @param from The send of the token.\n /// @param to The recipient of the token.\n /// @param id The id of the token.\n function safeTransferFrom(\n address from,\n address to,\n uint256 id\n ) external override {\n safeTransferFrom(from, to, id, \"\");\n }\n\n /// @notice Set the approval for an operator to manage all the tokens of the sender.\n /// @param operator The address receiving the approval.\n /// @param approved The determination of the approval.\n function setApprovalForAll(address operator, bool approved) external override {\n _setApprovalForAll(msg.sender, operator, approved);\n }\n\n /// @notice Get the number of tokens owned by an address.\n /// @param owner The address to look for.\n /// @return balance The number of tokens owned by the address.\n function balanceOf(address owner) public view override returns (uint256 balance) {\n require(owner != address(0), \"ZERO_ADDRESS_OWNER\");\n balance = _balances[owner];\n }\n\n /// @notice Get the owner of a token.\n /// @param id The id of the token.\n /// @return owner The address of the token owner.\n function ownerOf(uint256 id) external view override returns (address owner) {\n owner = _ownerOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n }\n\n /// @notice Get the owner of a token and the blockNumber of the last transfer, useful to voting mechanism.\n /// @param id The id of the token.\n /// @return owner The address of the token owner.\n /// @return blockNumber The blocknumber at which the last transfer of that id happened.\n function ownerAndLastTransferBlockNumberOf(uint256 id) internal view returns (address owner, uint256 blockNumber) {\n return _ownerAndBlockNumberOf(id);\n }\n\n struct OwnerData {\n address owner;\n uint256 lastTransferBlockNumber;\n }\n\n /// @notice Get the list of owner of a token and the blockNumber of its last transfer, useful to voting mechanism.\n /// @param ids The list of token ids to check.\n /// @return ownersData The list of (owner, lastTransferBlockNumber) for each ids given as input.\n function ownerAndLastTransferBlockNumberList(uint256[] calldata ids)\n external\n view\n returns (OwnerData[] memory ownersData)\n {\n ownersData = new OwnerData[](ids.length);\n for (uint256 i = 0; i < ids.length; i++) {\n uint256 data = _owners[ids[i]];\n ownersData[i].owner = address(uint160(data));\n ownersData[i].lastTransferBlockNumber = (data >> 160) & 0xFFFFFFFFFFFFFFFFFFFFFF;\n }\n }\n\n /// @notice Get the approved operator for a specific token.\n /// @param id The id of the token.\n /// @return The address of the operator.\n function getApproved(uint256 id) external view override returns (address) {\n (address owner, bool operatorEnabled) = _ownerAndOperatorEnabledOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n if (operatorEnabled) {\n return _operators[id];\n } else {\n return address(0);\n }\n }\n\n /// @notice Check if the sender approved the operator.\n /// @param owner The address of the owner.\n /// @param operator The address of the operator.\n /// @return isOperator The status of the approval.\n function isApprovedForAll(address owner, address operator) public view virtual override returns (bool isOperator) {\n return _operatorsForAll[owner][operator];\n }\n\n /// @notice Transfer a token between 2 addresses letting the receiver knows of the transfer.\n /// @param from The sender of the token.\n /// @param to The recipient of the token.\n /// @param id The id of the token.\n /// @param data Additional data.\n function safeTransferFrom(\n address from,\n address to,\n uint256 id,\n bytes memory data\n ) public override {\n (address owner, bool operatorEnabled) = _ownerAndOperatorEnabledOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n require(owner == from, \"NOT_OWNER\");\n require(to != address(0), \"NOT_TO_ZEROADDRESS\");\n require(to != address(this), \"NOT_TO_THIS\");\n if (msg.sender != from) {\n require(\n (operatorEnabled && _operators[id] == msg.sender) || isApprovedForAll(from, msg.sender),\n \"UNAUTHORIZED_TRANSFER\"\n );\n }\n _safeTransferFrom(from, to, id, data);\n }\n\n /// @notice Check if the contract supports an interface.\n /// @param id The id of the interface.\n /// @return Whether the interface is supported.\n function supportsInterface(bytes4 id) public pure virtual override returns (bool) {\n /// 0x01ffc9a7 is ERC165.\n /// 0x80ac58cd is ERC721\n /// 0x5b5e139f is for ERC721 metadata\n return id == 0x01ffc9a7 || id == 0x80ac58cd || id == 0x5b5e139f;\n }\n\n function _safeTransferFrom(\n address from,\n address to,\n uint256 id,\n bytes memory data\n ) internal {\n _transferFrom(from, to, id);\n if (to.isContract()) {\n require(_checkOnERC721Received(msg.sender, from, to, id, data), \"ERC721_TRANSFER_REJECTED\");\n }\n }\n\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 id\n ) internal virtual {}\n\n function _transferFrom(\n address from,\n address to,\n uint256 id\n ) internal {\n _beforeTokenTransfer(from, to, id);\n unchecked {\n _balances[to]++;\n if (from != address(0)) {\n _balances[from]--;\n }\n }\n _owners[id] = (block.number << 160) | uint256(uint160(to));\n emit Transfer(from, to, id);\n }\n\n /// @dev See approve.\n function _approveFor(\n address owner,\n uint256 blockNumber,\n address operator,\n uint256 id\n ) internal {\n if (operator == address(0)) {\n _owners[id] = (blockNumber << 160) | uint256(uint160(owner));\n } else {\n _owners[id] = OPERATOR_FLAG | (blockNumber << 160) | uint256(uint160(owner));\n _operators[id] = operator;\n }\n emit Approval(owner, operator, id);\n }\n\n /// @dev See setApprovalForAll.\n function _setApprovalForAll(\n address sender,\n address operator,\n bool approved\n ) internal {\n _operatorsForAll[sender][operator] = approved;\n\n emit ApprovalForAll(sender, operator, approved);\n }\n\n /// @dev Check if receiving contract accepts erc721 transfers.\n /// @param operator The address of the operator.\n /// @param from The from address, may be different from msg.sender.\n /// @param to The adddress we want to transfer to.\n /// @param id The id of the token we would like to transfer.\n /// @param _data Any additional data to send with the transfer.\n /// @return Whether the expected value of 0x150b7a02 is returned.\n function _checkOnERC721Received(\n address operator,\n address from,\n address to,\n uint256 id,\n bytes memory _data\n ) internal returns (bool) {\n bytes4 retval = IERC721Receiver(to).onERC721Received(operator, from, id, _data);\n return (retval == ERC721_RECEIVED);\n }\n\n /// @dev See ownerOf\n function _ownerOf(uint256 id) internal view returns (address owner) {\n return address(uint160(_owners[id]));\n }\n\n /// @dev Get the owner and operatorEnabled status of a token.\n /// @param id The token to query.\n /// @return owner The owner of the token.\n /// @return operatorEnabled Whether or not operators are enabled for this token.\n function _ownerAndOperatorEnabledOf(uint256 id) internal view returns (address owner, bool operatorEnabled) {\n uint256 data = _owners[id];\n owner = address(uint160(data));\n operatorEnabled = (data & OPERATOR_FLAG) == OPERATOR_FLAG;\n }\n\n // @dev Get the owner and operatorEnabled status of a token.\n /// @param id The token to query.\n /// @return owner The owner of the token.\n /// @return blockNumber the blockNumber at which the owner became the owner (last transfer).\n function _ownerAndBlockNumberOf(uint256 id) internal view returns (address owner, uint256 blockNumber) {\n uint256 data = _owners[id];\n owner = address(uint160(data));\n blockNumber = (data >> 160) & 0xFFFFFFFFFFFFFFFFFFFFFF;\n }\n\n // from https://github.com/Uniswap/v3-periphery/blob/main/contracts/base/Multicall.sol\n /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed.\n /// @dev The `msg.value` should not be trusted for any method callable from multicall.\n /// @param data The encoded function data for each of the calls to make to this contract.\n /// @return results The results from each of the calls passed in via data.\n function multicall(bytes[] calldata data) public payable returns (bytes[] memory results) {\n results = new bytes[](data.length);\n for (uint256 i = 0; i < data.length; i++) {\n (bool success, bytes memory result) = address(this).delegatecall(data[i]);\n\n if (!success) {\n // Next 5 lines from https://ethereum.stackexchange.com/a/83577\n if (result.length < 68) revert();\n assembly {\n result := add(result, 0x04)\n }\n revert(abi.decode(result, (string)));\n }\n\n results[i] = result;\n }\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n" + }, + "src/base/IERC4494.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\ninterface IERC4494 {\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n /// @notice Allows to retrieve current nonce for token\n /// @param tokenId token id\n /// @return current token nonce\n function nonces(uint256 tokenId) external view returns (uint256);\n\n /// @notice function to be called by anyone to approve `spender` using a Permit signature\n /// @dev Anyone can call this to approve `spender`, even a third-party\n /// @param spender the actor to approve\n /// @param tokenId the token id\n /// @param deadline the deadline for the permit to be used\n /// @param signature permit\n function permit(\n address spender,\n uint256 tokenId,\n uint256 deadline,\n bytes memory signature\n ) external;\n}\n\ninterface IERC4494Alternative {\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n /// @notice Allows to retrieve current nonce for token\n /// @param tokenId token id\n /// @return current token nonce\n function tokenNonces(uint256 tokenId) external view returns (uint256);\n\n /// @notice function to be called by anyone to approve `spender` using a Permit signature\n /// @dev Anyone can call this to approve `spender`, even a third-party\n /// @param spender the actor to approve\n /// @param tokenId the token id\n /// @param deadline the deadline for the permit to be used\n /// @param signature permit\n function permit(\n address spender,\n uint256 tokenId,\n uint256 deadline,\n bytes memory signature\n ) external;\n}\n" + }, + "src/bleeps/BleepsRoles.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ninterface ReverseRegistrar {\n function setName(string memory name) external returns (bytes32);\n}\n\ninterface ENS {\n function owner(bytes32 node) external view returns (address);\n}\n\ncontract BleepsRoles {\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n event TokenURIAdminSet(address newTokenURIAdmin);\n event RoyaltyAdminSet(address newRoyaltyAdmin);\n event MinterAdminSet(address newMinterAdmin);\n event GuardianSet(address newGuardian);\n event MinterSet(address newMinter);\n\n bytes32 internal constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2;\n ENS internal immutable _ens;\n\n ///@notice the address of the current owner, that is able to set ENS names and withdraw ERC20 owned by the contract.\n address public owner;\n\n /// @notice tokenURIAdmin can update the tokenURI contract, this is intended to be relinquished once the tokenURI has been heavily tested in the wild and that no modification are needed.\n address public tokenURIAdmin;\n\n /// @notice address allowed to set royalty parameters\n address public royaltyAdmin;\n\n /// @notice minterAdmin can update the minter. At the time being there is 576 Bleeps but there is space for extra instrument and the upper limit is 1024.\n /// could be given to the DAO later so instrument can be added, the sale of these new bleeps could benenfit the DAO too and add new members.\n address public minterAdmin;\n\n /// @notice address allowed to mint, allow the sale contract to be separated from the token contract that can focus on the core logic\n /// Once all 1024 potential bleeps (there could be less, at minimum there are 576 bleeps) are minted, no minter can mint anymore\n address public minter;\n\n /// @notice guardian has some special vetoing power to guide the direction of the DAO. It can only remove rights from the DAO. It could be used to immortalize rules.\n /// For example: the royalty setup could be frozen.\n address public guardian;\n\n constructor(\n address ens,\n address initialOwner,\n address initialTokenURIAdmin,\n address initialMinterAdmin,\n address initialRoyaltyAdmin,\n address initialGuardian\n ) {\n _ens = ENS(ens);\n owner = initialOwner;\n tokenURIAdmin = initialTokenURIAdmin;\n royaltyAdmin = initialRoyaltyAdmin;\n minterAdmin = initialMinterAdmin;\n guardian = initialGuardian;\n emit OwnershipTransferred(address(0), initialOwner);\n emit TokenURIAdminSet(initialTokenURIAdmin);\n emit RoyaltyAdminSet(initialRoyaltyAdmin);\n emit MinterAdminSet(initialMinterAdmin);\n emit GuardianSet(initialGuardian);\n }\n\n function setENSName(string memory name) external {\n require(msg.sender == owner, \"NOT_AUTHORIZED\");\n ReverseRegistrar reverseRegistrar = ReverseRegistrar(_ens.owner(ADDR_REVERSE_NODE));\n reverseRegistrar.setName(name);\n }\n\n function withdrawERC20(IERC20 token, address to) external {\n require(msg.sender == owner, \"NOT_AUTHORIZED\");\n token.transfer(to, token.balanceOf(address(this)));\n }\n\n /**\n * @notice Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) external {\n address oldOwner = owner;\n require(msg.sender == oldOwner);\n owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n\n /**\n * @notice set the new tokenURIAdmin that can change the tokenURI\n * Can only be called by the current tokenURI admin.\n */\n function setTokenURIAdmin(address newTokenURIAdmin) external {\n require(\n msg.sender == tokenURIAdmin || (msg.sender == guardian && newTokenURIAdmin == address(0)),\n \"NOT_AUTHORIZED\"\n );\n tokenURIAdmin = newTokenURIAdmin;\n emit TokenURIAdminSet(newTokenURIAdmin);\n }\n\n /**\n * @notice set the new royaltyAdmin that can change the royalties\n * Can only be called by the current royalty admin.\n */\n function setRoyaltyAdmin(address newRoyaltyAdmin) external {\n require(\n msg.sender == royaltyAdmin || (msg.sender == guardian && newRoyaltyAdmin == address(0)),\n \"NOT_AUTHORIZED\"\n );\n royaltyAdmin = newRoyaltyAdmin;\n emit RoyaltyAdminSet(newRoyaltyAdmin);\n }\n\n /**\n * @notice set the new minterAdmin that can set the minter for Bleeps\n * Can only be called by the current minter admin.\n */\n function setMinterAdmin(address newMinterAdmin) external {\n require(\n msg.sender == minterAdmin || (msg.sender == guardian && newMinterAdmin == address(0)),\n \"NOT_AUTHORIZED\"\n );\n minterAdmin = newMinterAdmin;\n emit MinterAdminSet(newMinterAdmin);\n }\n\n /**\n * @notice set the new guardian that can freeze the other admins (except owner).\n * Can only be called by the current guardian.\n */\n function setGuardian(address newGuardian) external {\n require(msg.sender == guardian, \"NOT_AUTHORIZED\");\n guardian = newGuardian;\n emit GuardianSet(newGuardian);\n }\n\n /**\n * @notice set the new minter that can mint Bleeps (up to 1024).\n * Can only be called by the minter admin.\n */\n function setMinter(address newMinter) external {\n require(msg.sender == minterAdmin, \"NOT_AUTHORIZED\");\n minter = newMinter;\n emit MinterSet(newMinter);\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC721/IERC721.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport \"../../utils/introspection/IERC165.sol\";\n\n/**\n * @dev Required interface of an ERC721 compliant contract.\n */\ninterface IERC721 is IERC165 {\n /**\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\n */\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\n */\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\n */\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\n\n /**\n * @dev Returns the number of tokens in ``owner``'s account.\n */\n function balanceOf(address owner) external view returns (uint256 balance);\n\n /**\n * @dev Returns the owner of the `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function ownerOf(uint256 tokenId) external view returns (address owner);\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n /**\n * @dev Transfers `tokenId` token from `from` to `to`.\n *\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n /**\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\n * The approval is cleared when the token is transferred.\n *\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\n *\n * Requirements:\n *\n * - The caller must own the token or be an approved operator.\n * - `tokenId` must exist.\n *\n * Emits an {Approval} event.\n */\n function approve(address to, uint256 tokenId) external;\n\n /**\n * @dev Returns the account approved for `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function getApproved(uint256 tokenId) external view returns (address operator);\n\n /**\n * @dev Approve or remove `operator` as an operator for the caller.\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\n *\n * Requirements:\n *\n * - The `operator` cannot be the caller.\n *\n * Emits an {ApprovalForAll} event.\n */\n function setApprovalForAll(address operator, bool _approved) external;\n\n /**\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\n *\n * See {setApprovalForAll}\n */\n function isApprovedForAll(address owner, address operator) external view returns (bool);\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId,\n bytes calldata data\n ) external;\n}\n" + }, + "@openzeppelin/contracts/utils/introspection/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n" + }, + "src/interfaces/ITokenURI.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\ninterface ITokenURI {\n function tokenURI(uint256 id) external view returns (string memory);\n\n function contractURI(address receiver, uint96 per10Thousands) external view returns (string memory);\n}\n" + }, + "src/base/WithSupportForOpenSeaProxies.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\ncontract OwnableDelegateProxy {}\n\ncontract ProxyRegistry {\n mapping(address => OwnableDelegateProxy) public proxies;\n}\n\nabstract contract WithSupportForOpenSeaProxies {\n address internal immutable _proxyRegistryAddress;\n\n constructor(address proxyRegistryAddress) {\n _proxyRegistryAddress = proxyRegistryAddress;\n }\n\n function _isOpenSeaProxy(address owner, address operator) internal view returns (bool) {\n if (_proxyRegistryAddress == address(0)) {\n return false;\n }\n // Whitelist OpenSea proxy contract for easy trading.\n ProxyRegistry proxyRegistry = ProxyRegistry(_proxyRegistryAddress);\n return address(proxyRegistry.proxies(owner)) == operator;\n }\n}\n" + }, + "@openzeppelin/contracts/interfaces/IERC1271.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC1271 standard signature validation method for\n * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].\n *\n * _Available since v4.1._\n */\ninterface IERC1271 {\n /**\n * @dev Should return whether the signature provided is valid for the provided data\n * @param hash Hash of the data to be signed\n * @param signature Signature byte array associated with _data\n */\n function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);\n}\n" + }, + "@openzeppelin/contracts/interfaces/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport \"../utils/introspection/IERC165.sol\";\n" + }, + "src/bleeps/Bleeps.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"./BleepsRoles.sol\";\nimport \"../base/ERC721Checkpointable.sol\";\n\nimport \"../interfaces/ITokenURI.sol\";\nimport \"@openzeppelin/contracts/token/ERC721/IERC721.sol\";\n\nimport \"../base/WithSupportForOpenSeaProxies.sol\";\n\ncontract Bleeps is IERC721, WithSupportForOpenSeaProxies, ERC721Checkpointable, BleepsRoles {\n event RoyaltySet(address receiver, uint256 royaltyPer10Thousands);\n event TokenURIContractSet(ITokenURI newTokenURIContract);\n event CheckpointingDisablerSet(address newCheckpointingDisabler);\n event CheckpointingDisabled();\n\n /// @notice the contract that actually generate the sound (and all metadata via the a data: uri via tokenURI call).\n ITokenURI public tokenURIContract;\n\n struct Royalty {\n address receiver;\n uint96 per10Thousands;\n }\n\n Royalty internal _royalty;\n\n /// @dev address that is able to switch off the use of checkpointing, this will make token transfers much cheaper in term of gas, but require the design of a new governance system.\n address public checkpointingDisabler;\n\n /// @dev Create the Bleeps contract\n /// @param ens ENS address for the network the contract is deployed to\n /// @param initialOwner address that can set the ENS name of the contract and that can witthdraw ERC20 tokens sent by mistake here.\n /// @param initialTokenURIAdmin admin able to update the tokenURI contract.\n /// @param initialMinterAdmin admin able to set the minter contract.\n /// @param initialRoyaltyAdmin admin able to update the royalty receiver and rates.\n /// @param initialGuardian guardian able to immortalize rules\n /// @param openseaProxyRegistry allow Bleeps to be sold on opensea without prior approval tx as long as the user have already an opensea proxy.\n /// @param initialRoyaltyReceiver receiver of royalties\n /// @param imitialRoyaltyPer10Thousands amount of royalty in 10,000 basis point\n /// @param initialTokenURIContract initial tokenURI contract that generate the metadata including the wav file.\n /// @param initialCheckpointingDisabler admin able to cancel checkpointing\n constructor(\n address ens,\n address initialOwner,\n address initialTokenURIAdmin,\n address initialMinterAdmin,\n address initialRoyaltyAdmin,\n address initialGuardian,\n address openseaProxyRegistry,\n address initialRoyaltyReceiver,\n uint96 imitialRoyaltyPer10Thousands,\n ITokenURI initialTokenURIContract,\n address initialCheckpointingDisabler\n )\n WithSupportForOpenSeaProxies(openseaProxyRegistry)\n BleepsRoles(ens, initialOwner, initialTokenURIAdmin, initialMinterAdmin, initialRoyaltyAdmin, initialGuardian)\n {\n tokenURIContract = initialTokenURIContract;\n emit TokenURIContractSet(initialTokenURIContract);\n checkpointingDisabler = initialCheckpointingDisabler;\n emit CheckpointingDisablerSet(initialCheckpointingDisabler);\n\n _royalty.receiver = initialRoyaltyReceiver;\n _royalty.per10Thousands = imitialRoyaltyPer10Thousands;\n emit RoyaltySet(initialRoyaltyReceiver, imitialRoyaltyPer10Thousands);\n }\n\n /// @notice A descriptive name for a collection of NFTs in this contract.\n function name() public pure override returns (string memory) {\n return \"Bleeps\";\n }\n\n /// @notice An abbreviated name for NFTs in this contract.\n function symbol() external pure returns (string memory) {\n return \"BLEEP\";\n }\n\n /// @notice Returns the Uniform Resource Identifier (URI) for the token collection.\n function contractURI() external view returns (string memory) {\n return tokenURIContract.contractURI(_royalty.receiver, _royalty.per10Thousands);\n }\n\n /// @notice Returns the Uniform Resource Identifier (URI) for token `id`.\n function tokenURI(uint256 id) external view returns (string memory) {\n return tokenURIContract.tokenURI(id);\n }\n\n /// @notice set a new tokenURI contract, that generate the metadata including the wav file, Can only be set by the `tokenURIAdmin`.\n /// @param newTokenURIContract The address of the new tokenURI contract.\n function setTokenURIContract(ITokenURI newTokenURIContract) external {\n require(msg.sender == tokenURIAdmin, \"NOT_AUTHORIZED\");\n tokenURIContract = newTokenURIContract;\n emit TokenURIContractSet(newTokenURIContract);\n }\n\n /// @notice give the list of owners for the list of ids given.\n /// @param ids The list if token ids to check.\n /// @return addresses The list of addresses, corresponding to the list of ids.\n function owners(uint256[] calldata ids) external view returns (address[] memory addresses) {\n addresses = new address[](ids.length);\n for (uint256 i = 0; i < ids.length; i++) {\n uint256 id = ids[i];\n addresses[i] = address(uint160(_owners[id]));\n }\n }\n\n /// @notice Check if the sender approved the operator.\n /// @param owner The address of the owner.\n /// @param operator The address of the operator.\n /// @return isOperator The status of the approval.\n function isApprovedForAll(address owner, address operator)\n public\n view\n virtual\n override(ERC721Base, IERC721)\n returns (bool isOperator)\n {\n return super.isApprovedForAll(owner, operator) || _isOpenSeaProxy(owner, operator);\n }\n\n /// @notice Check if the contract supports an interface.\n /// @param id The id of the interface.\n /// @return Whether the interface is supported.\n function supportsInterface(bytes4 id)\n public\n pure\n virtual\n override(ERC721BaseWithERC4494Permit, IERC165)\n returns (bool)\n {\n return super.supportsInterface(id) || id == 0x2a55205a; /// 0x2a55205a is ERC2981 (royalty standard)\n }\n\n /// @notice Called with the sale price to determine how much royalty is owed and to whom.\n /// @param id - the token queried for royalty information.\n /// @param salePrice - the sale price of the token specified by id.\n /// @return receiver - address of who should be sent the royalty payment.\n /// @return royaltyAmount - the royalty payment amount for salePrice.\n function royaltyInfo(uint256 id, uint256 salePrice)\n external\n view\n returns (address receiver, uint256 royaltyAmount)\n {\n receiver = _royalty.receiver;\n royaltyAmount = (salePrice * uint256(_royalty.per10Thousands)) / 10000;\n }\n\n /// @notice set a new royalty receiver and rate, Can only be set by the `royaltyAdmin`.\n /// @param newReceiver the address that should receive the royalty proceeds.\n /// @param royaltyPer10Thousands the share of the salePrice (in 1/10000) given to the receiver.\n function setRoyaltyParameters(address newReceiver, uint96 royaltyPer10Thousands) external {\n require(msg.sender == royaltyAdmin, \"NOT_AUTHORIZED\");\n // require(royaltyPer10Thousands <= 50, \"ROYALTY_TOO_HIGH\"); ?\n _royalty.receiver = newReceiver;\n _royalty.per10Thousands = royaltyPer10Thousands;\n emit RoyaltySet(newReceiver, royaltyPer10Thousands);\n }\n\n /// @notice disable checkpointing overhead\n /// This can be used if the governance system can switch to use ownerAndLastTransferBlockNumberOf instead of checkpoints\n function disableTheUseOfCheckpoints() external {\n require(msg.sender == checkpointingDisabler, \"NOT_AUTHORIZED\");\n _useCheckpoints = false;\n checkpointingDisabler = address(0);\n emit CheckpointingDisablerSet(address(0));\n emit CheckpointingDisabled();\n }\n\n /// @notice update the address that can disable the use of checkpinting, can be used to disable it entirely.\n /// @param newCheckpointingDisabler new address that can disable the use of checkpointing. can be the zero address to remove the ability to change.\n function setCheckpointingDisabler(address newCheckpointingDisabler) external {\n require(msg.sender == checkpointingDisabler, \"NOT_AUTHORIZED\");\n checkpointingDisabler = newCheckpointingDisabler;\n emit CheckpointingDisablerSet(newCheckpointingDisabler);\n }\n\n /// @notice mint one of bleep if not already minted. Can only be called by `minter`.\n /// @param id bleep id which represent a pair of (note, instrument).\n /// @param to address that will receive the Bleep.\n function mint(uint16 id, address to) external {\n require(msg.sender == minter, \"ONLY_MINTER_ALLOWED\");\n require(id < 1024, \"INVALID_BLEEP\");\n\n require(to != address(0), \"NOT_TO_ZEROADDRESS\");\n require(to != address(this), \"NOT_TO_THIS\");\n address owner = _ownerOf(id);\n require(owner == address(0), \"ALREADY_CREATED\");\n _safeTransferFrom(address(0), to, id, \"\");\n }\n\n /// @notice mint multiple bleeps if not already minted. Can only be called by `minter`.\n /// @param ids list of bleep id which represent each a pair of (note, instrument).\n /// @param tos addresses that will receive the Bleeps. (if only one, use for all)\n function multiMint(uint16[] calldata ids, address[] calldata tos) external {\n require(msg.sender == minter, \"ONLY_MINTER_ALLOWED\");\n\n address to;\n if (tos.length == 1) {\n to = tos[0];\n }\n for (uint256 i = 0; i < ids.length; i++) {\n uint256 id = ids[i];\n if (tos.length > 1) {\n to = tos[i];\n }\n require(to != address(0), \"NOT_TO_ZEROADDRESS\");\n require(to != address(this), \"NOT_TO_THIS\");\n require(id < 1024, \"INVALID_BLEEP\");\n address owner = _ownerOf(id);\n require(owner == address(0), \"ALREADY_CREATED\");\n _safeTransferFrom(address(0), to, id, \"\");\n }\n }\n\n /// @notice gives the note and instrument for a particular Bleep id.\n /// @param id bleep id which represent a pair of (note, instrument).\n /// @return note the note index (0 to 63) starting from C2 to D#7\n /// @return instrument the instrument index (0 to 16). At launch there is only 9 instrument but the DAO could add more (up to 16 in total).\n function sound(uint256 id) external pure returns (uint8 note, uint8 instrument) {\n note = uint8(id & 0x3F);\n instrument = uint8(uint256(id >> 6) & 0x0F);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/cryptography/ECDSA.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\n *\n * These functions can be used to verify that a message was signed by the holder\n * of the private keys of a given address.\n */\nlibrary ECDSA {\n enum RecoverError {\n NoError,\n InvalidSignature,\n InvalidSignatureLength,\n InvalidSignatureS,\n InvalidSignatureV\n }\n\n function _throwError(RecoverError error) private pure {\n if (error == RecoverError.NoError) {\n return; // no error: do nothing\n } else if (error == RecoverError.InvalidSignature) {\n revert(\"ECDSA: invalid signature\");\n } else if (error == RecoverError.InvalidSignatureLength) {\n revert(\"ECDSA: invalid signature length\");\n } else if (error == RecoverError.InvalidSignatureS) {\n revert(\"ECDSA: invalid signature 's' value\");\n } else if (error == RecoverError.InvalidSignatureV) {\n revert(\"ECDSA: invalid signature 'v' value\");\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature` or error string. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n *\n * Documentation for signature generation:\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\n *\n * _Available since v4.3._\n */\n function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {\n // Check the signature length\n // - case 65: r,s,v signature (standard)\n // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._\n if (signature.length == 65) {\n bytes32 r;\n bytes32 s;\n uint8 v;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n assembly {\n r := mload(add(signature, 0x20))\n s := mload(add(signature, 0x40))\n v := byte(0, mload(add(signature, 0x60)))\n }\n return tryRecover(hash, v, r, s);\n } else if (signature.length == 64) {\n bytes32 r;\n bytes32 vs;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n assembly {\n r := mload(add(signature, 0x20))\n vs := mload(add(signature, 0x40))\n }\n return tryRecover(hash, r, vs);\n } else {\n return (address(0), RecoverError.InvalidSignatureLength);\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature`. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n */\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, signature);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\n *\n * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address, RecoverError) {\n bytes32 s;\n uint8 v;\n assembly {\n s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)\n v := add(shr(255, vs), 27)\n }\n return tryRecover(hash, v, r, s);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\n *\n * _Available since v4.2._\n */\n function recover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, r, vs);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\n * `r` and `s` signature fields separately.\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address, RecoverError) {\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\n // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\n //\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\n // these malleable signatures as well.\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\n return (address(0), RecoverError.InvalidSignatureS);\n }\n if (v != 27 && v != 28) {\n return (address(0), RecoverError.InvalidSignatureV);\n }\n\n // If the signature is valid (and not malleable), return the signer address\n address signer = ecrecover(hash, v, r, s);\n if (signer == address(0)) {\n return (address(0), RecoverError.InvalidSignature);\n }\n\n return (signer, RecoverError.NoError);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function recover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, v, r, s);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\n // 32 is the length in bytes of hash,\n // enforced by the type signature above\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", hash));\n }\n\n /**\n * @dev Returns an Ethereum Signed Typed Data, created from a\n * `domainSeparator` and a `structHash`. This produces hash corresponding\n * to the one signed with the\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]\n * JSON-RPC method as part of EIP-712.\n *\n * See {recover}.\n */\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19\\x01\", domainSeparator, structHash));\n }\n}\n" + }, + "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport \"./ECDSA.sol\";\nimport \"../Address.sol\";\nimport \"../../interfaces/IERC1271.sol\";\n\n/**\n * @dev Signature verification helper: Provide a single mechanism to verify both private-key (EOA) ECDSA signature and\n * ERC1271 contract sigantures. Using this instead of ECDSA.recover in your contract will make them compatible with\n * smart contract wallets such as Argent and Gnosis.\n *\n * Note: unlike ECDSA signatures, contract signature's are revocable, and the outcome of this function can thus change\n * through time. It could return true at block N and false at block N+1 (or the opposite).\n *\n * _Available since v4.1._\n */\nlibrary SignatureChecker {\n function isValidSignatureNow(\n address signer,\n bytes32 hash,\n bytes memory signature\n ) internal view returns (bool) {\n (address recovered, ECDSA.RecoverError error) = ECDSA.tryRecover(hash, signature);\n if (error == ECDSA.RecoverError.NoError && recovered == signer) {\n return true;\n }\n\n (bool success, bytes memory result) = signer.staticcall(\n abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature)\n );\n return (success && result.length == 32 && abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector);\n }\n}\n" + }, + "src/base/ERC721BaseWithERC4494Permit.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"./ERC721Base.sol\";\nimport \"@openzeppelin/contracts/utils/Address.sol\";\nimport \"@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol\";\nimport \"./IERC4494.sol\";\n\nabstract contract ERC721BaseWithERC4494Permit is ERC721Base {\n using Address for address;\n\n bytes32 public constant PERMIT_TYPEHASH =\n keccak256(\"Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)\");\n bytes32 public constant PERMIT_FOR_ALL_TYPEHASH =\n keccak256(\"PermitForAll(address spender,uint256 nonce,uint256 deadline)\");\n bytes32 public constant DOMAIN_TYPEHASH =\n keccak256(\"EIP712Domain(string name,uint256 chainId,address verifyingContract)\");\n\n uint256 private immutable _deploymentChainId;\n bytes32 private immutable _deploymentDomainSeparator;\n\n mapping(address => uint256) internal _userNonces;\n\n constructor() {\n uint256 chainId;\n //solhint-disable-next-line no-inline-assembly\n assembly {\n chainId := chainid()\n }\n _deploymentChainId = chainId;\n _deploymentDomainSeparator = _calculateDomainSeparator(chainId);\n }\n\n /// @dev Return the DOMAIN_SEPARATOR.\n function DOMAIN_SEPARATOR() external view returns (bytes32) {\n return _DOMAIN_SEPARATOR();\n }\n\n /// @notice return the account nonce, used for approvalForAll permit or other account related matter\n /// @param account the account to query\n /// @return nonce\n function nonces(address account) external view virtual returns (uint256 nonce) {\n return _userNonces[account];\n }\n\n /// @notice return the token nonce, used for individual approve permit or other token related matter\n /// @param id token id to query\n /// @return nonce\n function nonces(uint256 id) public view virtual returns (uint256 nonce) {\n (address owner, uint256 blockNumber) = _ownerAndBlockNumberOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n return blockNumber;\n }\n\n /// @notice return the token nonce, used for individual approve permit or other token related matter\n /// @param id token id to query\n /// @return nonce\n function tokenNonces(uint256 id) external view returns (uint256 nonce) {\n return nonces(id);\n }\n\n function permit(\n address spender,\n uint256 tokenId,\n uint256 deadline,\n bytes memory sig\n ) external {\n require(deadline >= block.timestamp, \"PERMIT_DEADLINE_EXPIRED\");\n\n (address owner, uint256 blockNumber) = _ownerAndBlockNumberOf(tokenId);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n\n // We use blockNumber as nonce as we already store it per tokens. It can thus act as an increasing transfer counter.\n // while technically multiple transfer could happen in the same block, the signed message would be using a previous block.\n // And the transfer would use then a more recent blockNumber, invalidating that message when transfer is executed.\n _requireValidPermit(owner, spender, tokenId, deadline, blockNumber, sig);\n\n _approveFor(owner, blockNumber, spender, tokenId);\n }\n\n function permitForAll(\n address signer,\n address spender,\n uint256 deadline,\n bytes memory sig\n ) external {\n require(deadline >= block.timestamp, \"PERMIT_DEADLINE_EXPIRED\");\n\n _requireValidPermitForAll(signer, spender, deadline, _userNonces[signer]++, sig);\n\n _setApprovalForAll(signer, spender, true);\n }\n\n /// @notice Check if the contract supports an interface.\n /// @param id The id of the interface.\n /// @return Whether the interface is supported.\n function supportsInterface(bytes4 id) public pure virtual override returns (bool) {\n return\n super.supportsInterface(id) ||\n id == type(IERC4494).interfaceId ||\n id == type(IERC4494Alternative).interfaceId;\n }\n\n // -------------------------------------------------------- INTERNAL --------------------------------------------------------------------\n\n function _requireValidPermit(\n address signer,\n address spender,\n uint256 id,\n uint256 deadline,\n uint256 nonce,\n bytes memory sig\n ) internal view {\n bytes32 digest = keccak256(\n abi.encodePacked(\n \"\\x19\\x01\",\n _DOMAIN_SEPARATOR(),\n keccak256(abi.encode(PERMIT_TYPEHASH, spender, id, nonce, deadline))\n )\n );\n require(SignatureChecker.isValidSignatureNow(signer, digest, sig), \"INVALID_SIGNATURE\");\n }\n\n function _requireValidPermitForAll(\n address signer,\n address spender,\n uint256 deadline,\n uint256 nonce,\n bytes memory sig\n ) internal view {\n bytes32 digest = keccak256(\n abi.encodePacked(\n \"\\x19\\x01\",\n _DOMAIN_SEPARATOR(),\n keccak256(abi.encode(PERMIT_FOR_ALL_TYPEHASH, spender, nonce, deadline))\n )\n );\n require(SignatureChecker.isValidSignatureNow(signer, digest, sig), \"INVALID_SIGNATURE\");\n }\n\n /// @dev Return the DOMAIN_SEPARATOR.\n function _DOMAIN_SEPARATOR() internal view returns (bytes32) {\n uint256 chainId;\n //solhint-disable-next-line no-inline-assembly\n assembly {\n chainId := chainid()\n }\n\n // in case a fork happen, to support the chain that had to change its chainId,, we compute the domain operator\n return chainId == _deploymentChainId ? _deploymentDomainSeparator : _calculateDomainSeparator(chainId);\n }\n\n /// @dev Calculate the DOMAIN_SEPARATOR.\n function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) {\n return keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name())), chainId, address(this)));\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title ERC721 token receiver interface\n * @dev Interface for any contract that wants to support safeTransfers\n * from ERC721 asset contracts.\n */\ninterface IERC721Receiver {\n /**\n * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}\n * by `operator` from `from`, this function is called.\n *\n * It must return its Solidity selector to confirm the token transfer.\n * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.\n *\n * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.\n */\n function onERC721Received(\n address operator,\n address from,\n uint256 tokenId,\n bytes calldata data\n ) external returns (bytes4);\n}\n" + } + }, + "settings": { + "evmVersion": "london", + "libraries": {}, + "metadata": { + "bytecodeHash": "ipfs", + "useLiteralContent": true + }, + "optimizer": { + "enabled": true, + "runs": 999999 + }, + "remappings": [], + "outputSelection": { + "*": { + "*": [ + "evm.bytecode", + "evm.deployedBytecode", + "devdoc", + "userdoc", + "metadata", + "abi" + ] + } + } + } + }, + "ABI": "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"ens\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialOwner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialTokenURIAdmin\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialMinterAdmin\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialRoyaltyAdmin\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialGuardian\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"openseaProxyRegistry\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialRoyaltyReceiver\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"imitialRoyaltyPer10Thousands\",\"type\":\"uint96\"},{\"internalType\":\"contract ITokenURI\",\"name\":\"initialTokenURIContract\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialCheckpointingDisabler\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"approved\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"approved\",\"type\":\"bool\"}],\"name\":\"ApprovalForAll\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"CheckpointingDisabled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newCheckpointingDisabler\",\"type\":\"address\"}],\"name\":\"CheckpointingDisablerSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"fromDelegate\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"toDelegate\",\"type\":\"address\"}],\"name\":\"DelegateChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"delegate\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousBalance\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newBalance\",\"type\":\"uint256\"}],\"name\":\"DelegateVotesChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newGuardian\",\"type\":\"address\"}],\"name\":\"GuardianSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newMinterAdmin\",\"type\":\"address\"}],\"name\":\"MinterAdminSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newMinter\",\"type\":\"address\"}],\"name\":\"MinterSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newRoyaltyAdmin\",\"type\":\"address\"}],\"name\":\"RoyaltyAdminSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"royaltyPer10Thousands\",\"type\":\"uint256\"}],\"name\":\"RoyaltySet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newTokenURIAdmin\",\"type\":\"address\"}],\"name\":\"TokenURIAdminSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract ITokenURI\",\"name\":\"newTokenURIContract\",\"type\":\"address\"}],\"name\":\"TokenURIContractSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DELEGATION_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DOMAIN_SEPARATOR\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DOMAIN_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PERMIT_FOR_ALL_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PERMIT_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"checkpointingDisabler\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"checkpoints\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"fromBlock\",\"type\":\"uint32\"},{\"internalType\":\"uint96\",\"name\":\"votes\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"contractURI\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegatee\",\"type\":\"address\"}],\"name\":\"delegate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegatee\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"expiry\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"delegateBySig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"}],\"name\":\"delegates\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"disableTheUseOfCheckpoints\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"getApproved\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"getCurrentVotes\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"getPastVotes\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"getPriorVotes\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"getVotes\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"guardian\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"}],\"name\":\"isApprovedForAll\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isOperator\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"id\",\"type\":\"uint16\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minter\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minterAdmin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16[]\",\"name\":\"ids\",\"type\":\"uint16[]\"},{\"internalType\":\"address[]\",\"name\":\"tos\",\"type\":\"address[]\"}],\"name\":\"multiMint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes[]\",\"name\":\"data\",\"type\":\"bytes[]\"}],\"name\":\"multicall\",\"outputs\":[{\"internalType\":\"bytes[]\",\"name\":\"results\",\"type\":\"bytes[]\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"nonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"nonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"numCheckpoints\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"ids\",\"type\":\"uint256[]\"}],\"name\":\"ownerAndLastTransferBlockNumberList\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"lastTransferBlockNumber\",\"type\":\"uint256\"}],\"internalType\":\"struct ERC721Base.OwnerData[]\",\"name\":\"ownersData\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"ownerOf\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"ids\",\"type\":\"uint256[]\"}],\"name\":\"owners\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"addresses\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"sig\",\"type\":\"bytes\"}],\"name\":\"permit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"signer\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"sig\",\"type\":\"bytes\"}],\"name\":\"permitForAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"royaltyAdmin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"salePrice\",\"type\":\"uint256\"}],\"name\":\"royaltyInfo\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"royaltyAmount\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"safeTransferFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"safeTransferFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"approved\",\"type\":\"bool\"}],\"name\":\"setApprovalForAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newCheckpointingDisabler\",\"type\":\"address\"}],\"name\":\"setCheckpointingDisabler\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"name\":\"setENSName\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newGuardian\",\"type\":\"address\"}],\"name\":\"setGuardian\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newMinter\",\"type\":\"address\"}],\"name\":\"setMinter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newMinterAdmin\",\"type\":\"address\"}],\"name\":\"setMinterAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newRoyaltyAdmin\",\"type\":\"address\"}],\"name\":\"setRoyaltyAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newReceiver\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"royaltyPer10Thousands\",\"type\":\"uint96\"}],\"name\":\"setRoyaltyParameters\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newTokenURIAdmin\",\"type\":\"address\"}],\"name\":\"setTokenURIAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract ITokenURI\",\"name\":\"newTokenURIContract\",\"type\":\"address\"}],\"name\":\"setTokenURIContract\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"sound\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"note\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"instrument\",\"type\":\"uint8\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"id\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"tokenNonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"tokenURI\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tokenURIAdmin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tokenURIContract\",\"outputs\":[{\"internalType\":\"contract ITokenURI\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"}],\"name\":\"votesToDelegate\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"withdrawERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + "ContractName": "Bleeps", + "CompilerVersion": "v0.8.9+commit.e5eed63a", + "OptimizationUsed": 1, + "Runs": 999999, + "ConstructorArguments": "0x00000000000000000000000000000000000c2e074ec69a0dfb2997ba6c7d2e1e0000000000000000000000007773ae67403d2e30102a84c48cc939919c4c881c000000000000000000000000dca9d1fa839bb9fe65ddc4de5161bca43751d4b40000000000000000000000007773ae67403d2e30102a84c48cc939919c4c881c000000000000000000000000dca9d1fa839bb9fe65ddc4de5161bca43751d4b4000000000000000000000000dca9d1fa839bb9fe65ddc4de5161bca43751d4b4000000000000000000000000a5409ec958c83c3f309868babaca7c86dcb077c10000000000000000000000008350c9989ef11325b36ce6f7549004d418dbcee700000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000e114dce59a333f8d351371f54188f92c287b73e6000000000000000000000000dca9d1fa839bb9fe65ddc4de5161bca43751d4b4", + "EVMVersion": "Default", + "Library": "", + "LicenseType": "MIT", + "Proxy": 0, + "SwarmSource": "" + } +] \ No newline at end of file diff --git a/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/creation_data.json b/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/creation_data.json new file mode 100644 index 0000000000000..646ea065a473e --- /dev/null +++ b/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0xdb53f47ac61fe54f456a4eb3e09832d08dd7beec","contractCreator":"0xc7f8d87734ab2cbf70030ac8aa82abfe3e8126cb","txHash":"0x196898c69f6b1944f1011120b15c0903329d46259c8cdc0fbcad71da1fe58245"} \ No newline at end of file diff --git a/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/metadata.json b/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/metadata.json new file mode 100644 index 0000000000000..df45d9be39c9e --- /dev/null +++ b/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"@solidstate/contracts/token/ERC1155/base/ERC1155Base.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC1155 } from '../IERC1155.sol';\nimport { IERC1155Receiver } from '../IERC1155Receiver.sol';\nimport { ERC1155BaseInternal, ERC1155BaseStorage } from './ERC1155BaseInternal.sol';\n\n/**\n * @title Base ERC1155 contract\n * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license)\n */\nabstract contract ERC1155Base is IERC1155, ERC1155BaseInternal {\n /**\n * @inheritdoc IERC1155\n */\n function balanceOf(address account, uint256 id)\n public\n view\n virtual\n override\n returns (uint256)\n {\n return _balanceOf(account, id);\n }\n\n /**\n * @inheritdoc IERC1155\n */\n function balanceOfBatch(address[] memory accounts, uint256[] memory ids)\n public\n view\n virtual\n override\n returns (uint256[] memory)\n {\n require(\n accounts.length == ids.length,\n 'ERC1155: accounts and ids length mismatch'\n );\n\n mapping(uint256 => mapping(address => uint256))\n storage balances = ERC1155BaseStorage.layout().balances;\n\n uint256[] memory batchBalances = new uint256[](accounts.length);\n\n unchecked {\n for (uint256 i; i < accounts.length; i++) {\n require(\n accounts[i] != address(0),\n 'ERC1155: batch balance query for the zero address'\n );\n batchBalances[i] = balances[ids[i]][accounts[i]];\n }\n }\n\n return batchBalances;\n }\n\n /**\n * @inheritdoc IERC1155\n */\n function isApprovedForAll(address account, address operator)\n public\n view\n virtual\n override\n returns (bool)\n {\n return ERC1155BaseStorage.layout().operatorApprovals[account][operator];\n }\n\n /**\n * @inheritdoc IERC1155\n */\n function setApprovalForAll(address operator, bool status)\n public\n virtual\n override\n {\n require(\n msg.sender != operator,\n 'ERC1155: setting approval status for self'\n );\n ERC1155BaseStorage.layout().operatorApprovals[msg.sender][\n operator\n ] = status;\n emit ApprovalForAll(msg.sender, operator, status);\n }\n\n /**\n * @inheritdoc IERC1155\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) public virtual override {\n require(\n from == msg.sender || isApprovedForAll(from, msg.sender),\n 'ERC1155: caller is not owner nor approved'\n );\n _safeTransfer(msg.sender, from, to, id, amount, data);\n }\n\n /**\n * @inheritdoc IERC1155\n */\n function safeBatchTransferFrom(\n address from,\n address to,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) public virtual override {\n require(\n from == msg.sender || isApprovedForAll(from, msg.sender),\n 'ERC1155: caller is not owner nor approved'\n );\n _safeTransferBatch(msg.sender, from, to, ids, amounts, data);\n }\n}\n"},"@solidstate/contracts/introspection/IERC165.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title ERC165 interface registration interface\n * @dev see https://eips.ethereum.org/EIPS/eip-165\n */\ninterface IERC165 {\n /**\n * @notice query whether contract has registered support for given interface\n * @param interfaceId interface id\n * @return bool whether interface is supported\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n"},"abdk-libraries-solidity/ABDKMath64x64.sol":{"content":"// SPDX-License-Identifier: BSD-4-Clause\n/*\n * ABDK Math 64.64 Smart Contract Library. Copyright © 2019 by ABDK Consulting.\n * Author: Mikhail Vladimirov \n */\npragma solidity ^0.8.0;\n\n/**\n * Smart contract library of mathematical functions operating with signed\n * 64.64-bit fixed point numbers. Signed 64.64-bit fixed point number is\n * basically a simple fraction whose numerator is signed 128-bit integer and\n * denominator is 2^64. As long as denominator is always the same, there is no\n * need to store it, thus in Solidity signed 64.64-bit fixed point numbers are\n * represented by int128 type holding only the numerator.\n */\nlibrary ABDKMath64x64 {\n /*\n * Minimum value signed 64.64-bit fixed point number may have. \n */\n int128 private constant MIN_64x64 = -0x80000000000000000000000000000000;\n\n /*\n * Maximum value signed 64.64-bit fixed point number may have. \n */\n int128 private constant MAX_64x64 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;\n\n /**\n * Convert signed 256-bit integer number into signed 64.64-bit fixed point\n * number. Revert on overflow.\n *\n * @param x signed 256-bit integer number\n * @return signed 64.64-bit fixed point number\n */\n function fromInt (int256 x) internal pure returns (int128) {\n unchecked {\n require (x >= -0x8000000000000000 && x <= 0x7FFFFFFFFFFFFFFF);\n return int128 (x << 64);\n }\n }\n\n /**\n * Convert signed 64.64 fixed point number into signed 64-bit integer number\n * rounding down.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64-bit integer number\n */\n function toInt (int128 x) internal pure returns (int64) {\n unchecked {\n return int64 (x >> 64);\n }\n }\n\n /**\n * Convert unsigned 256-bit integer number into signed 64.64-bit fixed point\n * number. Revert on overflow.\n *\n * @param x unsigned 256-bit integer number\n * @return signed 64.64-bit fixed point number\n */\n function fromUInt (uint256 x) internal pure returns (int128) {\n unchecked {\n require (x <= 0x7FFFFFFFFFFFFFFF);\n return int128 (int256 (x << 64));\n }\n }\n\n /**\n * Convert signed 64.64 fixed point number into unsigned 64-bit integer\n * number rounding down. Revert on underflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @return unsigned 64-bit integer number\n */\n function toUInt (int128 x) internal pure returns (uint64) {\n unchecked {\n require (x >= 0);\n return uint64 (uint128 (x >> 64));\n }\n }\n\n /**\n * Convert signed 128.128 fixed point number into signed 64.64-bit fixed point\n * number rounding down. Revert on overflow.\n *\n * @param x signed 128.128-bin fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function from128x128 (int256 x) internal pure returns (int128) {\n unchecked {\n int256 result = x >> 64;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Convert signed 64.64 fixed point number into signed 128.128 fixed point\n * number.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 128.128 fixed point number\n */\n function to128x128 (int128 x) internal pure returns (int256) {\n unchecked {\n return int256 (x) << 64;\n }\n }\n\n /**\n * Calculate x + y. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function add (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n int256 result = int256(x) + y;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate x - y. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function sub (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n int256 result = int256(x) - y;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate x * y rounding down. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function mul (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n int256 result = int256(x) * y >> 64;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate x * y rounding towards zero, where x is signed 64.64 fixed point\n * number and y is signed 256-bit integer number. Revert on overflow.\n *\n * @param x signed 64.64 fixed point number\n * @param y signed 256-bit integer number\n * @return signed 256-bit integer number\n */\n function muli (int128 x, int256 y) internal pure returns (int256) {\n unchecked {\n if (x == MIN_64x64) {\n require (y >= -0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF &&\n y <= 0x1000000000000000000000000000000000000000000000000);\n return -y << 63;\n } else {\n bool negativeResult = false;\n if (x < 0) {\n x = -x;\n negativeResult = true;\n }\n if (y < 0) {\n y = -y; // We rely on overflow behavior here\n negativeResult = !negativeResult;\n }\n uint256 absoluteResult = mulu (x, uint256 (y));\n if (negativeResult) {\n require (absoluteResult <=\n 0x8000000000000000000000000000000000000000000000000000000000000000);\n return -int256 (absoluteResult); // We rely on overflow behavior here\n } else {\n require (absoluteResult <=\n 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n return int256 (absoluteResult);\n }\n }\n }\n }\n\n /**\n * Calculate x * y rounding down, where x is signed 64.64 fixed point number\n * and y is unsigned 256-bit integer number. Revert on overflow.\n *\n * @param x signed 64.64 fixed point number\n * @param y unsigned 256-bit integer number\n * @return unsigned 256-bit integer number\n */\n function mulu (int128 x, uint256 y) internal pure returns (uint256) {\n unchecked {\n if (y == 0) return 0;\n\n require (x >= 0);\n\n uint256 lo = (uint256 (int256 (x)) * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) >> 64;\n uint256 hi = uint256 (int256 (x)) * (y >> 128);\n\n require (hi <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n hi <<= 64;\n\n require (hi <=\n 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - lo);\n return hi + lo;\n }\n }\n\n /**\n * Calculate x / y rounding towards zero. Revert on overflow or when y is\n * zero.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function div (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n require (y != 0);\n int256 result = (int256 (x) << 64) / y;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate x / y rounding towards zero, where x and y are signed 256-bit\n * integer numbers. Revert on overflow or when y is zero.\n *\n * @param x signed 256-bit integer number\n * @param y signed 256-bit integer number\n * @return signed 64.64-bit fixed point number\n */\n function divi (int256 x, int256 y) internal pure returns (int128) {\n unchecked {\n require (y != 0);\n\n bool negativeResult = false;\n if (x < 0) {\n x = -x; // We rely on overflow behavior here\n negativeResult = true;\n }\n if (y < 0) {\n y = -y; // We rely on overflow behavior here\n negativeResult = !negativeResult;\n }\n uint128 absoluteResult = divuu (uint256 (x), uint256 (y));\n if (negativeResult) {\n require (absoluteResult <= 0x80000000000000000000000000000000);\n return -int128 (absoluteResult); // We rely on overflow behavior here\n } else {\n require (absoluteResult <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n return int128 (absoluteResult); // We rely on overflow behavior here\n }\n }\n }\n\n /**\n * Calculate x / y rounding towards zero, where x and y are unsigned 256-bit\n * integer numbers. Revert on overflow or when y is zero.\n *\n * @param x unsigned 256-bit integer number\n * @param y unsigned 256-bit integer number\n * @return signed 64.64-bit fixed point number\n */\n function divu (uint256 x, uint256 y) internal pure returns (int128) {\n unchecked {\n require (y != 0);\n uint128 result = divuu (x, y);\n require (result <= uint128 (MAX_64x64));\n return int128 (result);\n }\n }\n\n /**\n * Calculate -x. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function neg (int128 x) internal pure returns (int128) {\n unchecked {\n require (x != MIN_64x64);\n return -x;\n }\n }\n\n /**\n * Calculate |x|. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function abs (int128 x) internal pure returns (int128) {\n unchecked {\n require (x != MIN_64x64);\n return x < 0 ? -x : x;\n }\n }\n\n /**\n * Calculate 1 / x rounding towards zero. Revert on overflow or when x is\n * zero.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function inv (int128 x) internal pure returns (int128) {\n unchecked {\n require (x != 0);\n int256 result = int256 (0x100000000000000000000000000000000) / x;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate arithmetics average of x and y, i.e. (x + y) / 2 rounding down.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function avg (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n return int128 ((int256 (x) + int256 (y)) >> 1);\n }\n }\n\n /**\n * Calculate geometric average of x and y, i.e. sqrt (x * y) rounding down.\n * Revert on overflow or in case x * y is negative.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function gavg (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n int256 m = int256 (x) * int256 (y);\n require (m >= 0);\n require (m <\n 0x4000000000000000000000000000000000000000000000000000000000000000);\n return int128 (sqrtu (uint256 (m)));\n }\n }\n\n /**\n * Calculate x^y assuming 0^0 is 1, where x is signed 64.64 fixed point number\n * and y is unsigned 256-bit integer number. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y uint256 value\n * @return signed 64.64-bit fixed point number\n */\n function pow (int128 x, uint256 y) internal pure returns (int128) {\n unchecked {\n bool negative = x < 0 && y & 1 == 1;\n\n uint256 absX = uint128 (x < 0 ? -x : x);\n uint256 absResult;\n absResult = 0x100000000000000000000000000000000;\n\n if (absX <= 0x10000000000000000) {\n absX <<= 63;\n while (y != 0) {\n if (y & 0x1 != 0) {\n absResult = absResult * absX >> 127;\n }\n absX = absX * absX >> 127;\n\n if (y & 0x2 != 0) {\n absResult = absResult * absX >> 127;\n }\n absX = absX * absX >> 127;\n\n if (y & 0x4 != 0) {\n absResult = absResult * absX >> 127;\n }\n absX = absX * absX >> 127;\n\n if (y & 0x8 != 0) {\n absResult = absResult * absX >> 127;\n }\n absX = absX * absX >> 127;\n\n y >>= 4;\n }\n\n absResult >>= 64;\n } else {\n uint256 absXShift = 63;\n if (absX < 0x1000000000000000000000000) { absX <<= 32; absXShift -= 32; }\n if (absX < 0x10000000000000000000000000000) { absX <<= 16; absXShift -= 16; }\n if (absX < 0x1000000000000000000000000000000) { absX <<= 8; absXShift -= 8; }\n if (absX < 0x10000000000000000000000000000000) { absX <<= 4; absXShift -= 4; }\n if (absX < 0x40000000000000000000000000000000) { absX <<= 2; absXShift -= 2; }\n if (absX < 0x80000000000000000000000000000000) { absX <<= 1; absXShift -= 1; }\n\n uint256 resultShift = 0;\n while (y != 0) {\n require (absXShift < 64);\n\n if (y & 0x1 != 0) {\n absResult = absResult * absX >> 127;\n resultShift += absXShift;\n if (absResult > 0x100000000000000000000000000000000) {\n absResult >>= 1;\n resultShift += 1;\n }\n }\n absX = absX * absX >> 127;\n absXShift <<= 1;\n if (absX >= 0x100000000000000000000000000000000) {\n absX >>= 1;\n absXShift += 1;\n }\n\n y >>= 1;\n }\n\n require (resultShift < 64);\n absResult >>= 64 - resultShift;\n }\n int256 result = negative ? -int256 (absResult) : int256 (absResult);\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate sqrt (x) rounding down. Revert if x < 0.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function sqrt (int128 x) internal pure returns (int128) {\n unchecked {\n require (x >= 0);\n return int128 (sqrtu (uint256 (int256 (x)) << 64));\n }\n }\n\n /**\n * Calculate binary logarithm of x. Revert if x <= 0.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function log_2 (int128 x) internal pure returns (int128) {\n unchecked {\n require (x > 0);\n\n int256 msb = 0;\n int256 xc = x;\n if (xc >= 0x10000000000000000) { xc >>= 64; msb += 64; }\n if (xc >= 0x100000000) { xc >>= 32; msb += 32; }\n if (xc >= 0x10000) { xc >>= 16; msb += 16; }\n if (xc >= 0x100) { xc >>= 8; msb += 8; }\n if (xc >= 0x10) { xc >>= 4; msb += 4; }\n if (xc >= 0x4) { xc >>= 2; msb += 2; }\n if (xc >= 0x2) msb += 1; // No need to shift xc anymore\n\n int256 result = msb - 64 << 64;\n uint256 ux = uint256 (int256 (x)) << uint256 (127 - msb);\n for (int256 bit = 0x8000000000000000; bit > 0; bit >>= 1) {\n ux *= ux;\n uint256 b = ux >> 255;\n ux >>= 127 + b;\n result += bit * int256 (b);\n }\n\n return int128 (result);\n }\n }\n\n /**\n * Calculate natural logarithm of x. Revert if x <= 0.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function ln (int128 x) internal pure returns (int128) {\n unchecked {\n require (x > 0);\n\n return int128 (int256 (\n uint256 (int256 (log_2 (x))) * 0xB17217F7D1CF79ABC9E3B39803F2F6AF >> 128));\n }\n }\n\n /**\n * Calculate binary exponent of x. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function exp_2 (int128 x) internal pure returns (int128) {\n unchecked {\n require (x < 0x400000000000000000); // Overflow\n\n if (x < -0x400000000000000000) return 0; // Underflow\n\n uint256 result = 0x80000000000000000000000000000000;\n\n if (x & 0x8000000000000000 > 0)\n result = result * 0x16A09E667F3BCC908B2FB1366EA957D3E >> 128;\n if (x & 0x4000000000000000 > 0)\n result = result * 0x1306FE0A31B7152DE8D5A46305C85EDEC >> 128;\n if (x & 0x2000000000000000 > 0)\n result = result * 0x1172B83C7D517ADCDF7C8C50EB14A791F >> 128;\n if (x & 0x1000000000000000 > 0)\n result = result * 0x10B5586CF9890F6298B92B71842A98363 >> 128;\n if (x & 0x800000000000000 > 0)\n result = result * 0x1059B0D31585743AE7C548EB68CA417FD >> 128;\n if (x & 0x400000000000000 > 0)\n result = result * 0x102C9A3E778060EE6F7CACA4F7A29BDE8 >> 128;\n if (x & 0x200000000000000 > 0)\n result = result * 0x10163DA9FB33356D84A66AE336DCDFA3F >> 128;\n if (x & 0x100000000000000 > 0)\n result = result * 0x100B1AFA5ABCBED6129AB13EC11DC9543 >> 128;\n if (x & 0x80000000000000 > 0)\n result = result * 0x10058C86DA1C09EA1FF19D294CF2F679B >> 128;\n if (x & 0x40000000000000 > 0)\n result = result * 0x1002C605E2E8CEC506D21BFC89A23A00F >> 128;\n if (x & 0x20000000000000 > 0)\n result = result * 0x100162F3904051FA128BCA9C55C31E5DF >> 128;\n if (x & 0x10000000000000 > 0)\n result = result * 0x1000B175EFFDC76BA38E31671CA939725 >> 128;\n if (x & 0x8000000000000 > 0)\n result = result * 0x100058BA01FB9F96D6CACD4B180917C3D >> 128;\n if (x & 0x4000000000000 > 0)\n result = result * 0x10002C5CC37DA9491D0985C348C68E7B3 >> 128;\n if (x & 0x2000000000000 > 0)\n result = result * 0x1000162E525EE054754457D5995292026 >> 128;\n if (x & 0x1000000000000 > 0)\n result = result * 0x10000B17255775C040618BF4A4ADE83FC >> 128;\n if (x & 0x800000000000 > 0)\n result = result * 0x1000058B91B5BC9AE2EED81E9B7D4CFAB >> 128;\n if (x & 0x400000000000 > 0)\n result = result * 0x100002C5C89D5EC6CA4D7C8ACC017B7C9 >> 128;\n if (x & 0x200000000000 > 0)\n result = result * 0x10000162E43F4F831060E02D839A9D16D >> 128;\n if (x & 0x100000000000 > 0)\n result = result * 0x100000B1721BCFC99D9F890EA06911763 >> 128;\n if (x & 0x80000000000 > 0)\n result = result * 0x10000058B90CF1E6D97F9CA14DBCC1628 >> 128;\n if (x & 0x40000000000 > 0)\n result = result * 0x1000002C5C863B73F016468F6BAC5CA2B >> 128;\n if (x & 0x20000000000 > 0)\n result = result * 0x100000162E430E5A18F6119E3C02282A5 >> 128;\n if (x & 0x10000000000 > 0)\n result = result * 0x1000000B1721835514B86E6D96EFD1BFE >> 128;\n if (x & 0x8000000000 > 0)\n result = result * 0x100000058B90C0B48C6BE5DF846C5B2EF >> 128;\n if (x & 0x4000000000 > 0)\n result = result * 0x10000002C5C8601CC6B9E94213C72737A >> 128;\n if (x & 0x2000000000 > 0)\n result = result * 0x1000000162E42FFF037DF38AA2B219F06 >> 128;\n if (x & 0x1000000000 > 0)\n result = result * 0x10000000B17217FBA9C739AA5819F44F9 >> 128;\n if (x & 0x800000000 > 0)\n result = result * 0x1000000058B90BFCDEE5ACD3C1CEDC823 >> 128;\n if (x & 0x400000000 > 0)\n result = result * 0x100000002C5C85FE31F35A6A30DA1BE50 >> 128;\n if (x & 0x200000000 > 0)\n result = result * 0x10000000162E42FF0999CE3541B9FFFCF >> 128;\n if (x & 0x100000000 > 0)\n result = result * 0x100000000B17217F80F4EF5AADDA45554 >> 128;\n if (x & 0x80000000 > 0)\n result = result * 0x10000000058B90BFBF8479BD5A81B51AD >> 128;\n if (x & 0x40000000 > 0)\n result = result * 0x1000000002C5C85FDF84BD62AE30A74CC >> 128;\n if (x & 0x20000000 > 0)\n result = result * 0x100000000162E42FEFB2FED257559BDAA >> 128;\n if (x & 0x10000000 > 0)\n result = result * 0x1000000000B17217F7D5A7716BBA4A9AE >> 128;\n if (x & 0x8000000 > 0)\n result = result * 0x100000000058B90BFBE9DDBAC5E109CCE >> 128;\n if (x & 0x4000000 > 0)\n result = result * 0x10000000002C5C85FDF4B15DE6F17EB0D >> 128;\n if (x & 0x2000000 > 0)\n result = result * 0x1000000000162E42FEFA494F1478FDE05 >> 128;\n if (x & 0x1000000 > 0)\n result = result * 0x10000000000B17217F7D20CF927C8E94C >> 128;\n if (x & 0x800000 > 0)\n result = result * 0x1000000000058B90BFBE8F71CB4E4B33D >> 128;\n if (x & 0x400000 > 0)\n result = result * 0x100000000002C5C85FDF477B662B26945 >> 128;\n if (x & 0x200000 > 0)\n result = result * 0x10000000000162E42FEFA3AE53369388C >> 128;\n if (x & 0x100000 > 0)\n result = result * 0x100000000000B17217F7D1D351A389D40 >> 128;\n if (x & 0x80000 > 0)\n result = result * 0x10000000000058B90BFBE8E8B2D3D4EDE >> 128;\n if (x & 0x40000 > 0)\n result = result * 0x1000000000002C5C85FDF4741BEA6E77E >> 128;\n if (x & 0x20000 > 0)\n result = result * 0x100000000000162E42FEFA39FE95583C2 >> 128;\n if (x & 0x10000 > 0)\n result = result * 0x1000000000000B17217F7D1CFB72B45E1 >> 128;\n if (x & 0x8000 > 0)\n result = result * 0x100000000000058B90BFBE8E7CC35C3F0 >> 128;\n if (x & 0x4000 > 0)\n result = result * 0x10000000000002C5C85FDF473E242EA38 >> 128;\n if (x & 0x2000 > 0)\n result = result * 0x1000000000000162E42FEFA39F02B772C >> 128;\n if (x & 0x1000 > 0)\n result = result * 0x10000000000000B17217F7D1CF7D83C1A >> 128;\n if (x & 0x800 > 0)\n result = result * 0x1000000000000058B90BFBE8E7BDCBE2E >> 128;\n if (x & 0x400 > 0)\n result = result * 0x100000000000002C5C85FDF473DEA871F >> 128;\n if (x & 0x200 > 0)\n result = result * 0x10000000000000162E42FEFA39EF44D91 >> 128;\n if (x & 0x100 > 0)\n result = result * 0x100000000000000B17217F7D1CF79E949 >> 128;\n if (x & 0x80 > 0)\n result = result * 0x10000000000000058B90BFBE8E7BCE544 >> 128;\n if (x & 0x40 > 0)\n result = result * 0x1000000000000002C5C85FDF473DE6ECA >> 128;\n if (x & 0x20 > 0)\n result = result * 0x100000000000000162E42FEFA39EF366F >> 128;\n if (x & 0x10 > 0)\n result = result * 0x1000000000000000B17217F7D1CF79AFA >> 128;\n if (x & 0x8 > 0)\n result = result * 0x100000000000000058B90BFBE8E7BCD6D >> 128;\n if (x & 0x4 > 0)\n result = result * 0x10000000000000002C5C85FDF473DE6B2 >> 128;\n if (x & 0x2 > 0)\n result = result * 0x1000000000000000162E42FEFA39EF358 >> 128;\n if (x & 0x1 > 0)\n result = result * 0x10000000000000000B17217F7D1CF79AB >> 128;\n\n result >>= uint256 (int256 (63 - (x >> 64)));\n require (result <= uint256 (int256 (MAX_64x64)));\n\n return int128 (int256 (result));\n }\n }\n\n /**\n * Calculate natural exponent of x. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function exp (int128 x) internal pure returns (int128) {\n unchecked {\n require (x < 0x400000000000000000); // Overflow\n\n if (x < -0x400000000000000000) return 0; // Underflow\n\n return exp_2 (\n int128 (int256 (x) * 0x171547652B82FE1777D0FFDA0D23A7D12 >> 128));\n }\n }\n\n /**\n * Calculate x / y rounding towards zero, where x and y are unsigned 256-bit\n * integer numbers. Revert on overflow or when y is zero.\n *\n * @param x unsigned 256-bit integer number\n * @param y unsigned 256-bit integer number\n * @return unsigned 64.64-bit fixed point number\n */\n function divuu (uint256 x, uint256 y) private pure returns (uint128) {\n unchecked {\n require (y != 0);\n\n uint256 result;\n\n if (x <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)\n result = (x << 64) / y;\n else {\n uint256 msb = 192;\n uint256 xc = x >> 192;\n if (xc >= 0x100000000) { xc >>= 32; msb += 32; }\n if (xc >= 0x10000) { xc >>= 16; msb += 16; }\n if (xc >= 0x100) { xc >>= 8; msb += 8; }\n if (xc >= 0x10) { xc >>= 4; msb += 4; }\n if (xc >= 0x4) { xc >>= 2; msb += 2; }\n if (xc >= 0x2) msb += 1; // No need to shift xc anymore\n\n result = (x << 255 - msb) / ((y - 1 >> msb - 191) + 1);\n require (result <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n\n uint256 hi = result * (y >> 128);\n uint256 lo = result * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n\n uint256 xh = x >> 192;\n uint256 xl = x << 64;\n\n if (xl < lo) xh -= 1;\n xl -= lo; // We rely on overflow behavior here\n lo = hi << 128;\n if (xl < lo) xh -= 1;\n xl -= lo; // We rely on overflow behavior here\n\n assert (xh == hi >> 128);\n\n result += xl / y;\n }\n\n require (result <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n return uint128 (result);\n }\n }\n\n /**\n * Calculate sqrt (x) rounding down, where x is unsigned 256-bit integer\n * number.\n *\n * @param x unsigned 256-bit integer number\n * @return unsigned 128-bit integer number\n */\n function sqrtu (uint256 x) private pure returns (uint128) {\n unchecked {\n if (x == 0) return 0;\n else {\n uint256 xx = x;\n uint256 r = 1;\n if (xx >= 0x100000000000000000000000000000000) { xx >>= 128; r <<= 64; }\n if (xx >= 0x10000000000000000) { xx >>= 64; r <<= 32; }\n if (xx >= 0x100000000) { xx >>= 32; r <<= 16; }\n if (xx >= 0x10000) { xx >>= 16; r <<= 8; }\n if (xx >= 0x100) { xx >>= 8; r <<= 4; }\n if (xx >= 0x10) { xx >>= 4; r <<= 2; }\n if (xx >= 0x8) { r <<= 1; }\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1; // Seven iterations should be enough\n uint256 r1 = x / r;\n return uint128 (r < r1 ? r : r1);\n }\n }\n }\n}\n"},"@solidstate/contracts/token/ERC20/metadata/IERC20Metadata.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title ERC20 metadata interface\n */\ninterface IERC20Metadata {\n /**\n * @notice return token name\n * @return token name\n */\n function name() external view returns (string memory);\n\n /**\n * @notice return token symbol\n * @return token symbol\n */\n function symbol() external view returns (string memory);\n\n /**\n * @notice return token decimals, generally used only for display purposes\n * @return token decimals\n */\n function decimals() external view returns (uint8);\n}\n"},"contracts/staking/FeeDiscountStorage.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nlibrary FeeDiscountStorage {\r\n bytes32 internal constant STORAGE_SLOT =\r\n keccak256(\"premia.contracts.staking.PremiaFeeDiscount\");\r\n\r\n struct UserInfo {\r\n uint256 balance; // Balance staked by user\r\n uint64 stakePeriod; // Stake period selected by user\r\n uint64 lockedUntil; // Timestamp at which the lock ends\r\n }\r\n\r\n struct Layout {\r\n // User data with xPREMIA balance staked and date at which lock ends\r\n mapping(address => UserInfo) userInfo;\r\n }\r\n\r\n function layout() internal pure returns (Layout storage l) {\r\n bytes32 slot = STORAGE_SLOT;\r\n assembly {\r\n l.slot := slot\r\n }\r\n }\r\n}\r\n"},"contracts/libraries/OptionMath.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {ABDKMath64x64} from \"abdk-libraries-solidity/ABDKMath64x64.sol\";\r\n\r\nlibrary OptionMath {\r\n using ABDKMath64x64 for int128;\r\n\r\n struct QuoteArgs {\r\n int128 varianceAnnualized64x64; // 64x64 fixed point representation of annualized variance\r\n int128 strike64x64; // 64x64 fixed point representation of strike price\r\n int128 spot64x64; // 64x64 fixed point representation of spot price\r\n int128 timeToMaturity64x64; // 64x64 fixed point representation of duration of option contract (in years)\r\n int128 oldCLevel64x64; // 64x64 fixed point representation of C-Level of Pool before purchase\r\n int128 oldPoolState; // 64x64 fixed point representation of current state of the pool\r\n int128 newPoolState; // 64x64 fixed point representation of state of the pool after trade\r\n int128 steepness64x64; // 64x64 fixed point representation of Pool state delta multiplier\r\n int128 minAPY64x64; // 64x64 fixed point representation of minimum APY for capital locked up to underwrite options\r\n bool isCall; // whether to price \"call\" or \"put\" option\r\n }\r\n\r\n struct CalculateCLevelDecayArgs {\r\n int128 timeIntervalsElapsed64x64; // 64x64 fixed point representation of quantity of discrete arbitrary intervals elapsed since last update\r\n int128 oldCLevel64x64; // 64x64 fixed point representation of C-Level prior to accounting for decay\r\n int128 utilization64x64; // 64x64 fixed point representation of pool capital utilization rate\r\n int128 utilizationLowerBound64x64;\r\n int128 utilizationUpperBound64x64;\r\n int128 cLevelLowerBound64x64;\r\n int128 cLevelUpperBound64x64;\r\n int128 cConvergenceULowerBound64x64;\r\n int128 cConvergenceUUpperBound64x64;\r\n }\r\n\r\n // 64x64 fixed point integer constants\r\n int128 internal constant ONE_64x64 = 0x10000000000000000;\r\n int128 internal constant THREE_64x64 = 0x30000000000000000;\r\n\r\n // 64x64 fixed point constants used in Choudhury’s approximation of the Black-Scholes CDF\r\n int128 private constant CDF_CONST_0 = 0x09109f285df452394; // 2260 / 3989\r\n int128 private constant CDF_CONST_1 = 0x19abac0ea1da65036; // 6400 / 3989\r\n int128 private constant CDF_CONST_2 = 0x0d3c84b78b749bd6b; // 3300 / 3989\r\n\r\n /**\r\n * @notice recalculate C-Level based on change in liquidity\r\n * @param initialCLevel64x64 64x64 fixed point representation of C-Level of Pool before update\r\n * @param oldPoolState64x64 64x64 fixed point representation of liquidity in pool before update\r\n * @param newPoolState64x64 64x64 fixed point representation of liquidity in pool after update\r\n * @param steepness64x64 64x64 fixed point representation of steepness coefficient\r\n * @return 64x64 fixed point representation of new C-Level\r\n */\r\n function calculateCLevel(\r\n int128 initialCLevel64x64,\r\n int128 oldPoolState64x64,\r\n int128 newPoolState64x64,\r\n int128 steepness64x64\r\n ) external pure returns (int128) {\r\n return\r\n newPoolState64x64\r\n .sub(oldPoolState64x64)\r\n .div(\r\n oldPoolState64x64 > newPoolState64x64\r\n ? oldPoolState64x64\r\n : newPoolState64x64\r\n )\r\n .mul(steepness64x64)\r\n .neg()\r\n .exp()\r\n .mul(initialCLevel64x64);\r\n }\r\n\r\n /**\r\n * @notice calculate the price of an option using the Premia Finance model\r\n * @param args arguments of quotePrice\r\n * @return premiaPrice64x64 64x64 fixed point representation of Premia option price\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level of Pool after purchase\r\n */\r\n function quotePrice(QuoteArgs memory args)\r\n external\r\n pure\r\n returns (\r\n int128 premiaPrice64x64,\r\n int128 cLevel64x64,\r\n int128 slippageCoefficient64x64\r\n )\r\n {\r\n int128 deltaPoolState64x64 = args\r\n .newPoolState\r\n .sub(args.oldPoolState)\r\n .div(args.oldPoolState)\r\n .mul(args.steepness64x64);\r\n int128 tradingDelta64x64 = deltaPoolState64x64.neg().exp();\r\n\r\n int128 blackScholesPrice64x64 = _blackScholesPrice(\r\n args.varianceAnnualized64x64,\r\n args.strike64x64,\r\n args.spot64x64,\r\n args.timeToMaturity64x64,\r\n args.isCall\r\n );\r\n\r\n cLevel64x64 = tradingDelta64x64.mul(args.oldCLevel64x64);\r\n slippageCoefficient64x64 = ONE_64x64.sub(tradingDelta64x64).div(\r\n deltaPoolState64x64\r\n );\r\n\r\n premiaPrice64x64 = blackScholesPrice64x64.mul(cLevel64x64).mul(\r\n slippageCoefficient64x64\r\n );\r\n\r\n int128 intrinsicValue64x64;\r\n\r\n if (args.isCall && args.strike64x64 < args.spot64x64) {\r\n intrinsicValue64x64 = args.spot64x64.sub(args.strike64x64);\r\n } else if (!args.isCall && args.strike64x64 > args.spot64x64) {\r\n intrinsicValue64x64 = args.strike64x64.sub(args.spot64x64);\r\n }\r\n\r\n int128 collateralValue64x64 = args.isCall\r\n ? args.spot64x64\r\n : args.strike64x64;\r\n\r\n int128 minPrice64x64 = intrinsicValue64x64.add(\r\n collateralValue64x64.mul(args.minAPY64x64).mul(\r\n args.timeToMaturity64x64\r\n )\r\n );\r\n\r\n if (minPrice64x64 > premiaPrice64x64) {\r\n premiaPrice64x64 = minPrice64x64;\r\n }\r\n }\r\n\r\n /**\r\n * @notice calculate the decay of C-Level based on heat diffusion function\r\n * @param args structured CalculateCLevelDecayArgs\r\n * @return cLevelDecayed64x64 C-Level after accounting for decay\r\n */\r\n function calculateCLevelDecay(CalculateCLevelDecayArgs memory args)\r\n external\r\n pure\r\n returns (int128 cLevelDecayed64x64)\r\n {\r\n int128 convFHighU64x64 = (args.utilization64x64 >=\r\n args.utilizationUpperBound64x64 &&\r\n args.oldCLevel64x64 <= args.cLevelLowerBound64x64)\r\n ? ONE_64x64\r\n : int128(0);\r\n\r\n int128 convFLowU64x64 = (args.utilization64x64 <=\r\n args.utilizationLowerBound64x64 &&\r\n args.oldCLevel64x64 >= args.cLevelUpperBound64x64)\r\n ? ONE_64x64\r\n : int128(0);\r\n\r\n cLevelDecayed64x64 = args\r\n .oldCLevel64x64\r\n .sub(args.cConvergenceULowerBound64x64.mul(convFLowU64x64))\r\n .sub(args.cConvergenceUUpperBound64x64.mul(convFHighU64x64))\r\n .mul(\r\n convFLowU64x64\r\n .mul(ONE_64x64.sub(args.utilization64x64))\r\n .add(convFHighU64x64.mul(args.utilization64x64))\r\n .mul(args.timeIntervalsElapsed64x64)\r\n .neg()\r\n .exp()\r\n )\r\n .add(\r\n args.cConvergenceULowerBound64x64.mul(convFLowU64x64).add(\r\n args.cConvergenceUUpperBound64x64.mul(convFHighU64x64)\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * @notice calculate the exponential decay coefficient for a given interval\r\n * @param oldTimestamp timestamp of previous update\r\n * @param newTimestamp current timestamp\r\n * @return 64x64 fixed point representation of exponential decay coefficient\r\n */\r\n function _decay(uint256 oldTimestamp, uint256 newTimestamp)\r\n internal\r\n pure\r\n returns (int128)\r\n {\r\n return\r\n ONE_64x64.sub(\r\n (-ABDKMath64x64.divu(newTimestamp - oldTimestamp, 7 days)).exp()\r\n );\r\n }\r\n\r\n /**\r\n * @notice calculate Choudhury’s approximation of the Black-Scholes CDF\r\n * @param input64x64 64x64 fixed point representation of random variable\r\n * @return 64x64 fixed point representation of the approximated CDF of x\r\n */\r\n function _N(int128 input64x64) internal pure returns (int128) {\r\n // squaring via mul is cheaper than via pow\r\n int128 inputSquared64x64 = input64x64.mul(input64x64);\r\n\r\n int128 value64x64 = (-inputSquared64x64 >> 1).exp().div(\r\n CDF_CONST_0.add(CDF_CONST_1.mul(input64x64.abs())).add(\r\n CDF_CONST_2.mul(inputSquared64x64.add(THREE_64x64).sqrt())\r\n )\r\n );\r\n\r\n return input64x64 > 0 ? ONE_64x64.sub(value64x64) : value64x64;\r\n }\r\n\r\n /**\r\n * @notice calculate the price of an option using the Black-Scholes model\r\n * @param varianceAnnualized64x64 64x64 fixed point representation of annualized variance\r\n * @param strike64x64 64x64 fixed point representation of strike price\r\n * @param spot64x64 64x64 fixed point representation of spot price\r\n * @param timeToMaturity64x64 64x64 fixed point representation of duration of option contract (in years)\r\n * @param isCall whether to price \"call\" or \"put\" option\r\n * @return 64x64 fixed point representation of Black-Scholes option price\r\n */\r\n function _blackScholesPrice(\r\n int128 varianceAnnualized64x64,\r\n int128 strike64x64,\r\n int128 spot64x64,\r\n int128 timeToMaturity64x64,\r\n bool isCall\r\n ) internal pure returns (int128) {\r\n int128 cumulativeVariance64x64 = timeToMaturity64x64.mul(\r\n varianceAnnualized64x64\r\n );\r\n int128 cumulativeVarianceSqrt64x64 = cumulativeVariance64x64.sqrt();\r\n\r\n int128 d1_64x64 = spot64x64\r\n .div(strike64x64)\r\n .ln()\r\n .add(cumulativeVariance64x64 >> 1)\r\n .div(cumulativeVarianceSqrt64x64);\r\n int128 d2_64x64 = d1_64x64.sub(cumulativeVarianceSqrt64x64);\r\n\r\n if (isCall) {\r\n return\r\n spot64x64.mul(_N(d1_64x64)).sub(strike64x64.mul(_N(d2_64x64)));\r\n } else {\r\n return\r\n -spot64x64.mul(_N(-d1_64x64)).sub(\r\n strike64x64.mul(_N(-d2_64x64))\r\n );\r\n }\r\n }\r\n}\r\n"},"@solidstate/contracts/access/IERC173.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title Contract ownership standard interface\n * @dev see https://eips.ethereum.org/EIPS/eip-173\n */\ninterface IERC173 {\n event OwnershipTransferred(\n address indexed previousOwner,\n address indexed newOwner\n );\n\n /**\n * @notice get the ERC173 contract owner\n * @return conract owner\n */\n function owner() external view returns (address);\n\n /**\n * @notice transfer contract ownership to new account\n * @param account address of new owner\n */\n function transferOwnership(address account) external;\n}\n"},"contracts/libraries/ABDKMath64x64Token.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {ABDKMath64x64} from \"abdk-libraries-solidity/ABDKMath64x64.sol\";\r\n\r\nlibrary ABDKMath64x64Token {\r\n using ABDKMath64x64 for int128;\r\n\r\n /**\r\n * @notice convert 64x64 fixed point representation of token amount to decimal\r\n * @param value64x64 64x64 fixed point representation of token amount\r\n * @param decimals token display decimals\r\n * @return value decimal representation of token amount\r\n */\r\n function toDecimals(int128 value64x64, uint8 decimals)\r\n internal\r\n pure\r\n returns (uint256 value)\r\n {\r\n value = value64x64.mulu(10**decimals);\r\n }\r\n\r\n /**\r\n * @notice convert decimal representation of token amount to 64x64 fixed point\r\n * @param value decimal representation of token amount\r\n * @param decimals token display decimals\r\n * @return value64x64 64x64 fixed point representation of token amount\r\n */\r\n function fromDecimals(uint256 value, uint8 decimals)\r\n internal\r\n pure\r\n returns (int128 value64x64)\r\n {\r\n value64x64 = ABDKMath64x64.divu(value, 10**decimals);\r\n }\r\n\r\n /**\r\n * @notice convert 64x64 fixed point representation of token amount to wei (18 decimals)\r\n * @param value64x64 64x64 fixed point representation of token amount\r\n * @return value wei representation of token amount\r\n */\r\n function toWei(int128 value64x64) internal pure returns (uint256 value) {\r\n value = toDecimals(value64x64, 18);\r\n }\r\n\r\n /**\r\n * @notice convert wei representation (18 decimals) of token amount to 64x64 fixed point\r\n * @param value wei representation of token amount\r\n * @return value64x64 64x64 fixed point representation of token amount\r\n */\r\n function fromWei(uint256 value) internal pure returns (int128 value64x64) {\r\n value64x64 = fromDecimals(value, 18);\r\n }\r\n}\r\n"},"@solidstate/contracts/token/ERC1155/IERC1155.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC1155Internal } from './IERC1155Internal.sol';\nimport { IERC165 } from '../../introspection/IERC165.sol';\n\n/**\n * @notice ERC1155 interface\n * @dev see https://github.com/ethereum/EIPs/issues/1155\n */\ninterface IERC1155 is IERC1155Internal, IERC165 {\n /**\n * @notice query the balance of given token held by given address\n * @param account address to query\n * @param id token to query\n * @return token balance\n */\n function balanceOf(address account, uint256 id)\n external\n view\n returns (uint256);\n\n /**\n * @notice query the balances of given tokens held by given addresses\n * @param accounts addresss to query\n * @param ids tokens to query\n * @return token balances\n */\n function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)\n external\n view\n returns (uint256[] memory);\n\n /**\n * @notice query approval status of given operator with respect to given address\n * @param account address to query for approval granted\n * @param operator address to query for approval received\n * @return whether operator is approved to spend tokens held by account\n */\n function isApprovedForAll(address account, address operator)\n external\n view\n returns (bool);\n\n /**\n * @notice grant approval to or revoke approval from given operator to spend held tokens\n * @param operator address whose approval status to update\n * @param status whether operator should be considered approved\n */\n function setApprovalForAll(address operator, bool status) external;\n\n /**\n * @notice transfer tokens between given addresses, checking for ERC1155Receiver implementation if applicable\n * @param from sender of tokens\n * @param to receiver of tokens\n * @param id token ID\n * @param amount quantity of tokens to transfer\n * @param data data payload\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 id,\n uint256 amount,\n bytes calldata data\n ) external;\n\n /**\n * @notice transfer batch of tokens between given addresses, checking for ERC1155Receiver implementation if applicable\n * @param from sender of tokens\n * @param to receiver of tokens\n * @param ids list of token IDs\n * @param amounts list of quantities of tokens to transfer\n * @param data data payload\n */\n function safeBatchTransferFrom(\n address from,\n address to,\n uint256[] calldata ids,\n uint256[] calldata amounts,\n bytes calldata data\n ) external;\n}\n"},"contracts/staking/IFeeDiscount.sol":{"content":"// SPDX-License-Identifier: LGPL-3.0-or-later\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {FeeDiscountStorage} from \"./FeeDiscountStorage.sol\";\r\n\r\ninterface IFeeDiscount {\r\n event Staked(\r\n address indexed user,\r\n uint256 amount,\r\n uint256 stakePeriod,\r\n uint256 lockedUntil\r\n );\r\n event Unstaked(address indexed user, uint256 amount);\r\n\r\n struct StakeLevel {\r\n uint256 amount; // Amount to stake\r\n uint256 discount; // Discount when amount is reached\r\n }\r\n\r\n /**\r\n * @notice Stake using IERC2612 permit\r\n * @param amount The amount of xPremia to stake\r\n * @param period The lockup period (in seconds)\r\n * @param deadline Deadline after which permit will fail\r\n * @param v V\r\n * @param r R\r\n * @param s S\r\n */\r\n function stakeWithPermit(\r\n uint256 amount,\r\n uint256 period,\r\n uint256 deadline,\r\n uint8 v,\r\n bytes32 r,\r\n bytes32 s\r\n ) external;\r\n\r\n /**\r\n * @notice Lockup xPremia for protocol fee discounts\r\n * Longer period of locking will apply a multiplier on the amount staked, in the fee discount calculation\r\n * @param amount The amount of xPremia to stake\r\n * @param period The lockup period (in seconds)\r\n */\r\n function stake(uint256 amount, uint256 period) external;\r\n\r\n /**\r\n * @notice Unstake xPremia (If lockup period has ended)\r\n * @param amount The amount of xPremia to unstake\r\n */\r\n function unstake(uint256 amount) external;\r\n\r\n //////////\r\n // View //\r\n //////////\r\n\r\n /**\r\n * Calculate the stake amount of a user, after applying the bonus from the lockup period chosen\r\n * @param user The user from which to query the stake amount\r\n * @return The user stake amount after applying the bonus\r\n */\r\n function getStakeAmountWithBonus(address user)\r\n external\r\n view\r\n returns (uint256);\r\n\r\n /**\r\n * @notice Calculate the % of fee discount for user, based on his stake\r\n * @param user The _user for which the discount is for\r\n * @return Percentage of protocol fee discount (in basis point)\r\n * Ex : 1000 = 10% fee discount\r\n */\r\n function getDiscount(address user) external view returns (uint256);\r\n\r\n /**\r\n * @notice Get stake levels\r\n * @return Stake levels\r\n * Ex : 2500 = -25%\r\n */\r\n function getStakeLevels() external returns (StakeLevel[] memory);\r\n\r\n /**\r\n * @notice Get stake period multiplier\r\n * @param period The duration (in seconds) for which tokens are locked\r\n * @return The multiplier for this staking period\r\n * Ex : 20000 = x2\r\n */\r\n function getStakePeriodMultiplier(uint256 period)\r\n external\r\n returns (uint256);\r\n\r\n /**\r\n * @notice Get staking infos of a user\r\n * @param user The user address for which to get staking infos\r\n * @return The staking infos of the user\r\n */\r\n function getUserInfo(address user)\r\n external\r\n view\r\n returns (FeeDiscountStorage.UserInfo memory);\r\n}\r\n"},"@solidstate/contracts/token/ERC1155/base/ERC1155BaseInternal.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { AddressUtils } from '../../../utils/AddressUtils.sol';\nimport { IERC1155Internal } from '../IERC1155Internal.sol';\nimport { IERC1155Receiver } from '../IERC1155Receiver.sol';\nimport { ERC1155BaseStorage } from './ERC1155BaseStorage.sol';\n\n/**\n * @title Base ERC1155 internal functions\n * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license)\n */\nabstract contract ERC1155BaseInternal is IERC1155Internal {\n using AddressUtils for address;\n\n /**\n * @notice query the balance of given token held by given address\n * @param account address to query\n * @param id token to query\n * @return token balance\n */\n function _balanceOf(address account, uint256 id)\n internal\n view\n virtual\n returns (uint256)\n {\n require(\n account != address(0),\n 'ERC1155: balance query for the zero address'\n );\n return ERC1155BaseStorage.layout().balances[id][account];\n }\n\n /**\n * @notice mint given quantity of tokens for given address\n * @dev ERC1155Receiver implementation is not checked\n * @param account beneficiary of minting\n * @param id token ID\n * @param amount quantity of tokens to mint\n * @param data data payload\n */\n function _mint(\n address account,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) internal virtual {\n require(account != address(0), 'ERC1155: mint to the zero address');\n\n _beforeTokenTransfer(\n msg.sender,\n address(0),\n account,\n _asSingletonArray(id),\n _asSingletonArray(amount),\n data\n );\n\n mapping(address => uint256) storage balances = ERC1155BaseStorage\n .layout()\n .balances[id];\n balances[account] += amount;\n\n emit TransferSingle(msg.sender, address(0), account, id, amount);\n }\n\n /**\n * @notice mint given quantity of tokens for given address\n * @param account beneficiary of minting\n * @param id token ID\n * @param amount quantity of tokens to mint\n * @param data data payload\n */\n function _safeMint(\n address account,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) internal virtual {\n _mint(account, id, amount, data);\n\n _doSafeTransferAcceptanceCheck(\n msg.sender,\n address(0),\n account,\n id,\n amount,\n data\n );\n }\n\n /**\n * @notice mint batch of tokens for given address\n * @dev ERC1155Receiver implementation is not checked\n * @param account beneficiary of minting\n * @param ids list of token IDs\n * @param amounts list of quantities of tokens to mint\n * @param data data payload\n */\n function _mintBatch(\n address account,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual {\n require(account != address(0), 'ERC1155: mint to the zero address');\n require(\n ids.length == amounts.length,\n 'ERC1155: ids and amounts length mismatch'\n );\n\n _beforeTokenTransfer(\n msg.sender,\n address(0),\n account,\n ids,\n amounts,\n data\n );\n\n mapping(uint256 => mapping(address => uint256))\n storage balances = ERC1155BaseStorage.layout().balances;\n\n for (uint256 i; i < ids.length; i++) {\n balances[ids[i]][account] += amounts[i];\n }\n\n emit TransferBatch(msg.sender, address(0), account, ids, amounts);\n }\n\n /**\n * @notice mint batch of tokens for given address\n * @param account beneficiary of minting\n * @param ids list of token IDs\n * @param amounts list of quantities of tokens to mint\n * @param data data payload\n */\n function _safeMintBatch(\n address account,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual {\n _mintBatch(account, ids, amounts, data);\n\n _doSafeBatchTransferAcceptanceCheck(\n msg.sender,\n address(0),\n account,\n ids,\n amounts,\n data\n );\n }\n\n /**\n * @notice burn given quantity of tokens held by given address\n * @param account holder of tokens to burn\n * @param id token ID\n * @param amount quantity of tokens to burn\n */\n function _burn(\n address account,\n uint256 id,\n uint256 amount\n ) internal virtual {\n require(account != address(0), 'ERC1155: burn from the zero address');\n\n _beforeTokenTransfer(\n msg.sender,\n account,\n address(0),\n _asSingletonArray(id),\n _asSingletonArray(amount),\n ''\n );\n\n mapping(address => uint256) storage balances = ERC1155BaseStorage\n .layout()\n .balances[id];\n\n unchecked {\n require(\n balances[account] >= amount,\n 'ERC1155: burn amount exceeds balances'\n );\n balances[account] -= amount;\n }\n\n emit TransferSingle(msg.sender, account, address(0), id, amount);\n }\n\n /**\n * @notice burn given batch of tokens held by given address\n * @param account holder of tokens to burn\n * @param ids token IDs\n * @param amounts quantities of tokens to burn\n */\n function _burnBatch(\n address account,\n uint256[] memory ids,\n uint256[] memory amounts\n ) internal virtual {\n require(account != address(0), 'ERC1155: burn from the zero address');\n require(\n ids.length == amounts.length,\n 'ERC1155: ids and amounts length mismatch'\n );\n\n _beforeTokenTransfer(msg.sender, account, address(0), ids, amounts, '');\n\n mapping(uint256 => mapping(address => uint256))\n storage balances = ERC1155BaseStorage.layout().balances;\n\n unchecked {\n for (uint256 i; i < ids.length; i++) {\n uint256 id = ids[i];\n require(\n balances[id][account] >= amounts[i],\n 'ERC1155: burn amount exceeds balance'\n );\n balances[id][account] -= amounts[i];\n }\n }\n\n emit TransferBatch(msg.sender, account, address(0), ids, amounts);\n }\n\n /**\n * @notice transfer tokens between given addresses\n * @dev ERC1155Receiver implementation is not checked\n * @param operator executor of transfer\n * @param sender sender of tokens\n * @param recipient receiver of tokens\n * @param id token ID\n * @param amount quantity of tokens to transfer\n * @param data data payload\n */\n function _transfer(\n address operator,\n address sender,\n address recipient,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) internal virtual {\n require(\n recipient != address(0),\n 'ERC1155: transfer to the zero address'\n );\n\n _beforeTokenTransfer(\n operator,\n sender,\n recipient,\n _asSingletonArray(id),\n _asSingletonArray(amount),\n data\n );\n\n mapping(uint256 => mapping(address => uint256))\n storage balances = ERC1155BaseStorage.layout().balances;\n\n unchecked {\n uint256 senderBalance = balances[id][sender];\n require(\n senderBalance >= amount,\n 'ERC1155: insufficient balances for transfer'\n );\n balances[id][sender] = senderBalance - amount;\n }\n\n balances[id][recipient] += amount;\n\n emit TransferSingle(operator, sender, recipient, id, amount);\n }\n\n /**\n * @notice transfer tokens between given addresses\n * @param operator executor of transfer\n * @param sender sender of tokens\n * @param recipient receiver of tokens\n * @param id token ID\n * @param amount quantity of tokens to transfer\n * @param data data payload\n */\n function _safeTransfer(\n address operator,\n address sender,\n address recipient,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) internal virtual {\n _transfer(operator, sender, recipient, id, amount, data);\n\n _doSafeTransferAcceptanceCheck(\n operator,\n sender,\n recipient,\n id,\n amount,\n data\n );\n }\n\n /**\n * @notice transfer batch of tokens between given addresses\n * @dev ERC1155Receiver implementation is not checked\n * @param operator executor of transfer\n * @param sender sender of tokens\n * @param recipient receiver of tokens\n * @param ids token IDs\n * @param amounts quantities of tokens to transfer\n * @param data data payload\n */\n function _transferBatch(\n address operator,\n address sender,\n address recipient,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual {\n require(\n recipient != address(0),\n 'ERC1155: transfer to the zero address'\n );\n require(\n ids.length == amounts.length,\n 'ERC1155: ids and amounts length mismatch'\n );\n\n _beforeTokenTransfer(operator, sender, recipient, ids, amounts, data);\n\n mapping(uint256 => mapping(address => uint256))\n storage balances = ERC1155BaseStorage.layout().balances;\n\n for (uint256 i; i < ids.length; i++) {\n uint256 token = ids[i];\n uint256 amount = amounts[i];\n\n unchecked {\n uint256 senderBalance = balances[token][sender];\n require(\n senderBalance >= amount,\n 'ERC1155: insufficient balances for transfer'\n );\n balances[token][sender] = senderBalance - amount;\n }\n\n balances[token][recipient] += amount;\n }\n\n emit TransferBatch(operator, sender, recipient, ids, amounts);\n }\n\n /**\n * @notice transfer batch of tokens between given addresses\n * @param operator executor of transfer\n * @param sender sender of tokens\n * @param recipient receiver of tokens\n * @param ids token IDs\n * @param amounts quantities of tokens to transfer\n * @param data data payload\n */\n function _safeTransferBatch(\n address operator,\n address sender,\n address recipient,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual {\n _transferBatch(operator, sender, recipient, ids, amounts, data);\n\n _doSafeBatchTransferAcceptanceCheck(\n operator,\n sender,\n recipient,\n ids,\n amounts,\n data\n );\n }\n\n /**\n * @notice wrap given element in array of length 1\n * @param element element to wrap\n * @return singleton array\n */\n function _asSingletonArray(uint256 element)\n private\n pure\n returns (uint256[] memory)\n {\n uint256[] memory array = new uint256[](1);\n array[0] = element;\n return array;\n }\n\n /**\n * @notice revert if applicable transfer recipient is not valid ERC1155Receiver\n * @param operator executor of transfer\n * @param from sender of tokens\n * @param to receiver of tokens\n * @param id token ID\n * @param amount quantity of tokens to transfer\n * @param data data payload\n */\n function _doSafeTransferAcceptanceCheck(\n address operator,\n address from,\n address to,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) private {\n if (to.isContract()) {\n try\n IERC1155Receiver(to).onERC1155Received(\n operator,\n from,\n id,\n amount,\n data\n )\n returns (bytes4 response) {\n require(\n response == IERC1155Receiver.onERC1155Received.selector,\n 'ERC1155: ERC1155Receiver rejected tokens'\n );\n } catch Error(string memory reason) {\n revert(reason);\n } catch {\n revert('ERC1155: transfer to non ERC1155Receiver implementer');\n }\n }\n }\n\n /**\n * @notice revert if applicable transfer recipient is not valid ERC1155Receiver\n * @param operator executor of transfer\n * @param from sender of tokens\n * @param to receiver of tokens\n * @param ids token IDs\n * @param amounts quantities of tokens to transfer\n * @param data data payload\n */\n function _doSafeBatchTransferAcceptanceCheck(\n address operator,\n address from,\n address to,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) private {\n if (to.isContract()) {\n try\n IERC1155Receiver(to).onERC1155BatchReceived(\n operator,\n from,\n ids,\n amounts,\n data\n )\n returns (bytes4 response) {\n require(\n response ==\n IERC1155Receiver.onERC1155BatchReceived.selector,\n 'ERC1155: ERC1155Receiver rejected tokens'\n );\n } catch Error(string memory reason) {\n revert(reason);\n } catch {\n revert('ERC1155: transfer to non ERC1155Receiver implementer');\n }\n }\n }\n\n /**\n * @notice ERC1155 hook, called before all transfers including mint and burn\n * @dev function should be overridden and new implementation must call super\n * @dev called for both single and batch transfers\n * @param operator executor of transfer\n * @param from sender of tokens\n * @param to receiver of tokens\n * @param ids token IDs\n * @param amounts quantities of tokens to transfer\n * @param data data payload\n */\n function _beforeTokenTransfer(\n address operator,\n address from,\n address to,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual {}\n}\n"},"@solidstate/contracts/token/ERC20/IERC20Internal.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title Partial ERC20 interface needed by internal functions\n */\ninterface IERC20Internal {\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n}\n"},"@chainlink/contracts/src/v0.8/interfaces/AggregatorInterface.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorInterface {\n function latestAnswer()\n external\n view\n returns (\n int256\n );\n \n function latestTimestamp()\n external\n view\n returns (\n uint256\n );\n\n function latestRound()\n external\n view\n returns (\n uint256\n );\n\n function getAnswer(\n uint256 roundId\n )\n external\n view\n returns (\n int256\n );\n\n function getTimestamp(\n uint256 roundId\n )\n external\n view\n returns (\n uint256\n );\n\n event AnswerUpdated(\n int256 indexed current,\n uint256 indexed roundId,\n uint256 updatedAt\n );\n\n event NewRound(\n uint256 indexed roundId,\n address indexed startedBy,\n uint256 startedAt\n );\n}\n"},"contracts/pool/PoolExercise.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {ERC1155BaseStorage} from \"@solidstate/contracts/token/ERC1155/base/ERC1155BaseStorage.sol\";\r\n\r\nimport {PoolInternal} from \"./PoolInternal.sol\";\r\nimport {IPoolExercise} from \"./IPoolExercise.sol\";\r\n\r\n/**\r\n * @title Premia option pool\r\n * @dev deployed standalone and referenced by PoolProxy\r\n */\r\ncontract PoolExercise is IPoolExercise, PoolInternal {\r\n constructor(\r\n address ivolOracle,\r\n address weth,\r\n address premiaMining,\r\n address feeReceiver,\r\n address feeDiscountAddress,\r\n int128 fee64x64\r\n )\r\n PoolInternal(\r\n ivolOracle,\r\n weth,\r\n premiaMining,\r\n feeReceiver,\r\n feeDiscountAddress,\r\n fee64x64\r\n )\r\n {}\r\n\r\n /**\r\n * @inheritdoc IPoolExercise\r\n */\r\n function exerciseFrom(\r\n address holder,\r\n uint256 longTokenId,\r\n uint256 contractSize\r\n ) external override {\r\n if (msg.sender != holder) {\r\n require(\r\n ERC1155BaseStorage.layout().operatorApprovals[holder][\r\n msg.sender\r\n ],\r\n \"not approved\"\r\n );\r\n }\r\n\r\n _exercise(holder, longTokenId, contractSize);\r\n }\r\n\r\n /**\r\n * @inheritdoc IPoolExercise\r\n */\r\n function processExpired(uint256 longTokenId, uint256 contractSize)\r\n external\r\n override\r\n {\r\n _exercise(address(0), longTokenId, contractSize);\r\n }\r\n}\r\n"},"contracts/pool/IPoolExercise.sol":{"content":"// SPDX-License-Identifier: LGPL-3.0-or-later\r\n\r\npragma solidity ^0.8.0;\r\n\r\n/**\r\n * @notice Pool interface for exercising and processing of expired options\r\n */\r\ninterface IPoolExercise {\r\n /**\r\n * @notice exercise option on behalf of holder\r\n * @param holder owner of long option tokens to exercise\r\n * @param longTokenId long option token id\r\n * @param contractSize quantity of tokens to exercise\r\n */\r\n function exerciseFrom(\r\n address holder,\r\n uint256 longTokenId,\r\n uint256 contractSize\r\n ) external;\r\n\r\n /**\r\n * @notice process expired option, freeing liquidity and distributing profits\r\n * @param longTokenId long option token id\r\n * @param contractSize quantity of tokens to process\r\n */\r\n function processExpired(uint256 longTokenId, uint256 contractSize) external;\r\n}\r\n"},"contracts/pool/PoolStorage.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {AggregatorInterface} from \"@chainlink/contracts/src/v0.8/interfaces/AggregatorInterface.sol\";\r\nimport {AggregatorV3Interface} from \"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol\";\r\nimport {EnumerableSet, ERC1155EnumerableStorage} from \"@solidstate/contracts/token/ERC1155/enumerable/ERC1155EnumerableStorage.sol\";\r\n\r\nimport {ABDKMath64x64} from \"abdk-libraries-solidity/ABDKMath64x64.sol\";\r\nimport {ABDKMath64x64Token} from \"../libraries/ABDKMath64x64Token.sol\";\r\nimport {OptionMath} from \"../libraries/OptionMath.sol\";\r\n\r\nlibrary PoolStorage {\r\n using ABDKMath64x64 for int128;\r\n using PoolStorage for PoolStorage.Layout;\r\n\r\n enum TokenType {\r\n UNDERLYING_FREE_LIQ,\r\n BASE_FREE_LIQ,\r\n UNDERLYING_RESERVED_LIQ,\r\n BASE_RESERVED_LIQ,\r\n LONG_CALL,\r\n SHORT_CALL,\r\n LONG_PUT,\r\n SHORT_PUT\r\n }\r\n\r\n struct PoolSettings {\r\n address underlying;\r\n address base;\r\n address underlyingOracle;\r\n address baseOracle;\r\n }\r\n\r\n struct QuoteArgsInternal {\r\n address feePayer; // address of the fee payer\r\n uint64 maturity; // timestamp of option maturity\r\n int128 strike64x64; // 64x64 fixed point representation of strike price\r\n int128 spot64x64; // 64x64 fixed point representation of spot price\r\n uint256 contractSize; // size of option contract\r\n bool isCall; // true for call, false for put\r\n }\r\n\r\n struct QuoteResultInternal {\r\n int128 baseCost64x64; // 64x64 fixed point representation of option cost denominated in underlying currency (without fee)\r\n int128 feeCost64x64; // 64x64 fixed point representation of option fee cost denominated in underlying currency for call, or base currency for put\r\n int128 cLevel64x64; // 64x64 fixed point representation of C-Level of Pool after purchase\r\n int128 slippageCoefficient64x64; // 64x64 fixed point representation of slippage coefficient for given order size\r\n }\r\n\r\n struct BatchData {\r\n uint256 eta;\r\n uint256 totalPendingDeposits;\r\n }\r\n\r\n bytes32 internal constant STORAGE_SLOT =\r\n keccak256(\"premia.contracts.storage.Pool\");\r\n\r\n uint256 private constant C_DECAY_BUFFER = 12 hours;\r\n uint256 private constant C_DECAY_INTERVAL = 4 hours;\r\n\r\n struct Layout {\r\n // ERC20 token addresses\r\n address base;\r\n address underlying;\r\n // AggregatorV3Interface oracle addresses\r\n address baseOracle;\r\n address underlyingOracle;\r\n // token metadata\r\n uint8 underlyingDecimals;\r\n uint8 baseDecimals;\r\n // minimum amounts\r\n uint256 baseMinimum;\r\n uint256 underlyingMinimum;\r\n // deposit caps\r\n uint256 basePoolCap;\r\n uint256 underlyingPoolCap;\r\n // market state\r\n int128 _deprecated_steepness64x64;\r\n int128 cLevelBase64x64;\r\n int128 cLevelUnderlying64x64;\r\n uint256 cLevelBaseUpdatedAt;\r\n uint256 cLevelUnderlyingUpdatedAt;\r\n uint256 updatedAt;\r\n // User -> isCall -> depositedAt\r\n mapping(address => mapping(bool => uint256)) depositedAt;\r\n mapping(address => mapping(bool => uint256)) divestmentTimestamps;\r\n // doubly linked list of free liquidity intervals\r\n // isCall -> User -> User\r\n mapping(bool => mapping(address => address)) liquidityQueueAscending;\r\n mapping(bool => mapping(address => address)) liquidityQueueDescending;\r\n // minimum resolution price bucket => price\r\n mapping(uint256 => int128) bucketPrices64x64;\r\n // sequence id (minimum resolution price bucket / 256) => price update sequence\r\n mapping(uint256 => uint256) priceUpdateSequences;\r\n // isCall -> batch data\r\n mapping(bool => BatchData) nextDeposits;\r\n // user -> batch timestamp -> isCall -> pending amount\r\n mapping(address => mapping(uint256 => mapping(bool => uint256))) pendingDeposits;\r\n EnumerableSet.UintSet tokenIds;\r\n // user -> isCallPool -> total value locked of user (Used for liquidity mining)\r\n mapping(address => mapping(bool => uint256)) userTVL;\r\n // isCallPool -> total value locked\r\n mapping(bool => uint256) totalTVL;\r\n // steepness values\r\n int128 steepnessBase64x64;\r\n int128 steepnessUnderlying64x64;\r\n }\r\n\r\n function layout() internal pure returns (Layout storage l) {\r\n bytes32 slot = STORAGE_SLOT;\r\n assembly {\r\n l.slot := slot\r\n }\r\n }\r\n\r\n /**\r\n * @notice calculate ERC1155 token id for given option parameters\r\n * @param tokenType TokenType enum\r\n * @param maturity timestamp of option maturity\r\n * @param strike64x64 64x64 fixed point representation of strike price\r\n * @return tokenId token id\r\n */\r\n function formatTokenId(\r\n TokenType tokenType,\r\n uint64 maturity,\r\n int128 strike64x64\r\n ) internal pure returns (uint256 tokenId) {\r\n tokenId =\r\n (uint256(tokenType) << 248) +\r\n (uint256(maturity) << 128) +\r\n uint256(int256(strike64x64));\r\n }\r\n\r\n /**\r\n * @notice derive option maturity and strike price from ERC1155 token id\r\n * @param tokenId token id\r\n * @return tokenType TokenType enum\r\n * @return maturity timestamp of option maturity\r\n * @return strike64x64 option strike price\r\n */\r\n function parseTokenId(uint256 tokenId)\r\n internal\r\n pure\r\n returns (\r\n TokenType tokenType,\r\n uint64 maturity,\r\n int128 strike64x64\r\n )\r\n {\r\n assembly {\r\n tokenType := shr(248, tokenId)\r\n maturity := shr(128, tokenId)\r\n strike64x64 := tokenId\r\n }\r\n }\r\n\r\n function getTokenDecimals(Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (uint8 decimals)\r\n {\r\n decimals = isCall ? l.underlyingDecimals : l.baseDecimals;\r\n }\r\n\r\n /**\r\n * @notice get the total supply of free liquidity tokens, minus pending deposits\r\n * @param l storage layout struct\r\n * @param isCall whether query is for call or put pool\r\n * @return 64x64 fixed point representation of total free liquidity\r\n */\r\n function totalFreeLiquiditySupply64x64(Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (int128)\r\n {\r\n uint256 tokenId = formatTokenId(\r\n isCall ? TokenType.UNDERLYING_FREE_LIQ : TokenType.BASE_FREE_LIQ,\r\n 0,\r\n 0\r\n );\r\n\r\n return\r\n ABDKMath64x64Token.fromDecimals(\r\n ERC1155EnumerableStorage.layout().totalSupply[tokenId] -\r\n l.nextDeposits[isCall].totalPendingDeposits,\r\n l.getTokenDecimals(isCall)\r\n );\r\n }\r\n\r\n function getReinvestmentStatus(\r\n Layout storage l,\r\n address account,\r\n bool isCallPool\r\n ) internal view returns (bool) {\r\n uint256 timestamp = l.divestmentTimestamps[account][isCallPool];\r\n return timestamp == 0 || timestamp > block.timestamp;\r\n }\r\n\r\n function addUnderwriter(\r\n Layout storage l,\r\n address account,\r\n bool isCallPool\r\n ) internal {\r\n require(account != address(0));\r\n\r\n mapping(address => address) storage asc = l.liquidityQueueAscending[\r\n isCallPool\r\n ];\r\n mapping(address => address) storage desc = l.liquidityQueueDescending[\r\n isCallPool\r\n ];\r\n\r\n if (_isInQueue(account, asc, desc)) return;\r\n\r\n address last = desc[address(0)];\r\n\r\n asc[last] = account;\r\n desc[account] = last;\r\n desc[address(0)] = account;\r\n }\r\n\r\n function removeUnderwriter(\r\n Layout storage l,\r\n address account,\r\n bool isCallPool\r\n ) internal {\r\n require(account != address(0));\r\n\r\n mapping(address => address) storage asc = l.liquidityQueueAscending[\r\n isCallPool\r\n ];\r\n mapping(address => address) storage desc = l.liquidityQueueDescending[\r\n isCallPool\r\n ];\r\n\r\n if (!_isInQueue(account, asc, desc)) return;\r\n\r\n address prev = desc[account];\r\n address next = asc[account];\r\n asc[prev] = next;\r\n desc[next] = prev;\r\n delete asc[account];\r\n delete desc[account];\r\n }\r\n\r\n function isInQueue(\r\n Layout storage l,\r\n address account,\r\n bool isCallPool\r\n ) internal view returns (bool) {\r\n mapping(address => address) storage asc = l.liquidityQueueAscending[\r\n isCallPool\r\n ];\r\n mapping(address => address) storage desc = l.liquidityQueueDescending[\r\n isCallPool\r\n ];\r\n\r\n return _isInQueue(account, asc, desc);\r\n }\r\n\r\n function _isInQueue(\r\n address account,\r\n mapping(address => address) storage asc,\r\n mapping(address => address) storage desc\r\n ) private view returns (bool) {\r\n return asc[account] != address(0) || desc[address(0)] == account;\r\n }\r\n\r\n /**\r\n * @notice get current C-Level, without accounting for pending adjustments\r\n * @param l storage layout struct\r\n * @param isCall whether query is for call or put pool\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level\r\n */\r\n function getRawCLevel64x64(Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (int128 cLevel64x64)\r\n {\r\n cLevel64x64 = isCall ? l.cLevelUnderlying64x64 : l.cLevelBase64x64;\r\n }\r\n\r\n /**\r\n * @notice get current C-Level, accounting for unrealized decay\r\n * @param l storage layout struct\r\n * @param isCall whether query is for call or put pool\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level\r\n */\r\n function getDecayAdjustedCLevel64x64(Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (int128 cLevel64x64)\r\n {\r\n // get raw C-Level from storage\r\n cLevel64x64 = l.getRawCLevel64x64(isCall);\r\n\r\n // account for C-Level decay\r\n cLevel64x64 = l.applyCLevelDecayAdjustment(cLevel64x64, isCall);\r\n }\r\n\r\n /**\r\n * @notice calculate updated C-Level, accounting for unrealized decay\r\n * @param l storage layout struct\r\n * @param oldCLevel64x64 64x64 fixed point representation pool C-Level before accounting for decay\r\n * @param isCall whether query is for call or put pool\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level of Pool after accounting for decay\r\n */\r\n function applyCLevelDecayAdjustment(\r\n Layout storage l,\r\n int128 oldCLevel64x64,\r\n bool isCall\r\n ) internal view returns (int128 cLevel64x64) {\r\n uint256 timeElapsed = block.timestamp -\r\n (isCall ? l.cLevelUnderlyingUpdatedAt : l.cLevelBaseUpdatedAt);\r\n\r\n // do not apply C decay if less than 24 hours have elapsed\r\n\r\n if (timeElapsed > C_DECAY_BUFFER) {\r\n timeElapsed -= C_DECAY_BUFFER;\r\n } else {\r\n return oldCLevel64x64;\r\n }\r\n\r\n int128 timeIntervalsElapsed64x64 = ABDKMath64x64.divu(\r\n timeElapsed,\r\n C_DECAY_INTERVAL\r\n );\r\n\r\n uint256 tokenId = formatTokenId(\r\n isCall ? TokenType.UNDERLYING_FREE_LIQ : TokenType.BASE_FREE_LIQ,\r\n 0,\r\n 0\r\n );\r\n\r\n uint256 tvl = l.totalTVL[isCall];\r\n\r\n int128 utilization = ABDKMath64x64.divu(\r\n tvl -\r\n (ERC1155EnumerableStorage.layout().totalSupply[tokenId] -\r\n l.nextDeposits[isCall].totalPendingDeposits),\r\n tvl\r\n );\r\n\r\n return\r\n OptionMath.calculateCLevelDecay(\r\n OptionMath.CalculateCLevelDecayArgs(\r\n timeIntervalsElapsed64x64,\r\n oldCLevel64x64,\r\n utilization,\r\n 0xb333333333333333, // 0.7\r\n 0xe666666666666666, // 0.9\r\n 0x10000000000000000, // 1.0\r\n 0x10000000000000000, // 1.0\r\n 0xe666666666666666, // 0.9\r\n 0x56fc2a2c515da32ea // 2e\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * @notice calculate updated C-Level, accounting for pending deposits\r\n * @param l storage layout struct\r\n * @param oldCLevel64x64 64x64 fixed point representation pool C-Level before accounting for liquidity change\r\n * @param oldLiquidity64x64 64x64 fixed point representation of previous liquidity\r\n * @param isCall whether to update C-Level for call or put pool\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level\r\n * @return liquidity64x64 64x64 fixed point representation of new liquidity amount\r\n */\r\n function applyCLevelPendingDepositAdjustment(\r\n Layout storage l,\r\n int128 oldCLevel64x64,\r\n int128 oldLiquidity64x64,\r\n bool isCall\r\n ) internal view returns (int128 cLevel64x64, int128 liquidity64x64) {\r\n PoolStorage.BatchData storage batchData = l.nextDeposits[isCall];\r\n int128 pendingDeposits64x64;\r\n\r\n if (\r\n batchData.totalPendingDeposits > 0 &&\r\n batchData.eta != 0 &&\r\n block.timestamp >= batchData.eta\r\n ) {\r\n pendingDeposits64x64 = ABDKMath64x64Token.fromDecimals(\r\n batchData.totalPendingDeposits,\r\n l.getTokenDecimals(isCall)\r\n );\r\n\r\n liquidity64x64 = oldLiquidity64x64.add(pendingDeposits64x64);\r\n\r\n cLevel64x64 = l.applyCLevelLiquidityChangeAdjustment(\r\n oldCLevel64x64,\r\n oldLiquidity64x64,\r\n liquidity64x64,\r\n isCall\r\n );\r\n } else {\r\n cLevel64x64 = oldCLevel64x64;\r\n liquidity64x64 = oldLiquidity64x64;\r\n }\r\n }\r\n\r\n /**\r\n * @notice calculate updated C-Level, accounting for change in liquidity\r\n * @param l storage layout struct\r\n * @param oldCLevel64x64 64x64 fixed point representation pool C-Level before accounting for liquidity change\r\n * @param oldLiquidity64x64 64x64 fixed point representation of previous liquidity\r\n * @param newLiquidity64x64 64x64 fixed point representation of current liquidity\r\n * @param isCallPool whether to update C-Level for call or put pool\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level\r\n */\r\n function applyCLevelLiquidityChangeAdjustment(\r\n Layout storage l,\r\n int128 oldCLevel64x64,\r\n int128 oldLiquidity64x64,\r\n int128 newLiquidity64x64,\r\n bool isCallPool\r\n ) internal view returns (int128 cLevel64x64) {\r\n int128 steepness64x64 = isCallPool\r\n ? l.steepnessUnderlying64x64\r\n : l.steepnessBase64x64;\r\n\r\n // fallback to deprecated storage value if side-specific value is not set\r\n if (steepness64x64 == 0) steepness64x64 = l._deprecated_steepness64x64;\r\n\r\n cLevel64x64 = OptionMath.calculateCLevel(\r\n oldCLevel64x64,\r\n oldLiquidity64x64,\r\n newLiquidity64x64,\r\n steepness64x64\r\n );\r\n\r\n if (cLevel64x64 < 0xb333333333333333) {\r\n cLevel64x64 = int128(0xb333333333333333); // 64x64 fixed point representation of 0.7\r\n }\r\n }\r\n\r\n /**\r\n * @notice set C-Level to arbitrary pre-calculated value\r\n * @param cLevel64x64 new C-Level of pool\r\n * @param isCallPool whether to update C-Level for call or put pool\r\n */\r\n function setCLevel(\r\n Layout storage l,\r\n int128 cLevel64x64,\r\n bool isCallPool\r\n ) internal {\r\n if (isCallPool) {\r\n l.cLevelUnderlying64x64 = cLevel64x64;\r\n l.cLevelUnderlyingUpdatedAt = block.timestamp;\r\n } else {\r\n l.cLevelBase64x64 = cLevel64x64;\r\n l.cLevelBaseUpdatedAt = block.timestamp;\r\n }\r\n }\r\n\r\n function setOracles(\r\n Layout storage l,\r\n address baseOracle,\r\n address underlyingOracle\r\n ) internal {\r\n require(\r\n AggregatorV3Interface(baseOracle).decimals() ==\r\n AggregatorV3Interface(underlyingOracle).decimals(),\r\n \"Pool: oracle decimals must match\"\r\n );\r\n\r\n l.baseOracle = baseOracle;\r\n l.underlyingOracle = underlyingOracle;\r\n }\r\n\r\n function fetchPriceUpdate(Layout storage l)\r\n internal\r\n view\r\n returns (int128 price64x64)\r\n {\r\n int256 priceUnderlying = AggregatorInterface(l.underlyingOracle)\r\n .latestAnswer();\r\n int256 priceBase = AggregatorInterface(l.baseOracle).latestAnswer();\r\n\r\n return ABDKMath64x64.divi(priceUnderlying, priceBase);\r\n }\r\n\r\n /**\r\n * @notice set price update for hourly bucket corresponding to given timestamp\r\n * @param l storage layout struct\r\n * @param timestamp timestamp to update\r\n * @param price64x64 64x64 fixed point representation of price\r\n */\r\n function setPriceUpdate(\r\n Layout storage l,\r\n uint256 timestamp,\r\n int128 price64x64\r\n ) internal {\r\n uint256 bucket = timestamp / (1 hours);\r\n l.bucketPrices64x64[bucket] = price64x64;\r\n l.priceUpdateSequences[bucket >> 8] += 1 << (255 - (bucket & 255));\r\n }\r\n\r\n /**\r\n * @notice get price update for hourly bucket corresponding to given timestamp\r\n * @param l storage layout struct\r\n * @param timestamp timestamp to query\r\n * @return 64x64 fixed point representation of price\r\n */\r\n function getPriceUpdate(Layout storage l, uint256 timestamp)\r\n internal\r\n view\r\n returns (int128)\r\n {\r\n return l.bucketPrices64x64[timestamp / (1 hours)];\r\n }\r\n\r\n /**\r\n * @notice get first price update available following given timestamp\r\n * @param l storage layout struct\r\n * @param timestamp timestamp to query\r\n * @return 64x64 fixed point representation of price\r\n */\r\n function getPriceUpdateAfter(Layout storage l, uint256 timestamp)\r\n internal\r\n view\r\n returns (int128)\r\n {\r\n // price updates are grouped into hourly buckets\r\n uint256 bucket = timestamp / (1 hours);\r\n // divide by 256 to get the index of the relevant price update sequence\r\n uint256 sequenceId = bucket >> 8;\r\n\r\n // get position within sequence relevant to current price update\r\n\r\n uint256 offset = bucket & 255;\r\n // shift to skip buckets from earlier in sequence\r\n uint256 sequence = (l.priceUpdateSequences[sequenceId] << offset) >>\r\n offset;\r\n\r\n // iterate through future sequences until a price update is found\r\n // sequence corresponding to current timestamp used as upper bound\r\n\r\n uint256 currentPriceUpdateSequenceId = block.timestamp / (256 hours);\r\n\r\n while (sequence == 0 && sequenceId <= currentPriceUpdateSequenceId) {\r\n sequence = l.priceUpdateSequences[++sequenceId];\r\n }\r\n\r\n // if no price update is found (sequence == 0) function will return 0\r\n // this should never occur, as each relevant external function triggers a price update\r\n\r\n // the most significant bit of the sequence corresponds to the offset of the relevant bucket\r\n\r\n uint256 msb;\r\n\r\n for (uint256 i = 128; i > 0; i >>= 1) {\r\n if (sequence >> i > 0) {\r\n msb += i;\r\n sequence >>= i;\r\n }\r\n }\r\n\r\n return l.bucketPrices64x64[((sequenceId + 1) << 8) - msb - 1];\r\n }\r\n\r\n function fromBaseToUnderlyingDecimals(Layout storage l, uint256 value)\r\n internal\r\n view\r\n returns (uint256)\r\n {\r\n int128 valueFixed64x64 = ABDKMath64x64Token.fromDecimals(\r\n value,\r\n l.baseDecimals\r\n );\r\n return\r\n ABDKMath64x64Token.toDecimals(\r\n valueFixed64x64,\r\n l.underlyingDecimals\r\n );\r\n }\r\n\r\n function fromUnderlyingToBaseDecimals(Layout storage l, uint256 value)\r\n internal\r\n view\r\n returns (uint256)\r\n {\r\n int128 valueFixed64x64 = ABDKMath64x64Token.fromDecimals(\r\n value,\r\n l.underlyingDecimals\r\n );\r\n return ABDKMath64x64Token.toDecimals(valueFixed64x64, l.baseDecimals);\r\n }\r\n}\r\n"},"@solidstate/contracts/utils/AddressUtils.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nlibrary AddressUtils {\n function toString(address account) internal pure returns (string memory) {\n bytes32 value = bytes32(uint256(uint160(account)));\n bytes memory alphabet = '0123456789abcdef';\n bytes memory chars = new bytes(42);\n\n chars[0] = '0';\n chars[1] = 'x';\n\n for (uint256 i = 0; i < 20; i++) {\n chars[2 + i * 2] = alphabet[uint8(value[i + 12] >> 4)];\n chars[3 + i * 2] = alphabet[uint8(value[i + 12] & 0x0f)];\n }\n\n return string(chars);\n }\n\n function isContract(address account) internal view returns (bool) {\n uint256 size;\n assembly {\n size := extcodesize(account)\n }\n return size > 0;\n }\n\n function sendValue(address payable account, uint256 amount) internal {\n (bool success, ) = account.call{ value: amount }('');\n require(success, 'AddressUtils: failed to send value');\n }\n\n function functionCall(address target, bytes memory data)\n internal\n returns (bytes memory)\n {\n return\n functionCall(target, data, 'AddressUtils: failed low-level call');\n }\n\n function functionCall(\n address target,\n bytes memory data,\n string memory error\n ) internal returns (bytes memory) {\n return _functionCallWithValue(target, data, 0, error);\n }\n\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return\n functionCallWithValue(\n target,\n data,\n value,\n 'AddressUtils: failed low-level call with value'\n );\n }\n\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory error\n ) internal returns (bytes memory) {\n require(\n address(this).balance >= value,\n 'AddressUtils: insufficient balance for call'\n );\n return _functionCallWithValue(target, data, value, error);\n }\n\n function _functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory error\n ) private returns (bytes memory) {\n require(\n isContract(target),\n 'AddressUtils: function call to non-contract'\n );\n\n (bool success, bytes memory returnData) = target.call{ value: value }(\n data\n );\n\n if (success) {\n return returnData;\n } else if (returnData.length > 0) {\n assembly {\n let returnData_size := mload(returnData)\n revert(add(32, returnData), returnData_size)\n }\n } else {\n revert(error);\n }\n }\n}\n"},"@solidstate/contracts/token/ERC1155/enumerable/ERC1155Enumerable.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { EnumerableSet } from '../../../utils/EnumerableSet.sol';\nimport { ERC1155Base, ERC1155BaseInternal } from '../base/ERC1155Base.sol';\nimport { IERC1155Enumerable } from './IERC1155Enumerable.sol';\nimport { ERC1155EnumerableInternal, ERC1155EnumerableStorage } from './ERC1155EnumerableInternal.sol';\n\n/**\n * @title ERC1155 implementation including enumerable and aggregate functions\n */\nabstract contract ERC1155Enumerable is\n IERC1155Enumerable,\n ERC1155Base,\n ERC1155EnumerableInternal\n{\n using EnumerableSet for EnumerableSet.AddressSet;\n using EnumerableSet for EnumerableSet.UintSet;\n\n /**\n * @inheritdoc IERC1155Enumerable\n */\n function totalSupply(uint256 id)\n public\n view\n virtual\n override\n returns (uint256)\n {\n return ERC1155EnumerableStorage.layout().totalSupply[id];\n }\n\n /**\n * @inheritdoc IERC1155Enumerable\n */\n function totalHolders(uint256 id)\n public\n view\n virtual\n override\n returns (uint256)\n {\n return ERC1155EnumerableStorage.layout().accountsByToken[id].length();\n }\n\n /**\n * @inheritdoc IERC1155Enumerable\n */\n function accountsByToken(uint256 id)\n public\n view\n virtual\n override\n returns (address[] memory)\n {\n EnumerableSet.AddressSet storage accounts = ERC1155EnumerableStorage\n .layout()\n .accountsByToken[id];\n\n address[] memory addresses = new address[](accounts.length());\n\n for (uint256 i; i < accounts.length(); i++) {\n addresses[i] = accounts.at(i);\n }\n\n return addresses;\n }\n\n /**\n * @inheritdoc IERC1155Enumerable\n */\n function tokensByAccount(address account)\n public\n view\n virtual\n override\n returns (uint256[] memory)\n {\n EnumerableSet.UintSet storage tokens = ERC1155EnumerableStorage\n .layout()\n .tokensByAccount[account];\n\n uint256[] memory ids = new uint256[](tokens.length());\n\n for (uint256 i; i < tokens.length(); i++) {\n ids[i] = tokens.at(i);\n }\n\n return ids;\n }\n\n /**\n * @notice ERC1155 hook: update aggregate values\n * @inheritdoc ERC1155EnumerableInternal\n */\n function _beforeTokenTransfer(\n address operator,\n address from,\n address to,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n )\n internal\n virtual\n override(ERC1155BaseInternal, ERC1155EnumerableInternal)\n {\n super._beforeTokenTransfer(operator, from, to, ids, amounts, data);\n }\n}\n"},"@solidstate/contracts/token/ERC1155/IERC1155Receiver.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC165 } from '../../introspection/IERC165.sol';\n\n/**\n * @title ERC1155 transfer receiver interface\n */\ninterface IERC1155Receiver is IERC165 {\n /**\n * @notice validate receipt of ERC1155 transfer\n * @param operator executor of transfer\n * @param from sender of tokens\n * @param id token ID received\n * @param value quantity of tokens received\n * @param data data payload\n * @return function's own selector if transfer is accepted\n */\n function onERC1155Received(\n address operator,\n address from,\n uint256 id,\n uint256 value,\n bytes calldata data\n ) external returns (bytes4);\n\n /**\n * @notice validate receipt of ERC1155 batch transfer\n * @param operator executor of transfer\n * @param from sender of tokens\n * @param ids token IDs received\n * @param values quantities of tokens received\n * @param data data payload\n * @return function's own selector if transfer is accepted\n */\n function onERC1155BatchReceived(\n address operator,\n address from,\n uint256[] calldata ids,\n uint256[] calldata values,\n bytes calldata data\n ) external returns (bytes4);\n}\n"},"contracts/pool/IPoolEvents.sol":{"content":"// SPDX-License-Identifier: LGPL-3.0-or-later\r\n\r\npragma solidity ^0.8.0;\r\n\r\ninterface IPoolEvents {\r\n event Purchase(\r\n address indexed user,\r\n uint256 longTokenId,\r\n uint256 contractSize,\r\n uint256 baseCost,\r\n uint256 feeCost,\r\n int128 spot64x64\r\n );\r\n\r\n event Exercise(\r\n address indexed user,\r\n uint256 longTokenId,\r\n uint256 contractSize,\r\n uint256 exerciseValue,\r\n uint256 fee\r\n );\r\n\r\n event Underwrite(\r\n address indexed underwriter,\r\n address indexed longReceiver,\r\n uint256 shortTokenId,\r\n uint256 intervalContractSize,\r\n uint256 intervalPremium,\r\n bool isManualUnderwrite\r\n );\r\n\r\n event AssignExercise(\r\n address indexed underwriter,\r\n uint256 shortTokenId,\r\n uint256 freedAmount,\r\n uint256 intervalContractSize,\r\n uint256 fee\r\n );\r\n\r\n event Deposit(address indexed user, bool isCallPool, uint256 amount);\r\n\r\n event Withdrawal(\r\n address indexed user,\r\n bool isCallPool,\r\n uint256 depositedAt,\r\n uint256 amount\r\n );\r\n\r\n event FeeWithdrawal(bool indexed isCallPool, uint256 amount);\r\n\r\n event Annihilate(uint256 shortTokenId, uint256 amount);\r\n\r\n event UpdateCLevel(\r\n bool indexed isCall,\r\n int128 cLevel64x64,\r\n int128 oldLiquidity64x64,\r\n int128 newLiquidity64x64\r\n );\r\n\r\n event UpdateSteepness(int128 steepness64x64, bool isCallPool);\r\n}\r\n"},"contracts/oracle/VolatilitySurfaceOracleStorage.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {EnumerableSet} from \"@solidstate/contracts/utils/EnumerableSet.sol\";\r\n\r\nlibrary VolatilitySurfaceOracleStorage {\r\n bytes32 internal constant STORAGE_SLOT =\r\n keccak256(\"premia.contracts.storage.VolatilitySurfaceOracle\");\r\n\r\n uint256 internal constant COEFF_BITS = 51;\r\n uint256 internal constant COEFF_BITS_MINUS_ONE = 50;\r\n uint256 internal constant COEFF_AMOUNT = 5;\r\n // START_BIT = COEFF_BITS * (COEFF_AMOUNT - 1)\r\n uint256 internal constant START_BIT = 204;\r\n\r\n struct Update {\r\n uint256 updatedAt;\r\n bytes32 callCoefficients;\r\n bytes32 putCoefficients;\r\n }\r\n\r\n struct Layout {\r\n // Base token -> Underlying token -> Update\r\n mapping(address => mapping(address => Update)) volatilitySurfaces;\r\n // Relayer addresses which can be trusted to provide accurate option trades\r\n EnumerableSet.AddressSet whitelistedRelayers;\r\n }\r\n\r\n function layout() internal pure returns (Layout storage l) {\r\n bytes32 slot = STORAGE_SLOT;\r\n assembly {\r\n l.slot := slot\r\n }\r\n }\r\n\r\n function getCoefficients(\r\n Layout storage l,\r\n address baseToken,\r\n address underlyingToken,\r\n bool isCall\r\n ) internal view returns (bytes32) {\r\n Update storage u = l.volatilitySurfaces[baseToken][underlyingToken];\r\n return isCall ? u.callCoefficients : u.putCoefficients;\r\n }\r\n\r\n function parseVolatilitySurfaceCoefficients(bytes32 input)\r\n internal\r\n pure\r\n returns (int256[] memory coefficients)\r\n {\r\n coefficients = new int256[](COEFF_AMOUNT);\r\n\r\n // Value to add to negative numbers to cast them to int256\r\n int256 toAdd = (int256(-1) >> COEFF_BITS) << COEFF_BITS;\r\n\r\n assembly {\r\n let i := 0\r\n // Value equal to -1\r\n let mid := shl(COEFF_BITS_MINUS_ONE, 1)\r\n\r\n for {\r\n\r\n } lt(i, COEFF_AMOUNT) {\r\n\r\n } {\r\n let offset := sub(START_BIT, mul(COEFF_BITS, i))\r\n let coeff := shr(\r\n offset,\r\n sub(\r\n input,\r\n shl(\r\n add(offset, COEFF_BITS),\r\n shr(add(offset, COEFF_BITS), input)\r\n )\r\n )\r\n )\r\n\r\n // Check if value is a negative number and needs casting\r\n if or(eq(coeff, mid), gt(coeff, mid)) {\r\n coeff := add(coeff, toAdd)\r\n }\r\n\r\n // Store result in the coefficients array\r\n mstore(add(coefficients, add(0x20, mul(0x20, i))), coeff)\r\n\r\n i := add(i, 1)\r\n }\r\n }\r\n }\r\n\r\n function formatVolatilitySurfaceCoefficients(int256[5] memory coefficients)\r\n internal\r\n pure\r\n returns (bytes32 result)\r\n {\r\n for (uint256 i = 0; i < COEFF_AMOUNT; i++) {\r\n int256 max = int256(1 << COEFF_BITS_MINUS_ONE);\r\n require(\r\n coefficients[i] < max && coefficients[i] > -max,\r\n \"Out of bounds\"\r\n );\r\n }\r\n\r\n assembly {\r\n let i := 0\r\n\r\n for {\r\n\r\n } lt(i, COEFF_AMOUNT) {\r\n\r\n } {\r\n let offset := sub(START_BIT, mul(COEFF_BITS, i))\r\n let coeff := mload(add(coefficients, mul(0x20, i)))\r\n\r\n result := add(\r\n result,\r\n shl(\r\n offset,\r\n sub(coeff, shl(COEFF_BITS, shr(COEFF_BITS, coeff)))\r\n )\r\n )\r\n\r\n i := add(i, 1)\r\n }\r\n }\r\n }\r\n}\r\n"},"@solidstate/contracts/token/ERC1155/enumerable/IERC1155Enumerable.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title ERC1155 enumerable and aggregate function interface\n */\ninterface IERC1155Enumerable {\n /**\n * @notice query total minted supply of given token\n * @param id token id to query\n * @return token supply\n */\n function totalSupply(uint256 id) external view returns (uint256);\n\n /**\n * @notice query total number of holders for given token\n * @param id token id to query\n * @return quantity of holders\n */\n function totalHolders(uint256 id) external view returns (uint256);\n\n /**\n * @notice query holders of given token\n * @param id token id to query\n * @return list of holder addresses\n */\n function accountsByToken(uint256 id)\n external\n view\n returns (address[] memory);\n\n /**\n * @notice query tokens held by given address\n * @param account address to query\n * @return list of token ids\n */\n function tokensByAccount(address account)\n external\n view\n returns (uint256[] memory);\n}\n"},"@solidstate/contracts/token/ERC20/IERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC20Internal } from './IERC20Internal.sol';\n\n/**\n * @title ERC20 interface\n * @dev see https://github.com/ethereum/EIPs/issues/20\n */\ninterface IERC20 is IERC20Internal {\n /**\n * @notice query the total minted token supply\n * @return token supply\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @notice query the token balance of given account\n * @param account address to query\n * @return token balance\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @notice query the allowance granted from given holder to given spender\n * @param holder approver of allowance\n * @param spender recipient of allowance\n * @return token allowance\n */\n function allowance(address holder, address spender)\n external\n view\n returns (uint256);\n\n /**\n * @notice grant approval to spender to spend tokens\n * @dev prefer ERC20Extended functions to avoid transaction-ordering vulnerability (see https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729)\n * @param spender recipient of allowance\n * @param amount quantity of tokens approved for spending\n * @return success status (always true; otherwise function should revert)\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @notice transfer tokens to given recipient\n * @param recipient beneficiary of token transfer\n * @param amount quantity of tokens to transfer\n * @return success status (always true; otherwise function should revert)\n */\n function transfer(address recipient, uint256 amount)\n external\n returns (bool);\n\n /**\n * @notice transfer tokens to given recipient on behalf of given holder\n * @param holder holder of tokens prior to transfer\n * @param recipient beneficiary of token transfer\n * @param amount quantity of tokens to transfer\n * @return success status (always true; otherwise function should revert)\n */\n function transferFrom(\n address holder,\n address recipient,\n uint256 amount\n ) external returns (bool);\n}\n"},"contracts/mining/IPremiaMining.sol":{"content":"// SPDX-License-Identifier: LGPL-3.0-or-later\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {PremiaMiningStorage} from \"./PremiaMiningStorage.sol\";\r\n\r\ninterface IPremiaMining {\r\n function addPremiaRewards(uint256 _amount) external;\r\n\r\n function premiaRewardsAvailable() external view returns (uint256);\r\n\r\n function getTotalAllocationPoints() external view returns (uint256);\r\n\r\n function getPoolInfo(address pool, bool isCallPool)\r\n external\r\n view\r\n returns (PremiaMiningStorage.PoolInfo memory);\r\n\r\n function getPremiaPerYear() external view returns (uint256);\r\n\r\n function addPool(address _pool, uint256 _allocPoints) external;\r\n\r\n function setPoolAllocPoints(\r\n address[] memory _pools,\r\n uint256[] memory _allocPoints\r\n ) external;\r\n\r\n function pendingPremia(\r\n address _pool,\r\n bool _isCallPool,\r\n address _user\r\n ) external view returns (uint256);\r\n\r\n function updatePool(\r\n address _pool,\r\n bool _isCallPool,\r\n uint256 _totalTVL\r\n ) external;\r\n\r\n function allocatePending(\r\n address _user,\r\n address _pool,\r\n bool _isCallPool,\r\n uint256 _userTVLOld,\r\n uint256 _userTVLNew,\r\n uint256 _totalTVL\r\n ) external;\r\n\r\n function claim(\r\n address _user,\r\n address _pool,\r\n bool _isCallPool,\r\n uint256 _userTVLOld,\r\n uint256 _userTVLNew,\r\n uint256 _totalTVL\r\n ) external;\r\n}\r\n"},"@solidstate/contracts/token/ERC1155/enumerable/ERC1155EnumerableStorage.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { EnumerableSet } from '../../../utils/EnumerableSet.sol';\n\nlibrary ERC1155EnumerableStorage {\n struct Layout {\n mapping(uint256 => uint256) totalSupply;\n mapping(uint256 => EnumerableSet.AddressSet) accountsByToken;\n mapping(address => EnumerableSet.UintSet) tokensByAccount;\n }\n\n bytes32 internal constant STORAGE_SLOT =\n keccak256('solidstate.contracts.storage.ERC1155Enumerable');\n\n function layout() internal pure returns (Layout storage l) {\n bytes32 slot = STORAGE_SLOT;\n assembly {\n l.slot := slot\n }\n }\n}\n"},"@solidstate/contracts/token/ERC1155/enumerable/ERC1155EnumerableInternal.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { EnumerableSet } from '../../../utils/EnumerableSet.sol';\nimport { ERC1155BaseInternal, ERC1155BaseStorage } from '../base/ERC1155BaseInternal.sol';\nimport { ERC1155EnumerableStorage } from './ERC1155EnumerableStorage.sol';\n\n/**\n * @title ERC1155Enumerable internal functions\n */\nabstract contract ERC1155EnumerableInternal is ERC1155BaseInternal {\n using EnumerableSet for EnumerableSet.AddressSet;\n using EnumerableSet for EnumerableSet.UintSet;\n\n /**\n * @notice ERC1155 hook: update aggregate values\n * @inheritdoc ERC1155BaseInternal\n */\n function _beforeTokenTransfer(\n address operator,\n address from,\n address to,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual override {\n super._beforeTokenTransfer(operator, from, to, ids, amounts, data);\n\n if (from != to) {\n ERC1155EnumerableStorage.Layout storage l = ERC1155EnumerableStorage\n .layout();\n mapping(uint256 => EnumerableSet.AddressSet)\n storage tokenAccounts = l.accountsByToken;\n EnumerableSet.UintSet storage fromTokens = l.tokensByAccount[from];\n EnumerableSet.UintSet storage toTokens = l.tokensByAccount[to];\n\n for (uint256 i; i < ids.length; i++) {\n uint256 amount = amounts[i];\n\n if (amount > 0) {\n uint256 id = ids[i];\n\n if (from == address(0)) {\n l.totalSupply[id] += amount;\n } else if (_balanceOf(from, id) == amount) {\n tokenAccounts[id].remove(from);\n fromTokens.remove(id);\n }\n\n if (to == address(0)) {\n l.totalSupply[id] -= amount;\n } else if (_balanceOf(to, id) == 0) {\n tokenAccounts[id].add(to);\n toTokens.add(id);\n }\n }\n }\n }\n }\n}\n"},"contracts/oracle/IVolatilitySurfaceOracle.sol":{"content":"// SPDX-License-Identifier: LGPL-3.0-or-later\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {VolatilitySurfaceOracleStorage} from \"./VolatilitySurfaceOracleStorage.sol\";\r\n\r\ninterface IVolatilitySurfaceOracle {\r\n function getWhitelistedRelayers() external view returns (address[] memory);\r\n\r\n function getVolatilitySurface(address baseToken, address underlyingToken)\r\n external\r\n view\r\n returns (VolatilitySurfaceOracleStorage.Update memory);\r\n\r\n function getVolatilitySurfaceCoefficientsUnpacked(\r\n address baseToken,\r\n address underlyingToken,\r\n bool isCall\r\n ) external view returns (int256[] memory);\r\n\r\n function getTimeToMaturity64x64(uint64 maturity)\r\n external\r\n view\r\n returns (int128);\r\n\r\n function getAnnualizedVolatility64x64(\r\n address baseToken,\r\n address underlyingToken,\r\n int128 spot64x64,\r\n int128 strike64x64,\r\n int128 timeToMaturity64x64,\r\n bool isCall\r\n ) external view returns (int128);\r\n\r\n function getBlackScholesPrice64x64(\r\n address baseToken,\r\n address underlyingToken,\r\n int128 strike64x64,\r\n int128 spot64x64,\r\n int128 timeToMaturity64x64,\r\n bool isCall\r\n ) external view returns (int128);\r\n\r\n function getBlackScholesPrice(\r\n address baseToken,\r\n address underlyingToken,\r\n int128 strike64x64,\r\n int128 spot64x64,\r\n int128 timeToMaturity64x64,\r\n bool isCall\r\n ) external view returns (uint256);\r\n}\r\n"},"contracts/pool/PoolInternal.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {IERC173} from \"@solidstate/contracts/access/IERC173.sol\";\r\nimport {OwnableStorage} from \"@solidstate/contracts/access/OwnableStorage.sol\";\r\nimport {IERC20} from \"@solidstate/contracts/token/ERC20/IERC20.sol\";\r\nimport {ERC1155EnumerableInternal, ERC1155EnumerableStorage, EnumerableSet} from \"@solidstate/contracts/token/ERC1155/enumerable/ERC1155Enumerable.sol\";\r\nimport {IWETH} from \"@solidstate/contracts/utils/IWETH.sol\";\r\n\r\nimport {PoolStorage} from \"./PoolStorage.sol\";\r\n\r\nimport {ABDKMath64x64} from \"abdk-libraries-solidity/ABDKMath64x64.sol\";\r\nimport {ABDKMath64x64Token} from \"../libraries/ABDKMath64x64Token.sol\";\r\nimport {OptionMath} from \"../libraries/OptionMath.sol\";\r\nimport {IFeeDiscount} from \"../staking/IFeeDiscount.sol\";\r\nimport {IPoolEvents} from \"./IPoolEvents.sol\";\r\nimport {IPremiaMining} from \"../mining/IPremiaMining.sol\";\r\nimport {IVolatilitySurfaceOracle} from \"../oracle/IVolatilitySurfaceOracle.sol\";\r\n\r\n/**\r\n * @title Premia option pool\r\n * @dev deployed standalone and referenced by PoolProxy\r\n */\r\ncontract PoolInternal is IPoolEvents, ERC1155EnumerableInternal {\r\n using ABDKMath64x64 for int128;\r\n using EnumerableSet for EnumerableSet.AddressSet;\r\n using EnumerableSet for EnumerableSet.UintSet;\r\n using PoolStorage for PoolStorage.Layout;\r\n\r\n address internal immutable WETH_ADDRESS;\r\n address internal immutable PREMIA_MINING_ADDRESS;\r\n address internal immutable FEE_RECEIVER_ADDRESS;\r\n address internal immutable FEE_DISCOUNT_ADDRESS;\r\n address internal immutable IVOL_ORACLE_ADDRESS;\r\n\r\n int128 internal immutable FEE_64x64;\r\n\r\n uint256 internal immutable UNDERLYING_FREE_LIQ_TOKEN_ID;\r\n uint256 internal immutable BASE_FREE_LIQ_TOKEN_ID;\r\n\r\n uint256 internal immutable UNDERLYING_RESERVED_LIQ_TOKEN_ID;\r\n uint256 internal immutable BASE_RESERVED_LIQ_TOKEN_ID;\r\n\r\n uint256 internal constant INVERSE_BASIS_POINT = 1e4;\r\n uint256 internal constant BATCHING_PERIOD = 260;\r\n\r\n // Minimum APY for capital locked up to underwrite options.\r\n // The quote will return a minimum price corresponding to this APY\r\n int128 internal constant MIN_APY_64x64 = 0x4ccccccccccccccd; // 0.3\r\n\r\n constructor(\r\n address ivolOracle,\r\n address weth,\r\n address premiaMining,\r\n address feeReceiver,\r\n address feeDiscountAddress,\r\n int128 fee64x64\r\n ) {\r\n IVOL_ORACLE_ADDRESS = ivolOracle;\r\n WETH_ADDRESS = weth;\r\n PREMIA_MINING_ADDRESS = premiaMining;\r\n FEE_RECEIVER_ADDRESS = feeReceiver;\r\n // PremiaFeeDiscount contract address\r\n FEE_DISCOUNT_ADDRESS = feeDiscountAddress;\r\n FEE_64x64 = fee64x64;\r\n\r\n UNDERLYING_FREE_LIQ_TOKEN_ID = PoolStorage.formatTokenId(\r\n PoolStorage.TokenType.UNDERLYING_FREE_LIQ,\r\n 0,\r\n 0\r\n );\r\n BASE_FREE_LIQ_TOKEN_ID = PoolStorage.formatTokenId(\r\n PoolStorage.TokenType.BASE_FREE_LIQ,\r\n 0,\r\n 0\r\n );\r\n\r\n UNDERLYING_RESERVED_LIQ_TOKEN_ID = PoolStorage.formatTokenId(\r\n PoolStorage.TokenType.UNDERLYING_RESERVED_LIQ,\r\n 0,\r\n 0\r\n );\r\n BASE_RESERVED_LIQ_TOKEN_ID = PoolStorage.formatTokenId(\r\n PoolStorage.TokenType.BASE_RESERVED_LIQ,\r\n 0,\r\n 0\r\n );\r\n }\r\n\r\n modifier onlyProtocolOwner() {\r\n require(\r\n msg.sender == IERC173(OwnableStorage.layout().owner).owner(),\r\n \"Not protocol owner\"\r\n );\r\n _;\r\n }\r\n\r\n function _getFeeDiscount(address feePayer)\r\n internal\r\n view\r\n returns (uint256 discount)\r\n {\r\n if (FEE_DISCOUNT_ADDRESS != address(0)) {\r\n discount = IFeeDiscount(FEE_DISCOUNT_ADDRESS).getDiscount(feePayer);\r\n }\r\n }\r\n\r\n function _getFeeWithDiscount(address feePayer, uint256 fee)\r\n internal\r\n view\r\n returns (uint256)\r\n {\r\n uint256 discount = _getFeeDiscount(feePayer);\r\n return fee - ((fee * discount) / INVERSE_BASIS_POINT);\r\n }\r\n\r\n function _withdrawFees(bool isCall) internal returns (uint256 amount) {\r\n uint256 tokenId = _getReservedLiquidityTokenId(isCall);\r\n amount = _balanceOf(FEE_RECEIVER_ADDRESS, tokenId);\r\n\r\n if (amount > 0) {\r\n _burn(FEE_RECEIVER_ADDRESS, tokenId, amount);\r\n emit FeeWithdrawal(isCall, amount);\r\n }\r\n }\r\n\r\n /**\r\n * @notice calculate price of option contract\r\n * @param args structured quote arguments\r\n * @return result quote result\r\n */\r\n function _quote(PoolStorage.QuoteArgsInternal memory args)\r\n internal\r\n view\r\n returns (PoolStorage.QuoteResultInternal memory result)\r\n {\r\n require(\r\n args.strike64x64 > 0 && args.spot64x64 > 0 && args.maturity > 0,\r\n \"invalid args\"\r\n );\r\n PoolStorage.Layout storage l = PoolStorage.layout();\r\n\r\n int128 contractSize64x64 = ABDKMath64x64Token.fromDecimals(\r\n args.contractSize,\r\n l.underlyingDecimals\r\n );\r\n bool isCall = args.isCall;\r\n\r\n (int128 adjustedCLevel64x64, int128 oldLiquidity64x64) = l\r\n .applyCLevelPendingDepositAdjustment(\r\n l.getDecayAdjustedCLevel64x64(isCall),\r\n l.totalFreeLiquiditySupply64x64(isCall),\r\n isCall\r\n );\r\n\r\n require(oldLiquidity64x64 > 0, \"no liq\");\r\n\r\n int128 timeToMaturity64x64 = ABDKMath64x64.divu(\r\n args.maturity - block.timestamp,\r\n 365 days\r\n );\r\n\r\n int128 annualizedVolatility64x64 = IVolatilitySurfaceOracle(\r\n IVOL_ORACLE_ADDRESS\r\n ).getAnnualizedVolatility64x64(\r\n l.base,\r\n l.underlying,\r\n args.spot64x64,\r\n args.strike64x64,\r\n timeToMaturity64x64,\r\n isCall\r\n );\r\n\r\n require(annualizedVolatility64x64 > 0, \"vol = 0\");\r\n\r\n (\r\n int128 price64x64,\r\n int128 cLevel64x64,\r\n int128 slippageCoefficient64x64\r\n ) = OptionMath.quotePrice(\r\n OptionMath.QuoteArgs(\r\n annualizedVolatility64x64.mul(annualizedVolatility64x64),\r\n args.strike64x64,\r\n args.spot64x64,\r\n timeToMaturity64x64,\r\n adjustedCLevel64x64,\r\n oldLiquidity64x64,\r\n oldLiquidity64x64.sub(contractSize64x64),\r\n 0x10000000000000000, // 64x64 fixed point representation of 1\r\n MIN_APY_64x64,\r\n isCall\r\n )\r\n );\r\n\r\n result.baseCost64x64 = isCall\r\n ? price64x64.mul(contractSize64x64).div(args.spot64x64)\r\n : price64x64.mul(contractSize64x64);\r\n result.feeCost64x64 = result.baseCost64x64.mul(FEE_64x64);\r\n result.cLevel64x64 = cLevel64x64;\r\n result.slippageCoefficient64x64 = slippageCoefficient64x64;\r\n\r\n int128 discount = ABDKMath64x64.divu(\r\n _getFeeDiscount(args.feePayer),\r\n INVERSE_BASIS_POINT\r\n );\r\n result.feeCost64x64 -= result.feeCost64x64.mul(discount);\r\n }\r\n\r\n /**\r\n * @notice burn corresponding long and short option tokens\r\n * @param account holder of tokens to annihilate\r\n * @param maturity timestamp of option maturity\r\n * @param strike64x64 64x64 fixed point representation of strike price\r\n * @param isCall true for call, false for put\r\n * @param contractSize quantity of option contract tokens to annihilate\r\n */\r\n function _annihilate(\r\n address account,\r\n uint64 maturity,\r\n int128 strike64x64,\r\n bool isCall,\r\n uint256 contractSize\r\n ) internal {\r\n uint256 longTokenId = PoolStorage.formatTokenId(\r\n _getTokenType(isCall, true),\r\n maturity,\r\n strike64x64\r\n );\r\n uint256 shortTokenId = PoolStorage.formatTokenId(\r\n _getTokenType(isCall, false),\r\n maturity,\r\n strike64x64\r\n );\r\n\r\n _burn(account, longTokenId, contractSize);\r\n _burn(account, shortTokenId, contractSize);\r\n\r\n emit Annihilate(shortTokenId, contractSize);\r\n }\r\n\r\n /**\r\n * @notice purchase option\r\n * @param l storage layout struct\r\n * @param account recipient of purchased option\r\n * @param maturity timestamp of option maturity\r\n * @param strike64x64 64x64 fixed point representation of strike price\r\n * @param isCall true for call, false for put\r\n * @param contractSize size of option contract\r\n * @param newPrice64x64 64x64 fixed point representation of current spot price\r\n * @return baseCost quantity of tokens required to purchase long position\r\n * @return feeCost quantity of tokens required to pay fees\r\n */\r\n function _purchase(\r\n PoolStorage.Layout storage l,\r\n address account,\r\n uint64 maturity,\r\n int128 strike64x64,\r\n bool isCall,\r\n uint256 contractSize,\r\n int128 newPrice64x64\r\n ) internal returns (uint256 baseCost, uint256 feeCost) {\r\n require(maturity > block.timestamp, \"expired\");\r\n require(contractSize >= l.underlyingMinimum, \"too small\");\r\n\r\n {\r\n uint256 size = isCall\r\n ? contractSize\r\n : l.fromUnderlyingToBaseDecimals(\r\n strike64x64.mulu(contractSize)\r\n );\r\n\r\n require(\r\n size <=\r\n ERC1155EnumerableStorage.layout().totalSupply[\r\n _getFreeLiquidityTokenId(isCall)\r\n ] -\r\n l.nextDeposits[isCall].totalPendingDeposits,\r\n \"insuf liq\"\r\n );\r\n }\r\n\r\n PoolStorage.QuoteResultInternal memory quote = _quote(\r\n PoolStorage.QuoteArgsInternal(\r\n account,\r\n maturity,\r\n strike64x64,\r\n newPrice64x64,\r\n contractSize,\r\n isCall\r\n )\r\n );\r\n\r\n baseCost = ABDKMath64x64Token.toDecimals(\r\n quote.baseCost64x64,\r\n l.getTokenDecimals(isCall)\r\n );\r\n\r\n feeCost = ABDKMath64x64Token.toDecimals(\r\n quote.feeCost64x64,\r\n l.getTokenDecimals(isCall)\r\n );\r\n\r\n uint256 longTokenId = PoolStorage.formatTokenId(\r\n _getTokenType(isCall, true),\r\n maturity,\r\n strike64x64\r\n );\r\n\r\n uint256 shortTokenId = PoolStorage.formatTokenId(\r\n _getTokenType(isCall, false),\r\n maturity,\r\n strike64x64\r\n );\r\n\r\n // mint long option token for buyer\r\n _mint(account, longTokenId, contractSize);\r\n\r\n int128 oldLiquidity64x64 = l.totalFreeLiquiditySupply64x64(isCall);\r\n // burn free liquidity tokens from other underwriters\r\n _mintShortTokenLoop(\r\n l,\r\n account,\r\n contractSize,\r\n baseCost,\r\n shortTokenId,\r\n isCall\r\n );\r\n int128 newLiquidity64x64 = l.totalFreeLiquiditySupply64x64(isCall);\r\n\r\n _setCLevel(l, oldLiquidity64x64, newLiquidity64x64, isCall);\r\n\r\n // mint reserved liquidity tokens for fee receiver\r\n _mint(\r\n FEE_RECEIVER_ADDRESS,\r\n _getReservedLiquidityTokenId(isCall),\r\n feeCost\r\n );\r\n\r\n emit Purchase(\r\n account,\r\n longTokenId,\r\n contractSize,\r\n baseCost,\r\n feeCost,\r\n newPrice64x64\r\n );\r\n }\r\n\r\n /**\r\n * @notice reassign short position to new underwriter\r\n * @param l storage layout struct\r\n * @param account holder of positions to be reassigned\r\n * @param maturity timestamp of option maturity\r\n * @param strike64x64 64x64 fixed point representation of strike price\r\n * @param isCall true for call, false for put\r\n * @param contractSize quantity of option contract tokens to reassign\r\n * @param newPrice64x64 64x64 fixed point representation of current spot price\r\n * @return baseCost quantity of tokens required to reassign short position\r\n * @return feeCost quantity of tokens required to pay fees\r\n * @return amountOut quantity of liquidity freed\r\n */\r\n function _reassign(\r\n PoolStorage.Layout storage l,\r\n address account,\r\n uint64 maturity,\r\n int128 strike64x64,\r\n bool isCall,\r\n uint256 contractSize,\r\n int128 newPrice64x64\r\n )\r\n internal\r\n returns (\r\n uint256 baseCost,\r\n uint256 feeCost,\r\n uint256 amountOut\r\n )\r\n {\r\n (baseCost, feeCost) = _purchase(\r\n l,\r\n account,\r\n maturity,\r\n strike64x64,\r\n isCall,\r\n contractSize,\r\n newPrice64x64\r\n );\r\n\r\n _annihilate(account, maturity, strike64x64, isCall, contractSize);\r\n\r\n uint256 annihilateAmount = isCall\r\n ? contractSize\r\n : l.fromUnderlyingToBaseDecimals(strike64x64.mulu(contractSize));\r\n\r\n amountOut = annihilateAmount - baseCost - feeCost;\r\n }\r\n\r\n /**\r\n * @notice exercise option on behalf of holder\r\n * @dev used for processing of expired options if passed holder is zero address\r\n * @param holder owner of long option tokens to exercise\r\n * @param longTokenId long option token id\r\n * @param contractSize quantity of tokens to exercise\r\n */\r\n function _exercise(\r\n address holder,\r\n uint256 longTokenId,\r\n uint256 contractSize\r\n ) internal {\r\n uint64 maturity;\r\n int128 strike64x64;\r\n bool isCall;\r\n\r\n bool onlyExpired = holder == address(0);\r\n\r\n {\r\n PoolStorage.TokenType tokenType;\r\n (tokenType, maturity, strike64x64) = PoolStorage.parseTokenId(\r\n longTokenId\r\n );\r\n require(\r\n tokenType == PoolStorage.TokenType.LONG_CALL ||\r\n tokenType == PoolStorage.TokenType.LONG_PUT,\r\n \"invalid type\"\r\n );\r\n require(!onlyExpired || maturity < block.timestamp, \"not expired\");\r\n isCall = tokenType == PoolStorage.TokenType.LONG_CALL;\r\n }\r\n\r\n PoolStorage.Layout storage l = PoolStorage.layout();\r\n\r\n int128 spot64x64 = _update(l);\r\n\r\n if (maturity < block.timestamp) {\r\n spot64x64 = l.getPriceUpdateAfter(maturity);\r\n }\r\n\r\n require(\r\n onlyExpired ||\r\n (\r\n isCall\r\n ? (spot64x64 > strike64x64)\r\n : (spot64x64 < strike64x64)\r\n ),\r\n \"not ITM\"\r\n );\r\n\r\n uint256 exerciseValue;\r\n // option has a non-zero exercise value\r\n if (isCall) {\r\n if (spot64x64 > strike64x64) {\r\n exerciseValue = spot64x64.sub(strike64x64).div(spot64x64).mulu(\r\n contractSize\r\n );\r\n }\r\n } else {\r\n if (spot64x64 < strike64x64) {\r\n exerciseValue = l.fromUnderlyingToBaseDecimals(\r\n strike64x64.sub(spot64x64).mulu(contractSize)\r\n );\r\n }\r\n }\r\n\r\n uint256 totalFee;\r\n\r\n if (onlyExpired) {\r\n totalFee += _burnLongTokenLoop(\r\n contractSize,\r\n exerciseValue,\r\n longTokenId,\r\n isCall\r\n );\r\n } else {\r\n // burn long option tokens from sender\r\n _burn(holder, longTokenId, contractSize);\r\n\r\n uint256 fee;\r\n\r\n if (exerciseValue > 0) {\r\n fee = _getFeeWithDiscount(\r\n holder,\r\n FEE_64x64.mulu(exerciseValue)\r\n );\r\n totalFee += fee;\r\n\r\n _pushTo(holder, _getPoolToken(isCall), exerciseValue - fee);\r\n }\r\n\r\n emit Exercise(\r\n holder,\r\n longTokenId,\r\n contractSize,\r\n exerciseValue,\r\n fee\r\n );\r\n }\r\n\r\n totalFee += _burnShortTokenLoop(\r\n contractSize,\r\n exerciseValue,\r\n PoolStorage.formatTokenId(\r\n _getTokenType(isCall, false),\r\n maturity,\r\n strike64x64\r\n ),\r\n isCall\r\n );\r\n\r\n _mint(\r\n FEE_RECEIVER_ADDRESS,\r\n _getReservedLiquidityTokenId(isCall),\r\n totalFee\r\n );\r\n }\r\n\r\n function _mintShortTokenLoop(\r\n PoolStorage.Layout storage l,\r\n address buyer,\r\n uint256 contractSize,\r\n uint256 premium,\r\n uint256 shortTokenId,\r\n bool isCall\r\n ) internal {\r\n uint256 freeLiqTokenId = _getFreeLiquidityTokenId(isCall);\r\n (, , int128 strike64x64) = PoolStorage.parseTokenId(shortTokenId);\r\n\r\n uint256 toPay = isCall\r\n ? contractSize\r\n : l.fromUnderlyingToBaseDecimals(strike64x64.mulu(contractSize));\r\n\r\n while (toPay > 0) {\r\n address underwriter = l.liquidityQueueAscending[isCall][address(0)];\r\n uint256 balance = _balanceOf(underwriter, freeLiqTokenId);\r\n\r\n // If dust left, we remove underwriter and skip to next\r\n if (balance < _getMinimumAmount(l, isCall)) {\r\n l.removeUnderwriter(underwriter, isCall);\r\n continue;\r\n }\r\n\r\n if (!l.getReinvestmentStatus(underwriter, isCall)) {\r\n _burn(underwriter, freeLiqTokenId, balance);\r\n _mint(\r\n underwriter,\r\n _getReservedLiquidityTokenId(isCall),\r\n balance\r\n );\r\n _subUserTVL(l, underwriter, isCall, balance);\r\n continue;\r\n }\r\n\r\n // amount of liquidity provided by underwriter, accounting for reinvested premium\r\n uint256 intervalContractSize = ((balance -\r\n l.pendingDeposits[underwriter][l.nextDeposits[isCall].eta][\r\n isCall\r\n ]) * (toPay + premium)) / toPay;\r\n if (intervalContractSize == 0) continue;\r\n if (intervalContractSize > toPay) intervalContractSize = toPay;\r\n\r\n // amount of premium paid to underwriter\r\n uint256 intervalPremium = (premium * intervalContractSize) / toPay;\r\n premium -= intervalPremium;\r\n toPay -= intervalContractSize;\r\n _addUserTVL(l, underwriter, isCall, intervalPremium);\r\n\r\n // burn free liquidity tokens from underwriter\r\n _burn(\r\n underwriter,\r\n freeLiqTokenId,\r\n intervalContractSize - intervalPremium\r\n );\r\n\r\n if (isCall == false) {\r\n // For PUT, conversion to contract amount is done here (Prior to this line, it is token amount)\r\n intervalContractSize = l.fromBaseToUnderlyingDecimals(\r\n strike64x64.inv().mulu(intervalContractSize)\r\n );\r\n }\r\n\r\n // mint short option tokens for underwriter\r\n // toPay == 0 ? contractSize : intervalContractSize : To prevent minting less than amount,\r\n // because of rounding (Can happen for put, because of fixed point precision)\r\n _mint(\r\n underwriter,\r\n shortTokenId,\r\n toPay == 0 ? contractSize : intervalContractSize\r\n );\r\n\r\n emit Underwrite(\r\n underwriter,\r\n buyer,\r\n shortTokenId,\r\n toPay == 0 ? contractSize : intervalContractSize,\r\n intervalPremium,\r\n false\r\n );\r\n\r\n contractSize -= intervalContractSize;\r\n }\r\n }\r\n\r\n function _burnLongTokenLoop(\r\n uint256 contractSize,\r\n uint256 exerciseValue,\r\n uint256 longTokenId,\r\n bool isCall\r\n ) internal returns (uint256 totalFee) {\r\n EnumerableSet.AddressSet storage holders = ERC1155EnumerableStorage\r\n .layout()\r\n .accountsByToken[longTokenId];\r\n\r\n while (contractSize > 0) {\r\n address longTokenHolder = holders.at(holders.length() - 1);\r\n\r\n uint256 intervalContractSize = _balanceOf(\r\n longTokenHolder,\r\n longTokenId\r\n );\r\n if (intervalContractSize > contractSize)\r\n intervalContractSize = contractSize;\r\n\r\n uint256 intervalExerciseValue;\r\n\r\n uint256 fee;\r\n if (exerciseValue > 0) {\r\n intervalExerciseValue =\r\n (exerciseValue * intervalContractSize) /\r\n contractSize;\r\n\r\n fee = _getFeeWithDiscount(\r\n longTokenHolder,\r\n FEE_64x64.mulu(intervalExerciseValue)\r\n );\r\n totalFee += fee;\r\n\r\n exerciseValue -= intervalExerciseValue;\r\n _pushTo(\r\n longTokenHolder,\r\n _getPoolToken(isCall),\r\n intervalExerciseValue - fee\r\n );\r\n }\r\n\r\n contractSize -= intervalContractSize;\r\n\r\n emit Exercise(\r\n longTokenHolder,\r\n longTokenId,\r\n intervalContractSize,\r\n intervalExerciseValue - fee,\r\n fee\r\n );\r\n\r\n _burn(longTokenHolder, longTokenId, intervalContractSize);\r\n }\r\n }\r\n\r\n function _burnShortTokenLoop(\r\n uint256 contractSize,\r\n uint256 exerciseValue,\r\n uint256 shortTokenId,\r\n bool isCall\r\n ) internal returns (uint256 totalFee) {\r\n EnumerableSet.AddressSet storage underwriters = ERC1155EnumerableStorage\r\n .layout()\r\n .accountsByToken[shortTokenId];\r\n (, , int128 strike64x64) = PoolStorage.parseTokenId(shortTokenId);\r\n\r\n while (contractSize > 0) {\r\n address underwriter = underwriters.at(underwriters.length() - 1);\r\n\r\n // amount of liquidity provided by underwriter\r\n uint256 intervalContractSize = _balanceOf(\r\n underwriter,\r\n shortTokenId\r\n );\r\n if (intervalContractSize > contractSize)\r\n intervalContractSize = contractSize;\r\n\r\n // amount of value claimed by buyer\r\n uint256 intervalExerciseValue = (exerciseValue *\r\n intervalContractSize) / contractSize;\r\n exerciseValue -= intervalExerciseValue;\r\n contractSize -= intervalContractSize;\r\n\r\n uint256 freeLiq = isCall\r\n ? intervalContractSize - intervalExerciseValue\r\n : PoolStorage.layout().fromUnderlyingToBaseDecimals(\r\n strike64x64.mulu(intervalContractSize)\r\n ) - intervalExerciseValue;\r\n\r\n uint256 fee = _getFeeWithDiscount(\r\n underwriter,\r\n FEE_64x64.mulu(freeLiq)\r\n );\r\n totalFee += fee;\r\n\r\n uint256 tvlToSubtract = intervalExerciseValue;\r\n\r\n // mint free liquidity tokens for underwriter\r\n if (\r\n PoolStorage.layout().getReinvestmentStatus(underwriter, isCall)\r\n ) {\r\n _addToDepositQueue(underwriter, freeLiq - fee, isCall);\r\n tvlToSubtract += fee;\r\n } else {\r\n _mint(\r\n underwriter,\r\n _getReservedLiquidityTokenId(isCall),\r\n freeLiq - fee\r\n );\r\n tvlToSubtract += freeLiq;\r\n }\r\n\r\n _subUserTVL(\r\n PoolStorage.layout(),\r\n underwriter,\r\n isCall,\r\n tvlToSubtract\r\n );\r\n\r\n // burn short option tokens from underwriter\r\n _burn(underwriter, shortTokenId, intervalContractSize);\r\n\r\n emit AssignExercise(\r\n underwriter,\r\n shortTokenId,\r\n freeLiq - fee,\r\n intervalContractSize,\r\n fee\r\n );\r\n }\r\n }\r\n\r\n function _addToDepositQueue(\r\n address account,\r\n uint256 amount,\r\n bool isCallPool\r\n ) internal {\r\n PoolStorage.Layout storage l = PoolStorage.layout();\r\n\r\n _mint(account, _getFreeLiquidityTokenId(isCallPool), amount);\r\n\r\n uint256 nextBatch = (block.timestamp / BATCHING_PERIOD) *\r\n BATCHING_PERIOD +\r\n BATCHING_PERIOD;\r\n l.pendingDeposits[account][nextBatch][isCallPool] += amount;\r\n\r\n PoolStorage.BatchData storage batchData = l.nextDeposits[isCallPool];\r\n batchData.totalPendingDeposits += amount;\r\n batchData.eta = nextBatch;\r\n }\r\n\r\n function _processPendingDeposits(PoolStorage.Layout storage l, bool isCall)\r\n internal\r\n {\r\n PoolStorage.BatchData storage data = l.nextDeposits[isCall];\r\n\r\n if (data.eta == 0 || block.timestamp < data.eta) return;\r\n\r\n int128 oldLiquidity64x64 = l.totalFreeLiquiditySupply64x64(isCall);\r\n\r\n _setCLevel(\r\n l,\r\n oldLiquidity64x64,\r\n oldLiquidity64x64.add(\r\n ABDKMath64x64Token.fromDecimals(\r\n data.totalPendingDeposits,\r\n l.getTokenDecimals(isCall)\r\n )\r\n ),\r\n isCall\r\n );\r\n\r\n delete l.nextDeposits[isCall];\r\n }\r\n\r\n function _getFreeLiquidityTokenId(bool isCall)\r\n internal\r\n view\r\n returns (uint256 freeLiqTokenId)\r\n {\r\n freeLiqTokenId = isCall\r\n ? UNDERLYING_FREE_LIQ_TOKEN_ID\r\n : BASE_FREE_LIQ_TOKEN_ID;\r\n }\r\n\r\n function _getReservedLiquidityTokenId(bool isCall)\r\n internal\r\n view\r\n returns (uint256 reservedLiqTokenId)\r\n {\r\n reservedLiqTokenId = isCall\r\n ? UNDERLYING_RESERVED_LIQ_TOKEN_ID\r\n : BASE_RESERVED_LIQ_TOKEN_ID;\r\n }\r\n\r\n function _getPoolToken(bool isCall) internal view returns (address token) {\r\n token = isCall\r\n ? PoolStorage.layout().underlying\r\n : PoolStorage.layout().base;\r\n }\r\n\r\n function _getTokenType(bool isCall, bool isLong)\r\n internal\r\n pure\r\n returns (PoolStorage.TokenType tokenType)\r\n {\r\n if (isCall) {\r\n tokenType = isLong\r\n ? PoolStorage.TokenType.LONG_CALL\r\n : PoolStorage.TokenType.SHORT_CALL;\r\n } else {\r\n tokenType = isLong\r\n ? PoolStorage.TokenType.LONG_PUT\r\n : PoolStorage.TokenType.SHORT_PUT;\r\n }\r\n }\r\n\r\n function _getMinimumAmount(PoolStorage.Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (uint256 minimumAmount)\r\n {\r\n minimumAmount = isCall ? l.underlyingMinimum : l.baseMinimum;\r\n }\r\n\r\n function _getPoolCapAmount(PoolStorage.Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (uint256 poolCapAmount)\r\n {\r\n poolCapAmount = isCall ? l.underlyingPoolCap : l.basePoolCap;\r\n }\r\n\r\n function _setCLevel(\r\n PoolStorage.Layout storage l,\r\n int128 oldLiquidity64x64,\r\n int128 newLiquidity64x64,\r\n bool isCallPool\r\n ) internal {\r\n int128 oldCLevel64x64 = l.getDecayAdjustedCLevel64x64(isCallPool);\r\n\r\n int128 cLevel64x64 = l.applyCLevelLiquidityChangeAdjustment(\r\n oldCLevel64x64,\r\n oldLiquidity64x64,\r\n newLiquidity64x64,\r\n isCallPool\r\n );\r\n\r\n l.setCLevel(cLevel64x64, isCallPool);\r\n\r\n emit UpdateCLevel(\r\n isCallPool,\r\n cLevel64x64,\r\n oldLiquidity64x64,\r\n newLiquidity64x64\r\n );\r\n }\r\n\r\n /**\r\n * @notice calculate and store updated market state\r\n * @param l storage layout struct\r\n * @return newPrice64x64 64x64 fixed point representation of current spot price\r\n */\r\n function _update(PoolStorage.Layout storage l)\r\n internal\r\n returns (int128 newPrice64x64)\r\n {\r\n if (l.updatedAt == block.timestamp) {\r\n return (l.getPriceUpdate(block.timestamp));\r\n }\r\n\r\n newPrice64x64 = l.fetchPriceUpdate();\r\n\r\n if (l.getPriceUpdate(block.timestamp) == 0) {\r\n l.setPriceUpdate(block.timestamp, newPrice64x64);\r\n }\r\n\r\n l.updatedAt = block.timestamp;\r\n\r\n _processPendingDeposits(l, true);\r\n _processPendingDeposits(l, false);\r\n }\r\n\r\n /**\r\n * @notice transfer ERC20 tokens to message sender\r\n * @param token ERC20 token address\r\n * @param amount quantity of token to transfer\r\n */\r\n function _pushTo(\r\n address to,\r\n address token,\r\n uint256 amount\r\n ) internal {\r\n if (amount == 0) return;\r\n\r\n require(IERC20(token).transfer(to, amount), \"ERC20 transfer failed\");\r\n }\r\n\r\n /**\r\n * @notice transfer ERC20 tokens from message sender\r\n * @param from address from which tokens are pulled from\r\n * @param token ERC20 token address\r\n * @param amount quantity of token to transfer\r\n * @param skipWethDeposit if false, will not try to deposit weth from attach eth\r\n */\r\n function _pullFrom(\r\n address from,\r\n address token,\r\n uint256 amount,\r\n bool skipWethDeposit\r\n ) internal {\r\n if (!skipWethDeposit) {\r\n if (token == WETH_ADDRESS) {\r\n if (msg.value > 0) {\r\n if (msg.value > amount) {\r\n IWETH(WETH_ADDRESS).deposit{value: amount}();\r\n\r\n (bool success, ) = payable(msg.sender).call{\r\n value: msg.value - amount\r\n }(\"\");\r\n\r\n require(success, \"ETH refund failed\");\r\n\r\n amount = 0;\r\n } else {\r\n unchecked {\r\n amount -= msg.value;\r\n }\r\n\r\n IWETH(WETH_ADDRESS).deposit{value: msg.value}();\r\n }\r\n }\r\n } else {\r\n require(msg.value == 0, \"not WETH deposit\");\r\n }\r\n }\r\n\r\n if (amount > 0) {\r\n require(\r\n IERC20(token).transferFrom(from, address(this), amount),\r\n \"ERC20 transfer failed\"\r\n );\r\n }\r\n }\r\n\r\n function _mint(\r\n address account,\r\n uint256 tokenId,\r\n uint256 amount\r\n ) internal {\r\n _mint(account, tokenId, amount, \"\");\r\n }\r\n\r\n function _addUserTVL(\r\n PoolStorage.Layout storage l,\r\n address user,\r\n bool isCallPool,\r\n uint256 amount\r\n ) internal {\r\n uint256 userTVL = l.userTVL[user][isCallPool];\r\n uint256 totalTVL = l.totalTVL[isCallPool];\r\n\r\n IPremiaMining(PREMIA_MINING_ADDRESS).allocatePending(\r\n user,\r\n address(this),\r\n isCallPool,\r\n userTVL,\r\n userTVL + amount,\r\n totalTVL\r\n );\r\n\r\n l.userTVL[user][isCallPool] = userTVL + amount;\r\n l.totalTVL[isCallPool] = totalTVL + amount;\r\n }\r\n\r\n function _subUserTVL(\r\n PoolStorage.Layout storage l,\r\n address user,\r\n bool isCallPool,\r\n uint256 amount\r\n ) internal {\r\n uint256 userTVL = l.userTVL[user][isCallPool];\r\n uint256 totalTVL = l.totalTVL[isCallPool];\r\n\r\n IPremiaMining(PREMIA_MINING_ADDRESS).allocatePending(\r\n user,\r\n address(this),\r\n isCallPool,\r\n userTVL,\r\n userTVL - amount,\r\n totalTVL\r\n );\r\n l.userTVL[user][isCallPool] = userTVL - amount;\r\n l.totalTVL[isCallPool] = totalTVL - amount;\r\n }\r\n\r\n /**\r\n * @notice ERC1155 hook: track eligible underwriters\r\n * @param operator transaction sender\r\n * @param from token sender\r\n * @param to token receiver\r\n * @param ids token ids transferred\r\n * @param amounts token quantities transferred\r\n * @param data data payload\r\n */\r\n function _beforeTokenTransfer(\r\n address operator,\r\n address from,\r\n address to,\r\n uint256[] memory ids,\r\n uint256[] memory amounts,\r\n bytes memory data\r\n ) internal virtual override {\r\n super._beforeTokenTransfer(operator, from, to, ids, amounts, data);\r\n\r\n PoolStorage.Layout storage l = PoolStorage.layout();\r\n\r\n for (uint256 i; i < ids.length; i++) {\r\n uint256 id = ids[i];\r\n uint256 amount = amounts[i];\r\n\r\n if (amount == 0) continue;\r\n\r\n if (from == address(0)) {\r\n l.tokenIds.add(id);\r\n }\r\n\r\n if (\r\n to == address(0) &&\r\n ERC1155EnumerableStorage.layout().totalSupply[id] == 0\r\n ) {\r\n l.tokenIds.remove(id);\r\n }\r\n\r\n // prevent transfer of free and reserved liquidity during waiting period\r\n\r\n if (\r\n id == UNDERLYING_FREE_LIQ_TOKEN_ID ||\r\n id == BASE_FREE_LIQ_TOKEN_ID ||\r\n id == UNDERLYING_RESERVED_LIQ_TOKEN_ID ||\r\n id == BASE_RESERVED_LIQ_TOKEN_ID\r\n ) {\r\n if (from != address(0) && to != address(0)) {\r\n bool isCallPool = id == UNDERLYING_FREE_LIQ_TOKEN_ID ||\r\n id == UNDERLYING_RESERVED_LIQ_TOKEN_ID;\r\n\r\n require(\r\n l.depositedAt[from][isCallPool] + (1 days) <\r\n block.timestamp,\r\n \"liq lock 1d\"\r\n );\r\n }\r\n }\r\n\r\n if (\r\n id == UNDERLYING_FREE_LIQ_TOKEN_ID ||\r\n id == BASE_FREE_LIQ_TOKEN_ID\r\n ) {\r\n bool isCallPool = id == UNDERLYING_FREE_LIQ_TOKEN_ID;\r\n uint256 minimum = _getMinimumAmount(l, isCallPool);\r\n\r\n if (from != address(0)) {\r\n uint256 balance = _balanceOf(from, id);\r\n\r\n if (balance > minimum && balance <= amount + minimum) {\r\n require(\r\n balance -\r\n l.pendingDeposits[from][\r\n l.nextDeposits[isCallPool].eta\r\n ][isCallPool] >=\r\n amount,\r\n \"Insuf balance\"\r\n );\r\n l.removeUnderwriter(from, isCallPool);\r\n }\r\n\r\n if (to != address(0)) {\r\n _subUserTVL(l, from, isCallPool, amounts[i]);\r\n _addUserTVL(l, to, isCallPool, amounts[i]);\r\n }\r\n }\r\n\r\n if (to != address(0)) {\r\n uint256 balance = _balanceOf(to, id);\r\n\r\n if (balance <= minimum && balance + amount > minimum) {\r\n l.addUnderwriter(to, isCallPool);\r\n }\r\n }\r\n }\r\n\r\n // Update userTVL on SHORT options transfers\r\n (\r\n PoolStorage.TokenType tokenType,\r\n ,\r\n int128 strike64x64\r\n ) = PoolStorage.parseTokenId(id);\r\n\r\n if (\r\n (from != address(0) && to != address(0)) &&\r\n (tokenType == PoolStorage.TokenType.SHORT_CALL ||\r\n tokenType == PoolStorage.TokenType.SHORT_PUT)\r\n ) {\r\n bool isCall = tokenType == PoolStorage.TokenType.SHORT_CALL;\r\n uint256 collateral = isCall\r\n ? amount\r\n : l.fromUnderlyingToBaseDecimals(strike64x64.mulu(amount));\r\n\r\n _subUserTVL(l, from, isCall, collateral);\r\n _addUserTVL(l, to, isCall, collateral);\r\n }\r\n }\r\n }\r\n}\r\n"},"@solidstate/contracts/token/ERC1155/base/ERC1155BaseStorage.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nlibrary ERC1155BaseStorage {\n struct Layout {\n mapping(uint256 => mapping(address => uint256)) balances;\n mapping(address => mapping(address => bool)) operatorApprovals;\n }\n\n bytes32 internal constant STORAGE_SLOT =\n keccak256('solidstate.contracts.storage.ERC1155Base');\n\n function layout() internal pure returns (Layout storage l) {\n bytes32 slot = STORAGE_SLOT;\n assembly {\n l.slot := slot\n }\n }\n}\n"},"@solidstate/contracts/utils/EnumerableSet.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title Set implementation with enumeration functions\n * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts (MIT license)\n */\nlibrary EnumerableSet {\n struct Set {\n bytes32[] _values;\n // 1-indexed to allow 0 to signify nonexistence\n mapping(bytes32 => uint256) _indexes;\n }\n\n struct Bytes32Set {\n Set _inner;\n }\n\n struct AddressSet {\n Set _inner;\n }\n\n struct UintSet {\n Set _inner;\n }\n\n function at(Bytes32Set storage set, uint256 index)\n internal\n view\n returns (bytes32)\n {\n return _at(set._inner, index);\n }\n\n function at(AddressSet storage set, uint256 index)\n internal\n view\n returns (address)\n {\n return address(uint160(uint256(_at(set._inner, index))));\n }\n\n function at(UintSet storage set, uint256 index)\n internal\n view\n returns (uint256)\n {\n return uint256(_at(set._inner, index));\n }\n\n function contains(Bytes32Set storage set, bytes32 value)\n internal\n view\n returns (bool)\n {\n return _contains(set._inner, value);\n }\n\n function contains(AddressSet storage set, address value)\n internal\n view\n returns (bool)\n {\n return _contains(set._inner, bytes32(uint256(uint160(value))));\n }\n\n function contains(UintSet storage set, uint256 value)\n internal\n view\n returns (bool)\n {\n return _contains(set._inner, bytes32(value));\n }\n\n function indexOf(Bytes32Set storage set, bytes32 value)\n internal\n view\n returns (uint256)\n {\n return _indexOf(set._inner, value);\n }\n\n function indexOf(AddressSet storage set, address value)\n internal\n view\n returns (uint256)\n {\n return _indexOf(set._inner, bytes32(uint256(uint160(value))));\n }\n\n function indexOf(UintSet storage set, uint256 value)\n internal\n view\n returns (uint256)\n {\n return _indexOf(set._inner, bytes32(value));\n }\n\n function length(Bytes32Set storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n function length(AddressSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n function length(UintSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n function add(Bytes32Set storage set, bytes32 value)\n internal\n returns (bool)\n {\n return _add(set._inner, value);\n }\n\n function add(AddressSet storage set, address value)\n internal\n returns (bool)\n {\n return _add(set._inner, bytes32(uint256(uint160(value))));\n }\n\n function add(UintSet storage set, uint256 value) internal returns (bool) {\n return _add(set._inner, bytes32(value));\n }\n\n function remove(Bytes32Set storage set, bytes32 value)\n internal\n returns (bool)\n {\n return _remove(set._inner, value);\n }\n\n function remove(AddressSet storage set, address value)\n internal\n returns (bool)\n {\n return _remove(set._inner, bytes32(uint256(uint160(value))));\n }\n\n function remove(UintSet storage set, uint256 value)\n internal\n returns (bool)\n {\n return _remove(set._inner, bytes32(value));\n }\n\n function _at(Set storage set, uint256 index)\n private\n view\n returns (bytes32)\n {\n require(\n set._values.length > index,\n 'EnumerableSet: index out of bounds'\n );\n return set._values[index];\n }\n\n function _contains(Set storage set, bytes32 value)\n private\n view\n returns (bool)\n {\n return set._indexes[value] != 0;\n }\n\n function _indexOf(Set storage set, bytes32 value)\n private\n view\n returns (uint256)\n {\n unchecked {\n return set._indexes[value] - 1;\n }\n }\n\n function _length(Set storage set) private view returns (uint256) {\n return set._values.length;\n }\n\n function _add(Set storage set, bytes32 value) private returns (bool) {\n if (!_contains(set, value)) {\n set._values.push(value);\n set._indexes[value] = set._values.length;\n return true;\n } else {\n return false;\n }\n }\n\n function _remove(Set storage set, bytes32 value) private returns (bool) {\n uint256 valueIndex = set._indexes[value];\n\n if (valueIndex != 0) {\n uint256 index = valueIndex - 1;\n bytes32 last = set._values[set._values.length - 1];\n\n // move last value to now-vacant index\n\n set._values[index] = last;\n set._indexes[last] = index + 1;\n\n // clear last index\n\n set._values.pop();\n delete set._indexes[value];\n\n return true;\n } else {\n return false;\n }\n }\n}\n"},"contracts/mining/PremiaMiningStorage.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nlibrary PremiaMiningStorage {\r\n bytes32 internal constant STORAGE_SLOT =\r\n keccak256(\"premia.contracts.storage.PremiaMining\");\r\n\r\n // Info of each pool.\r\n struct PoolInfo {\r\n uint256 allocPoint; // How many allocation points assigned to this pool. PREMIA to distribute per block.\r\n uint256 lastRewardTimestamp; // Last timestamp that PREMIA distribution occurs\r\n uint256 accPremiaPerShare; // Accumulated PREMIA per share, times 1e12. See below.\r\n }\r\n\r\n // Info of each user.\r\n struct UserInfo {\r\n uint256 reward; // Total allocated unclaimed reward\r\n uint256 rewardDebt; // Reward debt. See explanation below.\r\n //\r\n // We do some fancy math here. Basically, any point in time, the amount of PREMIA\r\n // entitled to a user but is pending to be distributed is:\r\n //\r\n // pending reward = (user.amount * pool.accPremiaPerShare) - user.rewardDebt\r\n //\r\n // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:\r\n // 1. The pool's `accPremiaPerShare` (and `lastRewardBlock`) gets updated.\r\n // 2. User receives the pending reward sent to his/her address.\r\n // 3. User's `amount` gets updated.\r\n // 4. User's `rewardDebt` gets updated.\r\n }\r\n\r\n struct Layout {\r\n // Total PREMIA left to distribute\r\n uint256 premiaAvailable;\r\n // Amount of premia distributed per year\r\n uint256 premiaPerYear;\r\n // pool -> isCallPool -> PoolInfo\r\n mapping(address => mapping(bool => PoolInfo)) poolInfo;\r\n // pool -> isCallPool -> user -> UserInfo\r\n mapping(address => mapping(bool => mapping(address => UserInfo))) userInfo;\r\n // Total allocation points. Must be the sum of all allocation points in all pools.\r\n uint256 totalAllocPoint;\r\n }\r\n\r\n function layout() internal pure returns (Layout storage l) {\r\n bytes32 slot = STORAGE_SLOT;\r\n assembly {\r\n l.slot := slot\r\n }\r\n }\r\n}\r\n"},"@solidstate/contracts/utils/IWETH.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC20 } from '../token/ERC20/IERC20.sol';\nimport { IERC20Metadata } from '../token/ERC20/metadata/IERC20Metadata.sol';\n\n/**\n * @title WETH (Wrapped ETH) interface\n */\ninterface IWETH is IERC20, IERC20Metadata {\n /**\n * @notice convert ETH to WETH\n */\n function deposit() external payable;\n\n /**\n * @notice convert WETH to ETH\n * @dev if caller is a contract, it should have a fallback or receive function\n * @param amount quantity of WETH to convert, denominated in wei\n */\n function withdraw(uint256 amount) external;\n}\n"},"@solidstate/contracts/token/ERC1155/IERC1155Internal.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC165 } from '../../introspection/IERC165.sol';\n\n/**\n * @notice Partial ERC1155 interface needed by internal functions\n */\ninterface IERC1155Internal {\n event TransferSingle(\n address indexed operator,\n address indexed from,\n address indexed to,\n uint256 id,\n uint256 value\n );\n\n event TransferBatch(\n address indexed operator,\n address indexed from,\n address indexed to,\n uint256[] ids,\n uint256[] values\n );\n\n event ApprovalForAll(\n address indexed account,\n address indexed operator,\n bool approved\n );\n}\n"},"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorV3Interface {\n\n function decimals()\n external\n view\n returns (\n uint8\n );\n\n function description()\n external\n view\n returns (\n string memory\n );\n\n function version()\n external\n view\n returns (\n uint256\n );\n\n // getRoundData and latestRoundData should both raise \"No data present\"\n // if they do not have data to report, instead of returning unset values\n // which could be misinterpreted as actual reported values.\n function getRoundData(\n uint80 _roundId\n )\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n\n function latestRoundData()\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n\n}\n"},"@solidstate/contracts/access/OwnableStorage.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nlibrary OwnableStorage {\n struct Layout {\n address owner;\n }\n\n bytes32 internal constant STORAGE_SLOT =\n keccak256('solidstate.contracts.storage.Ownable');\n\n function layout() internal pure returns (Layout storage l) {\n bytes32 slot = STORAGE_SLOT;\n assembly {\n l.slot := slot\n }\n }\n\n function setOwner(Layout storage l, address owner) internal {\n l.owner = owner;\n }\n}\n"}},"settings":{"optimizer":{"enabled":true,"runs":200},"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}},"libraries":{"contracts/libraries/OptionMath.sol":{"OptionMath":"0x0f6e8ef18fb5bb61d545fee60f779d8aed60408f"}}}},"ABI":"[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"ivolOracle\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"weth\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"premiaMining\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"feeReceiver\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"feeDiscountAddress\",\"type\":\"address\"},{\"internalType\":\"int128\",\"name\":\"fee64x64\",\"type\":\"int128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"shortTokenId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Annihilate\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"approved\",\"type\":\"bool\"}],\"name\":\"ApprovalForAll\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"underwriter\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"shortTokenId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"freedAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"intervalContractSize\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"}],\"name\":\"AssignExercise\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isCallPool\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"longTokenId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"contractSize\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"exerciseValue\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"}],\"name\":\"Exercise\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bool\",\"name\":\"isCallPool\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"FeeWithdrawal\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"longTokenId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"contractSize\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"baseCost\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"feeCost\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"int128\",\"name\":\"spot64x64\",\"type\":\"int128\"}],\"name\":\"Purchase\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"ids\",\"type\":\"uint256[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"values\",\"type\":\"uint256[]\"}],\"name\":\"TransferBatch\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"TransferSingle\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"underwriter\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"longReceiver\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"shortTokenId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"intervalContractSize\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"intervalPremium\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isManualUnderwrite\",\"type\":\"bool\"}],\"name\":\"Underwrite\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bool\",\"name\":\"isCall\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"int128\",\"name\":\"cLevel64x64\",\"type\":\"int128\"},{\"indexed\":false,\"internalType\":\"int128\",\"name\":\"oldLiquidity64x64\",\"type\":\"int128\"},{\"indexed\":false,\"internalType\":\"int128\",\"name\":\"newLiquidity64x64\",\"type\":\"int128\"}],\"name\":\"UpdateCLevel\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"int128\",\"name\":\"steepness64x64\",\"type\":\"int128\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isCallPool\",\"type\":\"bool\"}],\"name\":\"UpdateSteepness\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isCallPool\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"depositedAt\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Withdrawal\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"holder\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"longTokenId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"contractSize\",\"type\":\"uint256\"}],\"name\":\"exerciseFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"longTokenId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"contractSize\",\"type\":\"uint256\"}],\"name\":\"processExpired\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]","ContractName":"PoolExercise","CompilerVersion":"v0.8.9+commit.e5eed63a","OptimizationUsed":1,"Runs":200,"ConstructorArguments":"0x0000000000000000000000003a87bb29b984d672664aa1dd2d19d2e8b24f0f2a000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009abb27581c2e46a114f8c367355851e0580e9703000000000000000000000000c4b2c51f969e0713e799de73b7f130fb7bb604cf000000000000000000000000f1bb87563a122211d40d393ebf1c633c330377f900000000000000000000000000000000000000000000000007ae147ae147ae14","EVMVersion":"Default","Library":"","LicenseType":"","Proxy":0,"SwarmSource":""}] \ No newline at end of file diff --git a/testdata/fixtures/Dir/depth1 b/testdata/fixtures/Dir/depth1 index febdc5997fa40..17c01c6ebd131 100644 --- a/testdata/fixtures/Dir/depth1 +++ b/testdata/fixtures/Dir/depth1 @@ -1 +1 @@ -Wow! 😀 +Wow! 😀 \ No newline at end of file diff --git a/testdata/fixtures/Json/Issue4402.json b/testdata/fixtures/Json/Issue4402.json new file mode 100644 index 0000000000000..e981817fe9051 --- /dev/null +++ b/testdata/fixtures/Json/Issue4402.json @@ -0,0 +1,4 @@ +{ + "tokens": ["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"], + "empty": [] +} \ No newline at end of file diff --git a/testdata/fixtures/Json/test.json b/testdata/fixtures/Json/test.json index 4e4ade7830170..1f59ba456269c 100644 --- a/testdata/fixtures/Json/test.json +++ b/testdata/fixtures/Json/test.json @@ -1,28 +1,31 @@ { - "str": "hai", - "uintArray": [42, 43], - "strArray": ["hai", "there"], - "bool": true, - "boolArray": [true, false], + "basicString": "hai", + "null": null, + "stringArray": ["hai", "there"], "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "addressArray": [ "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "0x7109709ECfa91a80626fF3989D68f67F5b1DD12D" ], "H160NotAddress": "0000000000000000000000000000000000001337", + "boolTrue": true, + "boolFalse": false, + "boolArray": [true, false], + "boolString": "true", + "boolStringArray": [true, "false"], + "uintNumber": 115792089237316195423570985008687907853269984665640564039457584007913129639935, + "uintString": "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "uintHex": "0x12C980", + "uintArray": [42, 43], + "uintStringArray": [1231232, "0x12C980", "1231232"], + "intNumber": -12, + "intString": "-12", + "intHex": "-0xC", + "bytesString": "0x01", + "bytesStringArray": ["0x01", "0x02"], "nestedObject": { "number": 115792089237316195423570985008687907853269984665640564039457584007913129639935, "str": "NEST" }, - "bytesArray": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000966666920776f726b730000000000000000000000000000000000000000000000", - "hexUint": "0x12C980", - "stringUint": "115792089237316195423570985008687907853269984665640564039457584007913129639935", - "numberUint": 115792089237316195423570985008687907853269984665640564039457584007913129639935, - "arrayUint": [1231232, "0x12C980", "1231232"], - "stringInt": "-12", - "numberInt": -12, - "hexInt": "-0xC", - "booleanString": "true", - "booleanArray": [true, "false"], "advancedJsonPath": [{ "id": 1 }, { "id": 2 }] } diff --git a/testdata/fixtures/Json/wholeJson.json b/testdata/fixtures/Json/whole_json.json similarity index 100% rename from testdata/fixtures/Json/wholeJson.json rename to testdata/fixtures/Json/whole_json.json diff --git a/testdata/fixtures/Toml/Issue4402.toml b/testdata/fixtures/Toml/Issue4402.toml new file mode 100644 index 0000000000000..8f7d110238c89 --- /dev/null +++ b/testdata/fixtures/Toml/Issue4402.toml @@ -0,0 +1,2 @@ +tokens = ["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"] +empty = [] diff --git a/testdata/fixtures/Toml/test.toml b/testdata/fixtures/Toml/test.toml new file mode 100644 index 0000000000000..ce735b8f18cf9 --- /dev/null +++ b/testdata/fixtures/Toml/test.toml @@ -0,0 +1,50 @@ +basicString = "hai" +nullString = "null" +multilineString = """ +hai +there +""" +stringArray = ["hai", "there"] + +address = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +addressArray = [ + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "0x7109709ECfa91a80626fF3989D68f67F5b1DD12D", +] +H160NotAddress = "0000000000000000000000000000000000001337" + +boolTrue = true +boolFalse = false +boolArray = [true, false] +boolString = "true" +boolStringArray = ["true", "false"] # Array values can't have mixed types + +datetime = 2021-08-10T14:48:00Z +datetimeArray = [2021-08-10T14:48:00Z, 2021-08-10T14:48:00Z] + +uintNumber = 9223372036854775807 # TOML is limited to 64-bit integers +uintString = "115792089237316195423570985008687907853269984665640564039457584007913129639935" +uintHex = "0x12C980" +uintArray = [42, 43] +uintStringArray = [ + "1231232", + "0x12C980", + "1231232", +] # Array values can't have mixed types + +intNumber = -12 +intString = "-12" +intHex = "-0xC" + +bytesString = "0x01" +bytesStringArray = ["0x01", "0x02"] + +[nestedObject] +number = 9223372036854775807 # TOML is limited to 64-bit integers +str = "NEST" + +[[advancedJsonPath]] +id = 1 + +[[advancedJsonPath]] +id = 2 diff --git a/testdata/fixtures/Toml/whole_toml.toml b/testdata/fixtures/Toml/whole_toml.toml new file mode 100644 index 0000000000000..badbd9fbbe5cd --- /dev/null +++ b/testdata/fixtures/Toml/whole_toml.toml @@ -0,0 +1,3 @@ +str = "hai" +uintArray = [42, 43] +strArray = ["hai", "there"] diff --git a/testdata/fixtures/Toml/write_complex_test.toml b/testdata/fixtures/Toml/write_complex_test.toml new file mode 100644 index 0000000000000..60692bc750201 --- /dev/null +++ b/testdata/fixtures/Toml/write_complex_test.toml @@ -0,0 +1,6 @@ +a = 123 +b = "test" + +[c] +a = 123 +b = "test" diff --git a/testdata/fixtures/Toml/write_test.toml b/testdata/fixtures/Toml/write_test.toml new file mode 100644 index 0000000000000..6c084e370e5a6 --- /dev/null +++ b/testdata/fixtures/Toml/write_test.toml @@ -0,0 +1,2 @@ +a = 123 +b = "0x000000000000000000000000000000000000bEEF" diff --git a/testdata/foundry.toml b/testdata/foundry.toml index 94bdeea394937..22df7b3e586a5 100644 --- a/testdata/foundry.toml +++ b/testdata/foundry.toml @@ -17,6 +17,7 @@ force = false invariant_fail_on_revert = false invariant_call_override = false invariant_shrink_sequence = true +invariant_preserve_state = false gas_limit = 9223372036854775807 gas_price = 0 gas_reports = ["*"] diff --git a/testdata/multi-version/Counter.sol b/testdata/multi-version/Counter.sol new file mode 100644 index 0000000000000..4f0c350335f8e --- /dev/null +++ b/testdata/multi-version/Counter.sol @@ -0,0 +1,11 @@ +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/testdata/multi-version/Importer.sol b/testdata/multi-version/Importer.sol new file mode 100644 index 0000000000000..d8e274e8f5447 --- /dev/null +++ b/testdata/multi-version/Importer.sol @@ -0,0 +1,7 @@ +pragma solidity 0.8.17; + +import "./Counter.sol"; + +// Please do not remove or change version pragma for this file. +// If you need to ensure that some of the files are compiled with +// solc 0.8.17, you should add imports of them to this file. diff --git a/testdata/multi-version/cheats/GetCode.t.sol b/testdata/multi-version/cheats/GetCode.t.sol new file mode 100644 index 0000000000000..e4a7bd14ae4e9 --- /dev/null +++ b/testdata/multi-version/cheats/GetCode.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../Counter.sol"; + +contract GetCodeTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testGetCodeMultiVersion() public { + assertEq(vm.getCode("Counter.sol"), type(Counter).creationCode); + require( + keccak256(vm.getCode("Counter.sol")) != keccak256(vm.getCode("Counter.sol:Counter:0.8.17")), + "Invalid artifact" + ); + assertEq(vm.getCode("Counter.sol"), vm.getCode("Counter.sol:Counter:0.8.18")); + } + + function testGetCodeByNameMultiVersion() public { + assertEq(vm.getCode("Counter"), type(Counter).creationCode); + require(keccak256(vm.getCode("Counter")) != keccak256(vm.getCode("Counter:0.8.17")), "Invalid artifact"); + assertEq(vm.getCode("Counter"), vm.getCode("Counter:0.8.18")); + } +} diff --git a/testdata/multi-version/cheats/GetCode17.t.sol b/testdata/multi-version/cheats/GetCode17.t.sol new file mode 100644 index 0000000000000..068a910cf7b65 --- /dev/null +++ b/testdata/multi-version/cheats/GetCode17.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.17; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../Counter.sol"; + +// Same as GetCode.t.sol but for 0.8.17 version +contract GetCodeTest17 is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testGetCodeMultiVersion() public { + assertEq(vm.getCode("Counter.sol"), type(Counter).creationCode); + require( + keccak256(vm.getCode("Counter.sol")) != keccak256(vm.getCode("Counter.sol:Counter:0.8.18")), + "Invalid artifact" + ); + assertEq(vm.getCode("Counter.sol"), vm.getCode("Counter.sol:Counter:0.8.17")); + } + + function testGetCodeByNameMultiVersion() public { + assertEq(vm.getCode("Counter"), type(Counter).creationCode); + require(keccak256(vm.getCode("Counter")) != keccak256(vm.getCode("Counter:0.8.18")), "Invalid artifact"); + assertEq(vm.getCode("Counter.sol"), vm.getCode("Counter:0.8.17")); + } +} diff --git a/testdata/script/broadcast/deploy.sol/31337/run-latest.json b/testdata/script/broadcast/deploy.sol/31337/run-latest.json deleted file mode 100644 index 9a05e1cb7ac2f..0000000000000 --- a/testdata/script/broadcast/deploy.sol/31337/run-latest.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "transactions": [ - { - "hash": null, - "type": "CREATE", - "contractName": null, - "contractAddress": "0x731a10897d267e19b34503ad902d0a29173ba4b1", - "function": null, - "arguments": null, - "transaction": { - "type": "0x02", - "from": "0x00a329c0648769a73afac7f9381e08fb43dbea72", - "gas": "0x54653", - "value": "0x0", - "data": "0x608060405234801561001057600080fd5b506103d9806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063d5dcf1271461003b578063f8194e4814610050575b600080fd5b61004e6100493660046100e9565b600155565b005b61006361005e366004610118565b610079565b60405161007091906101f9565b60405180910390f35b6060600061008783826102b5565b5060008260405160200161009b9190610375565b60405160208183030381529060405290507fefdeaaf566f7751d16a12c7fa8909eb74120f42cba334d07dd5246c48f1fba81816040516100db91906101f9565b60405180910390a192915050565b6000602082840312156100fb57600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561012a57600080fd5b813567ffffffffffffffff8082111561014257600080fd5b818401915084601f83011261015657600080fd5b81358181111561016857610168610102565b604051601f8201601f19908116603f0116810190838211818310171561019057610190610102565b816040528281528760208487010111156101a957600080fd5b826020860160208301376000928101602001929092525095945050505050565b60005b838110156101e45781810151838201526020016101cc565b838111156101f3576000848401525b50505050565b60208152600082518060208401526102188160408501602087016101c9565b601f01601f19169190910160400192915050565b600181811c9082168061024057607f821691505b60208210810361026057634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156102b057600081815260208120601f850160051c8101602086101561028d5750805b601f850160051c820191505b818110156102ac57828155600101610299565b5050505b505050565b815167ffffffffffffffff8111156102cf576102cf610102565b6102e3816102dd845461022c565b84610266565b602080601f83116001811461031857600084156103005750858301515b600019600386901b1c1916600185901b1785556102ac565b600085815260208120601f198616915b8281101561034757888601518255948401946001909101908401610328565b50858210156103655787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6502432b63637960d51b8152600082516103968160068501602087016101c9565b919091016006019291505056fea2646970667358221220a380cb042b6ca762a5a0f97e497c4cffa21c45dc21e2dab4107e5415921a704a64736f6c634300080f0033", - "nonce": "0x0", - "accessList": [] - } - }, - { - "hash": null, - "type": "CALL", - "contractName": null, - "contractAddress": "0x731a10897d267e19b34503ad902d0a29173ba4b1", - "function": null, - "arguments": null, - "transaction": { - "type": "0x02", - "from": "0x00a329c0648769a73afac7f9381e08fb43dbea72", - "to": "0x731a10897d267e19b34503ad902d0a29173ba4b1", - "gas": "0xef15", - "value": "0x0", - "data": "0xf8194e48000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000046a6f686e00000000000000000000000000000000000000000000000000000000", - "nonce": "0x1", - "accessList": [] - } - }, - { - "hash": null, - "type": "CALL", - "contractName": null, - "contractAddress": "0x731a10897d267e19b34503ad902d0a29173ba4b1", - "function": null, - "arguments": null, - "transaction": { - "type": "0x02", - "from": "0x00a329c0648769a73afac7f9381e08fb43dbea72", - "to": "0x731a10897d267e19b34503ad902d0a29173ba4b1", - "gas": "0xdcde", - "value": "0x0", - "data": "0xd5dcf127000000000000000000000000000000000000000000000000000000000000007b", - "nonce": "0x2", - "accessList": [] - } - } - ], - "receipts": [], - "libraries": [], - "pending": [], - "path": "broadcast/deploy.sol/31337/run-latest.json", - "returns": {}, - "timestamp": 1658913881 -} \ No newline at end of file