From 52f9c1c81cfa3b949f9e815e784936d16757e37c Mon Sep 17 00:00:00 2001 From: Dustin Brickwood Date: Fri, 31 Jan 2025 08:45:09 -0600 Subject: [PATCH 01/11] chore: builds successfully --- Cargo.lock | 19 + Cargo.toml | 5 + crates/core/Cargo.toml | 12 +- crates/core/src/data/HardhatConsole.json | 1 + crates/core/src/data/patches.rs | 674 +++++++++++++++++++++++ crates/core/src/lib.rs | 1 + crates/core/src/node/console.rs | 90 +++ crates/core/src/node/hardhat.rs | 61 ++ crates/core/src/node/mod.rs | 2 + crates/core/src/resolver.rs | 96 +++- crates/core/src/trace/abi_utils.rs | 194 +++++++ crates/core/src/trace/decode/mod.rs | 432 +++++++++++++++ crates/core/src/trace/mod.rs | 155 ++++++ crates/core/src/trace/signatures.rs | 238 ++++++++ crates/core/src/trace/types.rs | 342 ++++++++++++ crates/core/src/trace/writer.rs | 481 ++++++++++++++++ crates/core/src/utils.rs | 11 + 17 files changed, 2810 insertions(+), 4 deletions(-) create mode 100644 crates/core/src/data/HardhatConsole.json create mode 100644 crates/core/src/data/patches.rs create mode 100644 crates/core/src/node/console.rs create mode 100644 crates/core/src/node/hardhat.rs create mode 100644 crates/core/src/trace/abi_utils.rs create mode 100644 crates/core/src/trace/decode/mod.rs create mode 100644 crates/core/src/trace/mod.rs create mode 100644 crates/core/src/trace/signatures.rs create mode 100644 crates/core/src/trace/types.rs create mode 100644 crates/core/src/trace/writer.rs diff --git a/Cargo.lock b/Cargo.lock index a294e1f3..3a855526 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -555,13 +555,16 @@ name = "anvil_zksync_core" version = "0.2.5" dependencies = [ "alloy", + "anstyle", "anvil_zksync_config", "anvil_zksync_types", "anyhow", "async-trait", "backon", "chrono", + "colorchoice", "colored", + "derive_more", "eyre", "flate2", "futures 0.3.31", @@ -1380,6 +1383,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -1621,6 +1633,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ + "convert_case", "proc-macro2", "quote", "syn 2.0.89", @@ -5809,6 +5822,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.1.14" diff --git a/Cargo.toml b/Cargo.toml index df7eb62f..b276b5f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,6 +74,8 @@ tracing-subscriber = { version = "0.3", features = [ "local-time", ] } url = "2.5.4" +anstyle = { version = "1.0" } +colorchoice = "1.0" ######################### # Test dependencies # @@ -92,3 +94,6 @@ anvil_zksync_api_server = { path = "crates/api_server" } anvil_zksync_config = { path = "crates/config" } anvil_zksync_core = { path = "crates/core" } anvil_zksync_types = { path = "crates/types" } + +# macros +derive_more = { version = "1.0", features = ["full"] } diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 9a0992b2..18052af2 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -23,7 +23,7 @@ tokio.workspace = true futures.workspace = true once_cell.workspace = true -alloy = { workspace = true, default-features = false, features = ["json-abi", "dyn-abi"] } +alloy = { workspace = true, default-features = false, features = ["json-abi", "dyn-abi", "sol-types"] } reqwest.workspace = true serde.workspace = true @@ -44,9 +44,17 @@ thiserror.workspace = true async-trait.workspace = true url.workspace = true +derive_more.workspace = true +anstyle.workspace = true +colorchoice.workspace = true + [dev-dependencies] maplit.workspace = true httptest.workspace = true tempdir.workspace = true test-case.workspace = true -backon.workspace = true \ No newline at end of file +backon.workspace = true + +[features] +serde = [] +arbitrary = [] diff --git a/crates/core/src/data/HardhatConsole.json b/crates/core/src/data/HardhatConsole.json new file mode 100644 index 00000000..54e6d46d --- /dev/null +++ b/crates/core/src/data/HardhatConsole.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes10","name":"","type":"bytes10"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes11","name":"","type":"bytes11"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes25","name":"","type":"bytes25"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes3","name":"","type":"bytes3"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes17","name":"","type":"bytes17"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes27","name":"","type":"bytes27"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"int256","name":"","type":"int256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes29","name":"","type":"bytes29"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes7","name":"","type":"bytes7"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes8","name":"","type":"bytes8"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes20","name":"","type":"bytes20"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes19","name":"","type":"bytes19"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes16","name":"","type":"bytes16"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes1","name":"","type":"bytes1"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes12","name":"","type":"bytes12"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes9","name":"","type":"bytes9"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes14","name":"","type":"bytes14"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes13","name":"","type":"bytes13"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes5","name":"","type":"bytes5"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes23","name":"","type":"bytes23"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes6","name":"","type":"bytes6"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes31","name":"","type":"bytes31"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes18","name":"","type":"bytes18"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes28","name":"","type":"bytes28"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes22","name":"","type":"bytes22"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes15","name":"","type":"bytes15"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes2","name":"","type":"bytes2"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes21","name":"","type":"bytes21"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes30","name":"","type":"bytes30"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes24","name":"","type":"bytes24"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes26","name":"","type":"bytes26"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"string","name":"","type":"string"},{"internalType":"address","name":"","type":"address"}],"name":"log","outputs":[],"stateMutability":"pure","type":"function"}] \ No newline at end of file diff --git a/crates/core/src/data/patches.rs b/crates/core/src/data/patches.rs new file mode 100644 index 00000000..ad63a9fe --- /dev/null +++ b/crates/core/src/data/patches.rs @@ -0,0 +1,674 @@ +[ + // `log(int)` -> `log(int256)` + // `4e0c1d1d` -> `2d5b6cb9` + ([78, 12, 29, 29], [45, 91, 108, 185]), + // `log(uint)` -> `log(uint256)` + // `f5b1bba9` -> `f82c50f1` + ([245, 177, 187, 169], [248, 44, 80, 241]), + // `log(uint)` -> `log(uint256)` + // `f5b1bba9` -> `f82c50f1` + ([245, 177, 187, 169], [248, 44, 80, 241]), + // `log(int)` -> `log(int256)` + // `4e0c1d1d` -> `2d5b6cb9` + ([78, 12, 29, 29], [45, 91, 108, 185]), + // `log(uint,uint)` -> `log(uint256,uint256)` + // `6c0f6980` -> `f666715a` + ([108, 15, 105, 128], [246, 102, 113, 90]), + // `log(uint,string)` -> `log(uint256,string)` + // `0fa3f345` -> `643fd0df` + ([15, 163, 243, 69], [100, 63, 208, 223]), + // `log(uint,bool)` -> `log(uint256,bool)` + // `1e6dd4ec` -> `1c9d7eb3` + ([30, 109, 212, 236], [28, 157, 126, 179]), + // `log(uint,address)` -> `log(uint256,address)` + // `58eb860c` -> `69276c86` + ([88, 235, 134, 12], [105, 39, 108, 134]), + // `log(string,uint)` -> `log(string,uint256)` + // `9710a9d0` -> `b60e72cc` + ([151, 16, 169, 208], [182, 14, 114, 204]), + // `log(string,int)` -> `log(string,int256)` + // `af7faa38` -> `3ca6268e` + ([175, 127, 170, 56], [60, 166, 38, 142]), + // `log(bool,uint)` -> `log(bool,uint256)` + // `364b6a92` -> `399174d3` + ([54, 75, 106, 146], [57, 145, 116, 211]), + // `log(address,uint)` -> `log(address,uint256)` + // `2243cfa3` -> `8309e8a8` + ([34, 67, 207, 163], [131, 9, 232, 168]), + // `log(uint,uint,uint)` -> `log(uint256,uint256,uint256)` + // `e7820a74` -> `d1ed7a3c` + ([231, 130, 10, 116], [209, 237, 122, 60]), + // `log(uint,uint,string)` -> `log(uint256,uint256,string)` + // `7d690ee6` -> `71d04af2` + ([125, 105, 14, 230], [113, 208, 74, 242]), + // `log(uint,uint,bool)` -> `log(uint256,uint256,bool)` + // `67570ff7` -> `4766da72` + ([103, 87, 15, 247], [71, 102, 218, 114]), + // `log(uint,uint,address)` -> `log(uint256,uint256,address)` + // `be33491b` -> `5c96b331` + ([190, 51, 73, 27], [92, 150, 179, 49]), + // `log(uint,string,uint)` -> `log(uint256,string,uint256)` + // `5b6de83f` -> `37aa7d4c` + ([91, 109, 232, 63], [55, 170, 125, 76]), + // `log(uint,string,string)` -> `log(uint256,string,string)` + // `3f57c295` -> `b115611f` + ([63, 87, 194, 149], [177, 21, 97, 31]), + // `log(uint,string,bool)` -> `log(uint256,string,bool)` + // `46a7d0ce` -> `4ceda75a` + ([70, 167, 208, 206], [76, 237, 167, 90]), + // `log(uint,string,address)` -> `log(uint256,string,address)` + // `1f90f24a` -> `7afac959` + ([31, 144, 242, 74], [122, 250, 201, 89]), + // `log(uint,bool,uint)` -> `log(uint256,bool,uint256)` + // `5a4d9922` -> `20098014` + ([90, 77, 153, 34], [32, 9, 128, 20]), + // `log(uint,bool,string)` -> `log(uint256,bool,string)` + // `8b0e14fe` -> `85775021` + ([139, 14, 20, 254], [133, 119, 80, 33]), + // `log(uint,bool,bool)` -> `log(uint256,bool,bool)` + // `d5ceace0` -> `20718650` + ([213, 206, 172, 224], [32, 113, 134, 80]), + // `log(uint,bool,address)` -> `log(uint256,bool,address)` + // `424effbf` -> `35085f7b` + ([66, 78, 255, 191], [53, 8, 95, 123]), + // `log(uint,address,uint)` -> `log(uint256,address,uint256)` + // `884343aa` -> `5a9b5ed5` + ([136, 67, 67, 170], [90, 155, 94, 213]), + // `log(uint,address,string)` -> `log(uint256,address,string)` + // `ce83047b` -> `63cb41f9` + ([206, 131, 4, 123], [99, 203, 65, 249]), + // `log(uint,address,bool)` -> `log(uint256,address,bool)` + // `7ad0128e` -> `9b6ec042` + ([122, 208, 18, 142], [155, 110, 192, 66]), + // `log(uint,address,address)` -> `log(uint256,address,address)` + // `7d77a61b` -> `bcfd9be0` + ([125, 119, 166, 27], [188, 253, 155, 224]), + // `log(string,uint,uint)` -> `log(string,uint256,uint256)` + // `969cdd03` -> `ca47c4eb` + ([150, 156, 221, 3], [202, 71, 196, 235]), + // `log(string,uint,string)` -> `log(string,uint256,string)` + // `a3f5c739` -> `5970e089` + ([163, 245, 199, 57], [89, 112, 224, 137]), + // `log(string,uint,bool)` -> `log(string,uint256,bool)` + // `f102ee05` -> `ca7733b1` + ([241, 2, 238, 5], [202, 119, 51, 177]), + // `log(string,uint,address)` -> `log(string,uint256,address)` + // `e3849f79` -> `1c7ec448` + ([227, 132, 159, 121], [28, 126, 196, 72]), + // `log(string,string,uint)` -> `log(string,string,uint256)` + // `f362ca59` -> `5821efa1` + ([243, 98, 202, 89], [88, 33, 239, 161]), + // `log(string,bool,uint)` -> `log(string,bool,uint256)` + // `291bb9d0` -> `c95958d6` + ([41, 27, 185, 208], [201, 89, 88, 214]), + // `log(string,address,uint)` -> `log(string,address,uint256)` + // `07c81217` -> `0d26b925` + ([7, 200, 18, 23], [13, 38, 185, 37]), + // `log(bool,uint,uint)` -> `log(bool,uint256,uint256)` + // `3b5c03e0` -> `37103367` + ([59, 92, 3, 224], [55, 16, 51, 103]), + // `log(bool,uint,string)` -> `log(bool,uint256,string)` + // `c8397eb0` -> `c3fc3970` + ([200, 57, 126, 176], [195, 252, 57, 112]), + // `log(bool,uint,bool)` -> `log(bool,uint256,bool)` + // `1badc9eb` -> `e8defba9` + ([27, 173, 201, 235], [232, 222, 251, 169]), + // `log(bool,uint,address)` -> `log(bool,uint256,address)` + // `c4d23507` -> `088ef9d2` + ([196, 210, 53, 7], [8, 142, 249, 210]), + // `log(bool,string,uint)` -> `log(bool,string,uint256)` + // `c0382aac` -> `1093ee11` + ([192, 56, 42, 172], [16, 147, 238, 17]), + // `log(bool,bool,uint)` -> `log(bool,bool,uint256)` + // `b01365bb` -> `12f21602` + ([176, 19, 101, 187], [18, 242, 22, 2]), + // `log(bool,address,uint)` -> `log(bool,address,uint256)` + // `eb704baf` -> `5f7b9afb` + ([235, 112, 75, 175], [95, 123, 154, 251]), + // `log(address,uint,uint)` -> `log(address,uint256,uint256)` + // `8786135e` -> `b69bcaf6` + ([135, 134, 19, 94], [182, 155, 202, 246]), + // `log(address,uint,string)` -> `log(address,uint256,string)` + // `baf96849` -> `a1f2e8aa` + ([186, 249, 104, 73], [161, 242, 232, 170]), + // `log(address,uint,bool)` -> `log(address,uint256,bool)` + // `e54ae144` -> `678209a8` + ([229, 74, 225, 68], [103, 130, 9, 168]), + // `log(address,uint,address)` -> `log(address,uint256,address)` + // `97eca394` -> `7bc0d848` + ([151, 236, 163, 148], [123, 192, 216, 72]), + // `log(address,string,uint)` -> `log(address,string,uint256)` + // `1cdaf28a` -> `67dd6ff1` + ([28, 218, 242, 138], [103, 221, 111, 241]), + // `log(address,bool,uint)` -> `log(address,bool,uint256)` + // `2c468d15` -> `9c4f99fb` + ([44, 70, 141, 21], [156, 79, 153, 251]), + // `log(address,address,uint)` -> `log(address,address,uint256)` + // `6c366d72` -> `17fe6185` + ([108, 54, 109, 114], [23, 254, 97, 133]), + // `log(uint,uint,uint,uint)` -> `log(uint256,uint256,uint256,uint256)` + // `5ca0ad3e` -> `193fb800` + ([92, 160, 173, 62], [25, 63, 184, 0]), + // `log(uint,uint,uint,string)` -> `log(uint256,uint256,uint256,string)` + // `78ad7a0c` -> `59cfcbe3` + ([120, 173, 122, 12], [89, 207, 203, 227]), + // `log(uint,uint,uint,bool)` -> `log(uint256,uint256,uint256,bool)` + // `6452b9cb` -> `c598d185` + ([100, 82, 185, 203], [197, 152, 209, 133]), + // `log(uint,uint,uint,address)` -> `log(uint256,uint256,uint256,address)` + // `e0853f69` -> `fa8185af` + ([224, 133, 63, 105], [250, 129, 133, 175]), + // `log(uint,uint,string,uint)` -> `log(uint256,uint256,string,uint256)` + // `3894163d` -> `5da297eb` + ([56, 148, 22, 61], [93, 162, 151, 235]), + // `log(uint,uint,string,string)` -> `log(uint256,uint256,string,string)` + // `7c032a32` -> `27d8afd2` + ([124, 3, 42, 50], [39, 216, 175, 210]), + // `log(uint,uint,string,bool)` -> `log(uint256,uint256,string,bool)` + // `b22eaf06` -> `7af6ab25` + ([178, 46, 175, 6], [122, 246, 171, 37]), + // `log(uint,uint,string,address)` -> `log(uint256,uint256,string,address)` + // `433285a2` -> `42d21db7` + ([67, 50, 133, 162], [66, 210, 29, 183]), + // `log(uint,uint,bool,uint)` -> `log(uint256,uint256,bool,uint256)` + // `6c647c8c` -> `eb7f6fd2` + ([108, 100, 124, 140], [235, 127, 111, 210]), + // `log(uint,uint,bool,string)` -> `log(uint256,uint256,bool,string)` + // `efd9cbee` -> `a5b4fc99` + ([239, 217, 203, 238], [165, 180, 252, 153]), + // `log(uint,uint,bool,bool)` -> `log(uint256,uint256,bool,bool)` + // `94be3bb1` -> `ab085ae6` + ([148, 190, 59, 177], [171, 8, 90, 230]), + // `log(uint,uint,bool,address)` -> `log(uint256,uint256,bool,address)` + // `e117744f` -> `9a816a83` + ([225, 23, 116, 79], [154, 129, 106, 131]), + // `log(uint,uint,address,uint)` -> `log(uint256,uint256,address,uint256)` + // `610ba8c0` -> `88f6e4b2` + ([97, 11, 168, 192], [136, 246, 228, 178]), + // `log(uint,uint,address,string)` -> `log(uint256,uint256,address,string)` + // `d6a2d1de` -> `6cde40b8` + ([214, 162, 209, 222], [108, 222, 64, 184]), + // `log(uint,uint,address,bool)` -> `log(uint256,uint256,address,bool)` + // `a8e820ae` -> `15cac476` + ([168, 232, 32, 174], [21, 202, 196, 118]), + // `log(uint,uint,address,address)` -> `log(uint256,uint256,address,address)` + // `ca939b20` -> `56a5d1b1` + ([202, 147, 155, 32], [86, 165, 209, 177]), + // `log(uint,string,uint,uint)` -> `log(uint256,string,uint256,uint256)` + // `c0043807` -> `82c25b74` + ([192, 4, 56, 7], [130, 194, 91, 116]), + // `log(uint,string,uint,string)` -> `log(uint256,string,uint256,string)` + // `a2bc0c99` -> `b7b914ca` + ([162, 188, 12, 153], [183, 185, 20, 202]), + // `log(uint,string,uint,bool)` -> `log(uint256,string,uint256,bool)` + // `875a6e2e` -> `691a8f74` + ([135, 90, 110, 46], [105, 26, 143, 116]), + // `log(uint,string,uint,address)` -> `log(uint256,string,uint256,address)` + // `ab7bd9fd` -> `3b2279b4` + ([171, 123, 217, 253], [59, 34, 121, 180]), + // `log(uint,string,string,uint)` -> `log(uint256,string,string,uint256)` + // `76ec635e` -> `b028c9bd` + ([118, 236, 99, 94], [176, 40, 201, 189]), + // `log(uint,string,string,string)` -> `log(uint256,string,string,string)` + // `57dd0a11` -> `21ad0683` + ([87, 221, 10, 17], [33, 173, 6, 131]), + // `log(uint,string,string,bool)` -> `log(uint256,string,string,bool)` + // `12862b98` -> `b3a6b6bd` + ([18, 134, 43, 152], [179, 166, 182, 189]), + // `log(uint,string,string,address)` -> `log(uint256,string,string,address)` + // `cc988aa0` -> `d583c602` + ([204, 152, 138, 160], [213, 131, 198, 2]), + // `log(uint,string,bool,uint)` -> `log(uint256,string,bool,uint256)` + // `a4b48a7f` -> `cf009880` + ([164, 180, 138, 127], [207, 0, 152, 128]), + // `log(uint,string,bool,string)` -> `log(uint256,string,bool,string)` + // `8d489ca0` -> `d2d423cd` + ([141, 72, 156, 160], [210, 212, 35, 205]), + // `log(uint,string,bool,bool)` -> `log(uint256,string,bool,bool)` + // `51bc2bc1` -> `ba535d9c` + ([81, 188, 43, 193], [186, 83, 93, 156]), + // `log(uint,string,bool,address)` -> `log(uint256,string,bool,address)` + // `796f28a0` -> `ae2ec581` + ([121, 111, 40, 160], [174, 46, 197, 129]), + // `log(uint,string,address,uint)` -> `log(uint256,string,address,uint256)` + // `98e7f3f3` -> `e8d3018d` + ([152, 231, 243, 243], [232, 211, 1, 141]), + // `log(uint,string,address,string)` -> `log(uint256,string,address,string)` + // `f898577f` -> `9c3adfa1` + ([248, 152, 87, 127], [156, 58, 223, 161]), + // `log(uint,string,address,bool)` -> `log(uint256,string,address,bool)` + // `f93fff37` -> `90c30a56` + ([249, 63, 255, 55], [144, 195, 10, 86]), + // `log(uint,string,address,address)` -> `log(uint256,string,address,address)` + // `7fa5458b` -> `6168ed61` + ([127, 165, 69, 139], [97, 104, 237, 97]), + // `log(uint,bool,uint,uint)` -> `log(uint256,bool,uint256,uint256)` + // `56828da4` -> `c6acc7a8` + ([86, 130, 141, 164], [198, 172, 199, 168]), + // `log(uint,bool,uint,string)` -> `log(uint256,bool,uint256,string)` + // `e8ddbc56` -> `de03e774` + ([232, 221, 188, 86], [222, 3, 231, 116]), + // `log(uint,bool,uint,bool)` -> `log(uint256,bool,uint256,bool)` + // `d2abc4fd` -> `91a02e2a` + ([210, 171, 196, 253], [145, 160, 46, 42]), + // `log(uint,bool,uint,address)` -> `log(uint256,bool,uint256,address)` + // `4f40058e` -> `88cb6041` + ([79, 64, 5, 142], [136, 203, 96, 65]), + // `log(uint,bool,string,uint)` -> `log(uint256,bool,string,uint256)` + // `915fdb28` -> `2c1d0746` + ([145, 95, 219, 40], [44, 29, 7, 70]), + // `log(uint,bool,string,string)` -> `log(uint256,bool,string,string)` + // `a433fcfd` -> `68c8b8bd` + ([164, 51, 252, 253], [104, 200, 184, 189]), + // `log(uint,bool,string,bool)` -> `log(uint256,bool,string,bool)` + // `346eb8c7` -> `eb928d7f` + ([52, 110, 184, 199], [235, 146, 141, 127]), + // `log(uint,bool,string,address)` -> `log(uint256,bool,string,address)` + // `496e2bb4` -> `ef529018` + ([73, 110, 43, 180], [239, 82, 144, 24]), + // `log(uint,bool,bool,uint)` -> `log(uint256,bool,bool,uint256)` + // `bd25ad59` -> `7464ce23` + ([189, 37, 173, 89], [116, 100, 206, 35]), + // `log(uint,bool,bool,string)` -> `log(uint256,bool,bool,string)` + // `318ae59b` -> `dddb9561` + ([49, 138, 229, 155], [221, 219, 149, 97]), + // `log(uint,bool,bool,bool)` -> `log(uint256,bool,bool,bool)` + // `4e6c5315` -> `b6f577a1` + ([78, 108, 83, 21], [182, 245, 119, 161]), + // `log(uint,bool,bool,address)` -> `log(uint256,bool,bool,address)` + // `5306225d` -> `69640b59` + ([83, 6, 34, 93], [105, 100, 11, 89]), + // `log(uint,bool,address,uint)` -> `log(uint256,bool,address,uint256)` + // `41b5ef3b` -> `078287f5` + ([65, 181, 239, 59], [7, 130, 135, 245]), + // `log(uint,bool,address,string)` -> `log(uint256,bool,address,string)` + // `a230761e` -> `ade052c7` + ([162, 48, 118, 30], [173, 224, 82, 199]), + // `log(uint,bool,address,bool)` -> `log(uint256,bool,address,bool)` + // `91fb1242` -> `454d54a5` + ([145, 251, 18, 66], [69, 77, 84, 165]), + // `log(uint,bool,address,address)` -> `log(uint256,bool,address,address)` + // `86edc10c` -> `a1ef4cbb` + ([134, 237, 193, 12], [161, 239, 76, 187]), + // `log(uint,address,uint,uint)` -> `log(uint256,address,uint256,uint256)` + // `ca9a3eb4` -> `0c9cd9c1` + ([202, 154, 62, 180], [12, 156, 217, 193]), + // `log(uint,address,uint,string)` -> `log(uint256,address,uint256,string)` + // `3ed3bd28` -> `ddb06521` + ([62, 211, 189, 40], [221, 176, 101, 33]), + // `log(uint,address,uint,bool)` -> `log(uint256,address,uint256,bool)` + // `19f67369` -> `5f743a7c` + ([25, 246, 115, 105], [95, 116, 58, 124]), + // `log(uint,address,uint,address)` -> `log(uint256,address,uint256,address)` + // `fdb2ecd4` -> `15c127b5` + ([253, 178, 236, 212], [21, 193, 39, 181]), + // `log(uint,address,string,uint)` -> `log(uint256,address,string,uint256)` + // `a0c414e8` -> `46826b5d` + ([160, 196, 20, 232], [70, 130, 107, 93]), + // `log(uint,address,string,string)` -> `log(uint256,address,string,string)` + // `8d778624` -> `3e128ca3` + ([141, 119, 134, 36], [62, 18, 140, 163]), + // `log(uint,address,string,bool)` -> `log(uint256,address,string,bool)` + // `22a479a6` -> `cc32ab07` + ([34, 164, 121, 166], [204, 50, 171, 7]), + // `log(uint,address,string,address)` -> `log(uint256,address,string,address)` + // `cbe58efd` -> `9cba8fff` + ([203, 229, 142, 253], [156, 186, 143, 255]), + // `log(uint,address,bool,uint)` -> `log(uint256,address,bool,uint256)` + // `7b08e8eb` -> `5abd992a` + ([123, 8, 232, 235], [90, 189, 153, 42]), + // `log(uint,address,bool,string)` -> `log(uint256,address,bool,string)` + // `63f0e242` -> `90fb06aa` + ([99, 240, 226, 66], [144, 251, 6, 170]), + // `log(uint,address,bool,bool)` -> `log(uint256,address,bool,bool)` + // `7e27410d` -> `e351140f` + ([126, 39, 65, 13], [227, 81, 20, 15]), + // `log(uint,address,bool,address)` -> `log(uint256,address,bool,address)` + // `b6313094` -> `ef72c513` + ([182, 49, 48, 148], [239, 114, 197, 19]), + // `log(uint,address,address,uint)` -> `log(uint256,address,address,uint256)` + // `9a3cbf96` -> `736efbb6` + ([154, 60, 191, 150], [115, 110, 251, 182]), + // `log(uint,address,address,string)` -> `log(uint256,address,address,string)` + // `7943dc66` -> `031c6f73` + ([121, 67, 220, 102], [3, 28, 111, 115]), + // `log(uint,address,address,bool)` -> `log(uint256,address,address,bool)` + // `01550b04` -> `091ffaf5` + ([1, 85, 11, 4], [9, 31, 250, 245]), + // `log(uint,address,address,address)` -> `log(uint256,address,address,address)` + // `554745f9` -> `2488b414` + ([85, 71, 69, 249], [36, 136, 180, 20]), + // `log(string,uint,uint,uint)` -> `log(string,uint256,uint256,uint256)` + // `08ee5666` -> `a7a87853` + ([8, 238, 86, 102], [167, 168, 120, 83]), + // `log(string,uint,uint,string)` -> `log(string,uint256,uint256,string)` + // `a54ed4bd` -> `854b3496` + ([165, 78, 212, 189], [133, 75, 52, 150]), + // `log(string,uint,uint,bool)` -> `log(string,uint256,uint256,bool)` + // `f73c7e3d` -> `7626db92` + ([247, 60, 126, 61], [118, 38, 219, 146]), + // `log(string,uint,uint,address)` -> `log(string,uint256,uint256,address)` + // `bed728bf` -> `e21de278` + ([190, 215, 40, 191], [226, 29, 226, 120]), + // `log(string,uint,string,uint)` -> `log(string,uint256,string,uint256)` + // `a0c4b225` -> `c67ea9d1` + ([160, 196, 178, 37], [198, 126, 169, 209]), + // `log(string,uint,string,string)` -> `log(string,uint256,string,string)` + // `6c98dae2` -> `5ab84e1f` + ([108, 152, 218, 226], [90, 184, 78, 31]), + // `log(string,uint,string,bool)` -> `log(string,uint256,string,bool)` + // `e99f82cf` -> `7d24491d` + ([233, 159, 130, 207], [125, 36, 73, 29]), + // `log(string,uint,string,address)` -> `log(string,uint256,string,address)` + // `bb7235e9` -> `7c4632a4` + ([187, 114, 53, 233], [124, 70, 50, 164]), + // `log(string,uint,bool,uint)` -> `log(string,uint256,bool,uint256)` + // `550e6ef5` -> `e41b6f6f` + ([85, 14, 110, 245], [228, 27, 111, 111]), + // `log(string,uint,bool,string)` -> `log(string,uint256,bool,string)` + // `76cc6064` -> `abf73a98` + ([118, 204, 96, 100], [171, 247, 58, 152]), + // `log(string,uint,bool,bool)` -> `log(string,uint256,bool,bool)` + // `e37ff3d0` -> `354c36d6` + ([227, 127, 243, 208], [53, 76, 54, 214]), + // `log(string,uint,bool,address)` -> `log(string,uint256,bool,address)` + // `e5549d91` -> `e0e95b98` + ([229, 84, 157, 145], [224, 233, 91, 152]), + // `log(string,uint,address,uint)` -> `log(string,uint256,address,uint256)` + // `58497afe` -> `4f04fdc6` + ([88, 73, 122, 254], [79, 4, 253, 198]), + // `log(string,uint,address,string)` -> `log(string,uint256,address,string)` + // `3254c2e8` -> `9ffb2f93` + ([50, 84, 194, 232], [159, 251, 47, 147]), + // `log(string,uint,address,bool)` -> `log(string,uint256,address,bool)` + // `1106a8f7` -> `82112a42` + ([17, 6, 168, 247], [130, 17, 42, 66]), + // `log(string,uint,address,address)` -> `log(string,uint256,address,address)` + // `eac89281` -> `5ea2b7ae` + ([234, 200, 146, 129], [94, 162, 183, 174]), + // `log(string,string,uint,uint)` -> `log(string,string,uint256,uint256)` + // `d5cf17d0` -> `f45d7d2c` + ([213, 207, 23, 208], [244, 93, 125, 44]), + // `log(string,string,uint,string)` -> `log(string,string,uint256,string)` + // `8d142cdd` -> `5d1a971a` + ([141, 20, 44, 221], [93, 26, 151, 26]), + // `log(string,string,uint,bool)` -> `log(string,string,uint256,bool)` + // `e65658ca` -> `c3a8a654` + ([230, 86, 88, 202], [195, 168, 166, 84]), + // `log(string,string,uint,address)` -> `log(string,string,uint256,address)` + // `5d4f4680` -> `1023f7b2` + ([93, 79, 70, 128], [16, 35, 247, 178]), + // `log(string,string,string,uint)` -> `log(string,string,string,uint256)` + // `9fd009f5` -> `8eafb02b` + ([159, 208, 9, 245], [142, 175, 176, 43]), + // `log(string,string,bool,uint)` -> `log(string,string,bool,uint256)` + // `86818a7a` -> `d6aefad2` + ([134, 129, 138, 122], [214, 174, 250, 210]), + // `log(string,string,address,uint)` -> `log(string,string,address,uint256)` + // `4a81a56a` -> `7cc3c607` + ([74, 129, 165, 106], [124, 195, 198, 7]), + // `log(string,bool,uint,uint)` -> `log(string,bool,uint256,uint256)` + // `5dbff038` -> `64b5bb67` + ([93, 191, 240, 56], [100, 181, 187, 103]), + // `log(string,bool,uint,string)` -> `log(string,bool,uint256,string)` + // `42b9a227` -> `742d6ee7` + ([66, 185, 162, 39], [116, 45, 110, 231]), + // `log(string,bool,uint,bool)` -> `log(string,bool,uint256,bool)` + // `3cc5b5d3` -> `8af7cf8a` + ([60, 197, 181, 211], [138, 247, 207, 138]), + // `log(string,bool,uint,address)` -> `log(string,bool,uint256,address)` + // `71d3850d` -> `935e09bf` + ([113, 211, 133, 13], [147, 94, 9, 191]), + // `log(string,bool,string,uint)` -> `log(string,bool,string,uint256)` + // `34cb308d` -> `24f91465` + ([52, 203, 48, 141], [36, 249, 20, 101]), + // `log(string,bool,bool,uint)` -> `log(string,bool,bool,uint256)` + // `807531e8` -> `8e3f78a9` + ([128, 117, 49, 232], [142, 63, 120, 169]), + // `log(string,bool,address,uint)` -> `log(string,bool,address,uint256)` + // `28df4e96` -> `5d08bb05` + ([40, 223, 78, 150], [93, 8, 187, 5]), + // `log(string,address,uint,uint)` -> `log(string,address,uint256,uint256)` + // `daa394bd` -> `f8f51b1e` + ([218, 163, 148, 189], [248, 245, 27, 30]), + // `log(string,address,uint,string)` -> `log(string,address,uint256,string)` + // `4c55f234` -> `5a477632` + ([76, 85, 242, 52], [90, 71, 118, 50]), + // `log(string,address,uint,bool)` -> `log(string,address,uint256,bool)` + // `5ac1c13c` -> `fc4845f0` + ([90, 193, 193, 60], [252, 72, 69, 240]), + // `log(string,address,uint,address)` -> `log(string,address,uint256,address)` + // `a366ec80` -> `63fb8bc5` + ([163, 102, 236, 128], [99, 251, 139, 197]), + // `log(string,address,string,uint)` -> `log(string,address,string,uint256)` + // `8f624be9` -> `91d1112e` + ([143, 98, 75, 233], [145, 209, 17, 46]), + // `log(string,address,bool,uint)` -> `log(string,address,bool,uint256)` + // `c5d1bb8b` -> `3e9f866a` + ([197, 209, 187, 139], [62, 159, 134, 106]), + // `log(string,address,address,uint)` -> `log(string,address,address,uint256)` + // `6eb7943d` -> `8ef3f399` + ([110, 183, 148, 61], [142, 243, 243, 153]), + // `log(bool,uint,uint,uint)` -> `log(bool,uint256,uint256,uint256)` + // `32dfa524` -> `374bb4b2` + ([50, 223, 165, 36], [55, 75, 180, 178]), + // `log(bool,uint,uint,string)` -> `log(bool,uint256,uint256,string)` + // `da0666c8` -> `8e69fb5d` + ([218, 6, 102, 200], [142, 105, 251, 93]), + // `log(bool,uint,uint,bool)` -> `log(bool,uint256,uint256,bool)` + // `a41d81de` -> `be984353` + ([164, 29, 129, 222], [190, 152, 67, 83]), + // `log(bool,uint,uint,address)` -> `log(bool,uint256,uint256,address)` + // `f161b221` -> `00dd87b9` + ([241, 97, 178, 33], [0, 221, 135, 185]), + // `log(bool,uint,string,uint)` -> `log(bool,uint256,string,uint256)` + // `4180011b` -> `6a1199e2` + ([65, 128, 1, 27], [106, 17, 153, 226]), + // `log(bool,uint,string,string)` -> `log(bool,uint256,string,string)` + // `d32a6548` -> `f5bc2249` + ([211, 42, 101, 72], [245, 188, 34, 73]), + // `log(bool,uint,string,bool)` -> `log(bool,uint256,string,bool)` + // `91d2f813` -> `e5e70b2b` + ([145, 210, 248, 19], [229, 231, 11, 43]), + // `log(bool,uint,string,address)` -> `log(bool,uint256,string,address)` + // `a5c70d29` -> `fedd1fff` + ([165, 199, 13, 41], [254, 221, 31, 255]), + // `log(bool,uint,bool,uint)` -> `log(bool,uint256,bool,uint256)` + // `d3de5593` -> `7f9bbca2` + ([211, 222, 85, 147], [127, 155, 188, 162]), + // `log(bool,uint,bool,string)` -> `log(bool,uint256,bool,string)` + // `b6d569d4` -> `9143dbb1` + ([182, 213, 105, 212], [145, 67, 219, 177]), + // `log(bool,uint,bool,bool)` -> `log(bool,uint256,bool,bool)` + // `9e01f741` -> `ceb5f4d7` + ([158, 1, 247, 65], [206, 181, 244, 215]), + // `log(bool,uint,bool,address)` -> `log(bool,uint256,bool,address)` + // `4267c7f8` -> `9acd3616` + ([66, 103, 199, 248], [154, 205, 54, 22]), + // `log(bool,uint,address,uint)` -> `log(bool,uint256,address,uint256)` + // `caa5236a` -> `1537dc87` + ([202, 165, 35, 106], [21, 55, 220, 135]), + // `log(bool,uint,address,string)` -> `log(bool,uint256,address,string)` + // `18091341` -> `1bb3b09a` + ([24, 9, 19, 65], [27, 179, 176, 154]), + // `log(bool,uint,address,bool)` -> `log(bool,uint256,address,bool)` + // `65adf408` -> `b4c314ff` + ([101, 173, 244, 8], [180, 195, 20, 255]), + // `log(bool,uint,address,address)` -> `log(bool,uint256,address,address)` + // `8a2f90aa` -> `26f560a8` + ([138, 47, 144, 170], [38, 245, 96, 168]), + // `log(bool,string,uint,uint)` -> `log(bool,string,uint256,uint256)` + // `8e4ae86e` -> `28863fcb` + ([142, 74, 232, 110], [40, 134, 63, 203]), + // `log(bool,string,uint,string)` -> `log(bool,string,uint256,string)` + // `77a1abed` -> `1ad96de6` + ([119, 161, 171, 237], [26, 217, 109, 230]), + // `log(bool,string,uint,bool)` -> `log(bool,string,uint256,bool)` + // `20bbc9af` -> `6b0e5d53` + ([32, 187, 201, 175], [107, 14, 93, 83]), + // `log(bool,string,uint,address)` -> `log(bool,string,uint256,address)` + // `5b22b938` -> `1596a1ce` + ([91, 34, 185, 56], [21, 150, 161, 206]), + // `log(bool,string,string,uint)` -> `log(bool,string,string,uint256)` + // `5ddb2592` -> `7be0c3eb` + ([93, 219, 37, 146], [123, 224, 195, 235]), + // `log(bool,string,bool,uint)` -> `log(bool,string,bool,uint256)` + // `8d6f9ca5` -> `1606a393` + ([141, 111, 156, 165], [22, 6, 163, 147]), + // `log(bool,string,address,uint)` -> `log(bool,string,address,uint256)` + // `1b0b955b` -> `a5cada94` + ([27, 11, 149, 91], [165, 202, 218, 148]), + // `log(bool,bool,uint,uint)` -> `log(bool,bool,uint256,uint256)` + // `4667de8e` -> `0bb00eab` + ([70, 103, 222, 142], [11, 176, 14, 171]), + // `log(bool,bool,uint,string)` -> `log(bool,bool,uint256,string)` + // `50618937` -> `7dd4d0e0` + ([80, 97, 137, 55], [125, 212, 208, 224]), + // `log(bool,bool,uint,bool)` -> `log(bool,bool,uint256,bool)` + // `ab5cc1c4` -> `619e4d0e` + ([171, 92, 193, 196], [97, 158, 77, 14]), + // `log(bool,bool,uint,address)` -> `log(bool,bool,uint256,address)` + // `0bff950d` -> `54a7a9a0` + ([11, 255, 149, 13], [84, 167, 169, 160]), + // `log(bool,bool,string,uint)` -> `log(bool,bool,string,uint256)` + // `178b4685` -> `e3a9ca2f` + ([23, 139, 70, 133], [227, 169, 202, 47]), + // `log(bool,bool,bool,uint)` -> `log(bool,bool,bool,uint256)` + // `c248834d` -> `6d7045c1` + ([194, 72, 131, 77], [109, 112, 69, 193]), + // `log(bool,bool,address,uint)` -> `log(bool,bool,address,uint256)` + // `609386e7` -> `4c123d57` + ([96, 147, 134, 231], [76, 18, 61, 87]), + // `log(bool,address,uint,uint)` -> `log(bool,address,uint256,uint256)` + // `9bfe72bc` -> `7bf181a1` + ([155, 254, 114, 188], [123, 241, 129, 161]), + // `log(bool,address,uint,string)` -> `log(bool,address,uint256,string)` + // `a0685833` -> `51f09ff8` + ([160, 104, 88, 51], [81, 240, 159, 248]), + // `log(bool,address,uint,bool)` -> `log(bool,address,uint256,bool)` + // `ee8d8672` -> `d6019f1c` + ([238, 141, 134, 114], [214, 1, 159, 28]), + // `log(bool,address,uint,address)` -> `log(bool,address,uint256,address)` + // `68f158b5` -> `136b05dd` + ([104, 241, 88, 181], [19, 107, 5, 221]), + // `log(bool,address,string,uint)` -> `log(bool,address,string,uint256)` + // `0b99fc22` -> `c21f64c7` + ([11, 153, 252, 34], [194, 31, 100, 199]), + // `log(bool,address,bool,uint)` -> `log(bool,address,bool,uint256)` + // `4cb60fd1` -> `07831502` + ([76, 182, 15, 209], [7, 131, 21, 2]), + // `log(bool,address,address,uint)` -> `log(bool,address,address,uint256)` + // `5284bd6c` -> `0c66d1be` + ([82, 132, 189, 108], [12, 102, 209, 190]), + // `log(address,uint,uint,uint)` -> `log(address,uint256,uint256,uint256)` + // `3d0e9de4` -> `34f0e636` + ([61, 14, 157, 228], [52, 240, 230, 54]), + // `log(address,uint,uint,string)` -> `log(address,uint256,uint256,string)` + // `89340dab` -> `4a28c017` + ([137, 52, 13, 171], [74, 40, 192, 23]), + // `log(address,uint,uint,bool)` -> `log(address,uint256,uint256,bool)` + // `ec4ba8a2` -> `66f1bc67` + ([236, 75, 168, 162], [102, 241, 188, 103]), + // `log(address,uint,uint,address)` -> `log(address,uint256,uint256,address)` + // `1ef63434` -> `20e3984d` + ([30, 246, 52, 52], [32, 227, 152, 77]), + // `log(address,uint,string,uint)` -> `log(address,uint256,string,uint256)` + // `f512cf9b` -> `bf01f891` + ([245, 18, 207, 155], [191, 1, 248, 145]), + // `log(address,uint,string,string)` -> `log(address,uint256,string,string)` + // `7e56c693` -> `88a8c406` + ([126, 86, 198, 147], [136, 168, 196, 6]), + // `log(address,uint,string,bool)` -> `log(address,uint256,string,bool)` + // `a4024f11` -> `cf18105c` + ([164, 2, 79, 17], [207, 24, 16, 92]), + // `log(address,uint,string,address)` -> `log(address,uint256,string,address)` + // `dc792604` -> `5c430d47` + ([220, 121, 38, 4], [92, 67, 13, 71]), + // `log(address,uint,bool,uint)` -> `log(address,uint256,bool,uint256)` + // `698f4392` -> `22f6b999` + ([105, 143, 67, 146], [34, 246, 185, 153]), + // `log(address,uint,bool,string)` -> `log(address,uint256,bool,string)` + // `8e8e4e75` -> `c5ad85f9` + ([142, 142, 78, 117], [197, 173, 133, 249]), + // `log(address,uint,bool,bool)` -> `log(address,uint256,bool,bool)` + // `fea1d55a` -> `3bf5e537` + ([254, 161, 213, 90], [59, 245, 229, 55]), + // `log(address,uint,bool,address)` -> `log(address,uint256,bool,address)` + // `23e54972` -> `a31bfdcc` + ([35, 229, 73, 114], [163, 27, 253, 204]), + // `log(address,uint,address,uint)` -> `log(address,uint256,address,uint256)` + // `a5d98768` -> `100f650e` + ([165, 217, 135, 104], [16, 15, 101, 14]), + // `log(address,uint,address,string)` -> `log(address,uint256,address,string)` + // `5d71f39e` -> `1da986ea` + ([93, 113, 243, 158], [29, 169, 134, 234]), + // `log(address,uint,address,bool)` -> `log(address,uint256,address,bool)` + // `f181a1e9` -> `a1bcc9b3` + ([241, 129, 161, 233], [161, 188, 201, 179]), + // `log(address,uint,address,address)` -> `log(address,uint256,address,address)` + // `ec24846f` -> `478d1c62` + ([236, 36, 132, 111], [71, 141, 28, 98]), + // `log(address,string,uint,uint)` -> `log(address,string,uint256,uint256)` + // `a4c92a60` -> `1dc8e1b8` + ([164, 201, 42, 96], [29, 200, 225, 184]), + // `log(address,string,uint,string)` -> `log(address,string,uint256,string)` + // `5d1365c9` -> `448830a8` + ([93, 19, 101, 201], [68, 136, 48, 168]), + // `log(address,string,uint,bool)` -> `log(address,string,uint256,bool)` + // `7e250d5b` -> `0ef7e050` + ([126, 37, 13, 91], [14, 247, 224, 80]), + // `log(address,string,uint,address)` -> `log(address,string,uint256,address)` + // `dfd7d80b` -> `63183678` + ([223, 215, 216, 11], [99, 24, 54, 120]), + // `log(address,string,string,uint)` -> `log(address,string,string,uint256)` + // `a14fd039` -> `159f8927` + ([161, 79, 208, 57], [21, 159, 137, 39]), + // `log(address,string,bool,uint)` -> `log(address,string,bool,uint256)` + // `e720521c` -> `515e38b6` + ([231, 32, 82, 28], [81, 94, 56, 182]), + // `log(address,string,address,uint)` -> `log(address,string,address,uint256)` + // `8c1933a9` -> `457fe3cf` + ([140, 25, 51, 169], [69, 127, 227, 207]), + // `log(address,bool,uint,uint)` -> `log(address,bool,uint256,uint256)` + // `c210a01e` -> `386ff5f4` + ([194, 16, 160, 30], [56, 111, 245, 244]), + // `log(address,bool,uint,string)` -> `log(address,bool,uint256,string)` + // `9b588ecc` -> `0aa6cfad` + ([155, 88, 142, 204], [10, 166, 207, 173]), + // `log(address,bool,uint,bool)` -> `log(address,bool,uint256,bool)` + // `85cdc5af` -> `c4643e20` + ([133, 205, 197, 175], [196, 100, 62, 32]), + // `log(address,bool,uint,address)` -> `log(address,bool,uint256,address)` + // `0d8ce61e` -> `ccf790a1` + ([13, 140, 230, 30], [204, 247, 144, 161]), + // `log(address,bool,string,uint)` -> `log(address,bool,string,uint256)` + // `9e127b6e` -> `80e6a20b` + ([158, 18, 123, 110], [128, 230, 162, 11]), + // `log(address,bool,bool,uint)` -> `log(address,bool,bool,uint256)` + // `cfb58756` -> `8c4e5de6` + ([207, 181, 135, 86], [140, 78, 93, 230]), + // `log(address,bool,address,uint)` -> `log(address,bool,address,uint256)` + // `dc7116d2` -> `a75c59de` + ([220, 113, 22, 210], [167, 92, 89, 222]), + // `log(address,address,uint,uint)` -> `log(address,address,uint256,uint256)` + // `54fdf3e4` -> `be553481` + ([84, 253, 243, 228], [190, 85, 52, 129]), + // `log(address,address,uint,string)` -> `log(address,address,uint256,string)` + // `9dd12ead` -> `fdb4f990` + ([157, 209, 46, 173], [253, 180, 249, 144]), + // `log(address,address,uint,bool)` -> `log(address,address,uint256,bool)` + // `c2f688ec` -> `9b4254e2` + ([194, 246, 136, 236], [155, 66, 84, 226]), + // `log(address,address,uint,address)` -> `log(address,address,uint256,address)` + // `d6c65276` -> `8da6def5` + ([214, 198, 82, 118], [141, 166, 222, 245]), + // `log(address,address,string,uint)` -> `log(address,address,string,uint256)` + // `04289300` -> `ef1cefe7` + ([4, 40, 147, 0], [239, 28, 239, 231]), + // `log(address,address,bool,uint)` -> `log(address,address,bool,uint256)` + // `95d65f11` -> `3971e78c` + ([149, 214, 95, 17], [57, 113, 231, 140]), + // `log(address,address,address,uint)` -> `log(address,address,address,uint256)` + // `ed5eac87` -> `94250d77` + ([237, 94, 172, 135], [148, 37, 13, 119]), +] diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 70f6a911..9313036c 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -51,6 +51,7 @@ pub mod observability; pub mod resolver; pub mod system_contracts; pub mod utils; +pub mod trace; mod cache; mod testing; diff --git a/crates/core/src/node/console.rs b/crates/core/src/node/console.rs new file mode 100644 index 00000000..e4941ba2 --- /dev/null +++ b/crates/core/src/node/console.rs @@ -0,0 +1,90 @@ +use alloy::primitives::{hex, I256, U256}; +use alloy::sol; +use derive_more::Display; +use itertools::Itertools; + +// TODO: Use `UiFmt` + +sol! { +#[sol(abi)] +#[derive(Display)] +interface Console { + #[display("{val}")] + event log(string val); + + #[display("{}", hex::encode_prefixed(val))] + event logs(bytes val); + + #[display("{val}")] + event log_address(address val); + + #[display("{val}")] + event log_bytes32(bytes32 val); + + #[display("{val}")] + event log_int(int val); + + #[display("{val}")] + event log_uint(uint val); + + #[display("{}", hex::encode_prefixed(val))] + event log_bytes(bytes val); + + #[display("{val}")] + event log_string(string val); + + #[display("[{}]", val.iter().format(", "))] + event log_array(uint256[] val); + + #[display("[{}]", val.iter().format(", "))] + event log_array(int256[] val); + + #[display("[{}]", val.iter().format(", "))] + event log_array(address[] val); + + #[display("{key}: {val}")] + event log_named_address(string key, address val); + + #[display("{key}: {val}")] + event log_named_bytes32(string key, bytes32 val); + + #[display("{key}: {}", format_units_int(val, decimals))] + event log_named_decimal_int(string key, int val, uint decimals); + + #[display("{key}: {}", format_units_uint(val, decimals))] + event log_named_decimal_uint(string key, uint val, uint decimals); + + #[display("{key}: {val}")] + event log_named_int(string key, int val); + + #[display("{key}: {val}")] + event log_named_uint(string key, uint val); + + #[display("{key}: {}", hex::encode_prefixed(val))] + event log_named_bytes(string key, bytes val); + + #[display("{key}: {val}")] + event log_named_string(string key, string val); + + #[display("{key}: [{}]", val.iter().format(", "))] + event log_named_array(string key, uint256[] val); + + #[display("{key}: [{}]", val.iter().format(", "))] + event log_named_array(string key, int256[] val); + + #[display("{key}: [{}]", val.iter().format(", "))] + event log_named_array(string key, address[] val); +} +} + +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)) +} + +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/core/src/node/hardhat.rs b/crates/core/src/node/hardhat.rs new file mode 100644 index 00000000..c8b40a7d --- /dev/null +++ b/crates/core/src/node/hardhat.rs @@ -0,0 +1,61 @@ +use alloy::primitives::{address, Address, Selector}; +use alloy::sol; +use std::collections::HashMap; +use std::sync::LazyLock; + +sol!( + #[sol(abi)] + HardhatConsole, + "src/data/HardhatConsole.json" +); + +/// The Hardhat console address. +/// +/// See: +pub const HARDHAT_CONSOLE_ADDRESS: Address = address!("000000000000000000636F6e736F6c652e6c6f67"); + +/// 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 + } +} + +/// Maps all the `hardhat/console.log` log selectors that use the legacy ABI (`int`, `uint`) to +/// their normalized counterparts (`int256`, `uint256`). +/// +/// `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 Solidity and [`sol!`] use. +pub static HARDHAT_CONSOLE_SELECTOR_PATCHES: LazyLock> = + LazyLock::new(|| HashMap::from_iter(include!("../data/patches.rs"))); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hardhat_console_patch() { + for (hh, generated) in HARDHAT_CONSOLE_SELECTOR_PATCHES.iter() { + let mut hh = *hh; + patch_hh_console_selector(&mut hh); + assert_eq!(hh, *generated); + } + } +} diff --git a/crates/core/src/node/mod.rs b/crates/core/src/node/mod.rs index a675bdcb..4b8e690e 100644 --- a/crates/core/src/node/mod.rs +++ b/crates/core/src/node/mod.rs @@ -17,6 +17,8 @@ mod storage_logs; mod vm; mod zkos; mod zks; +pub mod hardhat; +pub mod console; pub use self::{ fee_model::TestNodeFeeInputProvider, impersonate::ImpersonationManager, keys::StorageKeyLayout, diff --git a/crates/core/src/resolver.rs b/crates/core/src/resolver.rs index 6b2d7ebd..fba923ab 100644 --- a/crates/core/src/resolver.rs +++ b/crates/core/src/resolver.rs @@ -161,7 +161,7 @@ impl SignEthClient { // using openchain signature database over 4byte // see https://github.com/foundry-rs/foundry/issues/1672 let url = match selector_type { - SelectorType::Function => { + SelectorType::Function | SelectorType::Error => { format!("{SELECTOR_DATABASE_URL}?function={selector}&filter=true") } SelectorType::Event => format!("{SELECTOR_DATABASE_URL}?event={selector}&filter=true"), @@ -180,7 +180,7 @@ impl SignEthClient { } let decoded = match selector_type { - SelectorType::Function => api_response.result.function, + SelectorType::Function | SelectorType::Error => api_response.result.function, SelectorType::Event => api_response.result.event, }; @@ -203,6 +203,97 @@ impl SignEthClient { .cloned()) } + /// Decodes the given function, error or event selectors using OpenChain. + 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![]); + } + + tracing::debug!(len = selectors.len(), "decoding selectors"); + tracing::trace!(?selectors, "decoding selectors"); + + // exit early if spurious connection + self.ensure_not_spurious()?; + + let expected_len = match selector_type { + SelectorType::Function | SelectorType::Error => 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, + } + + #[derive(Deserialize)] + struct ApiResult { + event: HashMap>>, + function: HashMap>>, + } + + #[derive(Deserialize)] + struct ApiResponse { + ok: bool, + result: ApiResult, + } + + let url = format!( + "{SELECTOR_DATABASE_URL}?{ltype}={selectors_str}", + ltype = match selector_type { + SelectorType::Function | SelectorType::Error => "function", + SelectorType::Event => "event", + }, + selectors_str = selectors.join(",") + ); + + let res = self.get_text(&url).await?; + let api_response = match serde_json::from_str::(&res) { + Ok(inner) => inner, + Err(err) => { + eyre::bail!("Could not decode response:\n {res}.\nError: {err}") + } + }; + + if !api_response.ok { + eyre::bail!("Failed to decode:\n {res}") + } + + let decoded = match selector_type { + SelectorType::Function | SelectorType::Error => api_response.result.function, + SelectorType::Event => api_response.result.event, + }; + + 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 api.openchain.xyz pub async fn decode_function_selector(&self, selector: &str) -> eyre::Result> { let prefixed_selector = format!("0x{}", selector.strip_prefix("0x").unwrap_or(selector)); @@ -223,6 +314,7 @@ impl SignEthClient { pub enum SelectorType { Function, Event, + Error, } /// Fetches a function signature given the selector using api.openchain.xyz pub async fn decode_function_selector(selector: &str) -> eyre::Result> { diff --git a/crates/core/src/trace/abi_utils.rs b/crates/core/src/trace/abi_utils.rs new file mode 100644 index 00000000..9b95794d --- /dev/null +++ b/crates/core/src/trace/abi_utils.rs @@ -0,0 +1,194 @@ +//! ABI related helper functions. + +use super::types::LogData; +use alloy::dyn_abi::{DynSolType, DynSolValue, FunctionExt, JsonAbiExt}; +use alloy::json_abi::{Error, Event, Function, Param}; +use alloy::primitives::hex; +use eyre::{Context, Result}; + +pub fn encode_args(inputs: &[Param], args: I) -> Result> +where + I: IntoIterator, + S: AsRef, +{ + std::iter::zip(inputs, args) + .map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref())) + .collect() +} + +/// Given a function and a vector of string arguments, it proceeds to convert the args to alloy +/// [DynSolValue]s and then ABI encode them. +pub fn encode_function_args(func: &Function, args: I) -> Result> +where + I: IntoIterator, + S: AsRef, +{ + Ok(func.abi_encode_input(&encode_args(&func.inputs, args)?)?) +} + +/// 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, + calldata: &str, + input: bool, + fn_selector: bool, +) -> Result> { + let func = get_func(sig)?; + let calldata = hex::decode(calldata)?; + + let mut calldata = calldata.as_slice(); + // If function selector is prefixed in "calldata", remove it (first 4 bytes) + if input && fn_selector && calldata.len() >= 4 { + calldata = &calldata[4..]; + } + + let res = if input { + func.abi_decode_input(calldata, false) + } else { + func.abi_decode_output(calldata, false) + }?; + + // in case the decoding worked but nothing was decoded + if res.is_empty() { + eyre::bail!("no data was decoded") + } + + Ok(res) +} + +/// Given a function signature string, it tries to parse it as a `Function` +pub fn get_func(sig: &str) -> Result { + 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 { + Event::parse(sig).wrap_err("could not parse event signature") +} + +/// Given an error signature string, it tries to parse it as a `Error` +pub fn get_error(sig: &str) -> Result { + Error::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: &LogData) -> Event { + if !event.anonymous && raw_log.topics().len() > 1 { + let indexed_params = raw_log.topics().len() - 1; + let num_inputs = event.inputs.len(); + let num_address_params = event.inputs.iter().filter(|p| p.ty == "address").count(); + + event + .inputs + .iter_mut() + .enumerate() + .for_each(|(index, param)| { + if param.name.is_empty() { + param.name = format!("param{index}"); + } + if num_inputs == indexed_params + || (num_address_params == indexed_params && param.ty == "address") + { + param.indexed = true; + } + }) + } + event +} + +/// Helper function to coerce a value to a [DynSolValue] given a type string +pub fn coerce_value(ty: &str, arg: &str) -> Result { + let ty = DynSolType::parse(ty)?; + Ok(DynSolType::coerce_str(&ty, arg)?) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy::dyn_abi::EventExt; + use alloy::primitives::{B256, U256, Address, FixedBytes}; + + #[test] + fn test_get_func() { + let func = get_func("function foo(uint256 a, uint256 b) returns (uint256)"); + assert!(func.is_ok()); + let func = func.unwrap(); + assert_eq!(func.name, "foo"); + assert_eq!(func.inputs.len(), 2); + assert_eq!(func.inputs[0].ty, "uint256"); + assert_eq!(func.inputs[1].ty, "uint256"); + + // Stripped down function, which [Function] can parse. + let func = get_func("foo(bytes4 a, uint8 b)(bytes4)"); + assert!(func.is_ok()); + let func = func.unwrap(); + assert_eq!(func.name, "foo"); + assert_eq!(func.inputs.len(), 2); + assert_eq!(func.inputs[0].ty, "bytes4"); + assert_eq!(func.inputs[1].ty, "uint8"); + assert_eq!(func.outputs[0].ty, "bytes4"); + } + +// #[test] +// fn test_indexed_only_address() { +// let event = get_event("event Ev(address,uint256,address)").unwrap(); + +// let param0 = B256::random(); +// let param1 = vec![3; 32]; +// let param2 = B256::random(); +// let log = LogData::new_unchecked(vec![event.selector(), param0, param2], param1.into()); +// let event = get_indexed_event(event, &log); + +// assert_eq!(event.inputs.len(), 3); + +// // Only the address fields get indexed since total_params > num_indexed_params +// let parsed = event.decode_log(&log, false).unwrap(); + +// assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 2); +// assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0))); +// assert_eq!(parsed.body[0], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256)); +// assert_eq!(parsed.indexed[1], DynSolValue::Address(Address::from_word(param2))); +// } + +// #[test] +// fn test_indexed_all() { +// let event = get_event("event Ev(address,uint256,address)").unwrap(); + +// let param0 = B256::random(); +// let param1 = vec![3; 32]; +// let param2 = B256::random(); +// let log = LogData::new_unchecked( +// vec![event.selector(), param0, B256::from_slice(¶m1), param2], +// vec![].into(), +// ); +// let event = get_indexed_event(event, &log); + +// assert_eq!(event.inputs.len(), 3); + +// // All parameters get indexed since num_indexed_params == total_params +// assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 3); +// let parsed = event.decode_log(&log, false).unwrap(); + +// assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0))); +// assert_eq!(parsed.indexed[1], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256)); +// assert_eq!(parsed.indexed[2], DynSolValue::Address(Address::from_word(param2))); +// } +} diff --git a/crates/core/src/trace/decode/mod.rs b/crates/core/src/trace/decode/mod.rs new file mode 100644 index 00000000..a2811ecc --- /dev/null +++ b/crates/core/src/trace/decode/mod.rs @@ -0,0 +1,432 @@ +use super::types::{ + CallTrace, CallTraceNode, DecodedCallData, DecodedCallEvent, DecodedCallTrace, +}; +use crate::node::console::Console; +use crate::node::hardhat::{HardhatConsole, HARDHAT_CONSOLE_SELECTOR_PATCHES}; +use crate::trace::signatures::SingleSignaturesIdentifier; +use crate::trace::types::KNOWN_ADDRESSES; +use crate::utils::format_token; +use alloy::dyn_abi::{DecodedEvent, DynSolValue, EventExt, FunctionExt, JsonAbiExt}; +use alloy::json_abi::{Event, Function}; +use alloy::primitives::LogData; +use alloy::primitives::{Selector, B256}; +use itertools::Itertools; +use std::{ + collections::{BTreeMap, HashMap}, + sync::OnceLock, +}; +use zksync_multivm::interface::VmEvent; +use zksync_types::{Address, H160}; + +/// The first four bytes of the call data for a function call specifies the function to be called. +pub const SELECTOR_LEN: usize = 4; + +/// The call trace decoder. +/// +/// The decoder collects address labels and ABIs from any number of [TraceIdentifier]s, which it +/// then uses to decode the call trace. +/// +/// Note that a call trace decoder is required for each new set of traces, since addresses in +/// different sets might overlap. +#[derive(Clone, Debug, Default)] +pub struct CallTraceDecoder { + /// Addresses identified to be a specific contract. + /// + /// The values are in the form `":"`. + pub contracts: HashMap, + /// Address labels. + pub labels: HashMap, + /// Contract addresses that have a receive function. + pub receive_contracts: Vec
, + /// Contract addresses that have fallback functions, mapped to function sigs. + pub fallback_contracts: HashMap>, + + /// All known events. + // todo perhaps change to VmEvent type? + pub events: BTreeMap<(B256, usize), Vec>, + + /// All known functions. + pub functions: HashMap>, + + /// A signature identifier for events and functions. + pub signature_identifier: Option, +} + +impl CallTraceDecoder { + /// Creates a new call trace decoder. + /// + /// The call trace decoder always knows how to decode calls to the cheatcode address, as well + /// as DSTest-style logs. + pub fn new() -> &'static Self { + // If you want to take arguments in this function, assign them to the fields of the cloned + // lazy instead of removing it + static INIT: OnceLock = OnceLock::new(); + INIT.get_or_init(Self::init) + } + + fn init() -> Self { + 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() + } + + let labels: HashMap = KNOWN_ADDRESSES + .iter() + .map(|(address, known_address)| (address.clone(), known_address.name.clone())) + .collect(); + + Self { + contracts: Default::default(), + labels, + receive_contracts: Default::default(), + fallback_contracts: Default::default(), + functions: hh_funcs() + .map(|(selector, func)| (selector, vec![func])) + .collect(), + events: Console::abi::events() + .into_values() + .flatten() + .map(|event| ((event.selector(), indexed_inputs(&event)), vec![event])) + .collect(), + signature_identifier: None, + } + } + + /// Populates the traces with decoded data by mutating the + /// [CallTrace] in place. See [CallTraceDecoder::decode_function] and + /// [CallTraceDecoder::decode_event] for more details. + pub async fn populate_traces(&self, traces: &mut Vec) { + for node in traces { + node.trace.decoded = self.decode_function(&node.trace).await; + // TODO: Decode logs + // for log in node.trace.execution_result.logs.events.iter_mut() { + // node.trace.decoded_events = self.decode_event(&log).await; + // } + for log in node.logs.iter_mut() { + log.decoded = self.decode_event(&log.raw_log).await; + } + } + } + + /// Decodes a call trace. + pub async fn decode_function(&self, trace: &CallTrace) -> DecodedCallTrace { + // if let Some(trace) = precompiles::decode(trace, 1) { + // return trace; + // } + let label = self.labels.get(&trace.address).cloned(); + let cdata = &trace.call.input; + + if cdata.len() >= SELECTOR_LEN { + let selector = &cdata[..SELECTOR_LEN]; + let mut functions = Vec::new(); + let functions = match self.functions.get(selector) { + Some(fs) => fs, + None => { + if let Some(identifier) = &self.signature_identifier { + if let Some(function) = + identifier.write().await.identify_function(selector).await + { + functions.push(function); + } + } + &functions + } + }; + let [func, ..] = &functions[..] else { + return DecodedCallTrace { + label, + call_data: None, + return_data: self.default_return_data(trace), + }; + }; + + // If traced contract is a fallback contract, check if it has the decoded function. + // If not, then replace call data signature with `fallback`. + let mut call_data = self.decode_function_input(trace, func); + if let Some(fallback_functions) = self.fallback_contracts.get(&trace.address) { + if !fallback_functions.contains(&func.signature()) { + call_data.signature = "fallback()".into(); + } + } + + DecodedCallTrace { + label, + call_data: Some(call_data), + return_data: self.decode_function_output(trace, functions), + } + } 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![hex::encode(&cdata)] + }; + DecodedCallTrace { + label, + call_data: Some(DecodedCallData { signature, args }), + return_data: self.default_return_data(trace), + } + } + } + + /// Decodes a function's input into the given trace. + fn decode_function_input(&self, trace: &CallTrace, func: &Function) -> DecodedCallData { + let mut args = None; + if trace.call.input.len() >= SELECTOR_LEN { + if args.is_none() { + if let Ok(v) = func.abi_decode_input(&trace.call.input[SELECTOR_LEN..], false) { + args = Some(v.iter().map(|value| self.format_value(value)).collect()); + } + } + } + DecodedCallData { + signature: func.signature(), + args: args.unwrap_or_default(), + } + } + + /// Decodes a function's output into the given trace. + fn decode_function_output(&self, trace: &CallTrace, funcs: &[Function]) -> Option { + if !trace.success { + return self.default_return_data(trace); + } + + if let Some(values) = funcs + .iter() + .find_map(|func| func.abi_decode_output(&trace.call.output, false).ok()) + { + // 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 None; + } + + return Some( + values + .iter() + .map(|value| self.format_value(value)) + .format(", ") + .to_string(), + ); + } + + None + } + + /// Decodes an event. + pub async fn decode_event(&self, vm_event: &VmEvent) -> DecodedCallEvent { + // let &[t0, ..] = log.topics() else { return DecodedCallEvent { name: None, params: None } }; + let Some(&t0) = vm_event.indexed_topics.get(0) else { + return DecodedCallEvent { + name: None, + params: None, + }; + }; + //let key = (t0, vm_event.indexed_topics.len() - 1); + + let mut events = Vec::new(); + let b256_t0 = B256::from_slice(t0.as_bytes()); // or your preferred conversion + let key = (b256_t0, vm_event.indexed_topics.len() - 1); + let events = match self.events.get(&key) { + // TODO + 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_for_vm(event, vm_event)); + } + } + &events + } + }; + let log_data = vm_event_to_log_data(&vm_event); + for event in events { + if let Ok(decoded) = event.decode_log(&log_data, false) { + let params = reconstruct_params(event, &decoded); + return DecodedCallEvent { + name: Some(event.name.clone()), + params: Some( + params + .into_iter() + .zip(event.inputs.iter()) + .map(|(param, input)| { + // undo patched names + let name = input.name.clone(); + (name, self.format_value(¶m)) + }) + .collect(), + ), + }; + } + } + + DecodedCallEvent { + name: None, + params: None, + } + } + + /// 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; + }; + + // TODO: events and logs + // let events_it = nodes + // .iter() + // .flat_map(|node| node.logs.iter().filter_map(|log| log.raw_log.topics().first())) + // .unique(); + // identifier.write().await.identify_events(events_it).await; + + let funcs_it = nodes + .iter() + .filter_map(|n| match n.trace.address { + _ => n.trace.call.input.get(..SELECTOR_LEN), + }) + .filter(|v| !self.functions.contains_key(*v)); + + identifier.write().await.identify_functions(funcs_it).await; + } + + /// The default decoded return data for a trace. + fn default_return_data(&self, trace: &CallTrace) -> Option { + (!trace.success).then(|| "Revert - to do decode output strings".to_string()) + } + + /// Pretty-prints a value. + fn format_value(&self, value: &DynSolValue) -> String { + if let DynSolValue::Address(addr) = value { + // TODO: handle error + let zksync_address = Address::from(<[u8; 20]>::try_from(addr.0.as_slice()).unwrap()); + + if let Some(label) = self.labels.get(&zksync_address) { + return format!("{label}: [{addr}]"); + } + } + format_token(value, false) + } +} + +fn _indexed_inputs_zksync(event: &VmEvent) -> usize { + event.indexed_topics.len() +} + +fn indexed_inputs(event: &Event) -> usize { + event.inputs.iter().filter(|param| param.indexed).count() +} +// TODO move +/// Given an `Event` without indexed parameters and a `VmEvent`, it tries to +/// return the `Event` with the proper indexed parameters. Otherwise, +/// it returns the original `Event`. +pub fn get_indexed_event_for_vm(mut event: Event, vm_event: &VmEvent) -> Event { + if !event.anonymous && vm_event.indexed_topics.len() > 1 { + let indexed_params = vm_event.indexed_topics.len() - 1; + let num_inputs = event.inputs.len(); + let num_address_params = event.inputs.iter().filter(|p| p.ty == "address").count(); + + event + .inputs + .iter_mut() + .enumerate() + .for_each(|(index, param)| { + if param.name.is_empty() { + param.name = format!("param{index}"); + } + if num_inputs == indexed_params + || (num_address_params == indexed_params && param.ty == "address") + { + param.indexed = true; + } + }) + } + event +} + +/// Restore the order of the params of a decoded event, +/// as Alloy returns the indexed and unindexed params separately. +fn reconstruct_params(event: &Event, decoded: &DecodedEvent) -> Vec { + let mut indexed = 0; + let mut unindexed = 0; + let mut inputs = vec![]; + for input in event.inputs.iter() { + // Prevent panic of event `Transfer(from, to)` decoded with a signature + // `Transfer(address indexed from, address indexed to, uint256 indexed tokenId)` by making + // sure the event inputs is not higher than decoded indexed / un-indexed values. + if input.indexed && indexed < decoded.indexed.len() { + inputs.push(decoded.indexed[indexed].clone()); + indexed += 1; + } else if unindexed < decoded.body.len() { + inputs.push(decoded.body[unindexed].clone()); + unindexed += 1; + } + } + + inputs +} + +pub fn vm_event_to_log_data(event: &VmEvent) -> LogData { + LogData::new_unchecked( + event + .indexed_topics + .iter() + .map(|h| B256::from_slice(h.as_bytes())) + .collect(), + event.value.clone().into(), + ) +} + +/// Build a new [CallTraceDecoder]. +#[derive(Default)] +#[must_use = "builders do nothing unless you call `build` on them"] +pub struct CallTraceDecoderBuilder { + decoder: CallTraceDecoder, +} + +impl CallTraceDecoderBuilder { + /// Create a new builder. + #[inline] + pub fn new() -> Self { + Self { + decoder: CallTraceDecoder::new().clone(), + } + } + + /// Add known labels to the decoder. + #[inline] + pub fn with_labels(mut self, labels: impl IntoIterator) -> Self { + self.decoder.labels.extend(labels); + self + } + + /// Sets the signature identifier for events and functions. + #[inline] + pub fn with_signature_identifier(mut self, identifier: SingleSignaturesIdentifier) -> Self { + self.decoder.signature_identifier = Some(identifier); + self + } + + /// Build the decoder. + #[inline] + pub fn build(self) -> CallTraceDecoder { + self.decoder + } +} diff --git a/crates/core/src/trace/mod.rs b/crates/core/src/trace/mod.rs new file mode 100644 index 00000000..d20041b7 --- /dev/null +++ b/crates/core/src/trace/mod.rs @@ -0,0 +1,155 @@ +use crate::trace::decode::CallTraceDecoder; +use crate::trace::writer::TraceWriter; +use crate::trace::types::{ + CallTrace, CallTraceArena, CallTraceNode, DecodedCallTrace, TraceMemberOrder, KNOWN_ADDRESSES, +}; +use types::{CallLog, DecodedCallEvent}; +use zksync_multivm::interface::{Call, VmExecutionResultAndLogs}; +use zksync_types::H160; +pub mod abi_utils; +pub mod decode; +pub mod writer; +pub mod signatures; +pub mod types; + +/// Decode a collection of call traces. +/// +/// The traces will be decoded using the given decoder, if possible. +pub async fn decode_trace_arena( + arena: &mut CallTraceArena, + decoder: &CallTraceDecoder, +) -> Result<(), std::fmt::Error> { + decoder.prefetch_signatures(arena.nodes()).await; + decoder.populate_traces(arena.nodes_mut()).await; + + Ok(()) +} + +/// Render a collection of call traces to a string optionally including contract creation bytecodes +/// and in JSON format. +pub fn render_trace_arena_inner(arena: &CallTraceArena, with_bytecodes: bool) -> String { + let mut w = TraceWriter::new(Vec::::new()).write_bytecodes(with_bytecodes); + w.write_arena(&arena).expect("Failed to write traces"); + String::from_utf8(w.into_writer()).expect("trace writer wrote invalid UTF-8") +} + +pub fn build_call_trace_arena( + calls: &[Call], + tx_result: VmExecutionResultAndLogs, +) -> CallTraceArena { + let mut arena = Vec::new(); + + // Add a virtual root node + let root_idx = arena.len(); + let root_node = CallTraceNode { + parent: None, + children: Vec::new(), + idx: root_idx, + trace: CallTrace { + depth: 0, + success: true, + caller: H160::zero(), + address: H160::zero(), + execution_result: tx_result.clone(), + decoded: DecodedCallTrace::default(), + call: Call::default(), + }, + logs: Vec::new(), + ordering: Vec::new(), + }; + arena.push(root_node); + + for call in calls { + process_call_and_subcalls(call, root_idx, 0, &mut arena, &tx_result); + } + + CallTraceArena { arena } +} + +fn process_call_and_subcalls( + call: &Call, + parent_idx: usize, + depth: usize, + arena: &mut Vec, + tx_result: &VmExecutionResultAndLogs, +) { + // Only add the current call to the arena if it's not System or Precompile + // let should_add_call = + // !CallTraceArena::is_precompile(&call.to) && !CallTraceArena::is_system(&call.to); + let should_add_call = true; + let idx = if should_add_call { + let idx = arena.len(); + // todo: FIX THIS UGLINESS + let logs_for_call: Vec = tx_result + .logs + .events + .iter() + .filter(|vm_event| vm_event.address == call.to) + .cloned() + .enumerate() + .map(|(i, vm_event)| CallLog { + raw_log: vm_event, + decoded: DecodedCallEvent::default(), + position: i as u64, + }) + .collect(); + + let call_trace = convert_call_to_call_trace(call, depth, tx_result.clone()); + + let mut node = CallTraceNode { + parent: Some(parent_idx), + children: Vec::new(), + idx, + trace: call_trace, + logs: logs_for_call, + ordering: Vec::new(), + }; + + let logs_count = node.logs.len(); + for i in 0..logs_count { + node.ordering.push(TraceMemberOrder::Log(i)); + } + arena.push(node); + + // Add as a child of the parent node + arena[parent_idx].children.push(idx); + let child_local_idx = arena[parent_idx].children.len() - 1; + arena[parent_idx] + .ordering + .push(TraceMemberOrder::Call(child_local_idx)); + + idx + } else { + // If the current call is skipped, maintain the same parent index for its subcalls + parent_idx + }; + + // Process subcalls recursively + for subcall in &call.calls { + process_call_and_subcalls(subcall, idx, depth + 1, arena, tx_result); + } +} + +/// Converts a single `Call` to a `CallTrace`. +fn convert_call_to_call_trace( + call: &Call, + depth: usize, + tx_result: VmExecutionResultAndLogs, +) -> CallTrace { + let label = KNOWN_ADDRESSES + .get(&call.to) + .map(|known| known.name.clone()); + + CallTrace { + depth, + success: !tx_result.result.is_failed(), + caller: call.from, + address: call.to, + execution_result: tx_result, + decoded: DecodedCallTrace { + label, + ..Default::default() + }, + call: call.clone(), + } +} diff --git a/crates/core/src/trace/signatures.rs b/crates/core/src/trace/signatures.rs new file mode 100644 index 00000000..29b100dd --- /dev/null +++ b/crates/core/src/trace/signatures.rs @@ -0,0 +1,238 @@ +use alloy::json_abi::{Error, Event, Function}; +use alloy::primitives::hex; + +// use foundry_common::{ +// abi::{get_error, get_event, get_func}, +// fs, +// }; +use crate::resolver::{SelectorType, SignEthClient}; +use crate::trace::abi_utils::{get_error, get_event, get_func}; +use crate::utils::{read_json_file, write_json_file}; +use serde::{Deserialize, Serialize}; +use std::{ + collections::{BTreeMap, HashMap}, + path::PathBuf, + sync::Arc, +}; +use tokio::sync::RwLock; + +pub type SingleSignaturesIdentifier = Arc>; + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct CachedSignatures { + pub errors: BTreeMap, + pub events: BTreeMap, + pub functions: BTreeMap, +} + +impl CachedSignatures { + pub fn load(cache_path: PathBuf) -> Self { + let path = cache_path.join("signatures"); + if path.is_file() { + read_json_file(&path) + .map_err( + |err| tracing::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) { + tracing::warn!(target: "evm::traces", "could not create signatures cache dir: {:?}", err); + } + Self::default() + } + } +} +/// An identifier that tries to identify functions and events using signatures found at +/// `https://openchain.xyz` or a local cache. +#[derive(Debug)] +pub struct SignaturesIdentifier { + /// Cached selectors for functions, events and custom errors. + cached: CachedSignatures, + /// Location where to save `CachedSignatures`. + cached_path: Option, + /// Selectors that were unavailable during the session. + unavailable: HashMap, + /// The OpenChain client to fetch signatures from. + client: Option, +} + +impl SignaturesIdentifier { + pub fn new( + cache_path: Option, + offline: bool, + ) -> eyre::Result { + let client = if !offline { + Some(SignEthClient::new()?) + } else { + None + }; + + let identifier = if let Some(cache_path) = cache_path { + let path = cache_path.join("signatures"); + tracing::trace!(target: "evm::traces", ?path, "reading signature cache"); + let cached = CachedSignatures::load(cache_path); + Self { + cached, + cached_path: Some(path), + unavailable: HashMap::default(), + client, + } + } else { + Self { + cached: Default::default(), + cached_path: None, + unavailable: HashMap::default(), + client, + } + }; + + Ok(Arc::new(RwLock::new(identifier))) + } + + 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) { + tracing::warn!(target: "evm::traces", ?parent, ?err, "failed to create cache"); + } + } + if let Err(err) = write_json_file(cached_path, &self.cached) { + tracing::warn!(target: "evm::traces", ?cached_path, ?err, "failed to flush signature cache"); + } else { + tracing::trace!(target: "evm::traces", ?cached_path, "flushed signature cache") + } + } + } +} + +impl SignaturesIdentifier { + async fn identify( + &mut self, + selector_type: SelectorType, + identifiers: impl IntoIterator>, + get_type: impl Fn(&str) -> eyre::Result, + ) -> Vec> { + let cache = match selector_type { + SelectorType::Function => &mut self.cached.functions, + SelectorType::Event => &mut self.cached.events, + SelectorType::Error => &mut self.cached.errors, + }; + + let hex_identifiers: Vec = + identifiers.into_iter().map(hex::encode_prefixed).collect(); + + if let Some(client) = &self.client { + let query: Vec<_> = hex_identifiers + .iter() + .filter(|v| !cache.contains_key(v.as_str())) + .filter(|v| !self.unavailable.contains_key(v.as_str())) + .collect(); + + if let Ok(res) = client.decode_selectors(selector_type, query.clone()).await { + 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(), hex_id.clone()); + } + } + } + } + + hex_identifiers + .iter() + .map(|v| cache.get(v).and_then(|v| get_type(v).ok())) + .collect() + } + + /// 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.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.identify_events(&[identifier]).await.pop().unwrap() + } + + /// Identifies `Error`s from its cache or `https://api.openchain.xyz`. + pub async fn identify_errors( + &mut self, + identifiers: impl IntoIterator>, + ) -> Vec> { + self.identify(SelectorType::Error, identifiers, get_error) + .await + } + + /// Identifies `Error` from its cache or `https://api.openchain.xyz`. + pub async fn identify_error(&mut self, identifier: &[u8]) -> Option { + self.identify_errors(&[identifier]).await.pop().unwrap() + } +} + +impl Drop for SignaturesIdentifier { + fn drop(&mut self) { + self.save(); + } +} + +#[cfg(test)] +#[allow(clippy::needless_return)] +mod tests { + use super::*; + use tempdir::TempDir; + + #[tokio::test(flavor = "multi_thread")] + async fn can_query_signatures() { + let tmp = TempDir::new("sig-test").expect("failed creating temporary dir"); + { + let sigs = SignaturesIdentifier::new(Some(tmp.path().into()), false).unwrap(); + + assert!(sigs.read().await.cached.events.is_empty()); + assert!(sigs.read().await.cached.functions.is_empty()); + + let func = sigs.write().await.identify_function(&[35, 184, 114, 221]).await.unwrap(); + let event = sigs + .write() + .await + .identify_event(&[ + 39, 119, 42, 220, 99, 219, 7, 170, 231, 101, 183, 30, 178, 181, 51, 6, 79, 167, + 129, 189, 87, 69, 126, 27, 19, 133, 146, 216, 25, 141, 9, 89, + ]) + .await + .unwrap(); + + assert_eq!(func, get_func("transferFrom(address,address,uint256)").unwrap()); + assert_eq!(event, get_event("Transfer(address,address,uint128)").unwrap()); + + // dropping saves the cache + } + + let sigs = SignaturesIdentifier::new(Some(tmp.path().into()), false).unwrap(); + assert_eq!(sigs.read().await.cached.events.len(), 1); + assert_eq!(sigs.read().await.cached.functions.len(), 1); + } +} diff --git a/crates/core/src/trace/types.rs b/crates/core/src/trace/types.rs new file mode 100644 index 00000000..25651762 --- /dev/null +++ b/crates/core/src/trace/types.rs @@ -0,0 +1,342 @@ +use lazy_static::lazy_static; +use serde::Deserialize; +use std::collections::HashMap; +use zksync_multivm::interface::{Call, ExecutionResult, VmEvent, VmExecutionResultAndLogs}; +use zksync_types::web3::Bytes; +use zksync_types::{Address, H160, H256}; + +#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +pub enum ContractType { + System, + Precompile, + Popular, + Unknown, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct KnownAddress { + pub address: H160, + pub name: String, + contract_type: ContractType, +} + +lazy_static! { + /// Loads the known contact addresses from the JSON file. + pub static ref KNOWN_ADDRESSES: HashMap = { + let json_value = serde_json::from_slice(include_bytes!("../data/address_map.json")).unwrap(); + let pairs: Vec = serde_json::from_value(json_value).unwrap(); + + pairs + .into_iter() + .map(|entry| (entry.address, entry)) + .collect() + }; +} + +/// An Ethereum event log object. +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] +#[cfg_attr( + feature = "arbitrary", + derive(derive_arbitrary::Arbitrary, proptest_derive::Arbitrary) +)] +pub struct LogData { + /// The indexed topic list. + topics: Vec, + /// The plain data. + pub data: Bytes, +} + +impl LogData { + /// Creates a new log, without length-checking. This allows creation of + /// invalid logs. May be safely used when the length of the topic list is + /// known to be 4 or less. + #[inline] + pub const fn new_unchecked(topics: Vec, data: Bytes) -> Self { + Self { topics, data } + } + + /// Creates a new log. + #[inline] + pub fn new(topics: Vec, data: Bytes) -> Option { + let this = Self::new_unchecked(topics, data); + this.is_valid().then_some(this) + } + + /// Creates a new empty log. + #[inline] + pub const fn empty() -> Self { + Self { + topics: Vec::new(), + data: Bytes(Vec::new()), + } + } + + /// True if valid, false otherwise. + #[inline] + pub fn is_valid(&self) -> bool { + self.topics.len() <= 4 + } + + /// Get the topic list. + #[inline] + pub fn topics(&self) -> &[H256] { + &self.topics + } + + /// Get the topic list, mutably. This gives access to the internal + /// array, without allowing extension of that array. + #[inline] + pub fn topics_mut(&mut self) -> &mut [H256] { + &mut self.topics + } + + /// Get a mutable reference to the topic list. This allows creation of + /// invalid logs. + #[inline] + pub fn topics_mut_unchecked(&mut self) -> &mut Vec { + &mut self.topics + } + + /// Set the topic list, without length-checking. This allows creation of + /// invalid logs. + #[inline] + pub fn set_topics_unchecked(&mut self, topics: Vec) { + self.topics = topics; + } + + /// Set the topic list, truncating to 4 topics. + #[inline] + pub fn set_topics_truncating(&mut self, mut topics: Vec) { + topics.truncate(4); + self.set_topics_unchecked(topics); + } + + /// Consumes the log data, returning the topic list and the data. + #[inline] + pub fn split(self) -> (Vec, Bytes) { + (self.topics, self.data) + } +} + +/// Decoded call data. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DecodedCallData { + /// The function signature. + pub signature: String, + /// The function arguments. + pub args: Vec, +} + +/// Additional decoded data enhancing the [CallTrace]. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DecodedCallTrace { + /// Optional decoded label for the call. + pub label: Option, + /// Optional decoded return data. + pub return_data: Option, + /// Optional decoded call data. + pub call_data: Option, +} + +/// Additional decoded data enhancing the [CallLog]. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DecodedCallLog { + /// The decoded event name. + pub name: Option, + /// The decoded log parameters, a vector of the parameter name (e.g. foo) and the parameter + /// value (e.g. 0x9d3...45ca). + pub params: Option>, +} + +/// A log with optional decoded data. +#[derive(Clone, Debug, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CallLog { + /// The raw log data. + pub raw_log: VmEvent, + /// Optional complementary decoded log data. + pub decoded: DecodedCallEvent, + /// The position of the log relative to subcalls within the same trace. + pub position: u64, +} + +// impl From for CallLog { +// /// Converts a [`Log`] into a [`CallLog`]. +// fn from(log: Log) -> Self { +// Self { +// position: Default::default(), +// raw_log: log.data, +// decoded: DecodedCallLog { name: None, params: None }, +// } +// } +// } + +impl CallLog { + /// Sets the position of the log. + #[inline] + pub fn with_position(mut self, position: u64) -> Self { + self.position = position; + self + } +} + +/// A trace of a call with optional decoded data. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CallTrace { + /// The depth of the call. + pub depth: usize, + /// Whether the call was successful. + pub success: bool, + /// The caller address. + pub caller: Address, + /// The target address of this call. + /// + /// This is: + /// - [`CallKind::Call`] and alike: the callee, the address of the contract being called + /// - [`CallKind::Create`] and alike: the address of the created contract + pub address: Address, + /// Whether this is a call to a precompile. + /// + /// Note: This is optional because not all tracers make use of this. + // pub maybe_precompile: Option, + pub execution_result: VmExecutionResultAndLogs, + /// Optional complementary decoded call data. + pub decoded: DecodedCallTrace, + + // pub decoded_events: DecodedCallEvent, + pub call: Call, +} + +/// Additional decoded data enhancing the [CallLog]. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DecodedCallEvent { + /// The decoded event name. + pub name: Option, + /// The decoded log parameters, a vector of the parameter name (e.g. foo) and the parameter + /// value (e.g. 0x9d3...45ca). + pub params: Option>, +} + +/// A node in the arena +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::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, + + /// Event logs + pub logs: Vec, + + /// Ordering of child calls and logs + pub ordering: Vec, +} + +/// Ordering enum for calls, logs and steps +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TraceMemberOrder { + /// Contains the index of the corresponding log + Log(usize), + /// Contains the index of the corresponding trace node + Call(usize), +} + +/// An arena of recorded traces. +/// +/// This type will be populated via the [TracingInspector](super::TracingInspector). +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CallTraceArena { + /// The arena of recorded trace nodes + pub(crate) arena: Vec, +} + +// impl Default for CallTraceArena { +// fn default() -> Self { +// // The first node is the root node +// Self { arena: vec![Default::default()] } +// } +// } + +impl CallTraceArena { + /// Returns the nodes in the arena. + pub fn nodes(&self) -> &[CallTraceNode] { + &self.arena + } + + /// Returns a mutable reference to the nodes in the arena. + pub fn nodes_mut(&mut self) -> &mut Vec { + &mut self.arena + } + + /// Consumes the arena and returns the nodes. + pub fn into_nodes(self) -> Vec { + self.arena + } + + /// Clears the arena + /// + /// Note that this method has no effect on the allocated capacity of the arena. + #[inline] + pub fn clear(&mut self) { + self.arena.clear(); + // TODO: circle back to this + //self.arena.push(Default::default()); + } + + /// Checks if the given address is a precompile based on `KNOWN_ADDRESSES`. + pub fn is_precompile(address: &Address) -> bool { + if let Some(known) = KNOWN_ADDRESSES.get(address) { + matches!(known.contract_type, ContractType::Precompile) + } else { + false + } + } + + /// Filters out precompile nodes from the arena. + pub fn filter_out_precompiles(&mut self) { + self.arena + .retain(|node| !Self::is_precompile(&node.trace.address)); + } + + /// Checks if the given address is a system contract based on `KNOWN_ADDRESSES`. + pub fn is_system(address: &Address) -> bool { + if let Some(known) = KNOWN_ADDRESSES.get(address) { + matches!(known.contract_type, ContractType::System) + } else { + false + } + } + + /// Filters out system contracts nodes from the arena. + pub fn filter_out_system_contracts(&mut self) { + self.arena + .retain(|node| !Self::is_system(&node.trace.address)); + } +} + +pub trait ExecutionResultDisplay { + fn display(&self) -> String; +} + +impl ExecutionResultDisplay for ExecutionResult { + fn display(&self) -> String { + match self { + ExecutionResult::Success { .. } => "Success".to_string(), + ExecutionResult::Revert { output } => format!("Revert: {}", output), + ExecutionResult::Halt { reason } => format!("Halt: {:?}", reason), + } + } +} diff --git a/crates/core/src/trace/writer.rs b/crates/core/src/trace/writer.rs new file mode 100644 index 00000000..c8bb71d8 --- /dev/null +++ b/crates/core/src/trace/writer.rs @@ -0,0 +1,481 @@ +//! Helper methods to display transaction data in more human readable way. + +use super::types::{CallLog, TraceMemberOrder}; +use crate::trace::types::{ + CallTrace, CallTraceArena, CallTraceNode, DecodedCallData, ExecutionResultDisplay, +}; +use anstyle::{AnsiColor, Color, Style}; +use colorchoice::ColorChoice; +use hex::encode; +use std::io::{self, Write}; +use std::str; +use zksync_multivm::interface::CallType; +use zksync_types::{ + zk_evm_types::FarCallOpcode, +}; + +const PIPE: &str = " │ "; +const EDGE: &str = " └─ "; +const BRANCH: &str = " ├─ "; +const CALL: &str = "→ "; +const RETURN: &str = "← "; + +const TRACE_KIND_STYLE: Style = AnsiColor::Yellow.on_default(); +const LOG_STYLE: Style = AnsiColor::Cyan.on_default(); + +/// Configuration for a [`TraceWriter`]. +#[derive(Clone, Debug)] +#[allow(missing_copy_implementations)] +pub struct TraceWriterConfig { + use_colors: bool, + color_cheatcodes: bool, + write_bytecodes: bool, + write_storage_changes: bool, +} + +impl Default for TraceWriterConfig { + fn default() -> Self { + Self::new() + } +} + +impl TraceWriterConfig { + /// Create a new `TraceWriterConfig` with default settings. + pub fn new() -> Self { + Self { + use_colors: use_colors(ColorChoice::Auto), + color_cheatcodes: false, + write_bytecodes: false, + write_storage_changes: false, + } + } + + /// Use colors in the output. Default: [`Auto`](ColorChoice::Auto). + pub fn color_choice(mut self, choice: ColorChoice) -> Self { + self.use_colors = use_colors(choice); + self + } + + /// Get the current color choice. `Auto` is lost, so this returns `true` if colors are enabled. + pub fn get_use_colors(&self) -> bool { + self.use_colors + } + + /// Color calls to the cheatcode address differently. Default: false. + pub fn color_cheatcodes(mut self, yes: bool) -> Self { + self.color_cheatcodes = yes; + self + } + + /// Returns `true` if calls to the cheatcode address are colored differently. + pub fn get_color_cheatcodes(&self) -> bool { + self.color_cheatcodes + } + + /// Write contract creation codes and deployed codes when writing "create" traces. + /// Default: false. + pub fn write_bytecodes(mut self, yes: bool) -> Self { + self.write_bytecodes = yes; + self + } + + /// Returns `true` if contract creation codes and deployed codes are written. + pub fn get_write_bytecodes(&self) -> bool { + self.write_bytecodes + } + + // /// Sets whether to write storage changes. + // pub fn write_storage_changes(mut self, yes: bool) -> Self { + // self.write_storage_changes = yes; + // self + // } + + // /// Returns `true` if storage changes are written to the writer. + // pub fn get_write_storage_changes(&self) -> bool { + // self.write_storage_changes + // } +} + +/// Formats [call traces](CallTraceArena) to an [`Write`] writer. +/// +/// Will never write invalid UTF-8. +#[derive(Clone, Debug)] +pub struct TraceWriter { + writer: W, + indentation_level: u16, + config: TraceWriterConfig, +} + +impl TraceWriter { + /// Create a new `TraceWriter` with the given writer. + #[inline] + pub fn new(writer: W) -> Self { + Self::with_config(writer, TraceWriterConfig::new()) + } + + /// Create a new `TraceWriter` with the given writer and configuration. + pub fn with_config(writer: W, config: TraceWriterConfig) -> Self { + Self { + writer, + indentation_level: 0, + config, + } + } + + /// Sets the color choice. + #[inline] + pub fn use_colors(mut self, color_choice: ColorChoice) -> Self { + self.config.use_colors = use_colors(color_choice); + self + } + + /// Sets whether to color calls to the cheatcode address differently. + #[inline] + pub fn color_cheatcodes(mut self, yes: bool) -> Self { + self.config.color_cheatcodes = yes; + self + } + + /// Sets the starting indentation level. + #[inline] + pub fn with_indentation_level(mut self, level: u16) -> Self { + self.indentation_level = level; + self + } + + /// Sets whether contract creation codes and deployed codes should be written. + #[inline] + pub fn write_bytecodes(mut self, yes: bool) -> Self { + self.config.write_bytecodes = yes; + self + } + + /// Sets whether to write storage changes. + #[inline] + pub fn with_storage_changes(mut self, yes: bool) -> Self { + self.config.write_storage_changes = yes; + self + } + + /// Returns a reference to the inner writer. + #[inline] + pub const fn writer(&self) -> &W { + &self.writer + } + + /// Returns a mutable reference to the inner writer. + #[inline] + pub fn writer_mut(&mut self) -> &mut W { + &mut self.writer + } + + /// Consumes the `TraceWriter` and returns the inner writer. + #[inline] + pub fn into_writer(self) -> W { + self.writer + } + + /// Writes a call trace arena to the writer. + pub fn write_arena(&mut self, arena: &CallTraceArena) -> io::Result<()> { + let root_node = &arena.arena[0]; + for &child_idx in &root_node.children { + self.write_node(arena.nodes(), child_idx)?; + } + self.writer.flush() + } + + /// Writes a single item of a single node to the writer. Returns the index of the next item to + /// be written. + /// + /// Note: this will return length of [CallTraceNode::ordering] when last item will get + /// processed. + /// Writes a single item of a single node to the writer. Returns the index of the next item to + /// be written. + /// + /// Note: this will return the length of [CallTraceNode::ordering] when the last item gets + /// processed. + fn write_item( + &mut self, + nodes: &[CallTraceNode], + node_idx: usize, + item_idx: usize, + ) -> io::Result { + let node = &nodes[node_idx]; + match &node.ordering[item_idx] { + TraceMemberOrder::Log(index) => { + self.write_log(&node.logs[*index])?; + Ok(item_idx + 1) + } + TraceMemberOrder::Call(index) => { + assert!(*index < node.children.len(), "Call index out of bounds"); + self.write_node(nodes, node.children[*index])?; + Ok(item_idx + 1) + } + } + } + + /// Writes items of a single node to the writer, starting from the given index, and until the + /// given predicate is false. + /// + /// Returns the index of the next item to be written. + fn write_items_until( + &mut self, + nodes: &[CallTraceNode], + node_idx: usize, + first_item_idx: usize, + f: impl Fn(usize) -> bool, + ) -> io::Result { + let mut item_idx = first_item_idx; + while !f(item_idx) { + item_idx = self.write_item(nodes, node_idx, item_idx)?; + } + Ok(item_idx) + } + + /// Writes all items of a single node to the writer. + fn write_items(&mut self, nodes: &[CallTraceNode], node_idx: usize) -> io::Result<()> { + let items_cnt = nodes[node_idx].ordering.len(); + self.write_items_until(nodes, node_idx, 0, |idx| idx == items_cnt)?; + Ok(()) + } + + /// Writes a single node and its children to the writer. + fn write_node(&mut self, nodes: &[CallTraceNode], idx: usize) -> io::Result<()> { + let node = &nodes[idx]; + + // Write header. + self.write_branch()?; + self.write_trace_header(&node.trace)?; + self.writer.write_all(b"\n")?; + + // Write logs and subcalls. + self.indentation_level += 1; + self.write_items(nodes, idx)?; + + // if self.config.write_storage_changes { + // self.write_storage_changes(node)?; + // } + + // Write return data. + self.write_edge()?; + self.write_trace_footer(&node.trace)?; + self.writer.write_all(b"\n")?; + + self.indentation_level -= 1; + + Ok(()) + } + + /// Writes the header of a call trace. + fn write_trace_header(&mut self, trace: &CallTrace) -> io::Result<()> { + write!(self.writer, "[{}] ", trace.call.gas_used)?; + + let trace_kind_style = self.trace_kind_style(); + let address = format!("0x{}", encode(trace.call.to)); + + match trace.call.r#type { + CallType::Create => { + write!( + self.writer, + "{trace_kind_style}{CALL}new{trace_kind_style:#} {label}@{address}", + label = trace.decoded.label.as_deref().unwrap_or("") + )?; + if self.config.write_bytecodes { + write!(self.writer, "({})", hex::encode(&trace.call.input))?; + } + } + CallType::Call(_) | CallType::NearCall => { + let (func_name, inputs) = match &trace.decoded.call_data { + Some(DecodedCallData { signature, args }) => { + let name = signature.split('(').next().unwrap(); + (name.to_string(), args.join(", ")) + } + None => { + if trace.call.input.len() < 4 { + ("fallback".to_string(), hex::encode(&trace.call.input)) + } else { + let (selector, data) = trace.call.input.split_at(4); + (hex::encode(selector), hex::encode(data)) + } + } + }; + + write!( + self.writer, + "{style}{addr}{style:#}::{style}{func_name}{style:#}", + style = self.trace_style(trace), + addr = trace.decoded.label.as_deref().unwrap_or(&address), + )?; + + if !trace.call.value.is_zero() { + write!(self.writer, "{{value: {}}}", trace.call.value)?; + } + + write!(self.writer, "({inputs})")?; + + let action = match trace.call.r#type { + CallType::Call(opcode) => match opcode { + FarCallOpcode::Normal => None, + FarCallOpcode::Delegate => Some(" [delegatecall]"), + FarCallOpcode::Mimic => Some(" [mimiccall]"), + }, + CallType::NearCall => Some("[nearcall]"), // TODO check if this is correct + CallType::Create => unreachable!(), // Create calls are handled separately. + }; + + if let Some(action) = action { + write!( + self.writer, + "{trace_kind_style}{action}{trace_kind_style:#}" + )?; + } + } + } + + Ok(()) + } + + fn write_log(&mut self, log: &CallLog) -> io::Result<()> { + let log_style = self.log_style(); + self.write_branch()?; + + if let Some(name) = &log.decoded.name { + write!(self.writer, "emit {name}({log_style}")?; + if let Some(params) = &log.decoded.params { + for (i, (param_name, value)) in params.iter().enumerate() { + if i > 0 { + self.writer.write_all(b", ")?; + } + write!(self.writer, "{param_name}: {value}")?; + } + } + writeln!(self.writer, "{log_style:#})")?; + } else { + for (i, topic) in log.raw_log.indexed_topics.iter().enumerate() { + if i == 0 { + self.writer.write_all(b" emit topic 0")?; + } else { + self.write_pipes()?; + write!(self.writer, " topic {i}")?; + } + writeln!(self.writer, ": {log_style}{topic}{log_style:#}")?; + } + if !log.raw_log.indexed_topics.is_empty() { + self.write_pipes()?; + } + writeln!( + self.writer, + " data: {log_style}{data}{log_style:#}", + data = encode(&log.raw_log.value) + )?; + } + Ok(()) + } + + /// Writes the footer of a call trace. + fn write_trace_footer(&mut self, trace: &CallTrace) -> io::Result<()> { + // Use the custom trait to format the execution result + let status_str = trace.execution_result.result.display(); + + // Write the execution result status using the formatted string + write!( + self.writer, + "{style}{RETURN}[{status}]{style:#}", + style = self.trace_style(trace), + status = status_str, + )?; + + // Write decoded return data if available + if let Some(decoded) = &trace.decoded.return_data { + write!(self.writer, " ")?; + return self.writer.write_all(decoded.as_bytes()); + } + + // Handle contract creation or output data + if !self.config.write_bytecodes + && matches!(trace.call.r#type, CallType::Create) + && !trace.execution_result.result.is_failed() + { + write!(self.writer, " {} bytes of code", trace.call.output.len())?; + } else if !trace.call.output.is_empty() { + write!(self.writer, " {}", hex::encode(&trace.call.output))?; + } + + Ok(()) + } + + fn write_indentation(&mut self) -> io::Result<()> { + self.writer.write_all(b" ")?; + for _ in 1..self.indentation_level { + self.writer.write_all(PIPE.as_bytes())?; + } + Ok(()) + } + + #[doc(alias = "left_prefix")] + fn write_branch(&mut self) -> io::Result<()> { + self.write_indentation()?; + if self.indentation_level != 0 { + self.writer.write_all(BRANCH.as_bytes())?; + } + Ok(()) + } + + #[doc(alias = "right_prefix")] + fn write_pipes(&mut self) -> io::Result<()> { + self.write_indentation()?; + self.writer.write_all(PIPE.as_bytes()) + } + + fn write_edge(&mut self) -> io::Result<()> { + self.write_indentation()?; + self.writer.write_all(EDGE.as_bytes()) + } + + fn trace_style(&self, trace: &CallTrace) -> Style { + if !self.config.use_colors { + return Style::default(); + } + let color = if self.config.color_cheatcodes { + AnsiColor::Blue + } else if trace.success { + AnsiColor::Green + } else { + AnsiColor::Red + }; + Color::Ansi(color).on_default() + } + + fn trace_kind_style(&self) -> Style { + if !self.config.use_colors { + return Style::default(); + } + TRACE_KIND_STYLE + } + + fn log_style(&self) -> Style { + if !self.config.use_colors { + return Style::default(); + } + LOG_STYLE + } +} + +fn use_colors(choice: ColorChoice) -> bool { + use io::IsTerminal; + match choice { + ColorChoice::Auto => io::stdout().is_terminal(), + ColorChoice::AlwaysAnsi | ColorChoice::Always => true, + ColorChoice::Never => false, + } +} + +// Formats the given U256 as a decimal number if it is short, otherwise as a hexadecimal +// byte-array. +// fn num_or_hex(x: U256) -> String { +// if x < U256::from(1e6 as u128) { +// x.to_string() +// } else { +// H256::from(x).to_string() +// } +// } diff --git a/crates/core/src/utils.rs b/crates/core/src/utils.rs index 4d58fd91..45130a73 100644 --- a/crates/core/src/utils.rs +++ b/crates/core/src/utils.rs @@ -4,6 +4,7 @@ use anyhow::Context; use chrono::{DateTime, Utc}; use colored::Colorize; use serde::Serialize; +use serde::de::DeserializeOwned; use std::future::Future; use std::sync::Arc; use std::{convert::TryInto, fmt}; @@ -206,6 +207,16 @@ pub fn write_json_file(path: &Path, obj: &T) -> anyhow::Result<()> Ok(()) } +/// Reads the JSON file at the specified path and deserializes it into the provided type. +/// Returns an error if the file cannot be read or deserialization fails. +pub fn read_json_file(path: &Path) -> anyhow::Result { + let file_content = std::fs::read_to_string(path) + .with_context(|| format!("Failed to read file '{}'", path.display()))?; + + serde_json::from_str(&file_content) + .with_context(|| format!("Failed to deserialize JSON from '{}'", path.display())) +} + /// Formats a token value for display. Adapted from `foundry-common-fmt`. pub fn format_token(value: &DynSolValue, raw: bool) -> String { match value { From ac89b336d6ceafbbc1ac39df80b1a8d86218799e Mon Sep 17 00:00:00 2001 From: Dustin Brickwood Date: Fri, 31 Jan 2025 08:51:06 -0600 Subject: [PATCH 02/11] feat: prints traces correctly structured --- crates/core/src/node/inner/in_memory_inner.rs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/crates/core/src/node/inner/in_memory_inner.rs b/crates/core/src/node/inner/in_memory_inner.rs index 6d20182b..7963b61c 100644 --- a/crates/core/src/node/inner/in_memory_inner.rs +++ b/crates/core/src/node/inner/in_memory_inner.rs @@ -20,10 +20,11 @@ use crate::node::{ }; use crate::system_contracts::SystemContracts; +use crate::trace::decode::CallTraceDecoderBuilder; use crate::utils::create_debug_output; use crate::{delegate_vm, formatter, utils}; use anvil_zksync_config::constants::{ - LEGACY_RICH_WALLETS, NON_FORK_FIRST_BLOCK_TIMESTAMP, RICH_WALLETS, + LEGACY_RICH_WALLETS, NON_FORK_FIRST_BLOCK_TIMESTAMP, RICH_WALLETS, DEFAULT_DISK_CACHE_DIR }; use anvil_zksync_config::TestNodeConfig; use anvil_zksync_types::{ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails}; @@ -32,6 +33,7 @@ use colored::Colorize; use indexmap::IndexMap; use once_cell::sync::OnceCell; use std::collections::{HashMap, HashSet}; +use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; use tokio::sync::RwLock; @@ -72,6 +74,8 @@ use zksync_types::{ ACCOUNT_CODE_STORAGE_ADDRESS, H160, H256, MAX_L2_TX_GAS_LIMIT, U256, U64, }; use zksync_web3_decl::error::Web3Error; +use crate::trace::signatures::SignaturesIdentifier; +use crate::trace::{build_call_trace_arena, decode_trace_arena, render_trace_arena_inner}; // TODO: Rename `InMemoryNodeInner` to something more sensible /// Helper struct for InMemoryNode. @@ -395,6 +399,31 @@ impl InMemoryNodeInner { formatter.print_vm_details(&tx_result); } + if let Some(call_traces) = call_traces { + let mut builder = CallTraceDecoderBuilder::new(); + builder = builder.with_signature_identifier( + SignaturesIdentifier::new( + Some(PathBuf::from(DEFAULT_DISK_CACHE_DIR)), //todo + self.config.offline, + ) + .unwrap(), + ); + let decoder = builder.build(); + let mut arena = build_call_trace_arena(&call_traces, tx_result.clone()); + tokio::task::block_in_place(|| { + // Run the async function using the current runtime + tokio::runtime::Handle::current().block_on(async { + decode_trace_arena(&mut arena, &decoder).await.unwrap(); + }); + }); + + // Render the arena once + let trace_output = render_trace_arena_inner(&arena, false); + + // Print the formatted traces using `{}` to interpret ANSI codes correctly + println!("Traces:\n{}", trace_output); + } + if let Some(call_traces) = call_traces { if !self.config.disable_console_log { self.console_log_handler.handle_calls_recursive(call_traces); From 4a11203eca8b0cd6befba9baeb5d4ba2dc0708dd Mon Sep 17 00:00:00 2001 From: Dustin Brickwood Date: Fri, 31 Jan 2025 09:38:07 -0600 Subject: [PATCH 03/11] chore: further clean up --- crates/core/src/data/address_map.json | 4 +- crates/core/src/trace/abi_utils.rs | 10 +- crates/core/src/trace/decode/mod.rs | 164 ++++++++++++-------------- crates/core/src/trace/signatures.rs | 16 ++- crates/core/src/trace/types.rs | 28 ++--- crates/core/src/trace/writer.rs | 69 ++--------- 6 files changed, 115 insertions(+), 176 deletions(-) diff --git a/crates/core/src/data/address_map.json b/crates/core/src/data/address_map.json index d3f53704..d5d6e5f6 100644 --- a/crates/core/src/data/address_map.json +++ b/crates/core/src/data/address_map.json @@ -41,5 +41,7 @@ ["0x0000000000000000000000000000000000000007", "EcMul", "Precompile"], ["0x0000000000000000000000000000000000000008", "EcPairing", "Precompile"], ["0xc706EC7dfA5D4Dc87f29f859094165E8290530f5", "GasBoundCaller", "System"], - ["0x0000000000000000000000000000000000000100", "P256Verify", "System"] + ["0x0000000000000000000000000000000000000100", "P256Verify", "System"], + ["0x000000000000000000636F6e736F6c652e6c6f67", "console", "Popular"], + ] diff --git a/crates/core/src/trace/abi_utils.rs b/crates/core/src/trace/abi_utils.rs index 9b95794d..32039634 100644 --- a/crates/core/src/trace/abi_utils.rs +++ b/crates/core/src/trace/abi_utils.rs @@ -1,4 +1,12 @@ -//! ABI related helper functions. +//! ABI related helper functions. +////////////////////////////////////////////////////////////////////////////////////// +// Attribution: File adapted from the `foundry-common` crate // +// // +// Full credit goes to its authors. See the original implementation here: // +// https://github.com/foundry-rs/foundry/blob/master/crates/common/src/abi.rs. // +// // +// Note: These methods are used under the terms of the original project's license. // +////////////////////////////////////////////////////////////////////////////////////// use super::types::LogData; use alloy::dyn_abi::{DynSolType, DynSolValue, FunctionExt, JsonAbiExt}; diff --git a/crates/core/src/trace/decode/mod.rs b/crates/core/src/trace/decode/mod.rs index a2811ecc..f6346f19 100644 --- a/crates/core/src/trace/decode/mod.rs +++ b/crates/core/src/trace/decode/mod.rs @@ -21,9 +21,46 @@ use zksync_types::{Address, H160}; /// The first four bytes of the call data for a function call specifies the function to be called. pub const SELECTOR_LEN: usize = 4; +/// Build a new [CallTraceDecoder]. +#[derive(Default)] +#[must_use = "builders do nothing unless you call `build` on them"] +pub struct CallTraceDecoderBuilder { + decoder: CallTraceDecoder, +} + +impl CallTraceDecoderBuilder { + /// Create a new builder. + #[inline] + pub fn new() -> Self { + Self { + decoder: CallTraceDecoder::new().clone(), + } + } + + /// Add known labels to the decoder. + #[inline] + pub fn with_labels(mut self, labels: impl IntoIterator) -> Self { + self.decoder.labels.extend(labels); + self + } + + /// Sets the signature identifier for events and functions. + #[inline] + pub fn with_signature_identifier(mut self, identifier: SingleSignaturesIdentifier) -> Self { + self.decoder.signature_identifier = Some(identifier); + self + } + + /// Build the decoder. + #[inline] + pub fn build(self) -> CallTraceDecoder { + self.decoder + } +} + /// The call trace decoder. /// -/// The decoder collects address labels and ABIs from any number of [TraceIdentifier]s, which it +/// The decoder collects address labels and ABIs which it /// then uses to decode the call trace. /// /// Note that a call trace decoder is required for each new set of traces, since addresses in @@ -40,14 +77,10 @@ pub struct CallTraceDecoder { pub receive_contracts: Vec
, /// Contract addresses that have fallback functions, mapped to function sigs. pub fallback_contracts: HashMap>, - /// All known events. - // todo perhaps change to VmEvent type? pub events: BTreeMap<(B256, usize), Vec>, - /// All known functions. pub functions: HashMap>, - /// A signature identifier for events and functions. pub signature_identifier: Option, } @@ -55,8 +88,7 @@ pub struct CallTraceDecoder { impl CallTraceDecoder { /// Creates a new call trace decoder. /// - /// The call trace decoder always knows how to decode calls to the cheatcode address, as well - /// as DSTest-style logs. + /// The call trace decoder always knows how to decode calls of DSTest-style logs pub fn new() -> &'static Self { // If you want to take arguments in this function, assign them to the fields of the cloned // lazy instead of removing it @@ -82,6 +114,7 @@ impl CallTraceDecoder { functions.into_iter() } + // Add known addresses (system contracts, precompiles) to the labels let labels: HashMap = KNOWN_ADDRESSES .iter() .map(|(address, known_address)| (address.clone(), known_address.name.clone())) @@ -110,10 +143,6 @@ impl CallTraceDecoder { pub async fn populate_traces(&self, traces: &mut Vec) { for node in traces { node.trace.decoded = self.decode_function(&node.trace).await; - // TODO: Decode logs - // for log in node.trace.execution_result.logs.events.iter_mut() { - // node.trace.decoded_events = self.decode_event(&log).await; - // } for log in node.logs.iter_mut() { log.decoded = self.decode_event(&log.raw_log).await; } @@ -122,9 +151,6 @@ impl CallTraceDecoder { /// Decodes a call trace. pub async fn decode_function(&self, trace: &CallTrace) -> DecodedCallTrace { - // if let Some(trace) = precompiles::decode(trace, 1) { - // return trace; - // } let label = self.labels.get(&trace.address).cloned(); let cdata = &trace.call.input; @@ -231,22 +257,19 @@ impl CallTraceDecoder { None } - /// Decodes an event. + /// Decodes an event from zksync type VmEvent. pub async fn decode_event(&self, vm_event: &VmEvent) -> DecodedCallEvent { - // let &[t0, ..] = log.topics() else { return DecodedCallEvent { name: None, params: None } }; let Some(&t0) = vm_event.indexed_topics.get(0) else { return DecodedCallEvent { name: None, params: None, }; }; - //let key = (t0, vm_event.indexed_topics.len() - 1); - + let mut events = Vec::new(); - let b256_t0 = B256::from_slice(t0.as_bytes()); // or your preferred conversion - let key = (b256_t0, vm_event.indexed_topics.len() - 1); + let b256_t0 = B256::from_slice(t0.as_bytes()); + let key = (b256_t0, indexed_inputs_zksync(vm_event) - 1); let events = match self.events.get(&key) { - // TODO Some(es) => es, None => { if let Some(identifier) = &self.signature_identifier { @@ -290,12 +313,11 @@ impl CallTraceDecoder { return; }; - // TODO: events and logs - // let events_it = nodes - // .iter() - // .flat_map(|node| node.logs.iter().filter_map(|log| log.raw_log.topics().first())) - // .unique(); - // identifier.write().await.identify_events(events_it).await; + let events_it = nodes + .iter() + .flat_map(|node| node.logs.iter().filter_map(|log| log.raw_log.indexed_topics.first())) + .unique(); + identifier.write().await.identify_events(events_it).await; let funcs_it = nodes .iter() @@ -309,7 +331,7 @@ impl CallTraceDecoder { /// The default decoded return data for a trace. fn default_return_data(&self, trace: &CallTrace) -> Option { - (!trace.success).then(|| "Revert - to do decode output strings".to_string()) + (!trace.success).then(|| "Revert - todo! decode output strings".to_string()) } /// Pretty-prints a value. @@ -326,14 +348,36 @@ impl CallTraceDecoder { } } -fn _indexed_inputs_zksync(event: &VmEvent) -> usize { +/// Restore the order of the params of a decoded event, +/// as Alloy returns the indexed and unindexed params separately. +fn reconstruct_params(event: &Event, decoded: &DecodedEvent) -> Vec { + let mut indexed = 0; + let mut unindexed = 0; + let mut inputs = vec![]; + for input in event.inputs.iter() { + // Prevent panic of event `Transfer(from, to)` decoded with a signature + // `Transfer(address indexed from, address indexed to, uint256 indexed tokenId)` by making + // sure the event inputs is not higher than decoded indexed / un-indexed values. + if input.indexed && indexed < decoded.indexed.len() { + inputs.push(decoded.indexed[indexed].clone()); + indexed += 1; + } else if unindexed < decoded.body.len() { + inputs.push(decoded.body[unindexed].clone()); + unindexed += 1; + } + } + + inputs +} + +fn indexed_inputs_zksync(event: &VmEvent) -> usize { event.indexed_topics.len() } fn indexed_inputs(event: &Event) -> usize { event.inputs.iter().filter(|param| param.indexed).count() } -// TODO move + /// Given an `Event` without indexed parameters and a `VmEvent`, it tries to /// return the `Event` with the proper indexed parameters. Otherwise, /// it returns the original `Event`. @@ -361,28 +405,7 @@ pub fn get_indexed_event_for_vm(mut event: Event, vm_event: &VmEvent) -> Event { event } -/// Restore the order of the params of a decoded event, -/// as Alloy returns the indexed and unindexed params separately. -fn reconstruct_params(event: &Event, decoded: &DecodedEvent) -> Vec { - let mut indexed = 0; - let mut unindexed = 0; - let mut inputs = vec![]; - for input in event.inputs.iter() { - // Prevent panic of event `Transfer(from, to)` decoded with a signature - // `Transfer(address indexed from, address indexed to, uint256 indexed tokenId)` by making - // sure the event inputs is not higher than decoded indexed / un-indexed values. - if input.indexed && indexed < decoded.indexed.len() { - inputs.push(decoded.indexed[indexed].clone()); - indexed += 1; - } else if unindexed < decoded.body.len() { - inputs.push(decoded.body[unindexed].clone()); - unindexed += 1; - } - } - - inputs -} - +/// Converts a `VmEvent` to a `LogData`. pub fn vm_event_to_log_data(event: &VmEvent) -> LogData { LogData::new_unchecked( event @@ -393,40 +416,3 @@ pub fn vm_event_to_log_data(event: &VmEvent) -> LogData { event.value.clone().into(), ) } - -/// Build a new [CallTraceDecoder]. -#[derive(Default)] -#[must_use = "builders do nothing unless you call `build` on them"] -pub struct CallTraceDecoderBuilder { - decoder: CallTraceDecoder, -} - -impl CallTraceDecoderBuilder { - /// Create a new builder. - #[inline] - pub fn new() -> Self { - Self { - decoder: CallTraceDecoder::new().clone(), - } - } - - /// Add known labels to the decoder. - #[inline] - pub fn with_labels(mut self, labels: impl IntoIterator) -> Self { - self.decoder.labels.extend(labels); - self - } - - /// Sets the signature identifier for events and functions. - #[inline] - pub fn with_signature_identifier(mut self, identifier: SingleSignaturesIdentifier) -> Self { - self.decoder.signature_identifier = Some(identifier); - self - } - - /// Build the decoder. - #[inline] - pub fn build(self) -> CallTraceDecoder { - self.decoder - } -} diff --git a/crates/core/src/trace/signatures.rs b/crates/core/src/trace/signatures.rs index 29b100dd..f1018829 100644 --- a/crates/core/src/trace/signatures.rs +++ b/crates/core/src/trace/signatures.rs @@ -1,10 +1,14 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Attribution: File adapted from the `evm` crate for zksync usage // +// // +// Full credit goes to its authors. See the original implementation here: // +// https://github.com/foundry-rs/foundry/blob/master/crates/evm/traces/src/idenitfier/signatures.rs. // +// // +// Note: These methods are used under the terms of the original project's license. // +//////////////////////////////////////////////////////////////////////////////////////////////////////////// + use alloy::json_abi::{Error, Event, Function}; use alloy::primitives::hex; - -// use foundry_common::{ -// abi::{get_error, get_event, get_func}, -// fs, -// }; use crate::resolver::{SelectorType, SignEthClient}; use crate::trace::abi_utils::{get_error, get_event, get_func}; use crate::utils::{read_json_file, write_json_file}; @@ -204,7 +208,7 @@ impl Drop for SignaturesIdentifier { mod tests { use super::*; use tempdir::TempDir; - + #[tokio::test(flavor = "multi_thread")] async fn can_query_signatures() { let tmp = TempDir::new("sig-test").expect("failed creating temporary dir"); diff --git a/crates/core/src/trace/types.rs b/crates/core/src/trace/types.rs index 25651762..5a9cfbde 100644 --- a/crates/core/src/trace/types.rs +++ b/crates/core/src/trace/types.rs @@ -5,6 +5,9 @@ use zksync_multivm::interface::{Call, ExecutionResult, VmEvent, VmExecutionResul use zksync_types::web3::Bytes; use zksync_types::{Address, H160, H256}; + +// Note: duplicated types from existing formatter.rs +// will be consolidated pending feedback #[derive(Debug, Deserialize, Clone, PartialEq, Eq)] pub enum ContractType { System, @@ -33,7 +36,7 @@ lazy_static! { }; } -/// An Ethereum event log object. +/// A ZKsync event log object. #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))] #[cfg_attr( @@ -164,17 +167,6 @@ pub struct CallLog { pub position: u64, } -// impl From for CallLog { -// /// Converts a [`Log`] into a [`CallLog`]. -// fn from(log: Log) -> Self { -// Self { -// position: Default::default(), -// raw_log: log.data, -// decoded: DecodedCallLog { name: None, params: None }, -// } -// } -// } - impl CallLog { /// Sets the position of the log. #[inline] @@ -200,19 +192,15 @@ pub struct CallTrace { /// - [`CallKind::Call`] and alike: the callee, the address of the contract being called /// - [`CallKind::Create`] and alike: the address of the created contract pub address: Address, - /// Whether this is a call to a precompile. - /// - /// Note: This is optional because not all tracers make use of this. - // pub maybe_precompile: Option, + /// The execution result of the call. pub execution_result: VmExecutionResultAndLogs, /// Optional complementary decoded call data. pub decoded: DecodedCallTrace, - - // pub decoded_events: DecodedCallEvent, + /// The call trace pub call: Call, } -/// Additional decoded data enhancing the [CallLog]. +/// Decoded ZKSync event data enhancing the [CallLog]. #[derive(Clone, Debug, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct DecodedCallEvent { @@ -235,10 +223,8 @@ pub struct CallTraceNode { pub idx: usize, /// The call trace pub trace: CallTrace, - /// Event logs pub logs: Vec, - /// Ordering of child calls and logs pub ordering: Vec, } diff --git a/crates/core/src/trace/writer.rs b/crates/core/src/trace/writer.rs index c8bb71d8..0d55d418 100644 --- a/crates/core/src/trace/writer.rs +++ b/crates/core/src/trace/writer.rs @@ -1,5 +1,14 @@ //! Helper methods to display transaction data in more human readable way. +//////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Attribution: File adapted from the `revm-inspectors`crate for zksync usage // +// // +// Full credit goes to its authors. See the original implementation here: // +// https://github.com/paradigmxyz/revm-inspectors/blob/main/src/tracing/writer.rs // +// // +// Note: These methods are used under the terms of the original project's license. // +//////////////////////////////////////////////////////////////////////////////////////////////////////////// + use super::types::{CallLog, TraceMemberOrder}; use crate::trace::types::{ CallTrace, CallTraceArena, CallTraceNode, DecodedCallData, ExecutionResultDisplay, @@ -28,9 +37,7 @@ const LOG_STYLE: Style = AnsiColor::Cyan.on_default(); #[allow(missing_copy_implementations)] pub struct TraceWriterConfig { use_colors: bool, - color_cheatcodes: bool, - write_bytecodes: bool, - write_storage_changes: bool, + write_bytecodes: bool, } impl Default for TraceWriterConfig { @@ -44,9 +51,7 @@ impl TraceWriterConfig { pub fn new() -> Self { Self { use_colors: use_colors(ColorChoice::Auto), - color_cheatcodes: false, write_bytecodes: false, - write_storage_changes: false, } } @@ -61,17 +66,6 @@ impl TraceWriterConfig { self.use_colors } - /// Color calls to the cheatcode address differently. Default: false. - pub fn color_cheatcodes(mut self, yes: bool) -> Self { - self.color_cheatcodes = yes; - self - } - - /// Returns `true` if calls to the cheatcode address are colored differently. - pub fn get_color_cheatcodes(&self) -> bool { - self.color_cheatcodes - } - /// Write contract creation codes and deployed codes when writing "create" traces. /// Default: false. pub fn write_bytecodes(mut self, yes: bool) -> Self { @@ -83,17 +77,6 @@ impl TraceWriterConfig { pub fn get_write_bytecodes(&self) -> bool { self.write_bytecodes } - - // /// Sets whether to write storage changes. - // pub fn write_storage_changes(mut self, yes: bool) -> Self { - // self.write_storage_changes = yes; - // self - // } - - // /// Returns `true` if storage changes are written to the writer. - // pub fn get_write_storage_changes(&self) -> bool { - // self.write_storage_changes - // } } /// Formats [call traces](CallTraceArena) to an [`Write`] writer. @@ -129,13 +112,6 @@ impl TraceWriter { self } - /// Sets whether to color calls to the cheatcode address differently. - #[inline] - pub fn color_cheatcodes(mut self, yes: bool) -> Self { - self.config.color_cheatcodes = yes; - self - } - /// Sets the starting indentation level. #[inline] pub fn with_indentation_level(mut self, level: u16) -> Self { @@ -150,13 +126,6 @@ impl TraceWriter { self } - /// Sets whether to write storage changes. - #[inline] - pub fn with_storage_changes(mut self, yes: bool) -> Self { - self.config.write_storage_changes = yes; - self - } - /// Returns a reference to the inner writer. #[inline] pub const fn writer(&self) -> &W { @@ -252,10 +221,6 @@ impl TraceWriter { self.indentation_level += 1; self.write_items(nodes, idx)?; - // if self.config.write_storage_changes { - // self.write_storage_changes(node)?; - // } - // Write return data. self.write_edge()?; self.write_trace_footer(&node.trace)?; @@ -436,9 +401,7 @@ impl TraceWriter { if !self.config.use_colors { return Style::default(); } - let color = if self.config.color_cheatcodes { - AnsiColor::Blue - } else if trace.success { + let color = if trace.success { AnsiColor::Green } else { AnsiColor::Red @@ -469,13 +432,3 @@ fn use_colors(choice: ColorChoice) -> bool { ColorChoice::Never => false, } } - -// Formats the given U256 as a decimal number if it is short, otherwise as a hexadecimal -// byte-array. -// fn num_or_hex(x: U256) -> String { -// if x < U256::from(1e6 as u128) { -// x.to_string() -// } else { -// H256::from(x).to_string() -// } -// } From de7b47f6c83c33c6fad1b3dc734efe5ca711e875 Mon Sep 17 00:00:00 2001 From: Dustin Brickwood Date: Fri, 31 Jan 2025 10:02:51 -0600 Subject: [PATCH 04/11] chore: add genesis and rich accounts to address map --- crates/core/src/data/address_map.json | 31 ++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/crates/core/src/data/address_map.json b/crates/core/src/data/address_map.json index d5d6e5f6..1e7e8679 100644 --- a/crates/core/src/data/address_map.json +++ b/crates/core/src/data/address_map.json @@ -43,5 +43,34 @@ ["0xc706EC7dfA5D4Dc87f29f859094165E8290530f5", "GasBoundCaller", "System"], ["0x0000000000000000000000000000000000000100", "P256Verify", "System"], ["0x000000000000000000636F6e736F6c652e6c6f67", "console", "Popular"], - + ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "GenesisAccount", "Popular"], + ["0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "GenesisAccount", "Popular"], + ["0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", "GenesisAccount", "Popular"], + ["0x90F79bf6EB2c4f870365E785982E1f101E93b906", "GenesisAccount", "Popular"], + ["0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65", "GenesisAccount", "Popular"], + ["0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "GenesisAccount", "Popular"], + ["0x976EA74026E726554dB657fA54763abd0C3a0aa9", "GenesisAccount", "Popular"], + ["0x14dC79964da2C08b23698B3D3cc7Ca32193d9955", "GenesisAccount", "Popular"], + ["0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f", "GenesisAccount", "Popular"], + ["0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", "GenesisAccount", "Popular"], + ["0xBC989fDe9e54cAd2aB4392Af6dF60f04873A033A", "RichAccount", "Popular"], + ["0x55bE1B079b53962746B2e86d12f158a41DF294A6", "RichAccount", "Popular"], + ["0xCE9e6063674DC585F6F3c7eaBe82B9936143Ba6C", "RichAccount", "Popular"], + ["0xd986b0cB0D1Ad4CCCF0C4947554003fC0Be548E9", "RichAccount", "Popular"], + ["0x87d6ab9fE5Adef46228fB490810f0F5CB16D6d04", "RichAccount", "Popular"], + ["0x78cAD996530109838eb016619f5931a03250489A", "RichAccount", "Popular"], + ["0xc981b213603171963F81C687B9fC880d33CaeD16", "RichAccount", "Popular"], + ["0x42F3dc38Da81e984B92A95CBdAAA5fA2bd5cb1Ba", "RichAccount", "Popular"], + ["0x64F47EeD3dC749d13e49291d46Ea8378755fB6DF", "RichAccount", "Popular"], + ["0xe2b8Cb53a43a56d4d2AB6131C81Bd76B86D3AFe5", "RichAccount", "Popular"], + ["0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", "RichAccount", "Popular"], + ["0xa61464658AfeAf65CccaaFD3a512b69A83B77618", "RichAccount", "Popular"], + ["0x0D43eB5B8a47bA8900d84AA36656c92024e9772e", "RichAccount", "Popular"], + ["0xA13c10C0D5bd6f79041B9835c63f91de35A15883", "RichAccount", "Popular"], + ["0x8002cD98Cfb563492A6fB3E7C8243b7B9Ad4cc92", "RichAccount", "Popular"], + ["0x4F9133D1d3F50011A6859807C837bdCB31Aaab13", "RichAccount", "Popular"], + ["0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA", "RichAccount", "Popular"], + ["0xedB6F5B4aab3dD95C7806Af42881FF12BE7e9daa", "RichAccount", "Popular"], + ["0xe706e60ab5Dc512C36A4646D719b889F398cbBcB", "RichAccount", "Popular"], + ["0xE90E12261CCb0F3F7976Ae611A29e84a6A85f424", "RichAccount", "Popular"] ] From ba76aa7d97c79e71be4079efb55599be02022245 Mon Sep 17 00:00:00 2001 From: Dustin Brickwood Date: Fri, 31 Jan 2025 12:29:58 -0600 Subject: [PATCH 05/11] feat: add verbosity flags --- crates/cli/src/cli.rs | 13 ++- crates/config/src/config.rs | 15 ++++ crates/core/src/node/inner/in_memory_inner.rs | 53 +++++++------ crates/core/src/trace/mod.rs | 79 +++++++++++-------- crates/core/src/trace/types.rs | 53 +++++++++++-- 5 files changed, 146 insertions(+), 67 deletions(-) diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index 34e3333a..24193814 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -16,7 +16,7 @@ use anvil_zksync_types::{ LogLevel, ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails, TransactionOrder, }; use anyhow::Result; -use clap::{arg, command, Parser, Subcommand}; +use clap::{arg, command, ArgAction, Parser, Subcommand}; use flate2::read::GzDecoder; use futures::FutureExt; use rand::{rngs::StdRng, SeedableRng}; @@ -131,6 +131,15 @@ pub struct Cli { /// May decrease performance. pub resolve_hashes: Option, + /// Increments verbosity each time it is used. (-v, -vv, -vvv) + /// + /// Example usage: + /// - `-v` => verbosity level 1 + /// - `-vv` => level 2 + /// - `-vvv` => level 3 + #[arg(short = 'v', long = "verbosity", action = ArgAction::Count, help_heading = "Debugging Options")] + pub verbosity: u8, + // Gas Configuration #[arg(long, help_heading = "Gas Configuration")] /// Custom L1 gas price (in wei). @@ -460,7 +469,7 @@ impl Cli { .with_resolve_hashes(self.resolve_hashes) .with_gas_limit_scale(self.limit_scale_factor) .with_price_scale(self.price_scale_factor) - .with_resolve_hashes(self.resolve_hashes) + .with_verbosity_level(self.verbosity) .with_show_node_config(self.show_node_config) .with_silent(self.silent) .with_system_contracts(self.dev_system_contracts) diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index 8fbc89cf..2ef3d400 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -60,6 +60,8 @@ pub struct TestNodeConfig { pub show_vm_details: ShowVMDetails, /// Level of detail for gas usage logs pub show_gas_details: ShowGasDetails, + /// Numeric verbosity derived from repeated `-v` flags (e.g. -v = 1, -vv = 2, etc.). + pub verbosity: u8, /// Whether to resolve hash references pub resolve_hashes: bool, /// Don’t print anything on startup if true @@ -153,6 +155,7 @@ impl Default for TestNodeConfig { show_vm_details: Default::default(), show_gas_details: Default::default(), resolve_hashes: false, + verbosity: 0, silent: false, system_contracts_options: Default::default(), override_bytecodes_dir: None, @@ -662,6 +665,18 @@ impl TestNodeConfig { self } + /// Sets the numeric verbosity derived from repeated `-v` flags + #[must_use] + pub fn with_verbosity_level(mut self, verbosity: u8) -> Self { + self.verbosity = verbosity; + self + } + + /// Get the numeric verbosity derived from repeated `-v` flags + pub fn get_verbosity_level(&self) -> u8 { + self.verbosity + } + /// Enable or disable silent mode #[must_use] pub fn with_silent(mut self, silent: Option) -> Self { diff --git a/crates/core/src/node/inner/in_memory_inner.rs b/crates/core/src/node/inner/in_memory_inner.rs index 7963b61c..ead15eba 100644 --- a/crates/core/src/node/inner/in_memory_inner.rs +++ b/crates/core/src/node/inner/in_memory_inner.rs @@ -24,7 +24,7 @@ use crate::trace::decode::CallTraceDecoderBuilder; use crate::utils::create_debug_output; use crate::{delegate_vm, formatter, utils}; use anvil_zksync_config::constants::{ - LEGACY_RICH_WALLETS, NON_FORK_FIRST_BLOCK_TIMESTAMP, RICH_WALLETS, DEFAULT_DISK_CACHE_DIR + DEFAULT_DISK_CACHE_DIR, LEGACY_RICH_WALLETS, NON_FORK_FIRST_BLOCK_TIMESTAMP, RICH_WALLETS, }; use anvil_zksync_config::TestNodeConfig; use anvil_zksync_types::{ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails}; @@ -47,6 +47,8 @@ use zksync_multivm::interface::{ }; use zksync_multivm::vm_latest::Vm; +use crate::trace::signatures::SignaturesIdentifier; +use crate::trace::{build_call_trace_arena, decode_trace_arena, render_trace_arena_inner}; use zksync_multivm::tracers::CallTracer; use zksync_multivm::utils::{ adjust_pubdata_price_for_tx, derive_base_fee_and_gas_per_pubdata, derive_overhead, @@ -74,8 +76,6 @@ use zksync_types::{ ACCOUNT_CODE_STORAGE_ADDRESS, H160, H256, MAX_L2_TX_GAS_LIMIT, U256, U64, }; use zksync_web3_decl::error::Web3Error; -use crate::trace::signatures::SignaturesIdentifier; -use crate::trace::{build_call_trace_arena, decode_trace_arena, render_trace_arena_inner}; // TODO: Rename `InMemoryNodeInner` to something more sensible /// Helper struct for InMemoryNode. @@ -399,29 +399,32 @@ impl InMemoryNodeInner { formatter.print_vm_details(&tx_result); } - if let Some(call_traces) = call_traces { - let mut builder = CallTraceDecoderBuilder::new(); - builder = builder.with_signature_identifier( - SignaturesIdentifier::new( - Some(PathBuf::from(DEFAULT_DISK_CACHE_DIR)), //todo - self.config.offline, - ) - .unwrap(), - ); - let decoder = builder.build(); - let mut arena = build_call_trace_arena(&call_traces, tx_result.clone()); - tokio::task::block_in_place(|| { - // Run the async function using the current runtime - tokio::runtime::Handle::current().block_on(async { - decode_trace_arena(&mut arena, &decoder).await.unwrap(); + if self.config.get_verbosity_level() >= 2 { + if let Some(call_traces) = call_traces { + let mut builder = CallTraceDecoderBuilder::new(); + builder = builder.with_signature_identifier( + SignaturesIdentifier::new( + Some(PathBuf::from(DEFAULT_DISK_CACHE_DIR)), // todo: this should be available from config + self.config.offline, + ) + .unwrap(), + ); + let decoder = builder.build(); + let mut arena = build_call_trace_arena( + &call_traces, + tx_result.clone(), + self.config.get_verbosity_level(), + ); + tokio::task::block_in_place(|| { + // Run the async function using the current runtime + tokio::runtime::Handle::current().block_on(async { + decode_trace_arena(&mut arena, &decoder).await.unwrap(); + }); }); - }); - - // Render the arena once - let trace_output = render_trace_arena_inner(&arena, false); - - // Print the formatted traces using `{}` to interpret ANSI codes correctly - println!("Traces:\n{}", trace_output); + let trace_output = render_trace_arena_inner(&arena, false); + // Print the formatted traces using `{}` to interpret ANSI codes correctly + println!("Traces:\n{}", trace_output); + } } if let Some(call_traces) = call_traces { diff --git a/crates/core/src/trace/mod.rs b/crates/core/src/trace/mod.rs index d20041b7..42006a04 100644 --- a/crates/core/src/trace/mod.rs +++ b/crates/core/src/trace/mod.rs @@ -25,61 +25,50 @@ pub async fn decode_trace_arena( Ok(()) } -/// Render a collection of call traces to a string optionally including contract creation bytecodes -/// and in JSON format. +/// Render a collection of call traces to a string pub fn render_trace_arena_inner(arena: &CallTraceArena, with_bytecodes: bool) -> String { let mut w = TraceWriter::new(Vec::::new()).write_bytecodes(with_bytecodes); w.write_arena(&arena).expect("Failed to write traces"); String::from_utf8(w.into_writer()).expect("trace writer wrote invalid UTF-8") } +/// Builds a call trace arena from the call tracer calls and transaction result. pub fn build_call_trace_arena( calls: &[Call], tx_result: VmExecutionResultAndLogs, + verbosity: u8, ) -> CallTraceArena { - let mut arena = Vec::new(); - - // Add a virtual root node - let root_idx = arena.len(); - let root_node = CallTraceNode { - parent: None, - children: Vec::new(), - idx: root_idx, - trace: CallTrace { - depth: 0, - success: true, - caller: H160::zero(), - address: H160::zero(), - execution_result: tx_result.clone(), - decoded: DecodedCallTrace::default(), - call: Call::default(), - }, - logs: Vec::new(), - ordering: Vec::new(), - }; - arena.push(root_node); + let mut arena = CallTraceArena::default(); + let root_idx = 0; + + // Update the root node's execution_result with the actual transaction result + if let Some(root_node) = arena.arena.get_mut(root_idx) { + root_node.trace.execution_result = tx_result.clone(); + } + // Process calls and their subcalls for call in calls { - process_call_and_subcalls(call, root_idx, 0, &mut arena, &tx_result); + process_call_and_subcalls(call, root_idx, 0, &mut arena.arena, &tx_result, verbosity); } - CallTraceArena { arena } + arena } +// Process a call and its subcalls recursively, adding them to the arena. fn process_call_and_subcalls( call: &Call, parent_idx: usize, depth: usize, arena: &mut Vec, tx_result: &VmExecutionResultAndLogs, + verbosity: u8, ) { - // Only add the current call to the arena if it's not System or Precompile - // let should_add_call = - // !CallTraceArena::is_precompile(&call.to) && !CallTraceArena::is_system(&call.to); - let should_add_call = true; + // Determine if the current call should be shown at this verbosity level + let should_add_call = should_include_call(&call.to, verbosity); + let idx = if should_add_call { let idx = arena.len(); - // todo: FIX THIS UGLINESS + // collect event logs let logs_for_call: Vec = tx_result .logs .events @@ -126,11 +115,37 @@ fn process_call_and_subcalls( // Process subcalls recursively for subcall in &call.calls { - process_call_and_subcalls(subcall, idx, depth + 1, arena, tx_result); + process_call_and_subcalls(subcall, idx, depth + 1, arena, tx_result, verbosity); + } +} + +/// Returns whether we should include the call in the trace based on +/// its address type and the current verbosity level. +/// +/// Verbosity levels (for quick reference): +/// - 2: user only +/// - 3: user + system +/// - 4: user + system + precompile +/// - 5+: everything + L1–L2 logs (future-proof) +fn should_include_call(address: &H160, verbosity: u8) -> bool { + let is_system = CallTraceArena::is_system(address); + let is_precompile = CallTraceArena::is_precompile(address); + + match verbosity { + // -v or less => 0 or 1 => show nothing + 0 | 1 => false, + // -vv => 2 => user calls only + 2 => !(is_system || is_precompile), + // -vvv => 3 => user + system + 3 => !is_precompile, + // -vvvv => 4 => user + system + precompile + 4 => true, + // -vvvvv => 5 => everything + future logs (e.g. L1-L2 logs, perhaps storage logs?) + _ => true, } } -/// Converts a single `Call` to a `CallTrace`. +// Converts a single `Call` to a `CallTrace`. fn convert_call_to_call_trace( call: &Call, depth: usize, diff --git a/crates/core/src/trace/types.rs b/crates/core/src/trace/types.rs index 5a9cfbde..9f834535 100644 --- a/crates/core/src/trace/types.rs +++ b/crates/core/src/trace/types.rs @@ -229,6 +229,27 @@ pub struct CallTraceNode { pub ordering: Vec, } +impl Default for CallTraceNode { + fn default() -> Self { + Self { + parent: None, + children: Vec::new(), + idx: 0, + trace: CallTrace { + depth: 0, + success: true, + caller: H160::zero(), + address: H160::zero(), + execution_result: VmExecutionResultAndLogs::mock(ExecutionResult::Success { output: vec![] }), + decoded: DecodedCallTrace::default(), + call: Call::default(), + }, + logs: Vec::new(), + ordering: Vec::new(), + } + } +} + /// Ordering enum for calls, logs and steps #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -249,12 +270,29 @@ pub struct CallTraceArena { pub(crate) arena: Vec, } -// impl Default for CallTraceArena { -// fn default() -> Self { -// // The first node is the root node -// Self { arena: vec![Default::default()] } -// } -// } +impl Default for CallTraceArena { + fn default() -> Self { + let root_node = CallTraceNode { + parent: None, + children: Vec::new(), + idx: 0, // Assuming root index is 0 + trace: CallTrace { + depth: 0, + success: true, + caller: H160::zero(), + address: H160::zero(), + execution_result: VmExecutionResultAndLogs::mock(ExecutionResult::Success { output: vec![] }), + decoded: DecodedCallTrace::default(), + call: Call::default(), + }, + logs: Vec::new(), + ordering: Vec::new(), + }; + + // Initialize CallTraceArena with the root node + Self { arena: vec![root_node] } + } +} impl CallTraceArena { /// Returns the nodes in the arena. @@ -278,8 +316,7 @@ impl CallTraceArena { #[inline] pub fn clear(&mut self) { self.arena.clear(); - // TODO: circle back to this - //self.arena.push(Default::default()); + self.arena.push(Default::default()); } /// Checks if the given address is a precompile based on `KNOWN_ADDRESSES`. From 7a57f3af0d61e61139c1a2b88029b0e197c5c57f Mon Sep 17 00:00:00 2001 From: Dustin Brickwood Date: Fri, 31 Jan 2025 12:35:35 -0600 Subject: [PATCH 06/11] chore: document --- crates/cli/src/cli.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/cli/src/cli.rs b/crates/cli/src/cli.rs index 24193814..a3048e4d 100644 --- a/crates/cli/src/cli.rs +++ b/crates/cli/src/cli.rs @@ -131,12 +131,13 @@ pub struct Cli { /// May decrease performance. pub resolve_hashes: Option, - /// Increments verbosity each time it is used. (-v, -vv, -vvv) + /// Increments verbosity each time it is used. (-vv, -vvv) /// /// Example usage: - /// - `-v` => verbosity level 1 - /// - `-vv` => level 2 - /// - `-vvv` => level 3 + /// - `-vv` => verbosity level 2 (includes user calls and event calls) + /// - `-vvv` => level 3 (includes system calls, system event calls) + /// - `-vvvv` => level 4 (includes system calls, system event calls, and precompiles) + /// - `-vvvvv` => level 5 (includes everything) #[arg(short = 'v', long = "verbosity", action = ArgAction::Count, help_heading = "Debugging Options")] pub verbosity: u8, From 4a849f9cc8904d30eb05d5c59e05d900b00f7329 Mon Sep 17 00:00:00 2001 From: Dustin Brickwood Date: Fri, 31 Jan 2025 14:34:10 -0600 Subject: [PATCH 07/11] chore: linting --- crates/core/src/lib.rs | 2 +- crates/core/src/node/inner/in_memory_inner.rs | 19 ++-- crates/core/src/node/mod.rs | 4 +- crates/core/src/trace/abi_utils.rs | 99 ++++++++++--------- crates/core/src/trace/decode/mod.rs | 21 +++- crates/core/src/trace/mod.rs | 10 +- crates/core/src/trace/signatures.rs | 21 +++- crates/core/src/trace/types.rs | 13 ++- crates/core/src/trace/writer.rs | 6 +- crates/core/src/utils.rs | 2 +- 10 files changed, 116 insertions(+), 81 deletions(-) diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 9313036c..2ec26bd6 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -50,8 +50,8 @@ pub mod node; pub mod observability; pub mod resolver; pub mod system_contracts; -pub mod utils; pub mod trace; +pub mod utils; mod cache; mod testing; diff --git a/crates/core/src/node/inner/in_memory_inner.rs b/crates/core/src/node/inner/in_memory_inner.rs index ead15eba..92328484 100644 --- a/crates/core/src/node/inner/in_memory_inner.rs +++ b/crates/core/src/node/inner/in_memory_inner.rs @@ -28,7 +28,7 @@ use anvil_zksync_config::constants::{ }; use anvil_zksync_config::TestNodeConfig; use anvil_zksync_types::{ShowCalls, ShowGasDetails, ShowStorageLogs, ShowVMDetails}; -use anyhow::Context; +use anyhow::{anyhow, Context, Result}; use colored::Colorize; use indexmap::IndexMap; use once_cell::sync::OnceCell; @@ -404,10 +404,10 @@ impl InMemoryNodeInner { let mut builder = CallTraceDecoderBuilder::new(); builder = builder.with_signature_identifier( SignaturesIdentifier::new( - Some(PathBuf::from(DEFAULT_DISK_CACHE_DIR)), // todo: this should be available from config + Some(PathBuf::from(DEFAULT_DISK_CACHE_DIR)), self.config.offline, ) - .unwrap(), + .map_err(|err| anyhow!("Failed to create SignaturesIdentifier: {:#}", err))?, ); let decoder = builder.build(); let mut arena = build_call_trace_arena( @@ -416,13 +416,16 @@ impl InMemoryNodeInner { self.config.get_verbosity_level(), ); tokio::task::block_in_place(|| { - // Run the async function using the current runtime tokio::runtime::Handle::current().block_on(async { - decode_trace_arena(&mut arena, &decoder).await.unwrap(); - }); - }); + decode_trace_arena(&mut arena, &decoder) + .await + .context("Failed to decode trace arena")?; + + Ok::<_, anyhow::Error>(()) + }) + })?; let trace_output = render_trace_arena_inner(&arena, false); - // Print the formatted traces using `{}` to interpret ANSI codes correctly + println!("Traces:\n{}", trace_output); } } diff --git a/crates/core/src/node/mod.rs b/crates/core/src/node/mod.rs index 4b8e690e..b6fb58af 100644 --- a/crates/core/src/node/mod.rs +++ b/crates/core/src/node/mod.rs @@ -1,10 +1,12 @@ //! anvil-zksync, that supports forking other networks. mod call_error_tracer; +pub mod console; mod debug; pub mod error; mod eth; mod fee_model; +pub mod hardhat; mod impersonate; mod in_memory; mod in_memory_ext; @@ -17,8 +19,6 @@ mod storage_logs; mod vm; mod zkos; mod zks; -pub mod hardhat; -pub mod console; pub use self::{ fee_model::TestNodeFeeInputProvider, impersonate::ImpersonationManager, keys::StorageKeyLayout, diff --git a/crates/core/src/trace/abi_utils.rs b/crates/core/src/trace/abi_utils.rs index 32039634..9ab8f2f7 100644 --- a/crates/core/src/trace/abi_utils.rs +++ b/crates/core/src/trace/abi_utils.rs @@ -1,4 +1,4 @@ -//! ABI related helper functions. +//! ABI related helper functions. ////////////////////////////////////////////////////////////////////////////////////// // Attribution: File adapted from the `foundry-common` crate // // // @@ -130,9 +130,13 @@ pub fn coerce_value(ty: &str, arg: &str) -> Result { #[cfg(test)] mod tests { + use crate::trace::decode::{get_indexed_event_for_vm, vm_event_to_log_data}; + use super::*; use alloy::dyn_abi::EventExt; - use alloy::primitives::{B256, U256, Address, FixedBytes}; + use alloy::primitives::{Address, B256, U256}; + use zksync_multivm::interface::VmEvent; + use zksync_types::H256; #[test] fn test_get_func() { @@ -155,48 +159,51 @@ mod tests { assert_eq!(func.outputs[0].ty, "bytes4"); } -// #[test] -// fn test_indexed_only_address() { -// let event = get_event("event Ev(address,uint256,address)").unwrap(); - -// let param0 = B256::random(); -// let param1 = vec![3; 32]; -// let param2 = B256::random(); -// let log = LogData::new_unchecked(vec![event.selector(), param0, param2], param1.into()); -// let event = get_indexed_event(event, &log); - -// assert_eq!(event.inputs.len(), 3); - -// // Only the address fields get indexed since total_params > num_indexed_params -// let parsed = event.decode_log(&log, false).unwrap(); - -// assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 2); -// assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0))); -// assert_eq!(parsed.body[0], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256)); -// assert_eq!(parsed.indexed[1], DynSolValue::Address(Address::from_word(param2))); -// } - -// #[test] -// fn test_indexed_all() { -// let event = get_event("event Ev(address,uint256,address)").unwrap(); - -// let param0 = B256::random(); -// let param1 = vec![3; 32]; -// let param2 = B256::random(); -// let log = LogData::new_unchecked( -// vec![event.selector(), param0, B256::from_slice(¶m1), param2], -// vec![].into(), -// ); -// let event = get_indexed_event(event, &log); - -// assert_eq!(event.inputs.len(), 3); - -// // All parameters get indexed since num_indexed_params == total_params -// assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 3); -// let parsed = event.decode_log(&log, false).unwrap(); - -// assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0))); -// assert_eq!(parsed.indexed[1], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256)); -// assert_eq!(parsed.indexed[2], DynSolValue::Address(Address::from_word(param2))); -// } + #[test] + fn test_indexed_only_address_vm() { + let event = get_event("event Ev(address,uint256,address)").unwrap(); + + let param0 = B256::new([0u8; 32]); + let param2 = B256::new([0u8; 32]); + let param1_data = vec![3u8; 32]; + + let vm_event = VmEvent { + indexed_topics: vec![ + H256::from_slice(&event.selector().0), + H256::from_slice(¶m0.0), + H256::from_slice(¶m2.0), + ], + value: param1_data.clone(), + ..Default::default() + }; + + // Convert the `Event` into its indexed form, matching the number of topics in `VmEvent`. + let updated_event = get_indexed_event_for_vm(event, &vm_event); + assert_eq!(updated_event.inputs.len(), 3); + + // Now convert the VmEvent into a `LogData` + let log_data = vm_event_to_log_data(&vm_event); + let decoded = updated_event.decode_log(&log_data, false).unwrap(); + + assert_eq!( + updated_event + .inputs + .iter() + .filter(|param| param.indexed) + .count(), + 2 + ); + assert_eq!( + decoded.indexed[0], + DynSolValue::Address(Address::from_word(param0)) + ); + assert_eq!( + decoded.body[0], + DynSolValue::Uint(U256::from_be_bytes([3u8; 32]), 256) + ); + assert_eq!( + decoded.indexed[1], + DynSolValue::Address(Address::from_word(param2)) + ); + } } diff --git a/crates/core/src/trace/decode/mod.rs b/crates/core/src/trace/decode/mod.rs index f6346f19..b27727b4 100644 --- a/crates/core/src/trace/decode/mod.rs +++ b/crates/core/src/trace/decode/mod.rs @@ -1,6 +1,13 @@ -use super::types::{ - CallTrace, CallTraceNode, DecodedCallData, DecodedCallEvent, DecodedCallTrace, -}; +//////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Attribution: File adapted from the `evm` crate for zksync usage // +// // +// Full credit goes to its authors. See the original implementation here: // +// https://github.com/foundry-rs/foundry/blob/master/crates/evm/traces/src/decoder/mod.rs. // +// // +// Note: These methods are used under the terms of the original project's license. // +//////////////////////////////////////////////////////////////////////////////////////////////////////////// + +use super::types::{CallTrace, CallTraceNode, DecodedCallData, DecodedCallEvent, DecodedCallTrace}; use crate::node::console::Console; use crate::node::hardhat::{HardhatConsole, HARDHAT_CONSOLE_SELECTOR_PATCHES}; use crate::trace::signatures::SingleSignaturesIdentifier; @@ -265,7 +272,7 @@ impl CallTraceDecoder { params: None, }; }; - + let mut events = Vec::new(); let b256_t0 = B256::from_slice(t0.as_bytes()); let key = (b256_t0, indexed_inputs_zksync(vm_event) - 1); @@ -315,7 +322,11 @@ impl CallTraceDecoder { let events_it = nodes .iter() - .flat_map(|node| node.logs.iter().filter_map(|log| log.raw_log.indexed_topics.first())) + .flat_map(|node| { + node.logs + .iter() + .filter_map(|log| log.raw_log.indexed_topics.first()) + }) .unique(); identifier.write().await.identify_events(events_it).await; diff --git a/crates/core/src/trace/mod.rs b/crates/core/src/trace/mod.rs index 42006a04..11ed754e 100644 --- a/crates/core/src/trace/mod.rs +++ b/crates/core/src/trace/mod.rs @@ -1,16 +1,16 @@ use crate::trace::decode::CallTraceDecoder; -use crate::trace::writer::TraceWriter; use crate::trace::types::{ CallTrace, CallTraceArena, CallTraceNode, DecodedCallTrace, TraceMemberOrder, KNOWN_ADDRESSES, }; +use crate::trace::writer::TraceWriter; use types::{CallLog, DecodedCallEvent}; use zksync_multivm::interface::{Call, VmExecutionResultAndLogs}; use zksync_types::H160; pub mod abi_utils; pub mod decode; -pub mod writer; pub mod signatures; pub mod types; +pub mod writer; /// Decode a collection of call traces. /// @@ -18,7 +18,7 @@ pub mod types; pub async fn decode_trace_arena( arena: &mut CallTraceArena, decoder: &CallTraceDecoder, -) -> Result<(), std::fmt::Error> { +) -> Result<(), anyhow::Error> { decoder.prefetch_signatures(arena.nodes()).await; decoder.populate_traces(arena.nodes_mut()).await; @@ -65,7 +65,7 @@ fn process_call_and_subcalls( ) { // Determine if the current call should be shown at this verbosity level let should_add_call = should_include_call(&call.to, verbosity); - + let idx = if should_add_call { let idx = arena.len(); // collect event logs @@ -130,7 +130,7 @@ fn process_call_and_subcalls( fn should_include_call(address: &H160, verbosity: u8) -> bool { let is_system = CallTraceArena::is_system(address); let is_precompile = CallTraceArena::is_precompile(address); - + match verbosity { // -v or less => 0 or 1 => show nothing 0 | 1 => false, diff --git a/crates/core/src/trace/signatures.rs b/crates/core/src/trace/signatures.rs index f1018829..a6e3e837 100644 --- a/crates/core/src/trace/signatures.rs +++ b/crates/core/src/trace/signatures.rs @@ -7,11 +7,11 @@ // Note: These methods are used under the terms of the original project's license. // //////////////////////////////////////////////////////////////////////////////////////////////////////////// -use alloy::json_abi::{Error, Event, Function}; -use alloy::primitives::hex; use crate::resolver::{SelectorType, SignEthClient}; use crate::trace::abi_utils::{get_error, get_event, get_func}; use crate::utils::{read_json_file, write_json_file}; +use alloy::json_abi::{Error, Event, Function}; +use alloy::primitives::hex; use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, HashMap}, @@ -218,7 +218,12 @@ mod tests { assert!(sigs.read().await.cached.events.is_empty()); assert!(sigs.read().await.cached.functions.is_empty()); - let func = sigs.write().await.identify_function(&[35, 184, 114, 221]).await.unwrap(); + let func = sigs + .write() + .await + .identify_function(&[35, 184, 114, 221]) + .await + .unwrap(); let event = sigs .write() .await @@ -229,8 +234,14 @@ mod tests { .await .unwrap(); - assert_eq!(func, get_func("transferFrom(address,address,uint256)").unwrap()); - assert_eq!(event, get_event("Transfer(address,address,uint128)").unwrap()); + assert_eq!( + func, + get_func("transferFrom(address,address,uint256)").unwrap() + ); + assert_eq!( + event, + get_event("Transfer(address,address,uint128)").unwrap() + ); // dropping saves the cache } diff --git a/crates/core/src/trace/types.rs b/crates/core/src/trace/types.rs index 9f834535..29449fe7 100644 --- a/crates/core/src/trace/types.rs +++ b/crates/core/src/trace/types.rs @@ -5,7 +5,6 @@ use zksync_multivm::interface::{Call, ExecutionResult, VmEvent, VmExecutionResul use zksync_types::web3::Bytes; use zksync_types::{Address, H160, H256}; - // Note: duplicated types from existing formatter.rs // will be consolidated pending feedback #[derive(Debug, Deserialize, Clone, PartialEq, Eq)] @@ -240,7 +239,9 @@ impl Default for CallTraceNode { success: true, caller: H160::zero(), address: H160::zero(), - execution_result: VmExecutionResultAndLogs::mock(ExecutionResult::Success { output: vec![] }), + execution_result: VmExecutionResultAndLogs::mock(ExecutionResult::Success { + output: vec![], + }), decoded: DecodedCallTrace::default(), call: Call::default(), }, @@ -281,7 +282,9 @@ impl Default for CallTraceArena { success: true, caller: H160::zero(), address: H160::zero(), - execution_result: VmExecutionResultAndLogs::mock(ExecutionResult::Success { output: vec![] }), + execution_result: VmExecutionResultAndLogs::mock(ExecutionResult::Success { + output: vec![], + }), decoded: DecodedCallTrace::default(), call: Call::default(), }, @@ -290,7 +293,9 @@ impl Default for CallTraceArena { }; // Initialize CallTraceArena with the root node - Self { arena: vec![root_node] } + Self { + arena: vec![root_node], + } } } diff --git a/crates/core/src/trace/writer.rs b/crates/core/src/trace/writer.rs index 0d55d418..4b85a2d1 100644 --- a/crates/core/src/trace/writer.rs +++ b/crates/core/src/trace/writer.rs @@ -19,9 +19,7 @@ use hex::encode; use std::io::{self, Write}; use std::str; use zksync_multivm::interface::CallType; -use zksync_types::{ - zk_evm_types::FarCallOpcode, -}; +use zksync_types::zk_evm_types::FarCallOpcode; const PIPE: &str = " │ "; const EDGE: &str = " └─ "; @@ -37,7 +35,7 @@ const LOG_STYLE: Style = AnsiColor::Cyan.on_default(); #[allow(missing_copy_implementations)] pub struct TraceWriterConfig { use_colors: bool, - write_bytecodes: bool, + write_bytecodes: bool, } impl Default for TraceWriterConfig { diff --git a/crates/core/src/utils.rs b/crates/core/src/utils.rs index 45130a73..dc9d228a 100644 --- a/crates/core/src/utils.rs +++ b/crates/core/src/utils.rs @@ -3,8 +3,8 @@ use alloy::primitives::{Sign, I256, U256 as AlloyU256}; use anyhow::Context; use chrono::{DateTime, Utc}; use colored::Colorize; -use serde::Serialize; use serde::de::DeserializeOwned; +use serde::Serialize; use std::future::Future; use std::sync::Arc; use std::{convert::TryInto, fmt}; From 64e541dc56101e2805e009508e7955b9f49b808c Mon Sep 17 00:00:00 2001 From: Dustin Brickwood Date: Fri, 31 Jan 2025 14:35:11 -0600 Subject: [PATCH 08/11] chore: linting --- crates/core/src/node/inner/in_memory_inner.rs | 2 +- crates/core/src/trace/decode/mod.rs | 20 ++++++++----------- crates/core/src/trace/mod.rs | 2 +- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/crates/core/src/node/inner/in_memory_inner.rs b/crates/core/src/node/inner/in_memory_inner.rs index 92328484..7a475691 100644 --- a/crates/core/src/node/inner/in_memory_inner.rs +++ b/crates/core/src/node/inner/in_memory_inner.rs @@ -411,7 +411,7 @@ impl InMemoryNodeInner { ); let decoder = builder.build(); let mut arena = build_call_trace_arena( - &call_traces, + call_traces, tx_result.clone(), self.config.get_verbosity_level(), ); diff --git a/crates/core/src/trace/decode/mod.rs b/crates/core/src/trace/decode/mod.rs index b27727b4..4df23ec1 100644 --- a/crates/core/src/trace/decode/mod.rs +++ b/crates/core/src/trace/decode/mod.rs @@ -124,7 +124,7 @@ impl CallTraceDecoder { // Add known addresses (system contracts, precompiles) to the labels let labels: HashMap = KNOWN_ADDRESSES .iter() - .map(|(address, known_address)| (address.clone(), known_address.name.clone())) + .map(|(address, known_address)| (*address, known_address.name.clone())) .collect(); Self { @@ -210,7 +210,7 @@ impl CallTraceDecoder { let args = if cdata.is_empty() { Vec::new() } else { - vec![hex::encode(&cdata)] + vec![hex::encode(cdata)] }; DecodedCallTrace { label, @@ -223,11 +223,9 @@ impl CallTraceDecoder { /// Decodes a function's input into the given trace. fn decode_function_input(&self, trace: &CallTrace, func: &Function) -> DecodedCallData { let mut args = None; - if trace.call.input.len() >= SELECTOR_LEN { - if args.is_none() { - if let Ok(v) = func.abi_decode_input(&trace.call.input[SELECTOR_LEN..], false) { - args = Some(v.iter().map(|value| self.format_value(value)).collect()); - } + if trace.call.input.len() >= SELECTOR_LEN && args.is_none() { + if let Ok(v) = func.abi_decode_input(&trace.call.input[SELECTOR_LEN..], false) { + args = Some(v.iter().map(|value| self.format_value(value)).collect()); } } DecodedCallData { @@ -266,7 +264,7 @@ impl CallTraceDecoder { /// Decodes an event from zksync type VmEvent. pub async fn decode_event(&self, vm_event: &VmEvent) -> DecodedCallEvent { - let Some(&t0) = vm_event.indexed_topics.get(0) else { + let Some(&t0) = vm_event.indexed_topics.first() else { return DecodedCallEvent { name: None, params: None, @@ -287,7 +285,7 @@ impl CallTraceDecoder { &events } }; - let log_data = vm_event_to_log_data(&vm_event); + let log_data = vm_event_to_log_data(vm_event); for event in events { if let Ok(decoded) = event.decode_log(&log_data, false) { let params = reconstruct_params(event, &decoded); @@ -332,9 +330,7 @@ impl CallTraceDecoder { let funcs_it = nodes .iter() - .filter_map(|n| match n.trace.address { - _ => n.trace.call.input.get(..SELECTOR_LEN), - }) + .filter_map(|n| n.trace.call.input.get(..SELECTOR_LEN)) .filter(|v| !self.functions.contains_key(*v)); identifier.write().await.identify_functions(funcs_it).await; diff --git a/crates/core/src/trace/mod.rs b/crates/core/src/trace/mod.rs index 11ed754e..5ff36f11 100644 --- a/crates/core/src/trace/mod.rs +++ b/crates/core/src/trace/mod.rs @@ -28,7 +28,7 @@ pub async fn decode_trace_arena( /// Render a collection of call traces to a string pub fn render_trace_arena_inner(arena: &CallTraceArena, with_bytecodes: bool) -> String { let mut w = TraceWriter::new(Vec::::new()).write_bytecodes(with_bytecodes); - w.write_arena(&arena).expect("Failed to write traces"); + w.write_arena(arena).expect("Failed to write traces"); String::from_utf8(w.into_writer()).expect("trace writer wrote invalid UTF-8") } From 69baf89a2d34cc02305a5f6949649581d84dd283 Mon Sep 17 00:00:00 2001 From: Dustin Brickwood Date: Fri, 31 Jan 2025 15:50:40 -0600 Subject: [PATCH 09/11] chore: fix formatting --- crates/core/src/trace/decode/mod.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/core/src/trace/decode/mod.rs b/crates/core/src/trace/decode/mod.rs index 4df23ec1..86d58e74 100644 --- a/crates/core/src/trace/decode/mod.rs +++ b/crates/core/src/trace/decode/mod.rs @@ -342,15 +342,21 @@ impl CallTraceDecoder { } /// Pretty-prints a value. - fn format_value(&self, value: &DynSolValue) -> String { + fn format_value(&self, value: &DynSolValue) -> String { if let DynSolValue::Address(addr) = value { - // TODO: handle error - let zksync_address = Address::from(<[u8; 20]>::try_from(addr.0.as_slice()).unwrap()); - - if let Some(label) = self.labels.get(&zksync_address) { - return format!("{label}: [{addr}]"); + match <[u8; 20]>::try_from(addr.0.as_slice()) { + Ok(raw_bytes_20) => { + let zksync_address = Address::from(raw_bytes_20); + if let Some(label) = self.labels.get(&zksync_address) { + return format!("{label}: [{addr}]"); + } + } + Err(e) => { + return format!("Invalid address ({}): {:?}", e, addr); + } } } + format_token(value, false) } } From 782d7f55286cf4e838be2870c8b568de15b345bd Mon Sep 17 00:00:00 2001 From: Dustin Brickwood Date: Sat, 1 Feb 2025 20:07:08 -0600 Subject: [PATCH 10/11] chore: cargo fmt and clean up --- crates/core/src/trace/abi_utils.rs | 4 ++-- crates/core/src/trace/decode/mod.rs | 19 +++++++------------ crates/core/src/trace/mod.rs | 4 +--- crates/core/src/trace/signatures.rs | 12 ++++++------ crates/core/src/trace/types.rs | 5 +++-- crates/core/src/trace/writer.rs | 2 +- 6 files changed, 20 insertions(+), 26 deletions(-) diff --git a/crates/core/src/trace/abi_utils.rs b/crates/core/src/trace/abi_utils.rs index 9ab8f2f7..f5557a94 100644 --- a/crates/core/src/trace/abi_utils.rs +++ b/crates/core/src/trace/abi_utils.rs @@ -130,7 +130,7 @@ pub fn coerce_value(ty: &str, arg: &str) -> Result { #[cfg(test)] mod tests { - use crate::trace::decode::{get_indexed_event_for_vm, vm_event_to_log_data}; + use crate::trace::decode::{get_indexed_event_from_vm_event, vm_event_to_log_data}; use super::*; use alloy::dyn_abi::EventExt; @@ -178,7 +178,7 @@ mod tests { }; // Convert the `Event` into its indexed form, matching the number of topics in `VmEvent`. - let updated_event = get_indexed_event_for_vm(event, &vm_event); + let updated_event = get_indexed_event_from_vm_event(event, &vm_event); assert_eq!(updated_event.inputs.len(), 3); // Now convert the VmEvent into a `LogData` diff --git a/crates/core/src/trace/decode/mod.rs b/crates/core/src/trace/decode/mod.rs index 86d58e74..a6e4e0aa 100644 --- a/crates/core/src/trace/decode/mod.rs +++ b/crates/core/src/trace/decode/mod.rs @@ -1,5 +1,5 @@ //////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Attribution: File adapted from the `evm` crate for zksync usage // +// Attribution: File adapted from the Foundry `evm` crate for ZKsync usage // // // // Full credit goes to its authors. See the original implementation here: // // https://github.com/foundry-rs/foundry/blob/master/crates/evm/traces/src/decoder/mod.rs. // @@ -67,11 +67,8 @@ impl CallTraceDecoderBuilder { /// The call trace decoder. /// -/// The decoder collects address labels and ABIs which it +/// The decoder collects address labels which it /// then uses to decode the call trace. -/// -/// Note that a call trace decoder is required for each new set of traces, since addresses in -/// different sets might overlap. #[derive(Clone, Debug, Default)] pub struct CallTraceDecoder { /// Addresses identified to be a specific contract. @@ -262,7 +259,7 @@ impl CallTraceDecoder { None } - /// Decodes an event from zksync type VmEvent. + /// Decodes an event from ZKsync type VmEvent. pub async fn decode_event(&self, vm_event: &VmEvent) -> DecodedCallEvent { let Some(&t0) = vm_event.indexed_topics.first() else { return DecodedCallEvent { @@ -279,7 +276,7 @@ impl CallTraceDecoder { None => { if let Some(identifier) = &self.signature_identifier { if let Some(event) = identifier.write().await.identify_event(&t0[..]).await { - events.push(get_indexed_event_for_vm(event, vm_event)); + events.push(get_indexed_event_from_vm_event(event, vm_event)); } } &events @@ -342,7 +339,7 @@ impl CallTraceDecoder { } /// Pretty-prints a value. - fn format_value(&self, value: &DynSolValue) -> String { + fn format_value(&self, value: &DynSolValue) -> String { if let DynSolValue::Address(addr) = value { match <[u8; 20]>::try_from(addr.0.as_slice()) { Ok(raw_bytes_20) => { @@ -356,7 +353,7 @@ impl CallTraceDecoder { } } } - + format_token(value, false) } } @@ -382,11 +379,9 @@ fn reconstruct_params(event: &Event, decoded: &DecodedEvent) -> Vec inputs } - fn indexed_inputs_zksync(event: &VmEvent) -> usize { event.indexed_topics.len() } - fn indexed_inputs(event: &Event) -> usize { event.inputs.iter().filter(|param| param.indexed).count() } @@ -394,7 +389,7 @@ fn indexed_inputs(event: &Event) -> usize { /// Given an `Event` without indexed parameters and a `VmEvent`, it tries to /// return the `Event` with the proper indexed parameters. Otherwise, /// it returns the original `Event`. -pub fn get_indexed_event_for_vm(mut event: Event, vm_event: &VmEvent) -> Event { +pub fn get_indexed_event_from_vm_event(mut event: Event, vm_event: &VmEvent) -> Event { if !event.anonymous && vm_event.indexed_topics.len() > 1 { let indexed_params = vm_event.indexed_topics.len() - 1; let num_inputs = event.inputs.len(); diff --git a/crates/core/src/trace/mod.rs b/crates/core/src/trace/mod.rs index 5ff36f11..dfb72991 100644 --- a/crates/core/src/trace/mod.rs +++ b/crates/core/src/trace/mod.rs @@ -14,7 +14,7 @@ pub mod writer; /// Decode a collection of call traces. /// -/// The traces will be decoded using the given decoder, if possible. +/// The traces will be decoded if possible using openchain. pub async fn decode_trace_arena( arena: &mut CallTraceArena, decoder: &CallTraceDecoder, @@ -40,12 +40,10 @@ pub fn build_call_trace_arena( ) -> CallTraceArena { let mut arena = CallTraceArena::default(); let root_idx = 0; - // Update the root node's execution_result with the actual transaction result if let Some(root_node) = arena.arena.get_mut(root_idx) { root_node.trace.execution_result = tx_result.clone(); } - // Process calls and their subcalls for call in calls { process_call_and_subcalls(call, root_idx, 0, &mut arena.arena, &tx_result, verbosity); diff --git a/crates/core/src/trace/signatures.rs b/crates/core/src/trace/signatures.rs index a6e3e837..c98b57c4 100644 --- a/crates/core/src/trace/signatures.rs +++ b/crates/core/src/trace/signatures.rs @@ -35,12 +35,12 @@ impl CachedSignatures { if path.is_file() { read_json_file(&path) .map_err( - |err| tracing::warn!(target: "evm::traces", ?path, ?err, "failed to read cache file"), + |err| tracing::warn!(target: "trace::signatures", ?path, ?err, "failed to read cache file"), ) .unwrap_or_default() } else { if let Err(err) = std::fs::create_dir_all(cache_path) { - tracing::warn!(target: "evm::traces", "could not create signatures cache dir: {:?}", err); + tracing::warn!(target: "trace::signatures", "could not create signatures cache dir: {:?}", err); } Self::default() } @@ -73,7 +73,7 @@ impl SignaturesIdentifier { let identifier = if let Some(cache_path) = cache_path { let path = cache_path.join("signatures"); - tracing::trace!(target: "evm::traces", ?path, "reading signature cache"); + tracing::trace!(target: "trace::signatures", ?path, "reading signature cache"); let cached = CachedSignatures::load(cache_path); Self { cached, @@ -97,13 +97,13 @@ impl SignaturesIdentifier { 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) { - tracing::warn!(target: "evm::traces", ?parent, ?err, "failed to create cache"); + tracing::warn!(target: "trace::signatures", ?parent, ?err, "failed to create cache"); } } if let Err(err) = write_json_file(cached_path, &self.cached) { - tracing::warn!(target: "evm::traces", ?cached_path, ?err, "failed to flush signature cache"); + tracing::warn!(target: "trace::signatures", ?cached_path, ?err, "failed to flush signature cache"); } else { - tracing::trace!(target: "evm::traces", ?cached_path, "flushed signature cache") + tracing::trace!(target: "trace::signatures", ?cached_path, "flushed signature cache") } } } diff --git a/crates/core/src/trace/types.rs b/crates/core/src/trace/types.rs index 29449fe7..9baa665d 100644 --- a/crates/core/src/trace/types.rs +++ b/crates/core/src/trace/types.rs @@ -5,7 +5,7 @@ use zksync_multivm::interface::{Call, ExecutionResult, VmEvent, VmExecutionResul use zksync_types::web3::Bytes; use zksync_types::{Address, H160, H256}; -// Note: duplicated types from existing formatter.rs +// TODO: duplicated types from existing formatter.rs // will be consolidated pending feedback #[derive(Debug, Deserialize, Clone, PartialEq, Eq)] pub enum ContractType { @@ -276,7 +276,7 @@ impl Default for CallTraceArena { let root_node = CallTraceNode { parent: None, children: Vec::new(), - idx: 0, // Assuming root index is 0 + idx: 0, trace: CallTrace { depth: 0, success: true, @@ -355,6 +355,7 @@ impl CallTraceArena { } } +/// A trait for displaying the execution result. pub trait ExecutionResultDisplay { fn display(&self) -> String; } diff --git a/crates/core/src/trace/writer.rs b/crates/core/src/trace/writer.rs index 4b85a2d1..3014c847 100644 --- a/crates/core/src/trace/writer.rs +++ b/crates/core/src/trace/writer.rs @@ -282,7 +282,7 @@ impl TraceWriter { FarCallOpcode::Delegate => Some(" [delegatecall]"), FarCallOpcode::Mimic => Some(" [mimiccall]"), }, - CallType::NearCall => Some("[nearcall]"), // TODO check if this is correct + CallType::NearCall => Some("[nearcall]"), CallType::Create => unreachable!(), // Create calls are handled separately. }; From 4d301cb7da344662653ab0611f519a95fcb30aa6 Mon Sep 17 00:00:00 2001 From: Dustin Brickwood Date: Fri, 14 Feb 2025 14:40:31 -0600 Subject: [PATCH 11/11] chore: address verbosity usage --- crates/cli/src/main.rs | 7 + crates/core/src/node/inner/in_memory_inner.rs | 60 ++-- crates/core/src/trace/mod.rs | 258 +++++++++++------- crates/core/src/trace/types.rs | 22 ++ e2e-tests-rust/Cargo.lock | 19 ++ 5 files changed, 233 insertions(+), 133 deletions(-) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index c767d04e..be0a84f7 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -2,6 +2,7 @@ use crate::bytecode_override::override_bytecodes; use crate::cli::{Cli, Command, ForkUrl, PeriodicStateDumper}; use crate::utils::update_with_fork_details; use anvil_zksync_api_server::NodeServerBuilder; +use anvil_zksync_common::shell::get_shell; use anvil_zksync_common::{sh_eprintln, sh_err, sh_warn}; use anvil_zksync_config::constants::{ DEFAULT_ESTIMATE_GAS_PRICE_SCALE_FACTOR, DEFAULT_ESTIMATE_GAS_SCALE_FACTOR, @@ -47,6 +48,12 @@ async fn main() -> anyhow::Result<()> { let mut config = opt.into_test_node_config().map_err(|e| anyhow!(e))?; + // Set verbosity level for the shell + { + let mut shell = get_shell(); + shell.verbosity = config.verbosity; + } + let log_level_filter = LevelFilter::from(config.log_level); let log_file = File::create(&config.log_file_path)?; diff --git a/crates/core/src/node/inner/in_memory_inner.rs b/crates/core/src/node/inner/in_memory_inner.rs index 098a9d13..f12a8d62 100644 --- a/crates/core/src/node/inner/in_memory_inner.rs +++ b/crates/core/src/node/inner/in_memory_inner.rs @@ -23,6 +23,7 @@ use crate::system_contracts::SystemContracts; use crate::trace::decode::CallTraceDecoderBuilder; use crate::utils::create_debug_output; use crate::{delegate_vm, formatter, utils}; +use anvil_zksync_common::shell::get_shell; use anvil_zksync_common::{sh_eprintln, sh_err, sh_println}; use anvil_zksync_config::constants::{ DEFAULT_DISK_CACHE_DIR, LEGACY_RICH_WALLETS, NON_FORK_FIRST_BLOCK_TIMESTAMP, RICH_WALLETS, @@ -48,7 +49,9 @@ use zksync_multivm::interface::{ use zksync_multivm::vm_latest::Vm; use crate::trace::signatures::SignaturesIdentifier; -use crate::trace::{build_call_trace_arena, decode_trace_arena, render_trace_arena_inner}; +use crate::trace::{ + build_call_trace_arena, decode_trace_arena, filter_call_trace_arena, render_trace_arena_inner, +}; use zksync_multivm::tracers::CallTracer; use zksync_multivm::utils::{ adjust_pubdata_price_for_tx, derive_base_fee_and_gas_per_pubdata, derive_overhead, @@ -397,35 +400,32 @@ impl InMemoryNodeInner { formatter.print_vm_details(&tx_result); } - if self.config.get_verbosity_level() >= 2 { - if let Some(call_traces) = call_traces { - let mut builder = CallTraceDecoderBuilder::new(); - builder = builder.with_signature_identifier( - SignaturesIdentifier::new( - Some(PathBuf::from(DEFAULT_DISK_CACHE_DIR)), - self.config.offline, - ) - .map_err(|err| anyhow!("Failed to create SignaturesIdentifier: {:#}", err))?, - ); - let decoder = builder.build(); - let mut arena = build_call_trace_arena( - call_traces, - tx_result.clone(), - self.config.get_verbosity_level(), - ); - tokio::task::block_in_place(|| { - tokio::runtime::Handle::current().block_on(async { - decode_trace_arena(&mut arena, &decoder) - .await - .context("Failed to decode trace arena")?; - - Ok::<_, anyhow::Error>(()) - }) - })?; - let trace_output = render_trace_arena_inner(&arena, false); - - println!("Traces:\n{}", trace_output); - } + if let Some(call_traces) = call_traces { + let mut builder = CallTraceDecoderBuilder::new(); + builder = builder.with_signature_identifier( + SignaturesIdentifier::new( + Some(PathBuf::from(DEFAULT_DISK_CACHE_DIR)), + self.config.offline, + ) + .map_err(|err| anyhow!("Failed to create SignaturesIdentifier: {:#}", err))?, + ); + let decoder = builder.build(); + let mut arena = build_call_trace_arena(call_traces, tx_result.clone()); + tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + decode_trace_arena(&mut arena, &decoder) + .await + .context("Failed to decode trace arena")?; + + Ok::<_, anyhow::Error>(()) + }) + })?; + + let verbosity = get_shell().verbosity; + let filtered_arena = filter_call_trace_arena(&arena, verbosity); + let trace_output = render_trace_arena_inner(&filtered_arena, false); + + sh_println!("Traces:\n{}", trace_output); } if let Some(call_traces) = call_traces { diff --git a/crates/core/src/trace/mod.rs b/crates/core/src/trace/mod.rs index dfb72991..fe71b9a0 100644 --- a/crates/core/src/trace/mod.rs +++ b/crates/core/src/trace/mod.rs @@ -12,108 +12,173 @@ pub mod signatures; pub mod types; pub mod writer; -/// Decode a collection of call traces. -/// -/// The traces will be decoded if possible using openchain. -pub async fn decode_trace_arena( - arena: &mut CallTraceArena, - decoder: &CallTraceDecoder, -) -> Result<(), anyhow::Error> { - decoder.prefetch_signatures(arena.nodes()).await; - decoder.populate_traces(arena.nodes_mut()).await; - - Ok(()) -} - -/// Render a collection of call traces to a string -pub fn render_trace_arena_inner(arena: &CallTraceArena, with_bytecodes: bool) -> String { - let mut w = TraceWriter::new(Vec::::new()).write_bytecodes(with_bytecodes); - w.write_arena(arena).expect("Failed to write traces"); - String::from_utf8(w.into_writer()).expect("trace writer wrote invalid UTF-8") +/// Converts a single call into a CallTrace. +#[inline] +fn convert_call_to_call_trace( + call: &Call, + depth: usize, + tx_result: VmExecutionResultAndLogs, +) -> CallTrace { + let label = KNOWN_ADDRESSES + .get(&call.to) + .map(|known| known.name.clone()); + CallTrace { + depth, + success: !tx_result.result.is_failed(), + caller: call.from, + address: call.to, + execution_result: tx_result, + decoded: DecodedCallTrace { + label, + ..Default::default() + }, + call: call.clone(), + } } -/// Builds a call trace arena from the call tracer calls and transaction result. +/// Builds a call trace arena from the calls and transaction result. pub fn build_call_trace_arena( calls: &[Call], tx_result: VmExecutionResultAndLogs, - verbosity: u8, ) -> CallTraceArena { let mut arena = CallTraceArena::default(); - let root_idx = 0; - // Update the root node's execution_result with the actual transaction result - if let Some(root_node) = arena.arena.get_mut(root_idx) { + + // Update the root node's execution result. + if let Some(root_node) = arena.arena.get_mut(0) { root_node.trace.execution_result = tx_result.clone(); } - // Process calls and their subcalls + for call in calls { - process_call_and_subcalls(call, root_idx, 0, &mut arena.arena, &tx_result, verbosity); + process_call_and_subcalls(call, 0, 0, &mut arena, &tx_result); } - arena } -// Process a call and its subcalls recursively, adding them to the arena. +/// Recursively process a call and its subcalls, adding them to the arena. fn process_call_and_subcalls( call: &Call, parent_idx: usize, depth: usize, - arena: &mut Vec, + arena: &mut CallTraceArena, tx_result: &VmExecutionResultAndLogs, - verbosity: u8, ) { - // Determine if the current call should be shown at this verbosity level - let should_add_call = should_include_call(&call.to, verbosity); - - let idx = if should_add_call { - let idx = arena.len(); - // collect event logs - let logs_for_call: Vec = tx_result - .logs - .events - .iter() - .filter(|vm_event| vm_event.address == call.to) - .cloned() - .enumerate() - .map(|(i, vm_event)| CallLog { - raw_log: vm_event, - decoded: DecodedCallEvent::default(), - position: i as u64, - }) - .collect(); - - let call_trace = convert_call_to_call_trace(call, depth, tx_result.clone()); - - let mut node = CallTraceNode { - parent: Some(parent_idx), - children: Vec::new(), - idx, - trace: call_trace, - logs: logs_for_call, - ordering: Vec::new(), - }; - - let logs_count = node.logs.len(); - for i in 0..logs_count { - node.ordering.push(TraceMemberOrder::Log(i)); - } - arena.push(node); - - // Add as a child of the parent node - arena[parent_idx].children.push(idx); - let child_local_idx = arena[parent_idx].children.len() - 1; - arena[parent_idx] - .ordering - .push(TraceMemberOrder::Call(child_local_idx)); - - idx - } else { - // If the current call is skipped, maintain the same parent index for its subcalls - parent_idx + // Collect logs for the current call. + let logs_for_call: Vec = tx_result + .logs + .events + .iter() + .enumerate() + .filter_map(|(i, vm_event)| { + if vm_event.address == call.to { + Some(CallLog { + raw_log: vm_event.clone(), + decoded: DecodedCallEvent::default(), + position: i as u64, + }) + } else { + None + } + }) + .collect(); + + let call_trace = convert_call_to_call_trace(call, depth, tx_result.clone()); + + let node = CallTraceNode { + parent: None, + children: Vec::new(), + idx: 0, + trace: call_trace, + logs: logs_for_call, + ordering: Vec::new(), }; - // Process subcalls recursively + let new_parent_idx = arena.add_node(Some(parent_idx), node); + + // Process subcalls under the new parent. for subcall in &call.calls { - process_call_and_subcalls(subcall, idx, depth + 1, arena, tx_result, verbosity); + process_call_and_subcalls(subcall, new_parent_idx, depth + 1, arena, tx_result); + } +} + +/// Render a collection of call traces to a string +pub fn render_trace_arena_inner(arena: &CallTraceArena, with_bytecodes: bool) -> String { + let mut w = TraceWriter::new(Vec::::new()).write_bytecodes(with_bytecodes); + w.write_arena(arena).expect("Failed to write traces"); + String::from_utf8(w.into_writer()).expect("trace writer wrote invalid UTF-8") +} + +/// Decode a collection of call traces. +/// +/// The traces will be decoded if possible using openchain. +pub async fn decode_trace_arena( + arena: &mut CallTraceArena, + decoder: &CallTraceDecoder, +) -> Result<(), anyhow::Error> { + decoder.prefetch_signatures(arena.nodes()).await; + decoder.populate_traces(arena.nodes_mut()).await; + + Ok(()) +} + +/// Filter a call trace arena based on verbosity level. +pub fn filter_call_trace_arena(arena: &CallTraceArena, verbosity: u8) -> CallTraceArena { + let mut filtered = CallTraceArena::default(); + + if arena.arena.is_empty() { + return filtered; + } + + let root_idx = 0; + let mut root_copy = arena.arena[root_idx].clone(); + root_copy.parent = None; + root_copy.idx = 0; + root_copy.children.clear(); + root_copy.ordering.clear(); + filtered.arena.push(root_copy); + + filter_node_recursively( + &arena.arena[root_idx], + arena, + &mut filtered, + Some(0), + verbosity, + ); + + // Rebuild ordering + for node in &mut filtered.arena { + rebuild_ordering(node); + } + + filtered +} + +fn filter_node_recursively( + orig_node: &CallTraceNode, + orig_arena: &CallTraceArena, + filtered_arena: &mut CallTraceArena, + parent_idx: Option, + verbosity: u8, +) { + for &child_idx in &orig_node.children { + let child = &orig_arena.arena[child_idx]; + if should_include_call(&child.trace.address, verbosity) { + let new_idx = filtered_arena.arena.len(); + let mut child_copy = child.clone(); + child_copy.idx = new_idx; + child_copy.parent = parent_idx; + child_copy.children.clear(); + child_copy.ordering.clear(); + + filtered_arena.arena.push(child_copy); + + if let Some(p_idx) = parent_idx { + filtered_arena.arena[p_idx].children.push(new_idx); + } + + filter_node_recursively(child, orig_arena, filtered_arena, Some(new_idx), verbosity); + } else { + filter_node_recursively(child, orig_arena, filtered_arena, parent_idx, verbosity); + } } } @@ -125,6 +190,7 @@ fn process_call_and_subcalls( /// - 3: user + system /// - 4: user + system + precompile /// - 5+: everything + L1–L2 logs (future-proof) +#[inline] fn should_include_call(address: &H160, verbosity: u8) -> bool { let is_system = CallTraceArena::is_system(address); let is_precompile = CallTraceArena::is_precompile(address); @@ -132,37 +198,23 @@ fn should_include_call(address: &H160, verbosity: u8) -> bool { match verbosity { // -v or less => 0 or 1 => show nothing 0 | 1 => false, - // -vv => 2 => user calls only + // -vv => 2 => user calls only (incl. L1–L2 user logs) 2 => !(is_system || is_precompile), - // -vvv => 3 => user + system + // -vvv => 3 => user + system (incl. L1–L2 system logs) 3 => !is_precompile, // -vvvv => 4 => user + system + precompile 4 => true, - // -vvvvv => 5 => everything + future logs (e.g. L1-L2 logs, perhaps storage logs?) + // -vvvvv => 5 => everything + future logs _ => true, } } -// Converts a single `Call` to a `CallTrace`. -fn convert_call_to_call_trace( - call: &Call, - depth: usize, - tx_result: VmExecutionResultAndLogs, -) -> CallTrace { - let label = KNOWN_ADDRESSES - .get(&call.to) - .map(|known| known.name.clone()); - - CallTrace { - depth, - success: !tx_result.result.is_failed(), - caller: call.from, - address: call.to, - execution_result: tx_result, - decoded: DecodedCallTrace { - label, - ..Default::default() - }, - call: call.clone(), +fn rebuild_ordering(node: &mut CallTraceNode) { + node.ordering.clear(); + for i in 0..node.logs.len() { + node.ordering.push(TraceMemberOrder::Log(i)); + } + for i in 0..node.children.len() { + node.ordering.push(TraceMemberOrder::Call(i)); } } diff --git a/crates/core/src/trace/types.rs b/crates/core/src/trace/types.rs index 9baa665d..633ec95a 100644 --- a/crates/core/src/trace/types.rs +++ b/crates/core/src/trace/types.rs @@ -300,6 +300,28 @@ impl Default for CallTraceArena { } impl CallTraceArena { + /// Adds a node to the arena, updating parent–child relationships and ordering. + pub fn add_node(&mut self, parent: Option, mut node: CallTraceNode) -> usize { + let idx = self.arena.len(); + node.idx = idx; + node.parent = parent; + + // Build ordering for the node based on its logs. + node.ordering = (0..node.logs.len()).map(TraceMemberOrder::Log).collect(); + + self.arena.push(node); + + // Update parent's children and ordering if a parent exists. + if let Some(parent_idx) = parent { + self.arena[parent_idx].children.push(idx); + let child_local_idx = self.arena[parent_idx].children.len() - 1; + self.arena[parent_idx] + .ordering + .push(TraceMemberOrder::Call(child_local_idx)); + } + idx + } + /// Returns the nodes in the arena. pub fn nodes(&self) -> &[CallTraceNode] { &self.arena diff --git a/e2e-tests-rust/Cargo.lock b/e2e-tests-rust/Cargo.lock index 6a9814b2..bd124817 100644 --- a/e2e-tests-rust/Cargo.lock +++ b/e2e-tests-rust/Cargo.lock @@ -898,13 +898,16 @@ name = "anvil_zksync_core" version = "0.3.0" dependencies = [ "alloy", + "anstyle", "anvil_zksync_common", "anvil_zksync_config", "anvil_zksync_types", "anyhow", "async-trait", "chrono", + "colorchoice", "colored", + "derive_more", "eyre", "flate2", "futures 0.3.31", @@ -1974,6 +1977,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -2245,6 +2257,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ + "convert_case", "proc-macro2", "quote", "syn 2.0.89", @@ -6742,6 +6755,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.1.14"