From 419914fdca28edc03bc4799cb206e08f9247c929 Mon Sep 17 00:00:00 2001 From: Oleksandr Zarudnyi Date: Thu, 4 Apr 2024 18:17:05 +0800 Subject: [PATCH] Update to zkVM v1.5.0 --- .gitignore | 17 +- .gitmodules | 4 +- Cargo.lock | 119 +++- benchmark_analyzer/Cargo.toml | 2 +- .../src/benchmark/group/element.rs | 13 +- benchmark_analyzer/src/benchmark/group/mod.rs | 34 +- .../src/benchmark/group/results.rs | 112 ++++ benchmark_analyzer/src/benchmark/mod.rs | 90 ++- compiler_tester/Cargo.toml | 19 +- .../src/compiler_tester/arguments.rs | 8 +- compiler_tester/src/compiler_tester/main.rs | 98 ++- compiler_tester/src/compilers/cache/mod.rs | 26 +- compiler_tester/src/compilers/cache/value.rs | 14 +- .../src/compilers/downloader/solc_list.rs | 2 +- .../src/compilers/{eravm.rs => eravm/mod.rs} | 43 +- .../{mode/eravm.rs => eravm/mode.rs} | 0 .../src/compilers/{llvm.rs => llvm/mod.rs} | 76 ++- .../compilers/{mode/llvm.rs => llvm/mode.rs} | 4 +- compiler_tester/src/compilers/mod.rs | 13 +- .../src/{ => compilers/mode}/llvm_options.rs | 0 compiler_tester/src/compilers/mode/mod.rs | 80 ++- .../{solc_cache_key.rs => cache_key.rs} | 8 +- compiler_tester/src/compilers/solidity/mod.rs | 138 ++--- .../{mode/solidity.rs => solidity/mode.rs} | 12 +- .../src/compilers/solidity/upstream/mod.rs | 380 ++++++++++++ .../src/compilers/solidity/upstream/mode.rs | 126 ++++ .../compilers/solidity/upstream/solc/mod.rs | 140 +++++ .../solc/standard_json/input/language.rs | 25 + .../upstream/solc/standard_json/input/mod.rs | 70 +++ .../solc/standard_json/input/settings/mod.rs | 66 ++ .../input/settings/optimizer/details.rs | 66 ++ .../input/settings/optimizer/mod.rs | 24 + .../input/settings/selection/file/flag.rs | 50 ++ .../input/settings/selection/file/mod.rs | 40 ++ .../input/settings/selection/mod.rs | 30 + .../solc/standard_json/input/source.rs | 43 ++ .../upstream/solc/standard_json/mod.rs | 6 + .../output/contract/evm/bytecode.rs | 15 + .../standard_json/output/contract/evm/mod.rs | 25 + .../solc/standard_json/output/contract/mod.rs | 50 ++ .../solc/standard_json/output/error/mod.rs | 37 ++ .../output/error/source_location.rs | 46 ++ .../upstream/solc/standard_json/output/mod.rs | 34 ++ .../solc/standard_json/output/source.rs | 42 ++ .../{vyper_cache_key.rs => cache_key.rs} | 8 +- compiler_tester/src/compilers/vyper/mod.rs | 168 +++--- .../{mode/vyper.rs => vyper/mode.rs} | 4 +- .../src/compilers/{yul.rs => yul/mod.rs} | 85 ++- .../compilers/{mode/yul.rs => yul/mode.rs} | 12 +- .../src/directories/ethereum/mod.rs | 47 +- .../src/directories/ethereum/test.rs | 462 ++++++-------- .../src/directories/matter_labs/mod.rs | 41 +- .../test/metadata/case/input/expected/mod.rs | 9 +- .../test/metadata/case/input/mod.rs | 4 +- .../test/metadata/case/input/storage.rs | 4 +- .../matter_labs/test/metadata/case/mod.rs | 127 ++-- .../matter_labs/test/metadata/evm_contract.rs | 32 + .../matter_labs/test/metadata/mod.rs | 20 +- .../src/directories/matter_labs/test/mod.rs | 570 +++++++++++------- compiler_tester/src/directories/mod.rs | 61 +- compiler_tester/src/filters.rs | 24 +- compiler_tester/src/lib.rs | 147 +++-- compiler_tester/src/summary/element/mod.rs | 10 +- .../summary/element/outcome/passed_variant.rs | 12 +- compiler_tester/src/summary/mod.rs | 116 ++-- compiler_tester/src/target.rs | 43 ++ .../src/test/case/input/balance.rs | 36 +- .../src/test/case/input/calldata.rs | 32 +- .../src/test/case/input/deploy_eravm.rs | 124 ++++ .../case/input/{deploy.rs => deploy_evm.rs} | 104 ++-- compiler_tester/src/test/case/input/mod.rs | 267 +++++--- .../src/test/case/input/output/event.rs | 33 +- .../src/test/case/input/output/mod.rs | 52 +- .../src/test/case/input/runtime.rs | 121 +++- .../src/test/case/input/storage.rs | 28 +- .../src/test/case/input/storage_empty.rs | 37 +- compiler_tester/src/test/case/input/value.rs | 19 +- compiler_tester/src/test/case/mod.rs | 76 ++- compiler_tester/src/test/eravm.rs | 69 --- compiler_tester/src/test/evm.rs | 74 --- .../test/{instance.rs => instance/eravm.rs} | 12 +- compiler_tester/src/test/instance/evm.rs | 41 ++ compiler_tester/src/test/instance/mod.rs | 103 ++++ compiler_tester/src/test/mod.rs | 119 +++- compiler_tester/src/utils.rs | 10 + compiler_tester/src/vm/address_iterator.rs | 29 + ...dress_predictor.rs => address_iterator.rs} | 45 +- .../{native_deployer.rs => dummy_deployer.rs} | 44 +- compiler_tester/src/vm/eravm/deployers/mod.rs | 22 +- .../deployers/system_contract_deployer.rs | 120 +++- compiler_tester/src/vm/eravm/input/mod.rs | 68 +++ compiler_tester/src/vm/eravm/mod.rs | 153 +++-- .../src/vm/eravm/system_context.rs | 8 +- .../src/vm/eravm/system_contracts.rs | 88 ++- compiler_tester/src/vm/eravm/vm2_adapter.rs | 273 ++++++++- .../src/vm/evm/address_iterator.rs | 72 +++ .../src/vm/evm/address_predictor.rs | 53 -- compiler_tester/src/vm/evm/input/mod.rs | 71 ++- compiler_tester/src/vm/evm/mod.rs | 40 +- compiler_tester/src/vm/evm/runtime.rs | 13 +- compiler_tester/src/vm/execution_result.rs | 24 +- compiler_tester/src/vm/mod.rs | 15 +- configs/solc-bin-default.json | 6 - configs/solc-bin-upstream.json | 478 +++++++++++++++ configs/solc-bin-zkevm-candidate-0.8.25.json | 16 - configs/solc-bin-zkevm-reference-0.8.25.json | 16 - coverage_watcher/Cargo.toml | 2 +- fuzz/Cargo.toml | 40 +- fuzz/fuzz_targets/common.rs | 19 +- fuzz/fuzz_targets/demo.rs | 6 +- fuzz/fuzz_targets/optimizer_bug.rs | 6 +- solidity_adapter/Cargo.toml | 2 +- .../parser/lexical/token/lexeme/keyword.rs | 4 - .../parser/syntax/parser/event.rs | 11 +- .../function_call/parser/syntax/parser/gas.rs | 22 +- .../parser/syntax/tree/literal/alignment.rs | 13 +- 116 files changed, 5384 insertions(+), 2005 deletions(-) rename compiler_tester/src/compilers/{eravm.rs => eravm/mod.rs} (71%) rename compiler_tester/src/compilers/{mode/eravm.rs => eravm/mode.rs} (100%) rename compiler_tester/src/compilers/{llvm.rs => llvm/mod.rs} (83%) rename compiler_tester/src/compilers/{mode/llvm.rs => llvm/mode.rs} (91%) rename compiler_tester/src/{ => compilers/mode}/llvm_options.rs (100%) rename compiler_tester/src/compilers/solidity/{solc_cache_key.rs => cache_key.rs} (85%) rename compiler_tester/src/compilers/{mode/solidity.rs => solidity/mode.rs} (92%) create mode 100644 compiler_tester/src/compilers/solidity/upstream/mod.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/mode.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/mod.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/language.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/mod.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/mod.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/optimizer/details.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/optimizer/mod.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/selection/file/flag.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/selection/file/mod.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/selection/mod.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/source.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/mod.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/contract/evm/bytecode.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/contract/evm/mod.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/contract/mod.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/error/mod.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/error/source_location.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/mod.rs create mode 100644 compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/source.rs rename compiler_tester/src/compilers/vyper/{vyper_cache_key.rs => cache_key.rs} (78%) rename compiler_tester/src/compilers/{mode/vyper.rs => vyper/mode.rs} (95%) rename compiler_tester/src/compilers/{yul.rs => yul/mod.rs} (76%) rename compiler_tester/src/compilers/{mode/yul.rs => yul/mode.rs} (76%) create mode 100644 compiler_tester/src/directories/matter_labs/test/metadata/evm_contract.rs create mode 100644 compiler_tester/src/target.rs create mode 100644 compiler_tester/src/test/case/input/deploy_eravm.rs rename compiler_tester/src/test/case/input/{deploy.rs => deploy_evm.rs} (71%) delete mode 100644 compiler_tester/src/test/eravm.rs delete mode 100644 compiler_tester/src/test/evm.rs rename compiler_tester/src/test/{instance.rs => instance/eravm.rs} (62%) create mode 100644 compiler_tester/src/test/instance/evm.rs create mode 100644 compiler_tester/src/test/instance/mod.rs create mode 100644 compiler_tester/src/vm/address_iterator.rs rename compiler_tester/src/vm/eravm/{deployers/address_predictor.rs => address_iterator.rs} (65%) rename compiler_tester/src/vm/eravm/deployers/{native_deployer.rs => dummy_deployer.rs} (83%) create mode 100644 compiler_tester/src/vm/evm/address_iterator.rs delete mode 100644 compiler_tester/src/vm/evm/address_predictor.rs create mode 100644 configs/solc-bin-upstream.json delete mode 100644 configs/solc-bin-zkevm-candidate-0.8.25.json delete mode 100644 configs/solc-bin-zkevm-reference-0.8.25.json diff --git a/.gitignore b/.gitignore index 04abd624..3802585b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,24 +4,25 @@ # These are backup files generated by rustfmt **/*.rs.bk -# MacOS -/**/.DS_Store - -# IDE -/.idea/ -/.vscode/ - # The LLVM framework source /llvm/ # External compilers /solc-bin/* +/solc-bin*/* /vyper-bin/* -# The debug and trace artifacts +# The debug, trace, benchmark artifacts /debug/ /trace/ # The dependency locks # /Cargo.lock # /LLVM.lock + +# IDE +/.idea/ +/.vscode/ + +# MacOS +/**/.DS_Store diff --git a/.gitmodules b/.gitmodules index b777b4bc..85e5e7ec 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,7 @@ [submodule "tests"] path = tests url = https://github.com/matter-labs/era-compiler-tests - branch = main + branch = v1.5.0 [submodule "solidity"] path = solidity url = https://github.com/ethereum/solidity @@ -9,4 +9,4 @@ [submodule "era-contracts"] path = era-contracts url = https://github.com/matter-labs/era-contracts - branch = release-v20-1.4.1-short-term-fee-model + branch = az-benchmark-evm-interpreter diff --git a/Cargo.lock b/Cargo.lock index 7b610881..3eba619e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,9 @@ name = "arbitrary" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "arrayvec" @@ -107,7 +110,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "benchmark-analyzer" -version = "1.4.1" +version = "1.5.0" dependencies = [ "anyhow", "colored", @@ -234,7 +237,7 @@ dependencies = [ [[package]] name = "compiler-tester" -version = "1.4.1" +version = "1.5.0" dependencies = [ "anyhow", "benchmark-analyzer", @@ -255,6 +258,7 @@ dependencies = [ "rayon", "regex", "reqwest", + "rlp", "ron", "semver", "serde", @@ -263,7 +267,9 @@ dependencies = [ "sha3", "solidity-adapter", "structopt", + "vm2", "web3", + "which", "zkevm-assembly", "zkevm_opcode_defs", "zkevm_tester", @@ -271,7 +277,7 @@ dependencies = [ [[package]] name = "compiler-tester-fuzz" -version = "0.0.0" +version = "1.5.0" dependencies = [ "anyhow", "compiler-tester", @@ -314,7 +320,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "coverage-watcher" -version = "1.4.1" +version = "1.5.0" dependencies = [ "anyhow", "era-compiler-common", @@ -392,9 +398,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -452,6 +470,7 @@ dependencies = [ "ff", "generic-array", "group", + "pem-rfc7468", "pkcs8", "rand_core 0.6.4", "sec1", @@ -468,6 +487,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "env_logger" version = "0.9.3" @@ -500,8 +531,8 @@ dependencies = [ [[package]] name = "era-compiler-llvm-context" -version = "1.4.1" -source = "git+https://github.com/matter-labs/era-compiler-llvm-context?branch=main#7a7d7f8d1772c558c9dc6543148176700a6d9d51" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-compiler-llvm-context?branch=v1.5.0#b63448f92a3f8fff7f1ea21cbbaa31c4c817543e" dependencies = [ "anyhow", "era-compiler-common", @@ -522,8 +553,8 @@ dependencies = [ [[package]] name = "era-compiler-solidity" -version = "1.4.1" -source = "git+https://github.com/matter-labs/era-compiler-solidity?branch=main#6a2861cc6fb7557bee9a2aa29075c950e9020b34" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-compiler-solidity?branch=v1.5.0#dcc04dbccf56cf114ae874a26a6c841ff079a848" dependencies = [ "anyhow", "colored", @@ -551,8 +582,8 @@ dependencies = [ [[package]] name = "era-compiler-vyper" -version = "1.4.1" -source = "git+https://github.com/matter-labs/era-compiler-vyper?branch=main#8c58782c8bb18cbd5abaf0c5bf0ce8f79f3d6444" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-compiler-vyper?branch=v1.5.0#cdfa9f1a8e3b9b646a0591262e291cb6e6f3f31d" dependencies = [ "anyhow", "colored", @@ -1430,6 +1461,18 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parity-scale-codec" version = "3.6.9" @@ -1485,6 +1528,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1539,6 +1591,15 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -2140,7 +2201,7 @@ dependencies = [ [[package]] name = "solidity-adapter" -version = "1.4.1" +version = "1.5.0" dependencies = [ "anyhow", "colored", @@ -2532,6 +2593,18 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2266fcb904c50fb17fda4c9a751a1715629ecf8b21f4c9d78b4890fb71525d71" +[[package]] +name = "vm2" +version = "0.1.0" +source = "git+https://github.com/matter-labs/vm2#dbcb1e2efcdf05621c9d4dc1a3846689da2e8667" +dependencies = [ + "arbitrary", + "enum_dispatch", + "primitive-types", + "zk_evm_abstractions", + "zkevm_opcode_defs", +] + [[package]] name = "want" version = "0.3.1" @@ -2872,8 +2945,8 @@ checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" [[package]] name = "zk_evm" -version = "1.4.1" -source = "git+https://github.com/matter-labs/era-zk_evm?branch=v1.4.1#6250dbf64b2d14ced87a127735da559f27a432d5" +version = "1.5.0" +source = "git+https://github.com/matter-labs/zk_evm?branch=v1.5.0#8ed110284f48dd30d869b09f2f74e6d5106eb2db" dependencies = [ "anyhow", "lazy_static", @@ -2882,13 +2955,12 @@ dependencies = [ "serde_json", "static_assertions", "zk_evm_abstractions", - "zkevm_opcode_defs", ] [[package]] name = "zk_evm_abstractions" -version = "1.4.1" -source = "git+https://github.com/matter-labs/era-zk_evm_abstractions?branch=v1.4.1#0aac08c3b097ee8147e748475117ac46bddcdcef" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-zk_evm_abstractions.git?branch=v1.5.0#e464b2cf2b146d883be80e7d690c752bf670ff05" dependencies = [ "anyhow", "num_enum", @@ -2899,8 +2971,8 @@ dependencies = [ [[package]] name = "zkevm-assembly" -version = "1.4.1" -source = "git+https://github.com/matter-labs/era-zkEVM-assembly?branch=v1.4.1#e59d5da67f18f8829c3cfaebb967265d73c168ed" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-zkEVM-assembly?branch=v1.5.0#2faea98303377cad71f4c7d8dacb9c6546874602" dependencies = [ "env_logger", "hex", @@ -2918,22 +2990,24 @@ dependencies = [ [[package]] name = "zkevm_opcode_defs" -version = "1.4.1" -source = "git+https://github.com/matter-labs/era-zkevm_opcode_defs?branch=v1.4.1#ba8228ff0582d21f64d6a319d50d0aec48e9e7b6" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-zkevm_opcode_defs?branch=v1.5.0#2aaab121eaff01e8224d8d113e446f700d88aba5" dependencies = [ "bitflags 2.5.0", "blake2", "ethereum-types", "k256", "lazy_static", + "p256", + "serde", "sha2", "sha3", ] [[package]] name = "zkevm_tester" -version = "1.4.1" -source = "git+https://github.com/matter-labs/era-zkevm_tester?branch=v1.4.1#aab8cdc167402558e8eb47c2b2701057eee4c33d" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-zkevm_tester?branch=v1.5.0#6097d4bdc67474747e437b023354cc06bba767d5" dependencies = [ "anyhow", "futures", @@ -2946,6 +3020,5 @@ dependencies = [ "tracing", "vlog", "zk_evm", - "zk_evm_abstractions", "zkevm-assembly", ] diff --git a/benchmark_analyzer/Cargo.toml b/benchmark_analyzer/Cargo.toml index 8eda70c3..547210a1 100644 --- a/benchmark_analyzer/Cargo.toml +++ b/benchmark_analyzer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "benchmark-analyzer" -version = "1.4.1" +version = "1.5.0" authors = [ "Oleksandr Zarudnyi ", ] diff --git a/benchmark_analyzer/src/benchmark/group/element.rs b/benchmark_analyzer/src/benchmark/group/element.rs index 70ff3a0b..550cae70 100644 --- a/benchmark_analyzer/src/benchmark/group/element.rs +++ b/benchmark_analyzer/src/benchmark/group/element.rs @@ -15,14 +15,21 @@ pub struct Element { /// The number of cycles. pub cycles: usize, /// The number of ergs. - pub ergs: u32, + pub ergs: u64, + /// The number of EVM gas. + pub gas: u64, } impl Element { /// /// A shortcut constructor. /// - pub fn new(size: Option, cycles: usize, ergs: u32) -> Self { - Self { size, cycles, ergs } + pub fn new(size: Option, cycles: usize, ergs: u64, gas: u64) -> Self { + Self { + size, + cycles, + ergs, + gas, + } } } diff --git a/benchmark_analyzer/src/benchmark/group/mod.rs b/benchmark_analyzer/src/benchmark/group/mod.rs index f7cd7335..199269f2 100644 --- a/benchmark_analyzer/src/benchmark/group/mod.rs +++ b/benchmark_analyzer/src/benchmark/group/mod.rs @@ -10,13 +10,15 @@ use std::collections::BTreeMap; use serde::Deserialize; use serde::Serialize; +use crate::benchmark::Benchmark; + use self::element::Element; use self::results::Results; /// /// The benchmark group representation. /// -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct Group { /// The group elements. pub elements: BTreeMap, @@ -76,8 +78,8 @@ impl Group { } cycles_factors.push(cycles_factor); - ergs_total_reference += reference.ergs as u64; - ergs_total_candidate += candidate.ergs as u64; + ergs_total_reference += reference.ergs; + ergs_total_candidate += candidate.ergs; let ergs_factor = (candidate.ergs as f64) / (reference.ergs as f64); if ergs_factor > 1.0 { ergs_negatives.push((ergs_factor, path.as_str())); @@ -149,4 +151,30 @@ impl Group { ergs_positives, ) } + + /// + /// Returns the EVM interpreter ergs/gas ratio. + /// + pub fn evm_interpreter_ratios(&self) -> Vec<(String, f64)> { + #[allow(clippy::unnecessary_to_owned)] + let elements: Vec<(String, Element)> = self.elements.to_owned().into_iter().collect(); + let mut results = Vec::with_capacity(Benchmark::EVM_OPCODES.len()); + for evm_opcode in Benchmark::EVM_OPCODES.into_iter() { + let name_substring = format!("test.json::{evm_opcode}["); + let mut template_and_full: Vec<(String, Element)> = elements + .iter() + .filter(|element| element.0.contains(name_substring.as_str())) + .rev() + .take(2) + .cloned() + .collect(); + let (full, template) = (template_and_full.remove(0).1, template_and_full.remove(0).1); + + let ergs_difference = full.ergs - template.ergs; + let gas_difference = full.gas - template.gas; + let ergs_gas_ratio = (ergs_difference as f64) / (gas_difference as f64); + results.push((evm_opcode.to_owned(), ergs_gas_ratio)); + } + results + } } diff --git a/benchmark_analyzer/src/benchmark/group/results.rs b/benchmark_analyzer/src/benchmark/group/results.rs index d84d959e..f95a3e1c 100644 --- a/benchmark_analyzer/src/benchmark/group/results.rs +++ b/benchmark_analyzer/src/benchmark/group/results.rs @@ -48,6 +48,11 @@ pub struct Results<'a> { pub ergs_negatives: Vec<(f64, &'a str)>, /// The ergs positive result test names. pub ergs_positives: Vec<(f64, &'a str)>, + + /// The EVM interpreter reference ratios. + pub evm_interpreter_reference_ratios: Option>, + /// The EVM interpreter candidate ratios. + pub evm_interpreter_candidate_ratios: Option>, } impl<'a> Results<'a> { @@ -98,9 +103,24 @@ impl<'a> Results<'a> { ergs_total, ergs_negatives, ergs_positives, + + evm_interpreter_reference_ratios: None, + evm_interpreter_candidate_ratios: None, } } + /// + /// Sets the EVM interpreter ratios. + /// + pub fn set_evm_interpreter_ratios( + &mut self, + reference_ratios: Vec<(String, f64)>, + candidate_ratios: Vec<(String, f64)>, + ) { + self.evm_interpreter_reference_ratios = Some(reference_ratios); + self.evm_interpreter_candidate_ratios = Some(candidate_ratios); + } + /// /// Sorts the worst results. /// @@ -328,6 +348,98 @@ impl<'a> Results<'a> { "Total".bright_white(), Self::format_geomean(self.ergs_total) )?; + if let (Some(gas_reference_ratios), Some(gas_candidate_ratios)) = ( + self.evm_interpreter_reference_ratios.as_deref(), + self.evm_interpreter_candidate_ratios.as_deref(), + ) { + writeln!( + w, + "╠═╡ {} ╞{}╡ {} ╞═╣", + "Ergs/gas".bright_white(), + "═".repeat(cmp::max(25 - group_name.len(), 0)), + group_name.bright_white() + )?; + for (opcode, reference_ratio) in gas_reference_ratios.iter() { + let reference_ratio = *reference_ratio; + let candidate_ratio = gas_candidate_ratios + .iter() + .find_map(|(key, value)| { + if key.as_str() == opcode.as_str() { + Some(*value) + } else { + None + } + }) + .expect("Always exists"); + let is_positive = candidate_ratio < reference_ratio; + let is_negative = candidate_ratio > reference_ratio; + + writeln!( + w, + "║ {:32} {} ║", + if is_positive { + opcode.green() + } else if is_negative { + opcode.bright_red() + } else { + opcode.bright_white() + }, + if is_positive { + format!("{candidate_ratio:8.3}").green() + } else if is_negative { + format!("{candidate_ratio:8.3}").bright_red() + } else { + format!("{candidate_ratio:8.3}").bright_white() + }, + )?; + } + + writeln!( + w, + "╠═╡ {} ╞{}╡ {} ╞═╣", + "Ergs/gas (-%)".bright_white(), + "═".repeat(cmp::max(20 - group_name.len(), 0)), + group_name.bright_white() + )?; + for (opcode, reference_ratio) in gas_reference_ratios.iter() { + let reference_ratio = *reference_ratio; + let candidate_ratio = gas_candidate_ratios + .iter() + .find_map(|(key, value)| { + if key.as_str() == opcode.as_str() { + Some(*value) + } else { + None + } + }) + .expect("Always exists"); + + let reduction = 100.0 - (candidate_ratio * 100.0 / reference_ratio); + if reduction >= 0.001 { + let is_positive = candidate_ratio < reference_ratio; + let is_negative = candidate_ratio > reference_ratio; + + writeln!( + w, + "║ {:32} {} ║", + if is_positive { + opcode.green() + } else if is_negative { + opcode.bright_red() + } else { + opcode.bright_white() + }, + if is_positive { + format!("{reduction:8.3}").green() + } else if is_negative { + format!("{reduction:8.3}").bright_red() + } else { + format!("{reduction:8.3}").bright_white() + }, + )?; + } + } + } writeln!(w, "╚═══════════════════════════════════════════╝")?; Ok(()) diff --git a/benchmark_analyzer/src/benchmark/mod.rs b/benchmark_analyzer/src/benchmark/mod.rs index 483413ae..c5d7967c 100644 --- a/benchmark_analyzer/src/benchmark/mod.rs +++ b/benchmark_analyzer/src/benchmark/mod.rs @@ -23,19 +23,101 @@ pub struct Benchmark { } impl Benchmark { + /// The EVM interpreter group identifier. + pub const EVM_INTERPRETER_GROUP_NAME: &'static str = "EVMInterpreter"; + + /// The EVM interpreter group identifier prefix. + pub const EVM_INTERPRETER_GROUP_PREFIX: &'static str = "EVMInterpreter M3B3"; + + /// The EVM opcodes to test. + pub const EVM_OPCODES: [&'static str; 58] = [ + "ADD", + "MUL", + "SUB", + "DIV", + "SDIV", + "MOD", + "SMOD", + "ADDMOD", + "MULMOD", + "EXP", + "SIGNEXTEND", + "LT", + "GT", + "SLT", + "SGT", + "EQ", + "ISZERO", + "AND", + "OR", + "XOR", + "NOT", + "BYTE", + "SHL", + "SHR", + "SAR", + "SGT", + "SHA3", + "ADDRESS", + "BALANCE", + "ORIGIN", + "CALLER", + "CALLVALUE", + "BLOCKHASH", + "COINBASE", + "TIMESTAMP", + "NUMBER", + "PREVRANDAO", + "GASLIMIT", + "CHAINID", + "SELFBALANCE", + "BASEFEE", + "POP", + "MLOAD", + "MSTORE", + "MSTORE8", + "SLOAD", + "SSTORE", + "JUMP", + "JUMPI", + "PC", + "MSIZE", + "GAS", + "JUMPDEST", + "PUSH1", + "RETURN", + "REVERT", + "INVALID", + "SELFDESTRUCT", + ]; + /// /// Compares two benchmarks. /// pub fn compare<'a>(reference: &'a Self, candidate: &'a Self) -> BTreeMap<&'a str, Results<'a>> { let mut results = BTreeMap::new(); - for (group_name, reference) in reference.groups.iter() { - let candidate = match candidate.groups.get(group_name) { - Some(candidate) => candidate, + for (group_name, reference_group) in reference.groups.iter() { + let candidate_group = match candidate.groups.get(group_name) { + Some(candidate_group) => candidate_group, None => continue, }; - let group_results = Group::compare(reference, candidate); + let mut group_results = Group::compare(reference_group, candidate_group); + if group_name.starts_with(Self::EVM_INTERPRETER_GROUP_PREFIX) { + if let (Some(reference_ratios), Some(candidate_ratios)) = ( + reference + .groups + .get(group_name.as_str()) + .map(|group| group.evm_interpreter_ratios()), + candidate + .groups + .get(group_name.as_str()) + .map(|group| group.evm_interpreter_ratios()), + ) { + group_results.set_evm_interpreter_ratios(reference_ratios, candidate_ratios); + } + } results.insert(group_name.as_str(), group_results); } diff --git a/compiler_tester/Cargo.toml b/compiler_tester/Cargo.toml index 10efbdda..ff48c545 100644 --- a/compiler_tester/Cargo.toml +++ b/compiler_tester/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "compiler-tester" -version = "1.4.1" +version = "1.5.0" authors = [ "Oleksandr Zarudnyi ", "Anton Dyadyuk ", @@ -19,6 +19,7 @@ doctest = false [dependencies] structopt = { version = "0.3", default-features = false } anyhow = "1.0" +which = "5.0" colored = "2.1" serde = { version = "1.0", features = ["derive"] } @@ -26,8 +27,9 @@ serde_json = "1.0" serde_yaml = "0.9" md5 = "0.7" hex = "0.4" -sha3 = "0.10" +sha3 = "0.10.6" ron = "0.8" +rlp = "0.5" regex = "1.9" glob = "0.3" semver = { version = "1.0", features = ["serde"] } @@ -38,14 +40,15 @@ lazy_static = "1.4" bincode = "1.3" evm = { git = "https://github.com/rust-ethereum/evm", branch = "master" } -zkevm-assembly = { git = "https://github.com/matter-labs/era-zkEVM-assembly", branch = "v1.4.1" } -zkevm_opcode_defs = { git = "https://github.com/matter-labs/era-zkevm_opcode_defs", branch = "v1.4.1" } -zkevm_tester = { git = "https://github.com/matter-labs/era-zkevm_tester", branch = "v1.4.1" } +zkevm-assembly = { git = "https://github.com/matter-labs/era-zkEVM-assembly", branch = "v1.5.0" } +zkevm_opcode_defs = { git = "https://github.com/matter-labs/era-zkevm_opcode_defs", branch = "v1.5.0" } +zkevm_tester = { git = "https://github.com/matter-labs/era-zkevm_tester", branch = "v1.5.0" } +vm2 = { git = "https://github.com/matter-labs/vm2", optional = true } era-compiler-common = { git = "https://github.com/matter-labs/era-compiler-common", branch = "main" } -era-compiler-llvm-context = { git = "https://github.com/matter-labs/era-compiler-llvm-context", branch = "main" } -era-compiler-solidity = { git = "https://github.com/matter-labs/era-compiler-solidity", branch = "main" } -era-compiler-vyper = { git = "https://github.com/matter-labs/era-compiler-vyper", branch = "main" } +era-compiler-llvm-context = { git = "https://github.com/matter-labs/era-compiler-llvm-context", branch = "v1.5.0" } +era-compiler-solidity = { git = "https://github.com/matter-labs/era-compiler-solidity", branch = "v1.5.0" } +era-compiler-vyper = { git = "https://github.com/matter-labs/era-compiler-vyper", branch = "v1.5.0" } # era-compiler-common = { path = "../../era-compiler-common" } # era-compiler-llvm-context = { path = "../../era-compiler-llvm-context" } diff --git a/compiler_tester/src/compiler_tester/arguments.rs b/compiler_tester/src/compiler_tester/arguments.rs index b42bb078..0282e878 100644 --- a/compiler_tester/src/compiler_tester/arguments.rs +++ b/compiler_tester/src/compiler_tester/arguments.rs @@ -75,11 +75,15 @@ pub struct Arguments { pub zkvyper: Option, /// Specify the target machine. - /// Available arguments: `eravm`, `evm`. - /// The default is `eravm`. + /// Available arguments: `EraVM`, `EVM`, `EVMInterpreter`. + /// The default is `EraVM`. #[structopt(long = "target")] pub target: Option, + /// Use the upstream `solc` compiler. + #[structopt(long = "use-upstream-solc")] + pub use_upstream_solc: bool, + /// Path to the default `solc` binaries download configuration file. #[structopt(long = "solc-bin-config-path")] pub solc_bin_config_path: Option, diff --git a/compiler_tester/src/compiler_tester/main.rs b/compiler_tester/src/compiler_tester/main.rs index cedd9ae5..7a9bd68b 100644 --- a/compiler_tester/src/compiler_tester/main.rs +++ b/compiler_tester/src/compiler_tester/main.rs @@ -40,17 +40,20 @@ fn main_inner(arguments: Arguments) -> anyhow::Result<()> { inkwell::support::get_commit_id().to_string(), ); - let target = match arguments.target { - Some(target) => era_compiler_llvm_context::Target::from_str(target.as_str())?, - None => era_compiler_llvm_context::Target::EraVM, - }; - inkwell::support::enable_llvm_pretty_stack_trace(); - era_compiler_llvm_context::initialize_target(target); + for target in [ + era_compiler_llvm_context::Target::EraVM, + era_compiler_llvm_context::Target::EVM, + ] + .into_iter() + { + era_compiler_llvm_context::initialize_target(target); + } compiler_tester::LLVMOptions::initialize( arguments.llvm_verify_each, arguments.llvm_debug_logging, )?; + era_compiler_solidity::EXECUTABLE .set( arguments @@ -96,9 +99,13 @@ fn main_inner(arguments: Arguments) -> anyhow::Result<()> { compiler_tester::CompilerTester::new(summary.clone(), filters, debug_config.clone())?; let binary_download_config_paths = vec![ - arguments - .solc_bin_config_path - .unwrap_or_else(|| PathBuf::from("./configs/solc-bin-default.json")), + arguments.solc_bin_config_path.unwrap_or_else(|| { + PathBuf::from(if arguments.use_upstream_solc { + "./configs/solc-bin-upstream.json" + } else { + "./configs/solc-bin-default.json" + }) + }), arguments .vyper_bin_config_path .unwrap_or_else(|| PathBuf::from("./configs/vyper-bin-default.json")), @@ -111,16 +118,25 @@ fn main_inner(arguments: Arguments) -> anyhow::Result<()> { rayon::current_num_threads(), ); + let target = match arguments.target { + Some(target) => compiler_tester::Target::from_str(target.as_str())?, + None => compiler_tester::Target::EraVM, + }; + match target { - era_compiler_llvm_context::Target::EraVM => { + compiler_tester::Target::EraVM => { zkevm_tester::runners::compiler_tests::set_tracing_mode( zkevm_tester::runners::compiler_tests::VmTracingOptions::from_u64( arguments.trace as u64, ), ); + + #[cfg(feature = "vm2")] + zkevm_assembly::set_encoding_mode(zkevm_assembly::RunningVmEncodingMode::Production); + #[cfg(not(feature = "vm2"))] zkevm_assembly::set_encoding_mode(zkevm_assembly::RunningVmEncodingMode::Testing); - let system_contract_debug_config = if arguments.dump_system { + let system_contracts_debug_config = if arguments.dump_system { debug_config } else { None @@ -128,7 +144,7 @@ fn main_inner(arguments: Arguments) -> anyhow::Result<()> { let vm = compiler_tester::EraVM::new( binary_download_config_paths, PathBuf::from("./configs/solc-bin-system-contracts.json"), - system_contract_debug_config, + system_contracts_debug_config, arguments.system_contracts_load_path, arguments.system_contracts_save_path, )?; @@ -138,22 +154,49 @@ fn main_inner(arguments: Arguments) -> anyhow::Result<()> { arguments.disable_value_simulator, ) { (true, true) => { - compiler_tester.run_eravm::(vm)? + compiler_tester.run_eravm::(vm) } (true, false) => { - compiler_tester.run_eravm::(vm)? + compiler_tester.run_eravm::(vm) } (false, true) => compiler_tester - .run_eravm::(vm)?, + .run_eravm::(vm), (false, false) => compiler_tester - .run_eravm::(vm)?, + .run_eravm::(vm), } } - era_compiler_llvm_context::Target::EVM => { + compiler_tester::Target::EVM => { compiler_tester::EVM::download(binary_download_config_paths)?; - compiler_tester.run_evm()?; + compiler_tester.run_evm(arguments.use_upstream_solc) } - } + compiler_tester::Target::EVMInterpreter => { + zkevm_tester::runners::compiler_tests::set_tracing_mode( + zkevm_tester::runners::compiler_tests::VmTracingOptions::from_u64( + arguments.trace as u64, + ), + ); + zkevm_assembly::set_encoding_mode(zkevm_assembly::RunningVmEncodingMode::Testing); + + let system_contract_debug_config = if arguments.dump_system { + debug_config + } else { + None + }; + let vm = compiler_tester::EraVM::new( + binary_download_config_paths, + PathBuf::from("./configs/solc-bin-system-contracts.json"), + system_contract_debug_config, + arguments.system_contracts_load_path, + arguments.system_contracts_save_path, + )?; + + compiler_tester + .run_evm_interpreter::( + vm, + arguments.use_upstream_solc, + ) + } + }?; let summary = compiler_tester::Summary::unwrap_arc(summary); print!("{summary}"); @@ -178,7 +221,9 @@ fn main_inner(arguments: Arguments) -> anyhow::Result<()> { #[cfg(test)] mod tests { - use super::*; + use std::path::PathBuf; + + use crate::arguments::Arguments; #[test] fn test_manually() { @@ -186,13 +231,15 @@ mod tests { zkevm_tester::runners::compiler_tests::VmTracingOptions::ManualVerbose, ); + std::env::set_current_dir("..").expect("Change directory failed"); + let arguments = Arguments { verbosity: false, quiet: false, debug: false, trace: 2, - modes: vec!["Y+M3B3 0.8.21".to_owned()], - paths: vec!["./tests/solidity/simple/default.sol".to_owned()], + modes: vec!["Y+M3B3 0.8.24".to_owned()], + paths: vec!["tests/solidity/simple/default.sol".to_owned()], groups: vec![], benchmark: None, threads: Some(1), @@ -203,15 +250,16 @@ mod tests { era_compiler_solidity::DEFAULT_EXECUTABLE_NAME, )), zkvyper: Some(PathBuf::from(era_compiler_vyper::DEFAULT_EXECUTABLE_NAME)), - target: Some(era_compiler_llvm_context::Target::EraVM.to_string()), + target: Some(compiler_tester::Target::EraVM.to_string()), + use_upstream_solc: false, solc_bin_config_path: Some(PathBuf::from("./configs/solc-bin-default.json")), vyper_bin_config_path: Some(PathBuf::from("./configs/vyper-bin-default.json")), - system_contracts_load_path: None, + system_contracts_load_path: Some(PathBuf::from("system-contracts-stable-build")), system_contracts_save_path: None, llvm_verify_each: false, llvm_debug_logging: false, }; - main_inner(arguments).expect("Manual testing failed"); + crate::main_inner(arguments).expect("Manual testing failed"); } } diff --git a/compiler_tester/src/compilers/cache/mod.rs b/compiler_tester/src/compilers/cache/mod.rs index 2e094ca0..710ed19d 100644 --- a/compiler_tester/src/compilers/cache/mod.rs +++ b/compiler_tester/src/compilers/cache/mod.rs @@ -37,9 +37,11 @@ where } /// - /// Compute and save the cache value, if a value already started computing will do nothing. + /// Evaluates and saves a cache value. /// - pub fn compute(&self, key: K, f: F) + /// If the value is already being evaluated, does nothing. + /// + pub fn evaluate(&self, key: K, f: F) where F: FnOnce() -> anyhow::Result, { @@ -60,30 +62,30 @@ where let mut inner = self.inner.write().expect("Sync"); let entry_value = inner .get_mut(&key) - .expect("The value is not being computed"); + .expect("The value is not being evaluated"); assert!( matches!(entry_value, Value::Waiter(_)), - "The value is already computed" + "The value is already evaluated" ); *entry_value = Value::Value(value); } /// - /// Checks if value for the key is cached. + /// Checks if the value for the key is present in the cache. /// pub fn contains(&self, key: &K) -> bool { self.inner.read().expect("Sync").contains_key(key) } /// - /// Get the cloned value by the key. - /// Will wait if the value is computing. + /// Get a cloned value by the key. + /// Will wait if the value is being evaluated. /// /// # Panics /// - /// If the value is not being computed. + /// If the value is not being evaluated. /// pub fn get_cloned(&self, key: &K) -> anyhow::Result { self.wait(key); @@ -91,7 +93,7 @@ where .read() .expect("Sync") .get(key) - .expect("The value is not being computed") + .expect("The value is not being evaluated") .unwrap_value() .as_ref() .map(|value| value.clone()) @@ -99,11 +101,11 @@ where } /// - /// Waits until value will be computed if needed. + /// Waits until value will be evaluated if needed. /// /// # Panics /// - /// If the value is not being computed. + /// If the value is not being evaluated. /// fn wait(&self, key: &K) { let waiter = if let Value::Waiter(waiter) = self @@ -111,7 +113,7 @@ where .read() .expect("Sync") .get(key) - .expect("The value is not being computed") + .expect("The value is not being evaluated") { waiter.clone() } else { diff --git a/compiler_tester/src/compilers/cache/value.rs b/compiler_tester/src/compilers/cache/value.rs index d7644829..4ea2df48 100644 --- a/compiler_tester/src/compilers/cache/value.rs +++ b/compiler_tester/src/compilers/cache/value.rs @@ -1,17 +1,17 @@ //! -//! The compilers cache value. +//! The compiler cache value. //! use std::sync::Arc; use std::sync::Mutex; /// -/// The compilers cache value. +/// The compiler cache value. /// pub enum Value { - /// The value is being computed. + /// The value is being evaluated. Waiter(Arc>), - /// The value is already computed. + /// The value is already evaluated. Value(T), } @@ -24,16 +24,16 @@ impl Value { } /// - /// Unwraps the value. + /// Unwraps the value and returns a reference. /// /// # Panics /// - /// If the value is computed. + /// If the value is evaluated. /// pub fn unwrap_value(&self) -> &T { match self { Self::Value(value) => value, - _ => panic!("Value is not computed"), + _ => panic!("Value is not evaluated"), } } } diff --git a/compiler_tester/src/compilers/downloader/solc_list.rs b/compiler_tester/src/compilers/downloader/solc_list.rs index a40cf009..517e5720 100644 --- a/compiler_tester/src/compilers/downloader/solc_list.rs +++ b/compiler_tester/src/compilers/downloader/solc_list.rs @@ -14,7 +14,7 @@ use serde::Deserialize; /// #[derive(Debug, Deserialize)] pub struct SolcList { - /// The compiler releases. + /// The collection of compiler releases. pub releases: BTreeMap, } diff --git a/compiler_tester/src/compilers/eravm.rs b/compiler_tester/src/compilers/eravm/mod.rs similarity index 71% rename from compiler_tester/src/compilers/eravm.rs rename to compiler_tester/src/compilers/eravm/mod.rs index 50e8da1e..15f40928 100644 --- a/compiler_tester/src/compilers/eravm.rs +++ b/compiler_tester/src/compilers/eravm/mod.rs @@ -2,32 +2,25 @@ //! The EraVM compiler. //! +pub mod mode; + use std::collections::BTreeMap; use std::collections::HashMap; -use super::mode::eravm::Mode as EraVMMode; -use super::mode::Mode; -use super::Compiler; +use crate::compilers::mode::Mode; +use crate::compilers::Compiler; use crate::vm::eravm::input::build::Build as EraVMBuild; use crate::vm::eravm::input::Input as EraVMInput; use crate::vm::evm::input::Input as EVMInput; +use self::mode::Mode as EraVMMode; + /// /// The EraVM compiler. /// #[derive(Default)] -#[allow(non_camel_case_types)] pub struct EraVMCompiler; -impl EraVMCompiler { - /// - /// A shortcut constructor. - /// - pub fn new() -> Self { - Self::default() - } -} - impl Compiler for EraVMCompiler { fn compile_for_eravm( &self, @@ -35,26 +28,24 @@ impl Compiler for EraVMCompiler { sources: Vec<(String, String)>, _libraries: BTreeMap>, _mode: &Mode, - _is_system_mode: bool, - _is_system_contracts_mode: bool, _debug_config: Option, ) -> anyhow::Result { + let last_contract = sources + .last() + .ok_or_else(|| anyhow::anyhow!("EraVM sources are empty"))? + .0 + .clone(); + let builds = sources - .iter() + .into_iter() .map(|(path, source_code)| { zkevm_assembly::Assembly::try_from(source_code.to_owned()) .map_err(anyhow::Error::new) .and_then(EraVMBuild::new) - .map(|build| (path.to_string(), build)) + .map(|build| (path, build)) }) .collect::>>()?; - let last_contract = sources - .last() - .ok_or_else(|| anyhow::anyhow!("Sources is empty"))? - .0 - .clone(); - Ok(EraVMInput::new(builds, None, last_contract)) } @@ -66,14 +57,14 @@ impl Compiler for EraVMCompiler { _mode: &Mode, _debug_config: Option, ) -> anyhow::Result { - anyhow::bail!("EraVM compiler does not support EVM compilation"); + anyhow::bail!("EraVM assembly cannot be compiled to EVM"); } - fn modes(&self) -> Vec { + fn all_modes(&self) -> Vec { vec![EraVMMode::default().into()] } - fn has_multiple_contracts(&self) -> bool { + fn allows_multi_contract_files(&self) -> bool { false } } diff --git a/compiler_tester/src/compilers/mode/eravm.rs b/compiler_tester/src/compilers/eravm/mode.rs similarity index 100% rename from compiler_tester/src/compilers/mode/eravm.rs rename to compiler_tester/src/compilers/eravm/mode.rs diff --git a/compiler_tester/src/compilers/llvm.rs b/compiler_tester/src/compilers/llvm/mod.rs similarity index 83% rename from compiler_tester/src/compilers/llvm.rs rename to compiler_tester/src/compilers/llvm/mod.rs index 94ad2a85..c863000f 100644 --- a/compiler_tester/src/compilers/llvm.rs +++ b/compiler_tester/src/compilers/llvm/mod.rs @@ -2,19 +2,22 @@ //! The LLVM compiler. //! +pub mod mode; + use std::collections::BTreeMap; use std::collections::HashMap; use sha3::Digest; -use super::mode::llvm::Mode as LLVMMode; -use super::mode::Mode; -use super::Compiler; +use crate::compilers::mode::Mode; +use crate::compilers::Compiler; use crate::vm::eravm::input::build::Build as EraVMBuild; use crate::vm::eravm::input::Input as EraVMInput; use crate::vm::evm::input::build::Build as EVMBuild; use crate::vm::evm::input::Input as EVMInput; +use self::mode::Mode as LLVMMode; + /// /// The LLVM compiler. /// @@ -23,7 +26,7 @@ pub struct LLVMCompiler; lazy_static::lazy_static! { /// - /// The LLVM compiler supported modes. + /// All supported modes. /// static ref MODES: Vec = { era_compiler_llvm_context::OptimizerSettings::combinations() @@ -33,15 +36,6 @@ lazy_static::lazy_static! { }; } -impl LLVMCompiler { - /// - /// A shortcut constructor. - /// - pub fn new() -> Self { - Self::default() - } -} - impl Compiler for LLVMCompiler { fn compile_for_eravm( &self, @@ -49,20 +43,24 @@ impl Compiler for LLVMCompiler { sources: Vec<(String, String)>, _libraries: BTreeMap>, mode: &Mode, - _is_system_mode: bool, - _is_system_contracts_mode: bool, debug_config: Option, ) -> anyhow::Result { let mode = LLVMMode::unwrap(mode); + let last_contract = sources + .last() + .ok_or_else(|| anyhow::anyhow!("LLVM IR sources are empty"))? + .0 + .clone(); + let builds = sources - .iter() + .into_iter() .map(|(path, source)| { let llvm = inkwell::context::Context::create(); let memory_buffer = inkwell::memory_buffer::MemoryBuffer::create_from_memory_range_copy( source.as_bytes(), - path, + path.as_str(), ); let module = llvm .create_module_from_ir(memory_buffer) @@ -76,23 +74,17 @@ impl Compiler for LLVMCompiler { >::new( &llvm, module, optimizer, None, true, debug_config.clone() ); - let build = context.build(path, Some(source_hash))?; + let build = context.build(path.as_str(), Some(source_hash))?; let assembly = zkevm_assembly::Assembly::from_string( build.assembly_text, build.metadata_hash, )?; let build = EraVMBuild::new(assembly)?; - Ok((path.to_owned(), build)) + Ok((path, build)) }) .collect::>>()?; - let last_contract = sources - .last() - .ok_or_else(|| anyhow::anyhow!("Sources is empty"))? - .0 - .clone(); - Ok(EraVMInput::new(builds, None, last_contract)) } @@ -106,22 +98,28 @@ impl Compiler for LLVMCompiler { ) -> anyhow::Result { let mode = LLVMMode::unwrap(mode); + let last_contract = sources + .last() + .ok_or_else(|| anyhow::anyhow!("LLVM IR sources are empty"))? + .0 + .clone(); + let builds = sources - .iter() + .into_iter() .map(|(path, source)| { + let optimizer = + era_compiler_llvm_context::Optimizer::new(mode.llvm_optimizer_settings.clone()); + let source_hash = sha3::Keccak256::digest(source.as_bytes()).into(); + let llvm = inkwell::context::Context::create(); let memory_buffer = inkwell::memory_buffer::MemoryBuffer::create_from_memory_range_copy( source.as_bytes(), - path, + path.as_str(), ); let module = llvm .create_module_from_ir(memory_buffer) .map_err(|error| anyhow::anyhow!(error.to_string()))?; - let optimizer = - era_compiler_llvm_context::Optimizer::new(mode.llvm_optimizer_settings.clone()); - let source_hash = sha3::Keccak256::digest(source.as_bytes()).into(); - let context = era_compiler_llvm_context::EVMContext::< era_compiler_llvm_context::EVMDummyDependency, >::new( @@ -133,27 +131,21 @@ impl Compiler for LLVMCompiler { true, debug_config.clone(), ); - let build = context.build(path, Some(source_hash))?; - let build = EVMBuild::new(era_compiler_llvm_context::EVMBuild::default(), build); + let build = context.build(path.as_str(), Some(source_hash))?; - Ok((path.to_owned(), build)) + let build = EVMBuild::new(era_compiler_llvm_context::EVMBuild::default(), build); + Ok((path, build)) }) .collect::>>()?; - let last_contract = sources - .last() - .ok_or_else(|| anyhow::anyhow!("Sources is empty"))? - .0 - .clone(); - Ok(EVMInput::new(builds, None, last_contract)) } - fn modes(&self) -> Vec { + fn all_modes(&self) -> Vec { MODES.clone() } - fn has_multiple_contracts(&self) -> bool { + fn allows_multi_contract_files(&self) -> bool { false } } diff --git a/compiler_tester/src/compilers/mode/llvm.rs b/compiler_tester/src/compilers/llvm/mode.rs similarity index 91% rename from compiler_tester/src/compilers/mode/llvm.rs rename to compiler_tester/src/compilers/llvm/mode.rs index c8053e2b..c34b849a 100644 --- a/compiler_tester/src/compilers/mode/llvm.rs +++ b/compiler_tester/src/compilers/llvm/mode.rs @@ -2,9 +2,9 @@ //! The compiler tester LLVM mode. //! -use crate::llvm_options::LLVMOptions; +use crate::compilers::mode::llvm_options::LLVMOptions; -use super::Mode as ModeWrapper; +use crate::compilers::mode::Mode as ModeWrapper; /// /// The compiler tester LLVM mode. diff --git a/compiler_tester/src/compilers/mod.rs b/compiler_tester/src/compilers/mod.rs index fc1025fa..be24286d 100644 --- a/compiler_tester/src/compilers/mod.rs +++ b/compiler_tester/src/compilers/mod.rs @@ -13,11 +13,11 @@ pub mod yul; use std::collections::BTreeMap; -use self::mode::Mode; - use crate::vm::eravm::input::Input as EraVMInput; use crate::vm::evm::input::Input as EVMInput; +use self::mode::Mode; + /// /// The compiler trait. /// @@ -25,15 +25,12 @@ pub trait Compiler: Send + Sync + 'static { /// /// Compile all sources for EraVM. /// - #[allow(clippy::too_many_arguments)] fn compile_for_eravm( &self, test_path: String, sources: Vec<(String, String)>, libraries: BTreeMap>, mode: &Mode, - is_system_mode: bool, - is_system_contracts_mode: bool, debug_config: Option, ) -> anyhow::Result; @@ -50,12 +47,12 @@ pub trait Compiler: Send + Sync + 'static { ) -> anyhow::Result; /// - /// Returns supported compiler modes. + /// Returns all supported combinations of compiler settings. /// - fn modes(&self) -> Vec; + fn all_modes(&self) -> Vec; /// /// Whether one source file can contains multiple contracts. /// - fn has_multiple_contracts(&self) -> bool; + fn allows_multi_contract_files(&self) -> bool; } diff --git a/compiler_tester/src/llvm_options.rs b/compiler_tester/src/compilers/mode/llvm_options.rs similarity index 100% rename from compiler_tester/src/llvm_options.rs rename to compiler_tester/src/compilers/mode/llvm_options.rs diff --git a/compiler_tester/src/compilers/mode/mod.rs b/compiler_tester/src/compilers/mode/mod.rs index 3b494a32..8f6493f9 100644 --- a/compiler_tester/src/compilers/mode/mod.rs +++ b/compiler_tester/src/compilers/mode/mod.rs @@ -2,42 +2,53 @@ //! The compiler mode. //! -pub mod eravm; -pub mod llvm; -pub mod solidity; -pub mod vyper; -pub mod yul; - -use self::eravm::Mode as EraVMMode; -use self::llvm::Mode as LLVMMode; -use self::solidity::Mode as SolidityMode; -use self::vyper::Mode as VyperMode; -use self::yul::Mode as YulMode; +pub mod llvm_options; + +use std::collections::HashSet; + +use crate::compilers::eravm::mode::Mode as EraVMMode; +use crate::compilers::llvm::mode::Mode as LLVMMode; +use crate::compilers::solidity::mode::Mode as SolidityMode; +use crate::compilers::solidity::upstream::mode::Mode as SolidityUpstreamMode; +use crate::compilers::vyper::mode::Mode as VyperMode; +use crate::compilers::yul::mode::Mode as YulMode; /// /// The compiler mode. /// #[derive(Debug, Clone)] -#[allow(non_camel_case_types)] #[allow(clippy::upper_case_acronyms)] pub enum Mode { - /// The `Yul` mode. - Yul(YulMode), /// The `Solidity` mode. Solidity(SolidityMode), + /// The `Solidity` upstream mode. + SolidityUpstream(SolidityUpstreamMode), + /// The `Yul` mode. + Yul(YulMode), + /// The `Vyper` mode. + Vyper(VyperMode), /// The `LLVM` mode. LLVM(LLVMMode), /// The `EraVM` mode. EraVM(EraVMMode), - /// The `Vyper` mode. - Vyper(VyperMode), } impl Mode { + /// + /// Sets the system mode if applicable. + /// + pub fn set_system_mode(&mut self, value: bool) { + match self { + Self::Solidity(mode) => mode.is_system_mode = value, + Self::Yul(mode) => mode.is_system_mode = value, + _ => {} + } + } + /// /// Checks if the mode is compatible with the filters. /// - pub fn check_filters(&self, filters: &[String]) -> bool { + pub fn check_filters(&self, filters: &HashSet) -> bool { filters.is_empty() || filters .iter() @@ -79,6 +90,7 @@ impl Mode { pub fn check_version(&self, versions: &semver::VersionReq) -> bool { let version = match self { Mode::Solidity(mode) => &mode.solc_version, + Mode::SolidityUpstream(mode) => &mode.solc_version, Mode::Vyper(mode) => &mode.vyper_version, _ => return false, }; @@ -91,6 +103,7 @@ impl Mode { pub fn check_pragmas(&self, sources: &[(String, String)]) -> bool { match self { Mode::Solidity(mode) => mode.check_pragmas(sources), + Mode::SolidityUpstream(mode) => mode.check_pragmas(sources), Mode::Vyper(mode) => mode.check_pragmas(sources), _ => true, } @@ -102,6 +115,7 @@ impl Mode { pub fn check_ethereum_tests_params(&self, params: &solidity_adapter::Params) -> bool { match self { Mode::Solidity(mode) => mode.check_ethereum_tests_params(params), + Mode::SolidityUpstream(mode) => mode.check_ethereum_tests_params(params), _ => true, } } @@ -112,6 +126,7 @@ impl Mode { pub fn llvm_optimizer_settings(&self) -> Option<&era_compiler_llvm_context::OptimizerSettings> { match self { Mode::Solidity(mode) => Some(&mode.llvm_optimizer_settings), + Mode::SolidityUpstream(_mode) => None, Mode::Yul(mode) => Some(&mode.llvm_optimizer_settings), Mode::Vyper(mode) => Some(&mode.llvm_optimizer_settings), Mode::LLVM(mode) => Some(&mode.llvm_optimizer_settings), @@ -169,7 +184,7 @@ impl Mode { if filter.starts_with('^') { match self { - Self::Solidity(_) | Self::Vyper(_) => { + Self::Solidity(_) | Self::SolidityUpstream(_) | Self::Vyper(_) => { current = regex::Regex::new("[+]") .expect("Always valid") .replace_all(current.as_str(), "^") @@ -189,15 +204,27 @@ impl Mode { } } +impl From for Mode { + fn from(inner: SolidityMode) -> Self { + Self::Solidity(inner) + } +} + +impl From for Mode { + fn from(inner: SolidityUpstreamMode) -> Self { + Self::SolidityUpstream(inner) + } +} + impl From for Mode { fn from(inner: YulMode) -> Self { Self::Yul(inner) } } -impl From for Mode { - fn from(inner: SolidityMode) -> Self { - Self::Solidity(inner) +impl From for Mode { + fn from(inner: VyperMode) -> Self { + Self::Vyper(inner) } } @@ -213,20 +240,15 @@ impl From for Mode { } } -impl From for Mode { - fn from(inner: VyperMode) -> Self { - Self::Vyper(inner) - } -} - impl std::fmt::Display for Mode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Yul(inner) => write!(f, "{inner}"), Self::Solidity(inner) => write!(f, "{inner}"), + Self::SolidityUpstream(inner) => write!(f, "{inner}"), + Self::Yul(inner) => write!(f, "{inner}"), + Self::Vyper(inner) => write!(f, "{inner}"), Self::LLVM(inner) => write!(f, "{inner}"), Self::EraVM(inner) => write!(f, "{inner}"), - Self::Vyper(inner) => write!(f, "{inner}"), } } } diff --git a/compiler_tester/src/compilers/solidity/solc_cache_key.rs b/compiler_tester/src/compilers/solidity/cache_key.rs similarity index 85% rename from compiler_tester/src/compilers/solidity/solc_cache_key.rs rename to compiler_tester/src/compilers/solidity/cache_key.rs index a2a047ba..2bb9e654 100644 --- a/compiler_tester/src/compilers/solidity/solc_cache_key.rs +++ b/compiler_tester/src/compilers/solidity/cache_key.rs @@ -1,12 +1,12 @@ //! -//! The Solidity subprocess compiler cache key. +//! The Solidity compiler cache key. //! /// -/// The Solidity subprocess compiler cache key. +/// The Solidity compiler cache key. /// #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct SolcCacheKey { +pub struct CacheKey { /// The test path. pub test_path: String, /// The Solidity compiler version. @@ -19,7 +19,7 @@ pub struct SolcCacheKey { pub optimize: bool, } -impl SolcCacheKey { +impl CacheKey { /// /// A shortcut constructor. /// diff --git a/compiler_tester/src/compilers/solidity/mod.rs b/compiler_tester/src/compilers/solidity/mod.rs index 872d3cbc..ae262180 100644 --- a/compiler_tester/src/compilers/solidity/mod.rs +++ b/compiler_tester/src/compilers/solidity/mod.rs @@ -1,8 +1,10 @@ //! -//! The Solidity compiler wrapper. +//! The Solidity compiler. //! -pub mod solc_cache_key; +pub mod cache_key; +pub mod mode; +pub mod upstream; use std::collections::BTreeMap; use std::collections::HashMap; @@ -10,39 +12,39 @@ use std::path::Path; use itertools::Itertools; -use super::cache::Cache; -use super::mode::solidity::Mode as SolidityMode; -use super::mode::Mode; -use super::Compiler; +use crate::compilers::cache::Cache; +use crate::compilers::mode::Mode; +use crate::compilers::Compiler; use crate::vm::eravm::input::build::Build as EraVMBuild; use crate::vm::eravm::input::Input as EraVMInput; use crate::vm::evm::input::build::Build as EVMBuild; use crate::vm::evm::input::Input as EVMInput; -use self::solc_cache_key::SolcCacheKey; +use self::cache_key::CacheKey; +use self::mode::Mode as SolidityMode; /// -/// The Solidity compiler wrapper. +/// The Solidity compiler. /// pub struct SolidityCompiler { /// The `solc` process output cache. - cache: Cache, + cache: Cache, } lazy_static::lazy_static! { /// - /// The Solidity compiler supported modes. + /// All supported modes. /// /// All compilers must be downloaded before initialization. /// static ref MODES: Vec = { let mut solc_pipeline_versions = Vec::new(); for (pipeline, optimize, via_ir) in [ - (era_compiler_solidity::SolcPipeline::Yul, false, true), - (era_compiler_solidity::SolcPipeline::Yul, true, true), (era_compiler_solidity::SolcPipeline::EVMLA, false, false), (era_compiler_solidity::SolcPipeline::EVMLA, true, false), (era_compiler_solidity::SolcPipeline::EVMLA, true, true), + (era_compiler_solidity::SolcPipeline::Yul, false, true), + (era_compiler_solidity::SolcPipeline::Yul, true, true), ] { for version in SolidityCompiler::all_versions(pipeline, via_ir).expect("`solc` versions analysis error") { solc_pipeline_versions.push((pipeline, optimize, via_ir, version)); @@ -60,6 +62,8 @@ lazy_static::lazy_static! { via_ir, optimize, llvm_optimizer_settings, + false, + false, ) .into() }, @@ -68,6 +72,12 @@ lazy_static::lazy_static! { }; } +impl Default for SolidityCompiler { + fn default() -> Self { + Self::new() + } +} + impl SolidityCompiler { /// The compiler binaries directory. const DIRECTORY: &'static str = "solc-bin/"; @@ -85,18 +95,18 @@ impl SolidityCompiler { } /// - /// Returns the `solc` compiler path by version. + /// Returns the `solc` executable by its version. /// - pub fn get_solc_by_version( + pub fn executable( version: &semver::Version, ) -> anyhow::Result { era_compiler_solidity::SolcCompiler::new(format!("{}/solc-{}", Self::DIRECTORY, version)) } /// - /// Returns the system contract `solc` compiler path. + /// Returns the `solc` executable used to compile system contracts. /// - pub fn get_system_contract_solc() -> anyhow::Result { + pub fn system_contract_executable() -> anyhow::Result { era_compiler_solidity::SolcCompiler::new(format!( "{}/solc-system-contracts", Self::DIRECTORY @@ -111,7 +121,7 @@ impl SolidityCompiler { via_ir: bool, ) -> anyhow::Result> { let mut versions = Vec::new(); - for entry in std::fs::read_dir("./solc-bin/")? { + for entry in std::fs::read_dir(Self::DIRECTORY)? { let entry = entry?; let path = entry.path(); let entry_type = entry.file_type().map_err(|error| { @@ -137,7 +147,9 @@ impl SolidityCompiler { Ok(version) => version, Err(_) => continue, }; - if era_compiler_solidity::SolcPipeline::Yul == pipeline && version.minor < 8 { + if era_compiler_solidity::SolcPipeline::Yul == pipeline + && version < era_compiler_solidity::SolcCompiler::FIRST_YUL_VERSION + { continue; } if era_compiler_solidity::SolcPipeline::EVMLA == pipeline @@ -155,16 +167,15 @@ impl SolidityCompiler { /// /// Runs the solc subprocess and returns the output. /// - fn run_solc( + fn standard_json_output( sources: &[(String, String)], libraries: &BTreeMap>, mode: &SolidityMode, - is_system_contracts_mode: bool, ) -> anyhow::Result { - let mut solc = if is_system_contracts_mode { - Self::get_system_contract_solc() + let mut solc = if mode.is_system_contracts_mode { + Self::system_contract_executable() } else { - Self::get_solc_by_version(&mode.solc_version) + Self::executable(&mode.solc_version) }?; let output_selection = @@ -181,7 +192,9 @@ impl SolidityCompiler { None, ); - let evm_version = if mode.solc_version == semver::Version::new(0, 8, 24) { + let evm_version = if mode.solc_version >= semver::Version::new(0, 8, 24) + /* TODO */ + { Some(era_compiler_common::EVMVersion::Cancun) } else { None @@ -198,7 +211,7 @@ impl SolidityCompiler { mode.via_ir, None, ) - .map_err(|error| anyhow::anyhow!("Failed to build solc input standard json: {}", error))?; + .map_err(|error| anyhow::anyhow!("Solidity standard JSON I/O error: {}", error))?; let allow_paths = Path::new(Self::SOLC_ALLOW_PATHS) .canonicalize() @@ -216,17 +229,16 @@ impl SolidityCompiler { } /// - /// Computes or loads from the cache solc output. Updates the cache if needed. + /// Evaluates the standard JSON output or loads it from the cache. /// - fn run_solc_cached( + fn standard_json_output_cached( &self, test_path: String, sources: &[(String, String)], libraries: &BTreeMap>, mode: &SolidityMode, - is_system_contracts_mode: bool, ) -> anyhow::Result { - let cache_key = SolcCacheKey::new( + let cache_key = CacheKey::new( test_path, mode.solc_version.clone(), mode.solc_pipeline, @@ -235,8 +247,8 @@ impl SolidityCompiler { ); if !self.cache.contains(&cache_key) { - self.cache.compute(cache_key.clone(), || { - Self::run_solc(sources, libraries, mode, is_system_contracts_mode) + self.cache.evaluate(cache_key.clone(), || { + Self::standard_json_output(sources, libraries, mode) }); } @@ -252,7 +264,7 @@ impl SolidityCompiler { let files = solc_output .contracts .as_ref() - .ok_or_else(|| anyhow::anyhow!("Contracts not found in the output"))?; + .ok_or_else(|| anyhow::anyhow!("Solidity contracts not found in the output"))?; let mut method_identifiers = BTreeMap::new(); for (path, contracts) in files.iter() { @@ -261,12 +273,14 @@ impl SolidityCompiler { for (entry, selector) in contract .evm .as_ref() - .ok_or_else(|| anyhow::anyhow!("EVM for contract {}:{} not found", path, name))? + .ok_or_else(|| { + anyhow::anyhow!("EVM object of the contract `{}:{}` not found", path, name) + })? .method_identifiers .as_ref() .ok_or_else(|| { anyhow::anyhow!( - "Method identifiers for contract {}:{} not found", + "Method identifiers of the contract `{}:{}` not found", path, name ) @@ -277,7 +291,8 @@ impl SolidityCompiler { u32::from_str_radix(selector, era_compiler_common::BASE_HEXADECIMAL) .map_err(|error| { anyhow::anyhow!( - "Invalid selector from the Solidity compiler: {}", + "Invalid selector `{}` received from the Solidity compiler: {}", + selector, error ) })?; @@ -299,26 +314,24 @@ impl SolidityCompiler { solc_output .sources .as_ref() - .ok_or_else(|| anyhow::anyhow!("Sources not found in the output")) + .ok_or_else(|| { + anyhow::anyhow!( + "The Solidity sources are empty. Found errors: {:?}", + solc_output.errors + ) + }) .and_then(|output_sources| { for (path, _source) in sources.iter().rev() { match output_sources .get(path) - .ok_or_else(|| anyhow::anyhow!("Last source not found in the output"))? + .ok_or_else(|| anyhow::anyhow!("The last source not found in the output"))? .last_contract_name() { Ok(name) => return Ok(format!("{path}:{name}")), Err(_error) => continue, } } - anyhow::bail!("Last contract not found in all contracts") - }) - .map_err(|error| { - anyhow::anyhow!( - "Failed to get the last contract: {}, output errors: {:?}", - error, - solc_output.errors - ) + anyhow::bail!("The last source not found in the output") }) } } @@ -330,21 +343,13 @@ impl Compiler for SolidityCompiler { sources: Vec<(String, String)>, libraries: BTreeMap>, mode: &Mode, - is_system_mode: bool, - is_system_contracts_mode: bool, debug_config: Option, ) -> anyhow::Result { let mode = SolidityMode::unwrap(mode); let mut solc_output = self - .run_solc_cached( - test_path, - &sources, - &libraries, - mode, - is_system_contracts_mode, - ) - .map_err(|error| anyhow::anyhow!("Failed to run solc: {}", error))?; + .standard_json_output_cached(test_path, &sources, &libraries, mode) + .map_err(|error| anyhow::anyhow!("Solidity standard JSON I/O error: {}", error))?; if let Some(errors) = solc_output.errors.as_deref() { let mut has_errors = false; @@ -358,7 +363,7 @@ impl Compiler for SolidityCompiler { } if has_errors { - anyhow::bail!("Errors found: {:?}", error_messages); + anyhow::bail!("`solc` errors found: {:?}", error_messages); } } @@ -366,7 +371,7 @@ impl Compiler for SolidityCompiler { .map_err(|error| anyhow::anyhow!("Failed to get method identifiers: {}", error))?; let last_contract = Self::get_last_contract(&solc_output, &sources) - .map_err(|error| anyhow::anyhow!("Failed to get last contract: {}", error))?; + .map_err(|error| anyhow::anyhow!("Failed to get the last contract: {}", error))?; let project = solc_output.try_to_project( sources.into_iter().collect::>(), @@ -378,7 +383,7 @@ impl Compiler for SolidityCompiler { let build = project.compile_to_eravm( mode.llvm_optimizer_settings.to_owned(), - is_system_mode, + mode.is_system_mode, false, zkevm_assembly::get_encoding_mode(), debug_config, @@ -435,9 +440,8 @@ impl Compiler for SolidityCompiler { ) -> anyhow::Result { let mode = SolidityMode::unwrap(mode); - let mut solc_output = self - .run_solc_cached(test_path, &sources, &libraries, mode, false) - .map_err(|error| anyhow::anyhow!("Failed to run solc: {}", error))?; + let mut solc_output = + self.standard_json_output_cached(test_path, &sources, &libraries, mode)?; if let Some(errors) = solc_output.errors.as_deref() { let mut has_errors = false; @@ -451,15 +455,13 @@ impl Compiler for SolidityCompiler { } if has_errors { - anyhow::bail!("Errors found: {:?}", error_messages); + anyhow::bail!("`solc` errors found: {:?}", error_messages); } } - let method_identifiers = Self::get_method_identifiers(&solc_output) - .map_err(|error| anyhow::anyhow!("Failed to get method identifiers: {}", error))?; + let method_identifiers = Self::get_method_identifiers(&solc_output)?; - let last_contract = Self::get_last_contract(&solc_output, &sources) - .map_err(|error| anyhow::anyhow!("Failed to get last contract: {}", error))?; + let last_contract = Self::get_last_contract(&solc_output, &sources)?; let project = solc_output.try_to_project( sources.into_iter().collect::>(), @@ -488,11 +490,11 @@ impl Compiler for SolidityCompiler { )) } - fn modes(&self) -> Vec { + fn all_modes(&self) -> Vec { MODES.clone() } - fn has_multiple_contracts(&self) -> bool { + fn allows_multi_contract_files(&self) -> bool { true } } diff --git a/compiler_tester/src/compilers/mode/solidity.rs b/compiler_tester/src/compilers/solidity/mode.rs similarity index 92% rename from compiler_tester/src/compilers/mode/solidity.rs rename to compiler_tester/src/compilers/solidity/mode.rs index dea07392..ffa6d27b 100644 --- a/compiler_tester/src/compilers/mode/solidity.rs +++ b/compiler_tester/src/compilers/solidity/mode.rs @@ -4,9 +4,9 @@ use itertools::Itertools; -use crate::llvm_options::LLVMOptions; +use crate::compilers::mode::llvm_options::LLVMOptions; -use super::Mode as ModeWrapper; +use crate::compilers::mode::Mode as ModeWrapper; /// /// The compiler tester Solidity mode. @@ -23,6 +23,10 @@ pub struct Mode { pub solc_optimize: bool, /// The optimizer settings. pub llvm_optimizer_settings: era_compiler_llvm_context::OptimizerSettings, + /// The system mode. + pub is_system_mode: bool, + /// The system contract mode. + pub is_system_contracts_mode: bool, } impl Mode { @@ -35,6 +39,8 @@ impl Mode { via_ir: bool, solc_optimize: bool, mut llvm_optimizer_settings: era_compiler_llvm_context::OptimizerSettings, + is_system_mode: bool, + is_system_contracts_mode: bool, ) -> Self { let llvm_options = LLVMOptions::get(); llvm_optimizer_settings.is_verify_each_enabled = llvm_options.is_verify_each_enabled(); @@ -46,6 +52,8 @@ impl Mode { via_ir, solc_optimize, llvm_optimizer_settings, + is_system_mode, + is_system_contracts_mode, } } diff --git a/compiler_tester/src/compilers/solidity/upstream/mod.rs b/compiler_tester/src/compilers/solidity/upstream/mod.rs new file mode 100644 index 00000000..88697a03 --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/mod.rs @@ -0,0 +1,380 @@ +//! +//! The upstream Solidity compiler. +//! + +pub mod mode; +pub mod solc; + +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::path::Path; + +use crate::compilers::cache::Cache; +use crate::compilers::mode::Mode; +use crate::compilers::solidity::cache_key::CacheKey; +use crate::compilers::Compiler; +use crate::vm::eravm::input::Input as EraVMInput; +use crate::vm::evm::input::build::Build as EVMBuild; +use crate::vm::evm::input::Input as EVMInput; + +use self::mode::Mode as SolidityUpstreamMode; +use self::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer; +use self::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection; +use self::solc::standard_json::input::Input as SolcStandardJsonInput; +use self::solc::standard_json::output::Output as SolcStandardJsonOutput; +use self::solc::Compiler as SolcUpstreamCompiler; + +/// +/// The upstream Solidity compiler. +/// +pub struct SolidityCompiler { + /// The `solc` process output cache. + cache: Cache, +} + +lazy_static::lazy_static! { + /// + /// The Solidity compiler supported modes. + /// + /// All compilers must be downloaded before initialization. + /// + static ref MODES: Vec = { + let mut modes = Vec::new(); + for (pipeline, optimize, via_ir) in [ + (era_compiler_solidity::SolcPipeline::EVMLA, false, false), + (era_compiler_solidity::SolcPipeline::EVMLA, false, true), + (era_compiler_solidity::SolcPipeline::EVMLA, true, false), + (era_compiler_solidity::SolcPipeline::EVMLA, true, true), + (era_compiler_solidity::SolcPipeline::Yul, false, true), + (era_compiler_solidity::SolcPipeline::Yul, true, true), + ] { + for version in SolidityCompiler::all_versions(pipeline, via_ir).expect("`solc` versions analysis error") { + modes.push(SolidityUpstreamMode::new(version, pipeline, via_ir, optimize).into()); + } + } + modes + }; +} + +impl Default for SolidityCompiler { + fn default() -> Self { + Self::new() + } +} + +impl SolidityCompiler { + /// The compiler binaries directory. + const DIRECTORY: &'static str = "solc-bin-upstream/"; + + /// The solc allow paths argument value. + const SOLC_ALLOW_PATHS: &'static str = "tests"; + + /// + /// A shortcut constructor. + /// + pub fn new() -> Self { + Self { + cache: Cache::new(), + } + } + + /// + /// Returns the `solc` executable by its version. + /// + pub fn executable(version: &semver::Version) -> anyhow::Result { + SolcUpstreamCompiler::new(format!("{}/solc-{}", Self::DIRECTORY, version)) + } + + /// + /// Returns the compiler versions downloaded for the specified compilation pipeline. + /// + pub fn all_versions( + pipeline: era_compiler_solidity::SolcPipeline, + via_ir: bool, + ) -> anyhow::Result> { + let mut versions = Vec::new(); + for entry in std::fs::read_dir(Self::DIRECTORY)? { + let entry = entry?; + let path = entry.path(); + let entry_type = entry.file_type().map_err(|error| { + anyhow::anyhow!( + "File `{}` type getting error: {}", + path.to_string_lossy(), + error + ) + })?; + if !entry_type.is_file() { + anyhow::bail!( + "Invalid `solc` binary file type: {}", + path.to_string_lossy() + ); + } + + let file_name = entry.file_name().to_string_lossy().to_string(); + let version_str = match file_name.strip_prefix("solc-") { + Some(version_str) => version_str, + None => continue, + }; + let version: semver::Version = match version_str.parse() { + Ok(version) => version, + Err(_) => continue, + }; + if era_compiler_solidity::SolcPipeline::Yul == pipeline + && version < SolcUpstreamCompiler::FIRST_YUL_VERSION + { + continue; + } + if era_compiler_solidity::SolcPipeline::EVMLA == pipeline + && via_ir + && version < SolcUpstreamCompiler::FIRST_VIA_IR_VERSION + { + continue; + } + + versions.push(version); + } + Ok(versions) + } + + /// + /// Runs the solc subprocess and returns the output. + /// + fn standard_json_output( + sources: &[(String, String)], + libraries: &BTreeMap>, + mode: &SolidityUpstreamMode, + ) -> anyhow::Result { + let mut solc = Self::executable(&mode.solc_version)?; + + let output_selection = + SolcStandardJsonInputSettingsSelection::new_required(mode.solc_pipeline); + + let optimizer = SolcStandardJsonInputSettingsOptimizer::new(mode.solc_optimize); + + let evm_version = if mode.solc_version >= SolcUpstreamCompiler::FIRST_CANCUN_VERSION { + Some(era_compiler_common::EVMVersion::Cancun) + } else { + None + }; + + let solc_input = SolcStandardJsonInput::try_from_sources( + evm_version, + sources.iter().cloned().collect(), + libraries.clone(), + None, + output_selection, + optimizer, + mode.via_ir, + ) + .map_err(|error| anyhow::anyhow!("Solidity standard JSON I/O error: {}", error))?; + + let allow_paths = Path::new(Self::SOLC_ALLOW_PATHS) + .canonicalize() + .expect("Always valid") + .to_string_lossy() + .to_string(); + + solc.standard_json(solc_input, None, vec![], Some(allow_paths)) + } + + /// + /// Evaluates the standard JSON output or loads it from the cache. + /// + fn standard_json_output_cached( + &self, + test_path: String, + sources: &[(String, String)], + libraries: &BTreeMap>, + mode: &SolidityUpstreamMode, + ) -> anyhow::Result { + let cache_key = CacheKey::new( + test_path, + mode.solc_version.clone(), + mode.solc_pipeline, + mode.via_ir, + mode.solc_optimize, + ); + + if !self.cache.contains(&cache_key) { + self.cache.evaluate(cache_key.clone(), || { + Self::standard_json_output(sources, libraries, mode) + }); + } + + self.cache.get_cloned(&cache_key) + } + + /// + /// Get the method identifiers from the solc output. + /// + fn get_method_identifiers( + solc_output: &SolcStandardJsonOutput, + ) -> anyhow::Result>> { + let files = solc_output + .contracts + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Solidity contracts not found in the output"))?; + + let mut method_identifiers = BTreeMap::new(); + for (path, contracts) in files.iter() { + for (name, contract) in contracts.iter() { + let mut contract_identifiers = BTreeMap::new(); + for (entry, selector) in contract + .evm + .as_ref() + .ok_or_else(|| { + anyhow::anyhow!("EVM object of the contract `{}:{}` not found", path, name) + })? + .method_identifiers + .as_ref() + .ok_or_else(|| { + anyhow::anyhow!( + "Method identifiers of the contract `{}:{}` not found", + path, + name + ) + })? + .iter() + { + let selector = + u32::from_str_radix(selector, era_compiler_common::BASE_HEXADECIMAL) + .map_err(|error| { + anyhow::anyhow!( + "Invalid selector `{}` received from the Solidity compiler: {}", + selector, + error + ) + })?; + contract_identifiers.insert(entry.clone(), selector); + } + method_identifiers.insert(format!("{path}:{name}"), contract_identifiers); + } + } + Ok(method_identifiers) + } + + /// + /// Get the last contract from the solc output. + /// + fn get_last_contract( + solc_output: &SolcStandardJsonOutput, + sources: &[(String, String)], + ) -> anyhow::Result { + solc_output + .sources + .as_ref() + .ok_or_else(|| { + anyhow::anyhow!( + "The Solidity sources are empty. Found errors: {:?}", + solc_output.errors + ) + }) + .and_then(|output_sources| { + for (path, _source) in sources.iter().rev() { + match output_sources + .get(path) + .ok_or_else(|| anyhow::anyhow!("The last source not found in the output"))? + .last_contract_name() + { + Ok(name) => return Ok(format!("{path}:{name}")), + Err(_error) => continue, + } + } + anyhow::bail!("The last source not found in the output") + }) + } +} + +impl Compiler for SolidityCompiler { + fn compile_for_eravm( + &self, + _test_path: String, + _sources: Vec<(String, String)>, + _libraries: BTreeMap>, + _mode: &Mode, + _debug_config: Option, + ) -> anyhow::Result { + anyhow::bail!("The upstream Solidity compiler cannot compile for EraVM"); + } + + fn compile_for_evm( + &self, + test_path: String, + sources: Vec<(String, String)>, + libraries: BTreeMap>, + mode: &Mode, + _debug_config: Option, + ) -> anyhow::Result { + let mode = SolidityUpstreamMode::unwrap(mode); + + let solc_output = + self.standard_json_output_cached(test_path, &sources, &libraries, mode)?; + + if let Some(errors) = solc_output.errors.as_deref() { + let mut has_errors = false; + let mut error_messages = Vec::with_capacity(errors.len()); + + for error in errors.iter() { + if error.severity.as_str() == "error" { + has_errors = true; + error_messages.push(error.formatted_message.to_owned()); + } + } + + if has_errors { + anyhow::bail!("`solc` errors found: {:?}", error_messages); + } + } + + let method_identifiers = Self::get_method_identifiers(&solc_output)?; + + let last_contract = Self::get_last_contract(&solc_output, &sources)?; + + let contracts = solc_output + .contracts + .ok_or_else(|| anyhow::anyhow!("Solidity contracts not found in the output"))?; + + let mut builds = HashMap::with_capacity(contracts.len()); + for (file, contracts) in contracts.into_iter() { + for (name, contract) in contracts.into_iter() { + let path = format!("{file}:{name}"); + let bytecode_string = contract + .evm + .as_ref() + .ok_or_else(|| { + anyhow::anyhow!("EVM object of the contract `{path}` not found") + })? + .bytecode + .as_ref() + .ok_or_else(|| { + anyhow::anyhow!("EVM bytecode of the contract `{path}` not found") + })? + .object + .as_str(); + let build = EVMBuild::new( + era_compiler_llvm_context::EVMBuild::new( + "".to_owned(), + None, + hex::decode(bytecode_string).expect("Always valid"), + ), + era_compiler_llvm_context::EVMBuild::default(), + ); + builds.insert(path, build); + } + } + + Ok(EVMInput::new( + builds, + Some(method_identifiers), + last_contract, + )) + } + + fn all_modes(&self) -> Vec { + MODES.clone() + } + + fn allows_multi_contract_files(&self) -> bool { + true + } +} diff --git a/compiler_tester/src/compilers/solidity/upstream/mode.rs b/compiler_tester/src/compilers/solidity/upstream/mode.rs new file mode 100644 index 00000000..bffdcbf1 --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/mode.rs @@ -0,0 +1,126 @@ +//! +//! The compiler tester Solidity mode. +//! + +use itertools::Itertools; + +use crate::compilers::mode::Mode as ModeWrapper; + +/// +/// The compiler tester Solidity mode. +/// +#[derive(Debug, Clone)] +pub struct Mode { + /// The Solidity compiler version. + pub solc_version: semver::Version, + /// The Solidity compiler output type. + pub solc_pipeline: era_compiler_solidity::SolcPipeline, + /// Whether to enable the EVMLA codegen via Yul IR. + pub via_ir: bool, + /// Whether to run the Solidity compiler optimizer. + pub solc_optimize: bool, +} + +impl Mode { + /// + /// A shortcut constructor. + /// + pub fn new( + solc_version: semver::Version, + solc_pipeline: era_compiler_solidity::SolcPipeline, + via_ir: bool, + solc_optimize: bool, + ) -> Self { + Self { + solc_version, + solc_pipeline, + via_ir, + solc_optimize, + } + } + + /// + /// Unwrap mode. + /// + /// # Panics + /// + /// Will panic if the inner is non-Solidity mode. + /// + pub fn unwrap(mode: &ModeWrapper) -> &Self { + match mode { + ModeWrapper::SolidityUpstream(mode) => mode, + _ => panic!("Non-Solidity-upstream mode"), + } + } + + /// + /// Checks if the mode is compatible with the source code pragmas. + /// + pub fn check_pragmas(&self, sources: &[(String, String)]) -> bool { + sources.iter().all(|(_, source_code)| { + match source_code.lines().find_map(|line| { + let mut split = line.split_whitespace(); + if let (Some("pragma"), Some("solidity")) = (split.next(), split.next()) { + let version = split.join(",").replace(';', ""); + semver::VersionReq::parse(version.as_str()).ok() + } else { + None + } + }) { + Some(pragma_version_req) => pragma_version_req.matches(&self.solc_version), + None => true, + } + }) + } + + /// + /// Checks if the mode is compatible with the Ethereum tests params. + /// + pub fn check_ethereum_tests_params(&self, params: &solidity_adapter::Params) -> bool { + if !params.evm_version.matches_any(&[ + solidity_adapter::EVM::TangerineWhistle, + solidity_adapter::EVM::SpuriousDragon, + solidity_adapter::EVM::Byzantium, + solidity_adapter::EVM::Constantinople, + solidity_adapter::EVM::Petersburg, + solidity_adapter::EVM::Istanbul, + solidity_adapter::EVM::Berlin, + solidity_adapter::EVM::London, + solidity_adapter::EVM::Paris, + solidity_adapter::EVM::Shanghai, + solidity_adapter::EVM::Cancun, + ]) { + return false; + } + + match self.solc_pipeline { + era_compiler_solidity::SolcPipeline::Yul => { + params.compile_via_yul != solidity_adapter::CompileViaYul::False + && params.abi_encoder_v1_only != solidity_adapter::ABIEncoderV1Only::True + } + era_compiler_solidity::SolcPipeline::EVMLA if self.via_ir => { + params.compile_via_yul != solidity_adapter::CompileViaYul::False + && params.abi_encoder_v1_only != solidity_adapter::ABIEncoderV1Only::True + } + era_compiler_solidity::SolcPipeline::EVMLA => { + params.compile_via_yul != solidity_adapter::CompileViaYul::True + } + } + } +} + +impl std::fmt::Display for Mode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}{} {}", + match self.solc_pipeline { + era_compiler_solidity::SolcPipeline::Yul => "Y", + era_compiler_solidity::SolcPipeline::EVMLA if self.via_ir => "y", + era_compiler_solidity::SolcPipeline::EVMLA => "E", + }, + if self.solc_optimize { '+' } else { '-' }, + self.solc_version, + ) + } +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/mod.rs b/compiler_tester/src/compilers/solidity/upstream/solc/mod.rs new file mode 100644 index 00000000..40dbe3e2 --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/mod.rs @@ -0,0 +1,140 @@ +//! +//! The Solidity compiler. +//! + +pub mod standard_json; + +use std::io::Write; +use std::path::Path; + +use self::standard_json::input::Input as StandardJsonInput; +use self::standard_json::output::Output as StandardJsonOutput; + +/// +/// The Solidity compiler. +/// +pub struct Compiler { + /// The binary executable name. + pub executable: String, +} + +impl Compiler { + /// The first version of `solc`, where Yul codegen is considered robust enough. + pub const FIRST_YUL_VERSION: semver::Version = semver::Version::new(0, 8, 0); + + /// The first version of `solc`, where `--via-ir` codegen mode is supported. + pub const FIRST_VIA_IR_VERSION: semver::Version = semver::Version::new(0, 8, 13); + + /// The first version of `solc`, where `Cancun` EVM version is supported. + pub const FIRST_CANCUN_VERSION: semver::Version = semver::Version::new(0, 8, 24); + + /// + /// A shortcut constructor. + /// + /// Different tools may use different `executable` names. For example, the integration tester + /// uses `solc-` format. + /// + pub fn new(executable: String) -> anyhow::Result { + if let Err(error) = which::which(executable.as_str()) { + anyhow::bail!( + "The `{executable}` executable not found in ${{PATH}}: {}", + error + ); + } + Ok(Self { executable }) + } + + /// + /// Compiles the Solidity `--standard-json` input into Yul IR. + /// + pub fn standard_json( + &mut self, + input: StandardJsonInput, + base_path: Option, + include_paths: Vec, + allow_paths: Option, + ) -> anyhow::Result { + let mut command = std::process::Command::new(self.executable.as_str()); + command.stdin(std::process::Stdio::piped()); + command.stdout(std::process::Stdio::piped()); + command.arg("--standard-json"); + + if let Some(base_path) = base_path { + command.arg("--base-path"); + command.arg(base_path); + } + for include_path in include_paths.into_iter() { + command.arg("--include-path"); + command.arg(include_path); + } + if let Some(allow_paths) = allow_paths { + command.arg("--allow-paths"); + command.arg(allow_paths); + } + + let input_json = serde_json::to_vec(&input).expect("Always valid"); + + let process = command.spawn().map_err(|error| { + anyhow::anyhow!("{} subprocess spawning error: {:?}", self.executable, error) + })?; + process + .stdin + .as_ref() + .ok_or_else(|| anyhow::anyhow!("{} stdin getting error", self.executable))? + .write_all(input_json.as_slice()) + .map_err(|error| { + anyhow::anyhow!("{} stdin writing error: {:?}", self.executable, error) + })?; + + let output = process.wait_with_output().map_err(|error| { + anyhow::anyhow!("{} subprocess output error: {:?}", self.executable, error) + })?; + if !output.status.success() { + anyhow::bail!( + "{} error: {}", + self.executable, + String::from_utf8_lossy(output.stderr.as_slice()).to_string() + ); + } + + let output: StandardJsonOutput = era_compiler_common::deserialize_from_slice( + output.stdout.as_slice(), + ) + .map_err(|error| { + anyhow::anyhow!( + "{} subprocess output parsing error: {}\n{}", + self.executable, + error, + era_compiler_common::deserialize_from_slice::( + output.stdout.as_slice() + ) + .map(|json| serde_json::to_string_pretty(&json).expect("Always valid")) + .unwrap_or_else(|_| String::from_utf8_lossy(output.stdout.as_slice()).to_string()), + ) + })?; + + Ok(output) + } + + /// + /// The `solc` Yul validator. + /// + pub fn validate_yul(&self, path: &Path) -> anyhow::Result<()> { + let mut command = std::process::Command::new(self.executable.as_str()); + command.arg("--strict-assembly"); + command.arg(path); + + let output = command.output().map_err(|error| { + anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error) + })?; + if !output.status.success() { + anyhow::bail!( + "{} error: {}", + self.executable, + String::from_utf8_lossy(output.stderr.as_slice()).to_string() + ); + } + + Ok(()) + } +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/language.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/language.rs new file mode 100644 index 00000000..98424ec4 --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/language.rs @@ -0,0 +1,25 @@ +//! +//! The `solc --standard-json` input language. +//! + +use serde::Serialize; + +/// +/// The `solc --standard-json` input language. +/// +#[derive(Debug, Serialize, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Language { + /// The Solidity language. + Solidity, + /// The Yul IR. + Yul, +} + +impl std::fmt::Display for Language { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Solidity => write!(f, "Solidity"), + Self::Yul => write!(f, "Yul"), + } + } +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/mod.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/mod.rs new file mode 100644 index 00000000..ca47583a --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/mod.rs @@ -0,0 +1,70 @@ +//! +//! The `solc --standard-json` input. +//! + +pub mod language; +pub mod settings; +pub mod source; + +use std::collections::BTreeMap; +use std::collections::BTreeSet; + +use rayon::iter::IntoParallelIterator; +use rayon::iter::ParallelIterator; +use serde::Serialize; + +use self::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer; +use self::settings::selection::Selection as SolcStandardJsonInputSettingsSelection; + +use self::language::Language; +use self::settings::Settings; +use self::source::Source; + +/// +/// The `solc --standard-json` input. +/// +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Input { + /// The input language. + pub language: Language, + /// The input source code files hashmap. + pub sources: BTreeMap, + /// The compiler settings. + pub settings: Settings, +} + +impl Input { + /// + /// A shortcut constructor from source code. + /// + /// Only for the integration test purposes. + /// + pub fn try_from_sources( + evm_version: Option, + sources: BTreeMap, + libraries: BTreeMap>, + remappings: Option>, + output_selection: SolcStandardJsonInputSettingsSelection, + optimizer: SolcStandardJsonInputSettingsOptimizer, + via_ir: bool, + ) -> anyhow::Result { + let sources = sources + .into_par_iter() + .map(|(path, content)| (path, Source::from(content))) + .collect(); + + Ok(Self { + language: Language::Solidity, + sources, + settings: Settings::new( + evm_version, + libraries, + remappings, + output_selection, + via_ir, + optimizer, + ), + }) + } +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/mod.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/mod.rs new file mode 100644 index 00000000..8b2c97b5 --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/mod.rs @@ -0,0 +1,66 @@ +//! +//! The `solc --standard-json` input settings. +//! + +pub mod optimizer; +pub mod selection; + +use std::collections::BTreeMap; +use std::collections::BTreeSet; + +use serde::Serialize; + +use self::optimizer::Optimizer; +use self::selection::Selection; + +/// +/// The `solc --standard-json` input settings. +/// +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Settings { + /// The target EVM version. + #[serde(skip_serializing_if = "Option::is_none")] + pub evm_version: Option, + /// The linker library addresses. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub libraries: Option>>, + /// The sorted list of remappings. + #[serde(skip_serializing_if = "Option::is_none")] + pub remappings: Option>, + /// The output selection filters. + #[serde(skip_serializing_if = "Option::is_none")] + pub output_selection: Option, + /// Whether to compile via IR. Only for testing with solc >=0.8.13. + #[serde( + rename = "viaIR", + skip_serializing_if = "Option::is_none", + skip_deserializing + )] + pub via_ir: Option, + /// The optimizer settings. + pub optimizer: Optimizer, +} + +impl Settings { + /// + /// A shortcut constructor. + /// + pub fn new( + evm_version: Option, + libraries: BTreeMap>, + remappings: Option>, + output_selection: Selection, + via_ir: bool, + optimizer: Optimizer, + ) -> Self { + Self { + evm_version, + libraries: Some(libraries), + remappings, + output_selection: Some(output_selection), + via_ir: if via_ir { Some(true) } else { None }, + optimizer, + } + } +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/optimizer/details.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/optimizer/details.rs new file mode 100644 index 00000000..cfe64dd2 --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/optimizer/details.rs @@ -0,0 +1,66 @@ +//! +//! The `solc --standard-json` input settings optimizer details. +//! + +use serde::Serialize; + +/// +/// The `solc --standard-json` input settings optimizer details. +/// +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Details { + /// Whether the pass is enabled. + pub peephole: bool, + /// Whether the pass is enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub inliner: Option, + /// Whether the pass is enabled. + pub jumpdest_remover: bool, + /// Whether the pass is enabled. + pub order_literals: bool, + /// Whether the pass is enabled. + pub deduplicate: bool, + /// Whether the pass is enabled. + pub cse: bool, + /// Whether the pass is enabled. + pub constant_optimizer: bool, +} + +impl Details { + /// + /// A shortcut constructor. + /// + pub fn new( + peephole: bool, + inliner: Option, + jumpdest_remover: bool, + order_literals: bool, + deduplicate: bool, + cse: bool, + constant_optimizer: bool, + ) -> Self { + Self { + peephole, + inliner, + jumpdest_remover, + order_literals, + deduplicate, + cse, + constant_optimizer, + } + } + + /// + /// Creates a set of disabled optimizations. + /// + pub fn disabled(version: &semver::Version) -> Self { + let inliner = if version >= &semver::Version::new(0, 8, 5) { + Some(false) + } else { + None + }; + + Self::new(false, inliner, false, false, false, false, false) + } +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/optimizer/mod.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/optimizer/mod.rs new file mode 100644 index 00000000..28a158ad --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/optimizer/mod.rs @@ -0,0 +1,24 @@ +//! +//! The `solc --standard-json` input settings optimizer. +//! + +use serde::Serialize; + +/// +/// The `solc --standard-json` input settings optimizer. +/// +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Optimizer { + /// Whether the optimizer is enabled. + pub enabled: bool, +} + +impl Optimizer { + /// + /// A shortcut constructor. + /// + pub fn new(enabled: bool) -> Self { + Self { enabled } + } +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/selection/file/flag.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/selection/file/flag.rs new file mode 100644 index 00000000..dc86bc50 --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/selection/file/flag.rs @@ -0,0 +1,50 @@ +//! +//! The `solc --standard-json` expected output selection flag. +//! + +use serde::Serialize; + +/// +/// The `solc --standard-json` expected output selection flag. +/// +#[derive(Debug, Serialize, Clone, Copy, PartialEq, Eq, Hash)] +#[allow(non_camel_case_types)] +#[allow(clippy::upper_case_acronyms)] +pub enum Flag { + /// The combined bytecode. + #[serde(rename = "evm.bytecode")] + Bytecode, + /// The function signature hashes JSON. + #[serde(rename = "evm.methodIdentifiers")] + MethodIdentifiers, + /// The AST JSON. + #[serde(rename = "ast")] + AST, + /// The Yul IR. + #[serde(rename = "irOptimized")] + Yul, + /// The EVM legacy assembly JSON. + #[serde(rename = "evm.legacyAssembly")] + EVMLA, +} + +impl From for Flag { + fn from(pipeline: era_compiler_solidity::SolcPipeline) -> Self { + match pipeline { + era_compiler_solidity::SolcPipeline::Yul => Self::Yul, + era_compiler_solidity::SolcPipeline::EVMLA => Self::EVMLA, + } + } +} + +impl std::fmt::Display for Flag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Bytecode => write!(f, "evm.bytecode"), + Self::MethodIdentifiers => write!(f, "evm.methodIdentifiers"), + Self::AST => write!(f, "ast"), + Self::Yul => write!(f, "irOptimized"), + Self::EVMLA => write!(f, "evm.legacyAssembly"), + } + } +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/selection/file/mod.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/selection/file/mod.rs new file mode 100644 index 00000000..70b82559 --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/selection/file/mod.rs @@ -0,0 +1,40 @@ +//! +//! The `solc --standard-json` output file selection. +//! + +pub mod flag; + +use std::collections::HashSet; + +use serde::Serialize; + +use self::flag::Flag as SelectionFlag; + +/// +/// The `solc --standard-json` output file selection. +/// +#[derive(Debug, Default, Serialize)] +pub struct File { + /// The per-file output selections. + #[serde(rename = "", skip_serializing_if = "Option::is_none")] + pub per_file: Option>, + /// The per-contract output selections. + #[serde(rename = "*", skip_serializing_if = "Option::is_none")] + pub per_contract: Option>, +} + +impl File { + /// + /// Creates the selection required by EVM compilation process. + /// + pub fn new_required(pipeline: era_compiler_solidity::SolcPipeline) -> Self { + Self { + per_file: Some(HashSet::from_iter([SelectionFlag::AST])), + per_contract: Some(HashSet::from_iter([ + SelectionFlag::Bytecode, + SelectionFlag::MethodIdentifiers, + SelectionFlag::from(pipeline), + ])), + } + } +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/selection/mod.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/selection/mod.rs new file mode 100644 index 00000000..c8f84ecd --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/settings/selection/mod.rs @@ -0,0 +1,30 @@ +//! +//! The `solc --standard-json` output selection. +//! + +pub mod file; + +use serde::Serialize; + +use self::file::File as FileSelection; + +/// +/// The `solc --standard-json` output selection. +/// +#[derive(Debug, Default, Serialize)] +pub struct Selection { + /// Only the 'all' wildcard is available for robustness reasons. + #[serde(rename = "*", skip_serializing_if = "Option::is_none")] + pub all: Option, +} + +impl Selection { + /// + /// Creates the selection required by EVM compilation process. + /// + pub fn new_required(pipeline: era_compiler_solidity::SolcPipeline) -> Self { + Self { + all: Some(FileSelection::new_required(pipeline)), + } + } +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/source.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/source.rs new file mode 100644 index 00000000..1ed78873 --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/input/source.rs @@ -0,0 +1,43 @@ +//! +//! The `solc --standard-json` input source. +//! + +use std::io::Read; +use std::path::Path; + +use serde::Serialize; + +/// +/// The `solc --standard-json` input source. +/// +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Source { + /// The source code file content. + pub content: String, +} + +impl From for Source { + fn from(content: String) -> Self { + Self { content } + } +} + +impl TryFrom<&Path> for Source { + type Error = anyhow::Error; + + fn try_from(path: &Path) -> Result { + let content = if path.to_string_lossy() == "-" { + let mut solidity_code = String::with_capacity(16384); + std::io::stdin() + .read_to_string(&mut solidity_code) + .map_err(|error| anyhow::anyhow!(" reading error: {}", error))?; + solidity_code + } else { + std::fs::read_to_string(path) + .map_err(|error| anyhow::anyhow!("File {:?} reading error: {}", path, error))? + }; + + Ok(Self { content }) + } +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/mod.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/mod.rs new file mode 100644 index 00000000..f97af813 --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/mod.rs @@ -0,0 +1,6 @@ +//! +//! The `solc .sol --standard-json`. +//! + +pub mod input; +pub mod output; diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/contract/evm/bytecode.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/contract/evm/bytecode.rs new file mode 100644 index 00000000..7ebf7871 --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/contract/evm/bytecode.rs @@ -0,0 +1,15 @@ +//! +//! The `solc --standard-json` output contract EVM bytecode. +//! + +use serde::Deserialize; + +/// +/// The `solc --standard-json` output contract EVM bytecode. +/// +#[derive(Debug, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Bytecode { + /// The bytecode object. + pub object: String, +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/contract/evm/mod.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/contract/evm/mod.rs new file mode 100644 index 00000000..617d0af6 --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/contract/evm/mod.rs @@ -0,0 +1,25 @@ +//! +//! The `solc --standard-json` output contract EVM data. +//! + +pub mod bytecode; + +use std::collections::BTreeMap; + +use serde::Deserialize; + +use self::bytecode::Bytecode; + +/// +/// The `solc --standard-json` output contract EVM data. +/// +#[derive(Debug, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct EVM { + /// The contract bytecode. + /// Is reset by that of EraVM before yielding the compiled project artifacts. + pub bytecode: Option, + /// The contract function signatures. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub method_identifiers: Option>, +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/contract/mod.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/contract/mod.rs new file mode 100644 index 00000000..7e487a5b --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/contract/mod.rs @@ -0,0 +1,50 @@ +//! +//! The `solc --standard-json` output contract. +//! + +pub mod evm; + +use std::collections::BTreeMap; +use std::collections::HashSet; + +use serde::Deserialize; + +use self::evm::EVM; + +/// +/// The `solc --standard-json` output contract. +/// +#[derive(Debug, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Contract { + /// The contract ABI. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub abi: Option, + /// The contract metadata. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// The contract developer documentation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub devdoc: Option, + /// The contract user documentation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub userdoc: Option, + /// The contract storage layout. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub storage_layout: Option, + /// Contract's bytecode and related objects + #[serde(default, skip_serializing_if = "Option::is_none")] + pub evm: Option, + /// The contract optimized IR code. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ir_optimized: Option, + /// The contract EraVM bytecode hash. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub hash: Option, + /// The contract factory dependencies. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub factory_dependencies: Option>, + /// The contract missing libraries. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub missing_libraries: Option>, +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/error/mod.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/error/mod.rs new file mode 100644 index 00000000..2f532ed3 --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/error/mod.rs @@ -0,0 +1,37 @@ +//! +//! The `solc --standard-json` output error. +//! + +pub mod source_location; + +use serde::Deserialize; + +use self::source_location::SourceLocation; + +/// +/// The `solc --standard-json` output error. +/// +#[derive(Debug, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Error { + /// The component type. + pub component: String, + /// The error code. + pub error_code: Option, + /// The formatted error message. + pub formatted_message: String, + /// The non-formatted error message. + pub message: String, + /// The error severity. + pub severity: String, + /// The error location data. + pub source_location: Option, + /// The error type. + pub r#type: String, +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.formatted_message) + } +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/error/source_location.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/error/source_location.rs new file mode 100644 index 00000000..4a7e9543 --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/error/source_location.rs @@ -0,0 +1,46 @@ +//! +//! The `solc --standard-json` output error source location. +//! + +use std::str::FromStr; + +use serde::Deserialize; + +/// +/// The `solc --standard-json` output error source location. +/// +#[derive(Debug, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct SourceLocation { + /// The source file path. + pub file: String, + /// The start location. + pub start: isize, + /// The end location. + pub end: isize, +} + +impl FromStr for SourceLocation { + type Err = anyhow::Error; + + fn from_str(string: &str) -> Result { + let mut parts = string.split(':'); + let start = parts + .next() + .map(|string| string.parse::()) + .and_then(Result::ok) + .unwrap_or_default(); + let length = parts + .next() + .map(|string| string.parse::()) + .and_then(Result::ok) + .unwrap_or_default(); + let file = parts.next().unwrap_or_default().to_owned(); + + Ok(Self { + file, + start, + end: start + length, + }) + } +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/mod.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/mod.rs new file mode 100644 index 00000000..134e3fff --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/mod.rs @@ -0,0 +1,34 @@ +//! +//! The `solc --standard-json` output. +//! + +pub mod contract; +pub mod error; +pub mod source; + +use std::collections::BTreeMap; + +use serde::Deserialize; + +use self::contract::Contract; +use self::error::Error; +use self::source::Source; + +/// +/// The `solc --standard-json` output. +/// +#[derive(Debug, Deserialize, Clone)] +pub struct Output { + /// The file-contract hashmap. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub contracts: Option>>, + /// The source code mapping data. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sources: Option>, + /// The compilation errors and warnings. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub errors: Option>, + /// The `solc` compiler version. + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, +} diff --git a/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/source.rs b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/source.rs new file mode 100644 index 00000000..0c23f2cc --- /dev/null +++ b/compiler_tester/src/compilers/solidity/upstream/solc/standard_json/output/source.rs @@ -0,0 +1,42 @@ +//! +//! The `solc --standard-json` output source. +//! + +use serde::Deserialize; + +/// +/// The `solc --standard-json` output source. +/// +#[derive(Debug, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Source { + /// The source code ID. + pub id: usize, + /// The source code AST. + pub ast: Option, +} + +impl Source { + /// + /// Returns the name of the last contract. + /// + pub fn last_contract_name(&self) -> anyhow::Result { + self.ast + .as_ref() + .ok_or_else(|| anyhow::anyhow!("The AST is empty"))? + .get("nodes") + .and_then(|value| value.as_array()) + .ok_or_else(|| { + anyhow::anyhow!("The last contract cannot be found in an empty list of nodes") + })? + .iter() + .filter_map( + |node| match node.get("nodeType").and_then(|node| node.as_str()) { + Some("ContractDefinition") => Some(node.get("name")?.as_str()?.to_owned()), + _ => None, + }, + ) + .last() + .ok_or_else(|| anyhow::anyhow!("The last contract not found in the AST")) + } +} diff --git a/compiler_tester/src/compilers/vyper/vyper_cache_key.rs b/compiler_tester/src/compilers/vyper/cache_key.rs similarity index 78% rename from compiler_tester/src/compilers/vyper/vyper_cache_key.rs rename to compiler_tester/src/compilers/vyper/cache_key.rs index 306139dd..0e8ea4c3 100644 --- a/compiler_tester/src/compilers/vyper/vyper_cache_key.rs +++ b/compiler_tester/src/compilers/vyper/cache_key.rs @@ -1,12 +1,12 @@ //! -//! The Vyper subprocess compiler cache key. +//! The Vyper compiler cache key. //! /// -/// The Vyper subprocess compiler cache key. +/// The Vyper compiler cache key. /// #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct VyperCacheKey { +pub struct CacheKey { /// The test path. pub test_path: String, /// The Vyper compiler version. @@ -15,7 +15,7 @@ pub struct VyperCacheKey { pub optimize: bool, } -impl VyperCacheKey { +impl CacheKey { /// /// A shortcut constructor. /// diff --git a/compiler_tester/src/compilers/vyper/mod.rs b/compiler_tester/src/compilers/vyper/mod.rs index 1bba62d1..aaa2fb42 100644 --- a/compiler_tester/src/compilers/vyper/mod.rs +++ b/compiler_tester/src/compilers/vyper/mod.rs @@ -1,8 +1,9 @@ //! -//! The Vyper compiler wrapper. +//! The Vyper compiler. //! -pub mod vyper_cache_key; +pub mod cache_key; +pub mod mode; use std::collections::BTreeMap; use std::collections::HashMap; @@ -12,26 +13,26 @@ use std::str::FromStr; use itertools::Itertools; -use super::cache::Cache; -use super::mode::vyper::Mode as VyperMode; -use super::mode::Mode; -use super::Compiler; +use crate::compilers::cache::Cache; +use crate::compilers::mode::Mode; +use crate::compilers::Compiler; use crate::vm::eravm::input::build::Build as EraVMBuild; use crate::vm::eravm::input::Input as EraVMInput; -use self::vyper_cache_key::VyperCacheKey; +use self::cache_key::CacheKey; +use self::mode::Mode as VyperMode; /// -/// The Vyper compiler wrapper. +/// The Vyper compiler. /// pub struct VyperCompiler { /// The vyper process output cache. - cache: Cache, + cache: Cache, } lazy_static::lazy_static! { /// - /// The Vyper compiler supported modes. + /// All supported modes. /// static ref MODES: Vec = { let vyper_versions = VyperCompiler::all_versions().expect("`vyper` versions analysis error"); @@ -49,6 +50,12 @@ lazy_static::lazy_static! { }; } +impl Default for VyperCompiler { + fn default() -> Self { + Self::new() + } +} + impl VyperCompiler { /// The compiler binaries directory. pub const DIRECTORY: &'static str = "vyper-bin/"; @@ -63,11 +70,9 @@ impl VyperCompiler { } /// - /// Returns the Vyper compiler instance by version. + /// Returns the Vyper executable by its version. /// - fn get_vyper_by_version( - version: &semver::Version, - ) -> anyhow::Result { + fn executable(version: &semver::Version) -> anyhow::Result { era_compiler_vyper::VyperCompiler::new( format!("{}/vyper-{}", Self::DIRECTORY, version).as_str(), ) @@ -78,7 +83,7 @@ impl VyperCompiler { /// fn all_versions() -> anyhow::Result> { let mut versions = Vec::new(); - for entry in std::fs::read_dir("./vyper-bin/")? { + for entry in std::fs::read_dir(Self::DIRECTORY)? { let entry = entry?; let path = entry.path(); let entry_type = entry.file_type().map_err(|error| { @@ -110,24 +115,25 @@ impl VyperCompiler { } /// - /// Runs the vyper subprocess and returns the project. + /// Runs the `vyper` subprocess and returns the project. /// - fn run_vyper( - sources: &[(String, String)], + fn get_project( + sources: Vec<(String, String)>, mode: &VyperMode, ) -> anyhow::Result { - let vyper = Self::get_vyper_by_version(&mode.vyper_version)?; + let vyper = Self::executable(&mode.vyper_version)?; let paths = sources - .iter() + .into_iter() .map(|(path, _)| { - PathBuf::from_str(path).map_err(|error| anyhow::anyhow!("Invalid path: {}", error)) + PathBuf::from_str(path.as_str()).map_err(|error| { + anyhow::anyhow!("Invalid source code path `{}`: {}", path, error) + }) }) .collect::>>()?; - // TODO: set Cancun for v0.3.10 let evm_version = if mode.vyper_version == semver::Version::new(0, 3, 10) { - Some(era_compiler_common::EVMVersion::Shanghai) + Some(era_compiler_common::EVMVersion::Cancun) } else { None }; @@ -136,60 +142,26 @@ impl VyperCompiler { } /// - /// Computes or loads from the cache vyper project. Updates the cache if needed. + /// Evaluates the Vyper project or loads it from the cache. /// - fn run_vyper_cached( + fn get_project_cached( &self, test_path: String, - sources: &[(String, String)], + sources: Vec<(String, String)>, mode: &VyperMode, ) -> anyhow::Result { - let cache_key = - VyperCacheKey::new(test_path, mode.vyper_version.clone(), mode.vyper_optimize); + let cache_key = CacheKey::new(test_path, mode.vyper_version.clone(), mode.vyper_optimize); if !self.cache.contains(&cache_key) { self.cache - .compute(cache_key.clone(), || Self::run_vyper(sources, mode)); + .evaluate(cache_key.clone(), || Self::get_project(sources, mode)); } self.cache.get_cloned(&cache_key) } /// - /// Compile the vyper project. - /// - fn compile( - project: era_compiler_vyper::Project, - mode: &VyperMode, - debug_config: Option, - ) -> anyhow::Result> { - let build = project.compile( - None, - mode.llvm_optimizer_settings.to_owned(), - true, - zkevm_assembly::get_encoding_mode(), - vec![], - debug_config, - )?; - build - .contracts - .into_iter() - .map(|(path, contract)| { - let assembly = zkevm_assembly::Assembly::from_string( - contract.build.assembly_text, - contract.build.metadata_hash, - ) - .expect("Always valid"); - Ok(( - path, - EraVMBuild::new_with_hash(assembly, contract.build.bytecode_hash)?, - )) - }) - .collect() - } - - /// - /// Get the method identifiers from the solc output. + /// Get the method identifiers from the `vyper` output. /// fn get_method_identifiers( project: &era_compiler_vyper::Project, @@ -198,17 +170,18 @@ impl VyperCompiler { for (path, contract) in project.contracts.iter() { let contract_abi = match contract { era_compiler_vyper::Contract::Vyper(inner) => &inner.abi, - era_compiler_vyper::Contract::LLVMIR(_inner) => { - panic!("Only used in the Vyper CLI") - } - era_compiler_vyper::Contract::ZKASM(_inner) => panic!("Only used in the Vyper CLI"), + _ => unreachable!("Invalid contract type"), }; let mut contract_identifiers = BTreeMap::new(); for (entry, hash) in contract_abi.iter() { let selector = u32::from_str_radix(&hash[2..], era_compiler_common::BASE_HEXADECIMAL) .map_err(|error| { - anyhow::anyhow!("Invalid selector from the Vyper compiler: {}", error) + anyhow::anyhow!( + "Invalid selector `{}` received from the Vyper compiler: {}", + hash, + error + ) })?; contract_identifiers.insert(entry.clone(), selector); } @@ -225,11 +198,10 @@ impl VyperCompiler { debug_config: &era_compiler_llvm_context::DebugConfig, mode: &VyperMode, ) -> anyhow::Result<()> { - let vyper = Self::get_vyper_by_version(&mode.vyper_version)?; + let vyper = Self::executable(&mode.vyper_version)?; - // TODO: set Cancun for v0.3.10 let evm_version = if mode.vyper_version == semver::Version::new(0, 3, 10) { - Some(era_compiler_common::EVMVersion::Shanghai) + Some(era_compiler_common::EVMVersion::Cancun) } else { None }; @@ -259,8 +231,6 @@ impl Compiler for VyperCompiler { sources: Vec<(String, String)>, _libraries: BTreeMap>, mode: &Mode, - _is_system_mode: bool, - _is_system_contracts_mode: bool, debug_config: Option, ) -> anyhow::Result { let mode = VyperMode::unwrap(mode); @@ -269,21 +239,43 @@ impl Compiler for VyperCompiler { Self::dump_lll(&sources, debug_config, mode)?; } + let last_contract = sources + .last() + .ok_or_else(|| anyhow::anyhow!("The Vyper sources are empty"))? + .0 + .clone(); + let project = self - .run_vyper_cached(test_path, &sources, mode) + .get_project_cached(test_path, sources, mode) .map_err(|error| anyhow::anyhow!("Failed to get vyper project: {}", error))?; let method_identifiers = Self::get_method_identifiers(&project) .map_err(|error| anyhow::anyhow!("Failed to get method identifiers: {}", error))?; - let last_contract = sources - .last() - .ok_or_else(|| anyhow::anyhow!("Sources is empty"))? - .0 - .clone(); + let build = project.compile( + None, + mode.llvm_optimizer_settings.to_owned(), + true, + zkevm_assembly::get_encoding_mode(), + vec![], + debug_config, + )?; - let builds = Self::compile(project, mode, debug_config) - .map_err(|error| anyhow::anyhow!("Failed to compile the contracts: {}", error))?; + let builds = build + .contracts + .into_iter() + .map(|(path, contract)| { + zkevm_assembly::Assembly::from_string( + contract.build.assembly_text, + contract.build.metadata_hash, + ) + .map_err(anyhow::Error::new) + .and_then(|assembly| { + EraVMBuild::new_with_hash(assembly, contract.build.bytecode_hash) + }) + .map(|build| (path, build)) + }) + .collect::>>()?; Ok(EraVMInput::new( builds, @@ -294,20 +286,20 @@ impl Compiler for VyperCompiler { fn compile_for_evm( &self, - test_path: String, - sources: Vec<(String, String)>, - libraries: BTreeMap>, - mode: &Mode, - debug_config: Option, + _test_path: String, + _sources: Vec<(String, String)>, + _libraries: BTreeMap>, + _mode: &Mode, + _debug_config: Option, ) -> anyhow::Result { todo!() } - fn modes(&self) -> Vec { + fn all_modes(&self) -> Vec { MODES.clone() } - fn has_multiple_contracts(&self) -> bool { + fn allows_multi_contract_files(&self) -> bool { false } } diff --git a/compiler_tester/src/compilers/mode/vyper.rs b/compiler_tester/src/compilers/vyper/mode.rs similarity index 95% rename from compiler_tester/src/compilers/mode/vyper.rs rename to compiler_tester/src/compilers/vyper/mode.rs index 03cafae4..be52be98 100644 --- a/compiler_tester/src/compilers/mode/vyper.rs +++ b/compiler_tester/src/compilers/vyper/mode.rs @@ -2,9 +2,9 @@ //! The compiler tester Vyper mode. //! -use crate::llvm_options::LLVMOptions; +use crate::compilers::mode::llvm_options::LLVMOptions; -use super::Mode as ModeWrapper; +use crate::compilers::mode::Mode as ModeWrapper; /// /// The compiler tester Vyper mode. diff --git a/compiler_tester/src/compilers/yul.rs b/compiler_tester/src/compilers/yul/mod.rs similarity index 76% rename from compiler_tester/src/compilers/yul.rs rename to compiler_tester/src/compilers/yul/mod.rs index 97d361fb..2f9da9c4 100644 --- a/compiler_tester/src/compilers/yul.rs +++ b/compiler_tester/src/compilers/yul/mod.rs @@ -2,19 +2,22 @@ //! The Yul compiler. //! +pub mod mode; + use std::collections::BTreeMap; use std::collections::HashMap; use std::path::PathBuf; -use super::mode::yul::Mode as YulMode; -use super::mode::Mode; -use super::solidity::SolidityCompiler; -use super::Compiler; +use crate::compilers::mode::Mode; +use crate::compilers::solidity::SolidityCompiler; +use crate::compilers::Compiler; use crate::vm::eravm::input::build::Build as EraVMBuild; use crate::vm::eravm::input::Input as EraVMInput; use crate::vm::evm::input::build::Build as EVMBuild; use crate::vm::evm::input::Input as EVMInput; +use self::mode::Mode as YulMode; + /// /// The Yul compiler. /// @@ -23,25 +26,16 @@ pub struct YulCompiler; lazy_static::lazy_static! { /// - /// The Yul compiler supported modes. + /// All supported modes. /// static ref MODES: Vec = { era_compiler_llvm_context::OptimizerSettings::combinations() .into_iter() - .map(|llvm_optimizer_settings| YulMode::new(llvm_optimizer_settings).into()) + .map(|llvm_optimizer_settings| YulMode::new(llvm_optimizer_settings, false).into()) .collect::>() }; } -impl YulCompiler { - /// - /// A shortcut constructor. - /// - pub fn new() -> Self { - Self::default() - } -} - impl Compiler for YulCompiler { fn compile_for_eravm( &self, @@ -49,20 +43,26 @@ impl Compiler for YulCompiler { sources: Vec<(String, String)>, _libraries: BTreeMap>, mode: &Mode, - is_system_mode: bool, - _is_system_contracts_mode: bool, debug_config: Option, ) -> anyhow::Result { let mode = YulMode::unwrap(mode); - let solc_validator = if is_system_mode { + let solc_validator = if mode.is_system_mode { None } else { - Some(SolidityCompiler::get_system_contract_solc()?) + Some(SolidityCompiler::executable( + &era_compiler_solidity::SolcCompiler::LAST_SUPPORTED_VERSION, + )?) }; + let last_contract = sources + .last() + .ok_or_else(|| anyhow::anyhow!("Yul sources are empty"))? + .0 + .clone(); + let builds = sources - .iter() + .into_iter() .map(|(path, source)| { let project = era_compiler_solidity::Project::try_from_yul_string( PathBuf::from(path.as_str()).as_path(), @@ -73,33 +73,28 @@ impl Compiler for YulCompiler { let contract = project .compile_to_eravm( mode.llvm_optimizer_settings.to_owned(), - is_system_mode, + mode.is_system_mode, true, zkevm_assembly::get_encoding_mode(), debug_config.clone(), )? .contracts - .remove(path) + .remove(path.as_str()) .ok_or_else(|| { - anyhow::anyhow!("Contract `{}` not found in yul project", path) + anyhow::anyhow!("Contract `{}` not found in the Yul project", path) })?; + let assembly = zkevm_assembly::Assembly::from_string( contract.build.assembly_text, contract.build.metadata_hash, ) - .expect("Always valid"); + .map_err(anyhow::Error::new)?; let build = EraVMBuild::new_with_hash(assembly, contract.build.bytecode_hash)?; - Ok((path.to_owned(), build)) + Ok((path, build)) }) .collect::>>()?; - let last_contract = sources - .last() - .ok_or_else(|| anyhow::anyhow!("Sources is empty"))? - .0 - .clone(); - Ok(EraVMInput::new(builds, None, last_contract)) } @@ -113,10 +108,18 @@ impl Compiler for YulCompiler { ) -> anyhow::Result { let mode = YulMode::unwrap(mode); - let solc_validator = Some(SolidityCompiler::get_system_contract_solc()?); + let solc_validator = Some(SolidityCompiler::executable( + &era_compiler_solidity::SolcCompiler::LAST_SUPPORTED_VERSION, + )?); + + let last_contract = sources + .last() + .ok_or_else(|| anyhow::anyhow!("Yul sources are empty"))? + .0 + .clone(); let builds = sources - .iter() + .into_iter() .map(|(path, source)| { let project = era_compiler_solidity::Project::try_from_yul_string( PathBuf::from(path.as_str()).as_path(), @@ -131,30 +134,24 @@ impl Compiler for YulCompiler { debug_config.clone(), )? .contracts - .remove(path) + .remove(path.as_str()) .ok_or_else(|| { - anyhow::anyhow!("Contract `{}` not found in yul project", path) + anyhow::anyhow!("Contract `{}` not found in the Yul project", path) })?; let build = EVMBuild::new(contract.deploy_build, contract.runtime_build); - Ok((path.to_owned(), build)) + Ok((path, build)) }) .collect::>>()?; - let last_contract = sources - .last() - .ok_or_else(|| anyhow::anyhow!("Sources is empty"))? - .0 - .clone(); - Ok(EVMInput::new(builds, None, last_contract)) } - fn modes(&self) -> Vec { + fn all_modes(&self) -> Vec { MODES.clone() } - fn has_multiple_contracts(&self) -> bool { + fn allows_multi_contract_files(&self) -> bool { false } } diff --git a/compiler_tester/src/compilers/mode/yul.rs b/compiler_tester/src/compilers/yul/mode.rs similarity index 76% rename from compiler_tester/src/compilers/mode/yul.rs rename to compiler_tester/src/compilers/yul/mode.rs index 30dc2197..d25a4450 100644 --- a/compiler_tester/src/compilers/mode/yul.rs +++ b/compiler_tester/src/compilers/yul/mode.rs @@ -2,9 +2,9 @@ //! The compiler tester Yul mode. //! -use crate::llvm_options::LLVMOptions; +use crate::compilers::mode::llvm_options::LLVMOptions; -use super::Mode as ModeWrapper; +use crate::compilers::mode::Mode as ModeWrapper; /// /// The compiler tester Yul mode. @@ -13,19 +13,25 @@ use super::Mode as ModeWrapper; pub struct Mode { /// The optimizer settings. pub llvm_optimizer_settings: era_compiler_llvm_context::OptimizerSettings, + /// The system mode. + pub is_system_mode: bool, } impl Mode { /// /// A shortcut constructor. /// - pub fn new(mut llvm_optimizer_settings: era_compiler_llvm_context::OptimizerSettings) -> Self { + pub fn new( + mut llvm_optimizer_settings: era_compiler_llvm_context::OptimizerSettings, + is_system_mode: bool, + ) -> Self { let llvm_options = LLVMOptions::get(); llvm_optimizer_settings.is_verify_each_enabled = llvm_options.is_verify_each_enabled(); llvm_optimizer_settings.is_debug_logging_enabled = llvm_options.is_debug_logging_enabled(); Self { llvm_optimizer_settings, + is_system_mode, } } diff --git a/compiler_tester/src/directories/ethereum/mod.rs b/compiler_tester/src/directories/ethereum/mod.rs index c78891f9..abeb0201 100644 --- a/compiler_tester/src/directories/ethereum/mod.rs +++ b/compiler_tester/src/directories/ethereum/mod.rs @@ -8,10 +8,9 @@ use std::path::Path; use std::sync::Arc; use std::sync::Mutex; +use crate::directories::Collection; use crate::filters::Filters; -use crate::Summary; - -use super::TestsDirectory; +use crate::summary::Summary; use self::test::EthereumTest; @@ -25,44 +24,32 @@ impl EthereumDirectory { /// The index file name. /// const INDEX_NAME: &'static str = "index.yaml"; + + /// + /// Reads the Ethereum test index. + /// + pub fn read_index(directory_path: &Path) -> anyhow::Result { + let mut index_path = directory_path.to_path_buf(); + index_path.push(Self::INDEX_NAME); + let index_data = std::fs::read_to_string(index_path)?; + let index: solidity_adapter::FSEntity = serde_yaml::from_str(index_data.as_str())?; + Ok(index) + } } -impl TestsDirectory for EthereumDirectory { +impl Collection for EthereumDirectory { type Test = EthereumTest; - fn all_tests( + fn read_all( directory_path: &Path, _extension: &'static str, summary: Arc>, filters: &Filters, ) -> anyhow::Result> { - let mut index_path = directory_path.to_path_buf(); - index_path.push(Self::INDEX_NAME); - let index_data = std::fs::read_to_string(index_path)?; - let index: solidity_adapter::FSEntity = serde_yaml::from_str(index_data.as_str())?; - let tests = index + Ok(Self::read_index(directory_path)? .into_enabled_list(directory_path) .into_iter() .filter_map(|test| EthereumTest::new(test, summary.clone(), filters)) - .collect(); - - Ok(tests) - } - - fn single_test( - directory_path: &Path, - test_path: &Path, - _extension: &'static str, - summary: Arc>, - filters: &Filters, - ) -> anyhow::Result> { - let mut index_path = directory_path.to_path_buf(); - index_path.push(Self::INDEX_NAME); - let index_data = std::fs::read_to_string(index_path.as_path())?; - let index: solidity_adapter::FSEntity = serde_yaml::from_str(index_data.as_str())?; - index - .into_enabled_test(directory_path, test_path) - .ok_or_else(|| anyhow::anyhow!("Test not found")) - .map(|test| EthereumTest::new(test, summary, filters)) + .collect()) } } diff --git a/compiler_tester/src/directories/ethereum/test.rs b/compiler_tester/src/directories/ethereum/test.rs index ff2671c9..b6c3c460 100644 --- a/compiler_tester/src/directories/ethereum/test.rs +++ b/compiler_tester/src/directories/ethereum/test.rs @@ -12,18 +12,20 @@ use crate::compilers::Compiler; use crate::directories::Buildable; use crate::filters::Filters; use crate::summary::Summary; +use crate::target::Target; use crate::test::case::Case; -use crate::test::eravm::Test as EraVMTest; -use crate::test::evm::Test as EVMTest; -use crate::test::instance::Instance; -use crate::vm::eravm::deployers::address_predictor::AddressPredictor as EraVMAddressPredictor; -use crate::vm::evm::address_predictor::AddressPredictor as EVMAddressPredictor; -use crate::vm::AddressPredictorIterator; +use crate::test::Test; +use crate::vm::address_iterator::AddressIterator; +use crate::vm::eravm::address_iterator::EraVMAddressIterator; +use crate::vm::evm::address_iterator::EVMAddressIterator; /// /// The Ethereum compiler test. /// +#[derive(Debug)] pub struct EthereumTest { + /// The test identifier. + pub identifier: String, /// The index test entity. pub index_entity: solidity_adapter::EnabledTest, /// The test data. @@ -39,9 +41,9 @@ impl EthereumTest { summary: Arc>, filters: &Filters, ) -> Option { - let test_path = index_entity.path.to_string_lossy().to_string(); + let identifier = index_entity.path.to_string_lossy().to_string(); - if !filters.check_case_path(&test_path) { + if !filters.check_case_path(&identifier) { return None; } @@ -52,102 +54,94 @@ impl EthereumTest { let test = match solidity_adapter::Test::try_from(index_entity.path.as_path()) { Ok(test) => test, Err(error) => { - Summary::invalid(summary, None, test_path, error); + Summary::invalid(summary, None, identifier, error); return None; } }; - Some(Self { index_entity, test }) + Some(Self { + identifier, + index_entity, + test, + }) } -} -impl Buildable for EthereumTest { - fn build_for_eravm( - &self, - mode: Mode, - compiler: Arc, - summary: Arc>, - filters: &Filters, - debug_config: Option, - ) -> Option { - let test_path = self.index_entity.path.to_string_lossy().to_string(); - - if !filters.check_mode(&mode) { + /// + /// Checks if the test is not filtered out. + /// + fn check_filters(&self, filters: &Filters, mode: &Mode) -> Option<()> { + if !filters.check_mode(mode) { return None; } - if let Some(filters) = self.index_entity.modes.as_ref() { if !mode.check_extended_filters(filters.as_slice()) { return None; } } - if let Some(versions) = self.index_entity.version.as_ref() { if !mode.check_version(versions) { return None; } } - if !mode.check_ethereum_tests_params(&self.test.params) { return None; } + Some(()) + } - let mut calls = self.test.calls.clone(); - if !calls + /// + /// Inserts necessary deploy transactions into the list of calls. + /// + fn insert_deploy_calls(&self, calls: &mut Vec) { + if calls .iter() .any(|call| matches!(call, solidity_adapter::FunctionCall::Constructor { .. })) { - let constructor = solidity_adapter::FunctionCall::Constructor { - calldata: vec![], - value: None, - events: vec![], - gas_options: vec![], - }; - let constructor_insert_index = calls - .iter() - .position(|call| !matches!(call, solidity_adapter::FunctionCall::Library { .. })) - .unwrap_or(calls.len()); - calls.insert(constructor_insert_index, constructor); + return; } - let last_source = match self.test.sources.last() { - Some(last_source) => last_source.0.clone(), - None => { - Summary::invalid( - summary, - Some(mode), - test_path, - anyhow::anyhow!("Sources is empty"), - ); - return None; - } + let constructor = solidity_adapter::FunctionCall::Constructor { + calldata: vec![], + value: None, + events: vec![], + gas_options: vec![], }; + let constructor_insert_index = calls + .iter() + .position(|call| !matches!(call, solidity_adapter::FunctionCall::Library { .. })) + .unwrap_or(calls.len()); + calls.insert(constructor_insert_index, constructor); + } - let mut address_predictor = EraVMAddressPredictor::new(); - - let mut contract_address = None; + /// + /// Returns all addresses. + /// + fn get_addresses( + &self, + mut address_iterator: impl AddressIterator, + calls: &[solidity_adapter::FunctionCall], + last_source: &str, + ) -> anyhow::Result<( + web3::types::Address, + BTreeMap, + BTreeMap>, + )> { let mut caller = solidity_adapter::account_address(solidity_adapter::DEFAULT_ACCOUNT_INDEX); - let mut libraries_addresses = HashMap::new(); + let mut contract_address = None; + let mut libraries_addresses = BTreeMap::new(); let mut libraries = BTreeMap::new(); - for call in calls.iter() { match call { solidity_adapter::FunctionCall::Constructor { .. } => { if contract_address.is_some() { - Summary::invalid( - summary, - Some(mode), - test_path, - anyhow::anyhow!("Two constructors in test"), - ); - return None; + anyhow::bail!("Two constructors are not allowed for a single instance"); } - contract_address = Some(address_predictor.next(&caller, true)); + contract_address = Some(address_iterator.next(&caller, true)); } solidity_adapter::FunctionCall::Library { name, source } => { - let source = source.clone().unwrap_or_else(|| last_source.clone()); - let address = address_predictor.next(&caller, true); + let source = source.clone().unwrap_or_else(|| last_source.to_owned()); + let address = address_iterator.next(&caller, true); libraries .entry(source.clone()) .or_insert_with(BTreeMap::new) @@ -160,110 +154,124 @@ impl Buildable for EthereumTest { solidity_adapter::FunctionCall::Account { input, expected } => { let address = solidity_adapter::account_address(*input); if !expected.eq(&address) { - Summary::invalid( - summary, - Some(mode), - test_path, - anyhow::anyhow!( - "Expected address: {}, but found {}", - expected, - address - ), - ); - return None; + anyhow::bail!("Expected address: `{}`, found `{}`", expected, address); } caller = address; } _ => {} } } - let contract_address = contract_address.expect("Always valid"); - let compiler_output = match compiler + Ok((contract_address, libraries_addresses, libraries)) + } + + /// + /// Returns the last source defined in the test. + /// + /// If the test has no sources, reports an `INVALID` and returns `None`. + /// + fn last_source(&self, summary: Arc>, mode: &Mode) -> Option { + match self.test.sources.last() { + Some(last_source) => Some(last_source.0.to_owned()), + None => { + Summary::invalid( + summary, + Some(mode.to_owned()), + self.identifier.to_owned(), + anyhow::anyhow!("The Ethereum test `{}` sources are empty", self.identifier), + ); + None + } + } + } +} + +impl Buildable for EthereumTest { + fn build_for_eravm( + &self, + mode: Mode, + compiler: Arc, + _target: Target, + summary: Arc>, + filters: &Filters, + debug_config: Option, + ) -> Option { + self.check_filters(filters, &mode)?; + + let mut calls = self.test.calls.clone(); + self.insert_deploy_calls(&mut calls); + + let last_source = self.last_source(summary.clone(), &mode)?; + + let (contract_address, libraries_addresses, libraries) = match self.get_addresses( + EraVMAddressIterator::new(), + calls.as_slice(), + last_source.as_str(), + ) { + Ok((contract_address, libraries_addresses, libraries)) => { + (contract_address, libraries_addresses, libraries) + } + Err(error) => { + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); + return None; + } + }; + + let eravm_input = match compiler .compile_for_eravm( - test_path.clone(), + self.identifier.to_owned(), self.test.sources.clone(), libraries, &mode, - false, - false, debug_config, ) .map_err(|error| anyhow::anyhow!("Failed to compile sources: {}", error)) { Ok(output) => output, Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); return None; } }; - let main_contract = compiler_output.last_contract; - - let main_contract_build = match compiler_output - .builds - .get(main_contract.as_str()) - .ok_or_else(|| { - anyhow::anyhow!("Main contract not found in the compiler build artifacts") - }) { - Ok(build) => build, + let instances = match eravm_input.get_instances( + &BTreeMap::new(), + libraries_addresses, + contract_address, + ) { + Ok(instance) => instance, Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); return None; } }; - let main_contract_instance = Instance::new( - main_contract, - Some(contract_address), - main_contract_build.bytecode_hash, - ); - - let mut libraries_instances = HashMap::with_capacity(libraries_addresses.len()); - - for (library_name, library_address) in libraries_addresses { - let build = match compiler_output.builds.get(&library_name).ok_or_else(|| { - anyhow::anyhow!( - "Library {} not found in the compiler build artifacts", - library_name - ) - }) { - Ok(build) => build, - Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); - return None; - } - }; - libraries_instances.insert( - library_name.clone(), - Instance::new(library_name, Some(library_address), build.bytecode_hash), - ); - } - let case = match Case::try_from_ethereum( - &calls, - &main_contract_instance, - &libraries_instances, - &last_source, - ) { + let case = match Case::try_from_ethereum(&calls, instances, &last_source) { Ok(case) => case, Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); + Summary::invalid( + summary.clone(), + Some(mode), + self.identifier.to_owned(), + error, + ); return None; } }; - let builds = compiler_output + let builds = eravm_input .builds .into_values() .map(|build| (build.bytecode_hash, build.assembly)) .collect(); - Some(EraVMTest::new( - test_path, + Some(Test::new( + self.identifier.to_owned(), self.index_entity.group.clone(), mode, builds, + HashMap::new(), vec![case], )) } @@ -272,123 +280,35 @@ impl Buildable for EthereumTest { &self, mode: Mode, compiler: Arc, + _target: Target, summary: Arc>, filters: &Filters, debug_config: Option, - ) -> Option { - let test_path = self.index_entity.path.to_string_lossy().to_string(); + ) -> Option { + self.check_filters(filters, &mode)?; - if !filters.check_mode(&mode) { - return None; - } + let mut calls = self.test.calls.clone(); + self.insert_deploy_calls(&mut calls); - if let Some(filters) = self.index_entity.modes.as_ref() { - if !mode.check_extended_filters(filters.as_slice()) { - return None; - } - } + let last_source = self.last_source(summary.clone(), &mode)?; - if let Some(versions) = self.index_entity.version.as_ref() { - if !mode.check_version(versions) { - return None; + let (contract_address, libraries_addresses, libraries) = match self.get_addresses( + EVMAddressIterator::new(false), + calls.as_slice(), + last_source.as_str(), + ) { + Ok((contract_address, libraries_addresses, libraries)) => { + (contract_address, libraries_addresses, libraries) } - } - - if !mode.check_ethereum_tests_params(&self.test.params) { - return None; - } - - let mut calls = self.test.calls.clone(); - if !calls - .iter() - .any(|call| matches!(call, solidity_adapter::FunctionCall::Constructor { .. })) - { - let constructor = solidity_adapter::FunctionCall::Constructor { - calldata: vec![], - value: None, - events: vec![], - gas_options: vec![], - }; - let constructor_insert_index = calls - .iter() - .position(|call| !matches!(call, solidity_adapter::FunctionCall::Library { .. })) - .unwrap_or(calls.len()); - calls.insert(constructor_insert_index, constructor); - } - - let last_source = match self.test.sources.last() { - Some(last_source) => last_source.0.clone(), - None => { - Summary::invalid( - summary, - Some(mode), - test_path, - anyhow::anyhow!("Sources is empty"), - ); + Err(error) => { + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); return None; } }; - let mut address_predictor = EVMAddressPredictor::new(); - - let mut contract_address = None; - let mut caller = solidity_adapter::account_address(solidity_adapter::DEFAULT_ACCOUNT_INDEX); - - let mut libraries_addresses = HashMap::new(); - let mut libraries = BTreeMap::new(); - - for call in calls.iter() { - match call { - solidity_adapter::FunctionCall::Constructor { .. } => { - if contract_address.is_some() { - Summary::invalid( - summary, - Some(mode), - test_path, - anyhow::anyhow!("Two constructors in test"), - ); - return None; - } - contract_address = Some(address_predictor.next(&caller, true)); - } - solidity_adapter::FunctionCall::Library { name, source } => { - let source = source.clone().unwrap_or_else(|| last_source.clone()); - let address = address_predictor.next(&caller, true); - libraries - .entry(source.clone()) - .or_insert_with(BTreeMap::new) - .insert( - name.clone(), - format!("0x{}", crate::utils::address_as_string(&address)), - ); - libraries_addresses.insert(format!("{source}:{name}"), address); - } - solidity_adapter::FunctionCall::Account { input, expected } => { - let address = solidity_adapter::account_address(*input); - if !expected.eq(&address) { - Summary::invalid( - summary, - Some(mode), - test_path, - anyhow::anyhow!( - "Expected address: {}, but found {}", - expected, - address - ), - ); - return None; - } - caller = address; - } - _ => {} - } - } - - let contract_address = contract_address.expect("Always valid"); - - let compiler_output = match compiler + let evm_input = match compiler .compile_for_evm( - test_path.clone(), + self.identifier.to_owned(), self.test.sources.clone(), libraries, &mode, @@ -398,80 +318,42 @@ impl Buildable for EthereumTest { { Ok(output) => output, Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); return None; } }; - let main_contract = compiler_output.last_contract; - - let main_contract_build = match compiler_output - .builds - .get(main_contract.as_str()) - .ok_or_else(|| { - anyhow::anyhow!("Main contract not found in the compiler build artifacts") - }) { - Ok(build) => build, + let instances = match evm_input.get_instances( + &BTreeMap::new(), + libraries_addresses, + Some(contract_address), + ) { + Ok(instance) => instance, Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); return None; } }; - let main_contract_instance = Instance::new( - main_contract, - Some(contract_address), - web3::types::U256::zero(), - ); - - let mut libraries_instances = HashMap::with_capacity(libraries_addresses.len()); - - for (library_name, library_address) in libraries_addresses { - let build = match compiler_output.builds.get(&library_name).ok_or_else(|| { - anyhow::anyhow!( - "Library {} not found in the compiler build artifacts", - library_name - ) - }) { - Ok(build) => build, - Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); - return None; - } - }; - libraries_instances.insert( - library_name.clone(), - Instance::new( - library_name, - Some(library_address), - web3::types::U256::zero(), - ), - ); - } - let case = match Case::try_from_ethereum( - &calls, - &main_contract_instance, - &libraries_instances, - &last_source, - ) { + let case = match Case::try_from_ethereum(&calls, instances, &last_source) { Ok(case) => case, Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); + Summary::invalid( + summary.clone(), + Some(mode), + self.identifier.to_owned(), + error, + ); return None; } }; - let builds = compiler_output - .builds - .into_values() - .map(|build| (web3::types::Address::zero(), build)) - .collect(); - - Some(EVMTest::new( - test_path, + Some(Test::new( + self.identifier.to_owned(), self.index_entity.group.clone(), mode, - builds, + HashMap::new(), + evm_input.builds, vec![case], )) } diff --git a/compiler_tester/src/directories/matter_labs/mod.rs b/compiler_tester/src/directories/matter_labs/mod.rs index f55cf7d1..4ab79fc9 100644 --- a/compiler_tester/src/directories/matter_labs/mod.rs +++ b/compiler_tester/src/directories/matter_labs/mod.rs @@ -9,11 +9,10 @@ use std::path::Path; use std::sync::Arc; use std::sync::Mutex; +use crate::directories::Collection; use crate::filters::Filters; use crate::summary::Summary; -use super::TestsDirectory; - use self::test::MatterLabsTest; /// @@ -21,10 +20,10 @@ use self::test::MatterLabsTest; /// pub struct MatterLabsDirectory; -impl TestsDirectory for MatterLabsDirectory { +impl Collection for MatterLabsDirectory { type Test = MatterLabsTest; - fn all_tests( + fn read_all( directory_path: &Path, extension: &'static str, summary: Arc>, @@ -37,17 +36,17 @@ impl TestsDirectory for MatterLabsDirectory { let path = entry.path(); let entry_type = entry.file_type().map_err(|error| { anyhow::anyhow!( - "Failed to get file(`{}`) type: {}", + "Failed to get the type of file `{}`: {}", path.to_string_lossy(), error ) })?; if entry_type.is_dir() { - tests.extend(Self::all_tests(&path, extension, summary.clone(), filters)?); + tests.extend(Self::read_all(&path, extension, summary.clone(), filters)?); continue; } else if !entry_type.is_file() { - anyhow::bail!("Invalid file type: {}", path.to_string_lossy()); + anyhow::bail!("Invalid type of file `{}`", path.to_string_lossy()); } if entry.file_name().to_string_lossy().starts_with('.') { @@ -55,7 +54,10 @@ impl TestsDirectory for MatterLabsDirectory { } let file_extension = path.extension().ok_or_else(|| { - anyhow::anyhow!("Failed to get file extension: {}", path.to_string_lossy()) + anyhow::anyhow!( + "Failed to get the extension of file `{}`", + path.to_string_lossy() + ) })?; if file_extension != extension { continue; @@ -68,27 +70,4 @@ impl TestsDirectory for MatterLabsDirectory { Ok(tests) } - - fn single_test( - directory_path: &Path, - test_path: &Path, - extension: &'static str, - summary: Arc>, - filters: &Filters, - ) -> anyhow::Result> { - let file_extension = test_path.extension().ok_or_else(|| { - anyhow::anyhow!( - "Failed to get file extension: {}", - test_path.to_string_lossy() - ) - })?; - if file_extension != extension { - anyhow::bail!("Invalid file extension"); - } - - let mut path = directory_path.to_path_buf(); - path.push(test_path); - - Ok(MatterLabsTest::new(path, summary, filters)) - } } diff --git a/compiler_tester/src/directories/matter_labs/test/metadata/case/input/expected/mod.rs b/compiler_tester/src/directories/matter_labs/test/metadata/case/input/expected/mod.rs index 28be74cc..c746f847 100644 --- a/compiler_tester/src/directories/matter_labs/test/metadata/case/input/expected/mod.rs +++ b/compiler_tester/src/directories/matter_labs/test/metadata/case/input/expected/mod.rs @@ -30,6 +30,13 @@ impl Expected { Self::Single(Variant::Simple(vec![format!("{instance}.address")])) } + /// + /// Creates EVM interpreter benchmark expected data. + /// + pub fn successful_evm_interpreter_benchmark() -> Self { + Self::Single(Variant::Simple(vec!["*".to_owned()])) + } + /// /// Returns exception flag for specified mode. /// @@ -50,7 +57,7 @@ impl Expected { None => true, } }) - .ok_or_else(|| anyhow::anyhow!("Version not covered"))?; + .ok_or_else(|| anyhow::anyhow!("Version is not covered"))?; Ok(match variant { Variant::Simple(_) => false, Variant::Extended(inner) => inner.exception, diff --git a/compiler_tester/src/directories/matter_labs/test/metadata/case/input/mod.rs b/compiler_tester/src/directories/matter_labs/test/metadata/case/input/mod.rs index 5d57dbad..4a5816f8 100644 --- a/compiler_tester/src/directories/matter_labs/test/metadata/case/input/mod.rs +++ b/compiler_tester/src/directories/matter_labs/test/metadata/case/input/mod.rs @@ -8,8 +8,6 @@ pub mod storage; use std::collections::HashMap; -use serde::Deserialize; - use crate::directories::matter_labs::test::default_caller_address; use crate::directories::matter_labs::test::simple_tests_instance; @@ -20,7 +18,7 @@ use self::storage::Storage; /// /// The Matter Labs compiler test metadata case input. /// -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, serde::Deserialize)] pub struct Input { /// The comment to an entry. pub comment: Option, diff --git a/compiler_tester/src/directories/matter_labs/test/metadata/case/input/storage.rs b/compiler_tester/src/directories/matter_labs/test/metadata/case/input/storage.rs index b93cd46e..c9831eb5 100644 --- a/compiler_tester/src/directories/matter_labs/test/metadata/case/input/storage.rs +++ b/compiler_tester/src/directories/matter_labs/test/metadata/case/input/storage.rs @@ -4,12 +4,10 @@ use std::collections::HashMap; -use serde::Deserialize; - /// /// The Matter Labs compiler test metadata case input contract storage. /// -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, serde::Deserialize)] #[serde(untagged)] pub enum Storage { /// The list, where the key starts from 0. diff --git a/compiler_tester/src/directories/matter_labs/test/metadata/case/mod.rs b/compiler_tester/src/directories/matter_labs/test/metadata/case/mod.rs index 62e531bd..3520e0b8 100644 --- a/compiler_tester/src/directories/matter_labs/test/metadata/case/mod.rs +++ b/compiler_tester/src/directories/matter_labs/test/metadata/case/mod.rs @@ -4,14 +4,17 @@ pub mod input; -use std::collections::BTreeSet; -use std::collections::HashMap; +use std::collections::BTreeMap; use std::str::FromStr; use serde::Deserialize; use crate::compilers::mode::Mode; -use crate::vm::AddressPredictorIterator; +use crate::target::Target; +use crate::test::instance::Instance; +use crate::vm::address_iterator::AddressIterator; +use crate::vm::eravm::address_iterator::EraVMAddressIterator; +use crate::vm::evm::address_iterator::EVMAddressIterator; use self::input::expected::Expected; use self::input::Input; @@ -39,26 +42,36 @@ pub struct Case { } impl Case { + /// + /// Normalizes the case. + /// + pub fn normalize( + mut self, + contracts: &BTreeMap, + instances: &BTreeMap, + target: Target, + ) -> anyhow::Result { + self.normalize_deployer_calls(contracts, instances, target)?; + self.normalize_expected(); + Ok(self) + } + /// /// Validates deployer calls, adds libraries deployer calls, contracts deployer calls if they are not present. /// pub fn normalize_deployer_calls( &mut self, - instances: &BTreeSet, - libraries: &[String], + contracts: &BTreeMap, + instances: &BTreeMap, + target: Target, ) -> anyhow::Result<()> { - let mut instances = instances.clone(); + let mut contracts = contracts.clone(); for (index, input) in self.inputs.iter().enumerate() { - let instance = &input.instance; - if !input.method.eq("#deployer") { + if input.method.as_str() != "#deployer" { continue; }; - if libraries.contains(instance) { - anyhow::bail!("Deployer call {} for library, note: libraries deployer calls generating automatically", index); - } - - if !instances.remove(instance) { + if contracts.remove(input.instance.as_str()).is_none() { anyhow::bail!( "Input {} is a second deployer call for the same instance or instance is invalid", index @@ -66,15 +79,42 @@ impl Case { } } - let mut inputs = Vec::with_capacity(libraries.len() + instances.len() + self.inputs.len()); + let mut inputs = Vec::with_capacity(instances.len() + self.inputs.len()); - for instance in libraries.iter() { - inputs.push(Input::empty_deployer_call(instance.clone())); + for (name, instance) in instances.iter() { + if instance.is_library() { + inputs.push(Input::empty_deployer_call(name.to_owned())); + } } - for instance in instances { - if !libraries.contains(&instance) { - inputs.push(Input::empty_deployer_call(instance.clone())); + for contract in contracts.keys() { + if !instances + .iter() + .any(|(filter_name, instance)| filter_name == contract && instance.is_library()) + { + inputs.push(Input::empty_deployer_call(contract.clone())); + } + } + + if let Target::EraVM = target { + for (name, instance) in instances.iter() { + if let Instance::EraVM { .. } = instance { + continue; + } + + if name != "BenchmarkCaller" + && name.split('_').next().unwrap_or_default() + != self.name.split('_').next().unwrap_or_default() + { + continue; + } + + if !instances + .iter() + .any(|(filter_name, instance)| filter_name == name && instance.is_library()) + { + inputs.push(Input::empty_deployer_call(name.to_owned())); + } } } @@ -98,24 +138,22 @@ impl Case { /// /// Returns all the instances addresses, except libraries. /// - pub fn instance_addresses( + pub fn set_instance_addresses( &self, - libraries: &BTreeSet, - address_predictor: &mut API, + instances: &mut BTreeMap, + mut eravm_address_iterator: EraVMAddressIterator, + mut evm_address_iterator: EVMAddressIterator, mode: &Mode, - ) -> anyhow::Result> - where - API: AddressPredictorIterator, - { - let mut instances_addresses = HashMap::new(); + ) -> anyhow::Result<()> { for (index, input) in self.inputs.iter().enumerate() { - if !input.method.eq("#deployer") { - continue; - } - let instance = &input.instance; - if libraries.contains(instance) { + if input.method.as_str() != "#deployer" + || instances.iter().any(|(name, instance)| { + name.as_str() == input.instance.as_str() && instance.is_library() + }) + { continue; } + let exception = match input.expected.as_ref() { Some(expected) => expected .exception(mode) @@ -125,10 +163,27 @@ impl Case { if exception { continue; } - let caller = web3::types::Address::from_str(input.caller.as_str()) - .map_err(|error| anyhow::anyhow!("Input #{}: invalid caller: {}", index, error))?; - instances_addresses.insert(instance.to_string(), address_predictor.next(&caller, true)); + + let caller = + web3::types::Address::from_str(input.caller.as_str()).map_err(|error| { + anyhow::anyhow!( + "Input #{} has invalid caller `{}`: {}", + index, + input.caller.as_str(), + error + ) + })?; + + match instances.get_mut(input.instance.as_str()) { + Some(instance @ Instance::EraVM(_)) => { + instance.set_address(eravm_address_iterator.next(&caller, true)); + } + Some(instance @ Instance::EVM(_)) => { + instance.set_address(evm_address_iterator.next(&caller, true)); + } + _ => unreachable!(), + } } - Ok(instances_addresses) + Ok(()) } } diff --git a/compiler_tester/src/directories/matter_labs/test/metadata/evm_contract.rs b/compiler_tester/src/directories/matter_labs/test/metadata/evm_contract.rs new file mode 100644 index 00000000..1228dc57 --- /dev/null +++ b/compiler_tester/src/directories/matter_labs/test/metadata/evm_contract.rs @@ -0,0 +1,32 @@ +//! +//! The Matter Labs compiler test metadata EVM contract. +//! + +use serde::Deserialize; + +/// +/// The Matter Labs compiler test metadata EVM contract. +/// +#[derive(Debug, Clone, Deserialize)] +pub struct EVMContract { + /// The runtime code. + runtime_code: String, +} + +impl EVMContract { + /// + /// Returns the init code. + /// + pub fn init_code(&self) -> String { + "608060405234801561000f575f80fd5b5060c08061001c5f395ff3fe".to_owned() + } + + /// + /// Returns the runtime code. + /// + pub fn runtime_code(&self) -> String { + let mut runtime_code = self.runtime_code.repeat(16 /* TODO */).to_owned(); + runtime_code.push_str("00"); + runtime_code + } +} diff --git a/compiler_tester/src/directories/matter_labs/test/metadata/mod.rs b/compiler_tester/src/directories/matter_labs/test/metadata/mod.rs index d83fdf48..ffc94347 100644 --- a/compiler_tester/src/directories/matter_labs/test/metadata/mod.rs +++ b/compiler_tester/src/directories/matter_labs/test/metadata/mod.rs @@ -3,33 +3,41 @@ //! pub mod case; +pub mod evm_contract; use std::collections::BTreeMap; -use std::collections::HashMap; use std::str::FromStr; -use serde::Deserialize; +use crate::target::Target; use self::case::Case; +use self::evm_contract::EVMContract; /// /// The Matter Labs compiler test metadata. /// -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, serde::Deserialize)] pub struct Metadata { /// The test cases. pub cases: Vec, /// The mode filter. pub modes: Option>, - /// The test contracts (key is instance name, value is path). + /// The test contracts. + /// The format is `instance -> path`. #[serde(default)] - pub contracts: HashMap, - /// The test libraries for linking (key is path value, value is instance name). + pub contracts: BTreeMap, + /// The EVM auxiliary contracts. + /// The format is `instance -> init code`. + #[serde(default)] + pub evm_contracts: BTreeMap, + /// The test libraries for linking. #[serde(default)] pub libraries: BTreeMap>, /// If build contracts in system mode. #[serde(default)] pub system_mode: bool, + /// The target to run the test on. + pub target: Option, /// If the entire test file must be ignored. #[serde(default)] pub ignore: bool, diff --git a/compiler_tester/src/directories/matter_labs/test/mod.rs b/compiler_tester/src/directories/matter_labs/test/mod.rs index a79a9ebd..23fa3f9c 100644 --- a/compiler_tester/src/directories/matter_labs/test/mod.rs +++ b/compiler_tester/src/directories/matter_labs/test/mod.rs @@ -5,7 +5,6 @@ pub mod metadata; use std::collections::BTreeMap; -use std::collections::BTreeSet; use std::collections::HashMap; use std::collections::HashSet; use std::path::PathBuf; @@ -18,14 +17,18 @@ use crate::compilers::Compiler; use crate::directories::Buildable; use crate::filters::Filters; use crate::summary::Summary; +use crate::target::Target; use crate::test::case::Case; -use crate::test::eravm::Test as EraVMTest; -use crate::test::evm::Test as EVMTest; use crate::test::instance::Instance; -use crate::vm::eravm::deployers::address_predictor::AddressPredictor as EraVMAddressPredictor; -use crate::vm::evm::address_predictor::AddressPredictor as EVMAddressPredictor; -use crate::vm::AddressPredictorIterator; - +use crate::test::Test; +use crate::vm::address_iterator::AddressIterator; +use crate::vm::eravm::address_iterator::EraVMAddressIterator; +use crate::vm::evm::address_iterator::EVMAddressIterator; + +use self::metadata::case::input::calldata::Calldata as MatterLabsCaseInputCalldata; +use self::metadata::case::input::expected::Expected as MatterLabsCaseInputExpected; +use self::metadata::case::input::Input as MatterLabsCaseInput; +use self::metadata::case::Case as MatterLabsCase; use self::metadata::Metadata; /// The default simple contract name. @@ -54,9 +57,12 @@ pub fn default_caller_address() -> String { /// /// The Matter Labs compiler test. /// +#[derive(Debug)] pub struct MatterLabsTest { /// The test path. path: PathBuf, + /// The test identifier. + identifier: String, /// The test metadata. metadata: Metadata, /// The test sources. @@ -68,32 +74,32 @@ impl MatterLabsTest { /// Try to create new test. /// pub fn new(path: PathBuf, summary: Arc>, filters: &Filters) -> Option { - let test_path = path.to_string_lossy().to_string(); + let identifier = path.to_string_lossy().to_string(); - if !filters.check_test_path(&test_path) { + if !filters.check_test_path(identifier.as_str()) { return None; } - let metadata_file_string = match std::fs::read_to_string(path.as_path()) { - Ok(metadata_file_string) => metadata_file_string, + let main_file_string = match std::fs::read_to_string(path.as_path()) { + Ok(data) => data, Err(error) => { - Summary::invalid(summary, None, test_path, error); + Summary::invalid(summary, None, identifier.clone(), error); return None; } }; - let mut metadata = match Metadata::from_str(metadata_file_string.as_str()) - .map_err(|err| anyhow::anyhow!("Invalid metadata json: {}", err)) + let mut metadata = match Metadata::from_str(main_file_string.as_str()) + .map_err(|error| anyhow::anyhow!("Invalid metadata JSON: {}", error)) { Ok(metadata) => metadata, Err(error) => { - Summary::invalid(summary, None, test_path, error); + Summary::invalid(summary, None, identifier.clone(), error); return None; } }; if metadata.ignore { - Summary::ignored(summary, test_path); + Summary::ignored(summary, identifier.clone()); return None; } @@ -102,7 +108,7 @@ impl MatterLabsTest { } let sources = if metadata.contracts.is_empty() { - vec![(path.to_string_lossy().to_string(), metadata_file_string)] + vec![(path.to_string_lossy().to_string(), main_file_string)] } else { let mut sources = HashMap::new(); let mut paths = HashSet::with_capacity(metadata.contracts.len()); @@ -120,6 +126,7 @@ impl MatterLabsTest { }; paths.insert(file_path.to_string_lossy().to_string()); } + let mut test_directory_path = path.clone(); test_directory_path.pop(); for entry in @@ -132,11 +139,11 @@ impl MatterLabsTest { for path in paths.into_iter() { let source_code = match std::fs::read_to_string(path.as_str()) - .map_err(|err| anyhow::anyhow!("Failed to read source code file: {}", err)) + .map_err(|err| anyhow::anyhow!("Reading source file error: {}", err)) { Ok(source) => source, Err(error) => { - Summary::invalid(summary, None, test_path, error); + Summary::invalid(summary, None, identifier.clone(), error); return None; } }; @@ -146,7 +153,7 @@ impl MatterLabsTest { }; metadata.cases.retain(|case| { - let case_name = format!("{}::{}", test_path, case.name); + let case_name = format!("{}::{}", identifier, case.name); if case.ignore { Summary::ignored(summary.clone(), case_name); return false; @@ -160,168 +167,341 @@ impl MatterLabsTest { Some(Self { path, + identifier, metadata, sources, }) } -} -impl Buildable for MatterLabsTest { - fn build_for_eravm( - &self, - mode: Mode, - compiler: Arc, - summary: Arc>, - filters: &Filters, - debug_config: Option, - ) -> Option { - let test_path = self.path.to_string_lossy().to_string(); - if !filters.check_mode(&mode) { + /// + /// Checks if the test is not filtered out. + /// + fn check_filters(&self, filters: &Filters, mode: &Mode) -> Option<()> { + if !filters.check_mode(mode) { return None; } - if let Some(filters) = self.metadata.modes.as_ref() { if !mode.check_extended_filters(filters.as_slice()) { return None; } } - if !mode.check_pragmas(&self.sources) { return None; } + Some(()) + } - let mut contracts = self.metadata.contracts.clone(); + /// + /// Adds the default contract to the list of contracts if it is empty. + /// + fn push_default_contract( + &self, + contracts: &mut BTreeMap, + is_multi_contract: bool, + ) { if contracts.is_empty() { - let contract_name = if compiler.has_multiple_contracts() { - format!("{}:{}", test_path, SIMPLE_TESTS_CONTRACT_NAME) + let contract_name = if is_multi_contract { + format!("{}:{}", self.identifier, SIMPLE_TESTS_CONTRACT_NAME) } else { - test_path.clone() + self.identifier.to_owned() }; contracts.insert(SIMPLE_TESTS_INSTANCE.to_owned(), contract_name); } + } - let mut address_predictor = EraVMAddressPredictor::new(); + /// + /// Adds the BenchmarkCaller contract as a proxy for the EVM interpreter. + /// + fn push_benchmark_caller( + &self, + sources: &mut Vec<(String, String)>, + contracts: &mut BTreeMap, + ) -> anyhow::Result<()> { + let benchmark_caller_string = std::fs::read_to_string(PathBuf::from( + "tests/solidity/complex/interpreter/BenchmarkCaller.sol", + ))?; + sources.push(( + "tests/solidity/complex/interpreter/BenchmarkCaller.sol".to_owned(), + benchmark_caller_string, + )); // TODO + contracts.insert( + "BenchmarkCaller".to_owned(), + "tests/solidity/complex/interpreter/BenchmarkCaller.sol:BenchmarkCaller".to_owned(), + ); + Ok(()) + } - let mut libraries_instances_names = Vec::new(); - let mut libraries_for_compiler = BTreeMap::new(); - let mut libraries_instances_addresses = HashMap::new(); + /// + /// Returns library information. + /// + fn get_libraries( + &self, + address_iterator: &mut API, + ) -> ( + BTreeMap>, + BTreeMap, + ) + where + API: AddressIterator, + { + let mut libraries = BTreeMap::new(); + let mut library_addresses = BTreeMap::new(); for (file, metadata_file_libraries) in self.metadata.libraries.iter() { let mut file_path = self.path.clone(); file_path.pop(); file_path.push(file); + let mut file_libraries = BTreeMap::new(); - for (name, instance) in metadata_file_libraries.iter() { - let address = address_predictor.next( - &web3::types::Address::from_str(DEFAULT_CALLER_ADDRESS) - .expect("Invalid default caller address constant"), + for name in metadata_file_libraries.keys() { + let address = address_iterator.next( + &web3::types::Address::from_str(DEFAULT_CALLER_ADDRESS).expect("Always valid"), true, ); file_libraries.insert( name.to_owned(), format!("0x{}", crate::utils::address_as_string(&address)), ); - libraries_instances_addresses.insert(instance.to_owned(), address); - libraries_instances_names.push(instance.to_owned()); + library_addresses.insert( + format!("{}:{}", file_path.to_string_lossy().as_ref(), name), + address, + ); } - libraries_for_compiler.insert(file_path.to_string_lossy().to_string(), file_libraries); + libraries.insert(file_path.to_string_lossy().to_string(), file_libraries); } - let vm_input = match compiler + (libraries, library_addresses) + } + + /// + /// Returns precompiled EVM contract instances. + /// + fn get_evm_instances(&self) -> anyhow::Result> { + let mut instances = BTreeMap::new(); + + for (instance, evm_contract) in self.metadata.evm_contracts.iter() { + let mut bytecode = evm_contract.init_code(); + bytecode.push_str(evm_contract.runtime_code().as_str()); + + let bytecode = hex::decode(bytecode.as_str()).map_err(|error| { + anyhow::anyhow!("Invalid bytecode of EVM instance `{}`: {}", instance, error) + })?; + instances.insert( + instance.to_owned(), + Instance::evm(instance.to_owned(), None, false, false, bytecode.to_owned()), + ); + } + + Ok(instances) + } + + /// + /// Returns cases needed for running benchmarks on the EVM interpreter. + /// + fn evm_interpreter_benchmark_cases(&self) -> Vec { + if self.metadata.group.as_deref() + != Some(benchmark_analyzer::Benchmark::EVM_INTERPRETER_GROUP_NAME) + { + return vec![]; + } + + let mut evm_contracts: Vec = self + .metadata + .evm_contracts + .keys() + .filter(|name| name.contains("Template") || name.contains("Full")) + .cloned() + .collect(); + evm_contracts.sort(); + + let mut metadata_cases = Vec::with_capacity(evm_contracts.len() / 2); + for pair_of_bytecodes in evm_contracts.chunks(2) { + let full = &pair_of_bytecodes[0]; + let template = &pair_of_bytecodes[1]; + + metadata_cases.push(MatterLabsCase { + comment: None, + name: template + .strip_suffix("_Template") + .expect("Always exists") + .to_owned(), + modes: None, + inputs: vec![ + MatterLabsCaseInput { + comment: None, + instance: "Proxy".to_owned(), + caller: default_caller_address(), + method: "benchmark".to_owned(), + calldata: MatterLabsCaseInputCalldata::List(vec![ + "BenchmarkCaller.address".to_owned(), + format!("{template}.address"), + ]), + value: None, + storage: HashMap::new(), + expected: Some( + MatterLabsCaseInputExpected::successful_evm_interpreter_benchmark(), + ), + }, + MatterLabsCaseInput { + comment: None, + instance: "Proxy".to_owned(), + caller: default_caller_address(), + method: "benchmark".to_owned(), + calldata: MatterLabsCaseInputCalldata::List(vec![ + "BenchmarkCaller.address".to_owned(), + format!("{full}.address"), + ]), + value: None, + storage: HashMap::new(), + expected: Some( + MatterLabsCaseInputExpected::successful_evm_interpreter_benchmark(), + ), + }, + MatterLabsCaseInput { + comment: None, + instance: "Proxy".to_owned(), + caller: default_caller_address(), + method: "benchmark".to_owned(), + calldata: MatterLabsCaseInputCalldata::List(vec![ + "BenchmarkCaller.address".to_owned(), + format!("{template}.address"), + ]), + value: None, + storage: HashMap::new(), + expected: Some( + MatterLabsCaseInputExpected::successful_evm_interpreter_benchmark(), + ), + }, + MatterLabsCaseInput { + comment: None, + instance: "Proxy".to_owned(), + caller: default_caller_address(), + method: "benchmark".to_owned(), + calldata: MatterLabsCaseInputCalldata::List(vec![ + "BenchmarkCaller.address".to_owned(), + format!("{full}.address"), + ]), + value: None, + storage: HashMap::new(), + expected: Some( + MatterLabsCaseInputExpected::successful_evm_interpreter_benchmark(), + ), + }, + ], + expected: MatterLabsCaseInputExpected::successful_evm_interpreter_benchmark(), + ignore: false, + cycles: None, + }) + } + metadata_cases + } +} + +impl Buildable for MatterLabsTest { + fn build_for_eravm( + &self, + mut mode: Mode, + compiler: Arc, + target: Target, + summary: Arc>, + filters: &Filters, + debug_config: Option, + ) -> Option { + mode.set_system_mode(self.metadata.system_mode); + + self.check_filters(filters, &mode)?; + + let mut contracts = self.metadata.contracts.clone(); + self.push_default_contract(&mut contracts, compiler.allows_multi_contract_files()); + + let mut eravm_address_iterator = EraVMAddressIterator::new(); + let evm_address_iterator = + EVMAddressIterator::new(matches!(target, Target::EVMInterpreter)); + + let (libraries, library_addresses) = self.get_libraries(&mut eravm_address_iterator); + + let eravm_input = match compiler .compile_for_eravm( - test_path.clone(), + self.identifier.to_owned(), self.sources.clone(), - libraries_for_compiler, + libraries, &mode, - self.metadata.system_mode, - false, debug_config, ) .map_err(|error| anyhow::anyhow!("Failed to compile sources: {}", error)) { - Ok(output) => output, + Ok(vm_input) => vm_input, Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); return None; } }; - let instances_names = contracts.keys().cloned().collect::>(); - let mut instances = HashMap::new(); + let mut instances = match eravm_input.get_instances( + &contracts, + library_addresses, + web3::types::Address::zero(), + ) { + Ok(instances) => instances, + Err(error) => { + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); + return None; + } + }; - for (instance, path) in contracts.into_iter() { - let build = match vm_input.builds.get(&path).ok_or_else(|| { - anyhow::anyhow!("{} not found in the compiler build artifacts", path) - }) { - Ok(build) => build, - Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); - return None; - } - }; - let hash = build.bytecode_hash; - let address = libraries_instances_addresses.get(&instance).cloned(); - instances.insert(instance, Instance::new(path, address, hash)); - } + let evm_instances = match self.get_evm_instances() { + Ok(evm_instances) => evm_instances, + Err(error) => { + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); + return None; + } + }; + instances.extend(evm_instances); - let mut cases = Vec::with_capacity(self.metadata.cases.len()); - for case in self.metadata.cases.iter() { + let mut metadata_cases = self.metadata.cases.to_owned(); + metadata_cases.extend(self.evm_interpreter_benchmark_cases()); + + let mut cases = Vec::with_capacity(metadata_cases.len()); + for case in metadata_cases.into_iter() { if let Some(filters) = case.modes.as_ref() { if !mode.check_extended_filters(filters.as_slice()) { continue; } } - let mut case = case.clone(); - match case.normalize_deployer_calls(&instances_names, &libraries_instances_names) { - Ok(_) => {} + let case = match case.normalize(&contracts, &instances, target) { + Ok(case) => case, Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); return None; } - } - case.normalize_expected(); - - let mut address_predictor = address_predictor.clone(); - - let instances_addresses = match case - .instance_addresses( - &libraries_instances_names.clone().into_iter().collect(), - &mut address_predictor, - &mode, - ) - .map_err(|error| { - anyhow::anyhow!( - "Case `{}` is invalid: Failed to compute instances addresses: {}", - case.name, - error - ) - }) { - Ok(addresses) => addresses, + }; + + match case.set_instance_addresses( + &mut instances, + eravm_address_iterator.clone(), + evm_address_iterator.clone(), + &mode, + ) { + Ok(_) => {} Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); return None; } - }; - let mut instances = instances.clone(); - for (instance, address) in instances_addresses { - let instance = instances - .get_mut(&instance) - .expect("Redundant instance from the instances_addresses case method"); - instance.address = Some(address); } + let case_name = case.name.to_owned(); let case = match Case::try_from_matter_labs( - &case, + case, &mode, &instances, - &vm_input.method_identifiers, + &eravm_input.method_identifiers, ) - .map_err(|error| anyhow::anyhow!("Case `{}` is invalid: {}", case.name, error)) + .map_err(|error| anyhow::anyhow!("Case `{}` is invalid: {}", case_name, error)) { Ok(case) => case, Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); return None; } }; @@ -329,17 +509,18 @@ impl Buildable for MatterLabsTest { cases.push(case); } - let builds = vm_input + let builds = eravm_input .builds .into_values() .map(|build| (build.bytecode_hash, build.assembly)) .collect(); - Some(EraVMTest::new( - test_path, + Some(Test::new( + self.identifier.to_owned(), self.metadata.group.clone(), mode, builds, + HashMap::new(), cases, )) } @@ -348,67 +529,36 @@ impl Buildable for MatterLabsTest { &self, mode: Mode, compiler: Arc, + target: Target, summary: Arc>, filters: &Filters, debug_config: Option, - ) -> Option { - let test_path = self.path.to_string_lossy().to_string(); - if !filters.check_mode(&mode) { - return None; - } + ) -> Option { + self.check_filters(filters, &mode)?; - if let Some(filters) = self.metadata.modes.as_ref() { - if !mode.check_extended_filters(filters.as_slice()) { + let mut contracts = self.metadata.contracts.clone(); + self.push_default_contract(&mut contracts, compiler.allows_multi_contract_files()); + let sources = if let Target::EVMInterpreter = target { + let mut sources = self.sources.to_owned(); + if let Err(error) = self.push_benchmark_caller(&mut sources, &mut contracts) { + Summary::invalid(summary, None, self.identifier.to_owned(), error); return None; } - } - - if !mode.check_pragmas(&self.sources) { - return None; - } - - let mut contracts = self.metadata.contracts.clone(); - if contracts.is_empty() { - let contract_name = if compiler.has_multiple_contracts() { - format!("{}:{}", test_path, SIMPLE_TESTS_CONTRACT_NAME) - } else { - test_path.clone() - }; - contracts.insert(SIMPLE_TESTS_INSTANCE.to_owned(), contract_name); - } - - let mut address_predictor = EVMAddressPredictor::new(); + sources + } else { + self.sources.to_owned() + }; - let mut libraries_instances_names = Vec::new(); - let mut libraries_for_compiler = BTreeMap::new(); - let mut libraries_instances_addresses = HashMap::new(); + let mut evm_address_iterator = + EVMAddressIterator::new(matches!(target, Target::EVMInterpreter)); - for (file, metadata_file_libraries) in self.metadata.libraries.iter() { - let mut file_path = self.path.clone(); - file_path.pop(); - file_path.push(file); - let mut file_libraries = BTreeMap::new(); - for (name, instance) in metadata_file_libraries.iter() { - let address = address_predictor.next( - &web3::types::Address::from_str(DEFAULT_CALLER_ADDRESS) - .expect("Invalid default caller address constant"), - true, - ); - file_libraries.insert( - name.to_owned(), - format!("0x{}", crate::utils::address_as_string(&address)), - ); - libraries_instances_addresses.insert(instance.to_owned(), address); - libraries_instances_names.push(instance.to_owned()); - } - libraries_for_compiler.insert(file_path.to_string_lossy().to_string(), file_libraries); - } + let (libraries, library_addresses) = self.get_libraries(&mut evm_address_iterator); - let mut vm_input = match compiler + let evm_input = match compiler .compile_for_evm( - test_path.clone(), - self.sources.clone(), - BTreeMap::new(), + self.identifier.to_owned(), + sources, + libraries, &mode, debug_config, ) @@ -416,30 +566,18 @@ impl Buildable for MatterLabsTest { { Ok(output) => output, Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); return None; } }; - let instances_names = contracts.keys().cloned().collect::>(); - let mut instances = HashMap::new(); - - for (instance, path) in contracts.into_iter() { - let build = match vm_input.builds.get_mut(&path).ok_or_else(|| { - anyhow::anyhow!("{} not found in the compiler build artifacts", path) - }) { - Ok(build) => build, - Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); - return None; - } - }; - let address = libraries_instances_addresses.get(&instance).cloned(); - instances.insert( - instance, - Instance::new(path, address, web3::types::U256::zero()), - ); - } + let mut instances = match evm_input.get_instances(&contracts, library_addresses, None) { + Ok(instances) => instances, + Err(error) => { + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); + return None; + } + }; let mut cases = Vec::with_capacity(self.metadata.cases.len()); for case in self.metadata.cases.iter() { @@ -449,56 +587,39 @@ impl Buildable for MatterLabsTest { } } - let mut case = case.clone(); - match case.normalize_deployer_calls(&instances_names, &libraries_instances_names) { - Ok(_) => {} + let case = match case.to_owned().normalize(&contracts, &instances, target) { + Ok(case) => case, Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); return None; } - } - case.normalize_expected(); - - let mut address_predictor = address_predictor.clone(); - - let instances_addresses = match case - .instance_addresses( - &libraries_instances_names.clone().into_iter().collect(), - &mut address_predictor, - &mode, - ) - .map_err(|error| { - anyhow::anyhow!( - "Case `{}` is invalid: Failed to compute instances addresses: {}", - case.name, - error - ) - }) { - Ok(addresses) => addresses, + }; + + match case.set_instance_addresses( + &mut instances, + EraVMAddressIterator::new(), + evm_address_iterator.clone(), + &mode, + ) { + Ok(_) => {} Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); return None; } - }; - let mut instances = instances.clone(); - for (instance, address) in instances_addresses { - let instance = instances - .get_mut(&instance) - .expect("Redundant instance from the instances_addresses case method"); - instance.address = Some(address); } + let case_name = case.name.to_owned(); let case = match Case::try_from_matter_labs( - &case, + case, &mode, &instances, - &vm_input.method_identifiers, + &evm_input.method_identifiers, ) - .map_err(|error| anyhow::anyhow!("Case `{}` is invalid: {}", case.name, error)) + .map_err(|error| anyhow::anyhow!("Case `{}` is invalid: {}", case_name, error)) { Ok(case) => case, Err(error) => { - Summary::invalid(summary, Some(mode), test_path, error); + Summary::invalid(summary, Some(mode), self.identifier.to_owned(), error); return None; } }; @@ -506,17 +627,12 @@ impl Buildable for MatterLabsTest { cases.push(case); } - let builds = vm_input - .builds - .into_values() - .map(|build| (web3::types::Address::zero(), build)) - .collect(); - - Some(EVMTest::new( - test_path, + Some(Test::new( + self.identifier.to_owned(), self.metadata.group.clone(), mode, - builds, + HashMap::new(), + evm_input.builds, cases, )) } diff --git a/compiler_tester/src/directories/mod.rs b/compiler_tester/src/directories/mod.rs index 66e5759e..c22a2b63 100644 --- a/compiler_tester/src/directories/mod.rs +++ b/compiler_tester/src/directories/mod.rs @@ -13,8 +13,28 @@ use crate::compilers::mode::Mode; use crate::compilers::Compiler; use crate::filters::Filters; use crate::summary::Summary; -use crate::test::eravm::Test as EraVMTest; -use crate::test::evm::Test as EVMTest; +use crate::target::Target; +use crate::test::Test; + +/// +/// The compiler tests directory trait. +/// +pub trait Collection { + /// + /// The test type. + /// + type Test: Buildable + std::fmt::Debug; + + /// + /// Returns all directory tests. + /// + fn read_all( + directory_path: &Path, + extension: &'static str, + summary: Arc>, + filters: &Filters, + ) -> anyhow::Result>; +} /// /// The buildable compiler test trait. @@ -27,10 +47,11 @@ pub trait Buildable: Send + Sync + 'static { &self, mode: Mode, compiler: Arc, + target: Target, summary: Arc>, filters: &Filters, debug_config: Option, - ) -> Option; + ) -> Option; /// /// Builds the test for EVM. @@ -39,39 +60,9 @@ pub trait Buildable: Send + Sync + 'static { &self, mode: Mode, compiler: Arc, + target: Target, summary: Arc>, filters: &Filters, debug_config: Option, - ) -> Option; -} - -/// -/// The compiler tests directory trait. -/// -pub trait TestsDirectory { - /// - /// The test type. - /// - type Test: Buildable; - - /// - /// Returns all directory tests. - /// - fn all_tests( - directory_path: &Path, - extension: &'static str, - summary: Arc>, - filters: &Filters, - ) -> anyhow::Result>; - - /// - /// Returns a single test. - /// - fn single_test( - directory_path: &Path, - test_path: &Path, - extension: &'static str, - summary: Arc>, - filters: &Filters, - ) -> anyhow::Result>; + ) -> Option; } diff --git a/compiler_tester/src/filters.rs b/compiler_tester/src/filters.rs index 73b2fbd8..7e09992c 100644 --- a/compiler_tester/src/filters.rs +++ b/compiler_tester/src/filters.rs @@ -9,11 +9,12 @@ use crate::compilers::mode::Mode; /// /// The compiler tester filters. /// +#[derive(Debug)] pub struct Filters { /// The path filters. - path_filters: Vec, + path_filters: HashSet, /// The mode filters. - mode_filters: Vec, + mode_filters: HashSet, /// The group filters. group_filters: HashSet, } @@ -28,8 +29,8 @@ impl Filters { group_filters: Vec, ) -> Self { Self { - path_filters, - mode_filters, + path_filters: path_filters.into_iter().collect(), + mode_filters: mode_filters.into_iter().collect(), group_filters: group_filters.into_iter().collect(), } } @@ -38,11 +39,13 @@ impl Filters { /// Check if the test path is compatible with the filters. /// pub fn check_test_path(&self, path: &str) -> bool { - self.path_filters.is_empty() - || self - .path_filters - .iter() - .any(|filter| path.contains(&filter[..filter.find("::").unwrap_or(filter.len())])) + if self.path_filters.is_empty() { + return true; + } + + self.path_filters + .iter() + .any(|filter| path.contains(&filter[..filter.find("::").unwrap_or(filter.len())])) } /// @@ -56,7 +59,7 @@ impl Filters { /// Check if the mode is compatible with the filters. /// pub fn check_mode(&self, mode: &Mode) -> bool { - mode.check_filters(self.mode_filters.as_slice()) + mode.check_filters(&self.mode_filters) } /// @@ -66,6 +69,7 @@ impl Filters { if self.group_filters.is_empty() { return true; } + if let Some(group) = group { !self.group_filters.contains(group) } else { diff --git a/compiler_tester/src/lib.rs b/compiler_tester/src/lib.rs index 26d7dae7..1c4a99c2 100644 --- a/compiler_tester/src/lib.rs +++ b/compiler_tester/src/lib.rs @@ -2,23 +2,19 @@ //! The compiler tester library. //! +#![allow(non_camel_case_types)] +#![allow(clippy::upper_case_acronyms)] +#![allow(clippy::too_many_arguments)] + pub(crate) mod compilers; pub(crate) mod directories; pub(crate) mod filters; -pub(crate) mod llvm_options; pub(crate) mod summary; +pub(crate) mod target; pub(crate) mod test; pub(crate) mod utils; pub(crate) mod vm; -pub use self::filters::Filters; -pub use self::llvm_options::LLVMOptions; -pub use self::summary::Summary; -pub use crate::vm::eravm::deployers::native_deployer::NativeDeployer as EraVMNativeDeployer; -pub use crate::vm::eravm::deployers::system_contract_deployer::SystemContractDeployer as EraVMSystemContractDeployer; -pub use crate::vm::eravm::EraVM; -pub use crate::vm::evm::EVM; - use std::path::Path; use std::sync::Arc; use std::sync::Mutex; @@ -29,8 +25,10 @@ use rayon::iter::ParallelIterator; pub use crate::compilers::eravm::EraVMCompiler; pub use crate::compilers::llvm::LLVMCompiler; -pub use crate::compilers::mode::solidity::Mode as SolidityMode; +pub use crate::compilers::mode::llvm_options::LLVMOptions; pub use crate::compilers::mode::Mode; +pub use crate::compilers::solidity::mode::Mode as SolidityMode; +pub use crate::compilers::solidity::upstream::SolidityCompiler as SolidityUpstreamCompiler; pub use crate::compilers::solidity::SolidityCompiler; pub use crate::compilers::vyper::VyperCompiler; pub use crate::compilers::yul::YulCompiler; @@ -39,8 +37,15 @@ pub use crate::directories::ethereum::test::EthereumTest; pub use crate::directories::ethereum::EthereumDirectory; pub use crate::directories::matter_labs::MatterLabsDirectory; pub use crate::directories::Buildable; -pub use crate::directories::TestsDirectory; -pub use crate::vm::eravm::deployers::Deployer as EraVMDeployer; +pub use crate::directories::Collection; +pub use crate::filters::Filters; +pub use crate::summary::Summary; +pub use crate::target::Target; +pub use crate::vm::eravm::deployers::dummy_deployer::DummyDeployer as EraVMNativeDeployer; +pub use crate::vm::eravm::deployers::system_contract_deployer::SystemContractDeployer as EraVMSystemContractDeployer; +pub use crate::vm::eravm::deployers::EraVMDeployer; +pub use crate::vm::eravm::EraVM; +pub use crate::vm::evm::EVM; /// The debug directory path. pub const DEBUG_DIRECTORY: &str = "./debug/"; @@ -113,7 +118,7 @@ impl CompilerTester { where D: EraVMDeployer, { - let tests = self.all_tests()?; + let tests = self.all_tests(false)?; let vm = Arc::new(vm); let _: Vec<()> = tests @@ -122,11 +127,12 @@ impl CompilerTester { if let Some(test) = test.build_for_eravm( mode, compiler, + Target::EraVM, self.summary.clone(), &self.filters, self.debug_config.clone(), ) { - test.run::(self.summary.clone(), vm.clone()); + test.run_eravm::(self.summary.clone(), vm.clone()); } }) .collect(); @@ -135,10 +141,10 @@ impl CompilerTester { } /// - /// Runs all tests on EraVM. + /// Runs all tests on EVM. /// - pub fn run_evm(self) -> anyhow::Result<()> { - let tests = self.all_tests()?; + pub fn run_evm(self, use_upstream_solc: bool) -> anyhow::Result<()> { + let tests = self.all_tests(use_upstream_solc)?; let _: Vec<()> = tests .into_par_iter() @@ -146,11 +152,12 @@ impl CompilerTester { if let Some(test) = test.build_for_evm( mode, compiler, + Target::EVM, self.summary.clone(), &self.filters, self.debug_config.clone(), ) { - test.run(self.summary.clone()); + test.run_evm(self.summary.clone()); } }) .collect(); @@ -159,49 +166,59 @@ impl CompilerTester { } /// - /// Returns all tests from the specified directory for the specified compiler. + /// Runs all tests on EVM interpreter. /// - fn directory( - &self, - path: &str, - extension: &'static str, - compiler: Arc, - ) -> anyhow::Result> + pub fn run_evm_interpreter( + self, + vm: EraVM, + use_upstream_solc: bool, + ) -> anyhow::Result<()> where - T: TestsDirectory, + D: EraVMDeployer, { - Ok(T::all_tests( - Path::new(path), - extension, - self.summary.clone(), - &self.filters, - ) - .map_err(|error| { - anyhow::anyhow!("Failed to read the tests directory `{}`: {}", path, error) - })? - .into_iter() - .map(|test| Arc::new(test) as Arc) - .cartesian_product(compiler.modes()) - .map(|(test, mode)| (test, compiler.clone() as Arc, mode)) - .collect()) + let tests = self.all_tests(use_upstream_solc)?; + let vm = Arc::new(vm); + + let _: Vec<()> = tests + .into_par_iter() + .map(|(test, compiler, mode)| { + if let Some(test) = test.build_for_evm( + mode, + compiler, + Target::EVMInterpreter, + self.summary.clone(), + &self.filters, + self.debug_config.clone(), + ) { + test.run_evm_interpreter::(self.summary.clone(), vm.clone()); + } + }) + .collect(); + + Ok(()) } /// /// Returns all tests from all directories. /// - fn all_tests(&self) -> anyhow::Result> { + fn all_tests(&self, use_upstream_solc: bool) -> anyhow::Result> { let solidity_compiler = Arc::new(SolidityCompiler::new()); + let solidity_upstream_compiler = Arc::new(SolidityUpstreamCompiler::new()); let vyper_compiler = Arc::new(VyperCompiler::new()); - let yul_compiler = Arc::new(YulCompiler::new()); - let llvm_compiler = Arc::new(LLVMCompiler::new()); - let eravm_compiler = Arc::new(EraVMCompiler::new()); + let yul_compiler = Arc::new(YulCompiler); + let llvm_compiler = Arc::new(LLVMCompiler); + let eravm_compiler = Arc::new(EraVMCompiler); let mut tests = Vec::with_capacity(16384); tests.extend(self.directory::( Self::SOLIDITY_SIMPLE, era_compiler_common::EXTENSION_SOLIDITY, - solidity_compiler.clone(), + if use_upstream_solc { + solidity_upstream_compiler.clone() + } else { + solidity_compiler.clone() + }, )?); tests.extend(self.directory::( Self::VYPER_SIMPLE, @@ -227,7 +244,11 @@ impl CompilerTester { tests.extend(self.directory::( Self::SOLIDITY_COMPLEX, era_compiler_common::EXTENSION_JSON, - solidity_compiler.clone(), + if use_upstream_solc { + solidity_upstream_compiler.clone() + } else { + solidity_compiler.clone() + }, )?); tests.extend(self.directory::( Self::VYPER_COMPLEX, @@ -238,7 +259,11 @@ impl CompilerTester { tests.extend(self.directory::( Self::SOLIDITY_ETHEREUM, era_compiler_common::EXTENSION_SOLIDITY, - solidity_compiler, + if use_upstream_solc { + solidity_upstream_compiler.clone() + } else { + solidity_compiler.clone() + }, )?); tests.extend(self.directory::( Self::VYPER_ETHEREUM, @@ -248,4 +273,32 @@ impl CompilerTester { Ok(tests) } + + /// + /// Returns all tests from the specified directory for the specified compiler. + /// + fn directory( + &self, + path: &str, + extension: &'static str, + compiler: Arc, + ) -> anyhow::Result> + where + T: Collection, + { + Ok(T::read_all( + Path::new(path), + extension, + self.summary.clone(), + &self.filters, + ) + .map_err(|error| { + anyhow::anyhow!("Failed to read the tests directory `{}`: {}", path, error) + })? + .into_iter() + .map(|test| Arc::new(test) as Arc) + .cartesian_product(compiler.all_modes()) + .map(|(test, mode)| (test, compiler.clone() as Arc, mode)) + .collect()) + } } diff --git a/compiler_tester/src/summary/element/mod.rs b/compiler_tester/src/summary/element/mod.rs index 7389a9c1..fd0f6e48 100644 --- a/compiler_tester/src/summary/element/mod.rs +++ b/compiler_tester/src/summary/element/mod.rs @@ -63,10 +63,14 @@ impl Element { details.push(format!("size {size}").bright_white().to_string()) }; match variant { - PassedVariant::Deploy { cycles, gas, .. } - | PassedVariant::Runtime { cycles, gas } => { + PassedVariant::Deploy { cycles, ergs, .. } => { details.push(format!("cycles {cycles}").bright_white().to_string()); - details.push(format!("gas {gas}").bright_white().to_string()) + details.push(format!("ergs {ergs}").bright_white().to_string()); + } + PassedVariant::Runtime { cycles, ergs, gas } => { + details.push(format!("cycles {cycles}").bright_white().to_string()); + details.push(format!("ergs {ergs}").bright_white().to_string()); + details.push(format!("gas {gas}").bright_white().to_string()); } _ => {} }; diff --git a/compiler_tester/src/summary/element/outcome/passed_variant.rs b/compiler_tester/src/summary/element/outcome/passed_variant.rs index fde33cae..85ba54cf 100644 --- a/compiler_tester/src/summary/element/outcome/passed_variant.rs +++ b/compiler_tester/src/summary/element/outcome/passed_variant.rs @@ -13,15 +13,19 @@ pub enum PassedVariant { size: usize, /// The number of execution cycles. cycles: usize, - /// The amount of gas used. - gas: u32, + /// The number of used ergs. + ergs: u64, + /// The number of used gas. + gas: u64, }, /// The contract call. Runtime { /// The number of execution cycles. cycles: usize, - /// The amount of gas used. - gas: u32, + /// The number of used ergs. + ergs: u64, + /// The number of used gas. + gas: u64, }, /// The special function call. Special, diff --git a/compiler_tester/src/summary/mod.rs b/compiler_tester/src/summary/mod.rs index 0c328c56..4a8d4b89 100644 --- a/compiler_tester/src/summary/mod.rs +++ b/compiler_tester/src/summary/mod.rs @@ -39,7 +39,7 @@ pub struct Summary { impl Summary { /// The elements vector default capacity. - pub const ELEMENTS_INITIAL_CAPACITY: usize = 65536; + pub const ELEMENTS_INITIAL_CAPACITY: usize = 1024 * 4096; /// /// A shortcut constructor. @@ -95,15 +95,21 @@ impl Summary { ); for element in self.elements.iter() { - let (size, cycles, gas, group) = match &element.outcome { + let (size, cycles, ergs, group, gas) = match &element.outcome { Outcome::Passed { - variant: PassedVariant::Deploy { size, cycles, gas }, + variant: + PassedVariant::Deploy { + size, + cycles, + ergs, + gas, + }, group, - } => (Some(*size), *cycles, *gas, group.clone()), + } => (Some(*size), *cycles, *ergs, group.clone(), *gas), Outcome::Passed { - variant: PassedVariant::Runtime { cycles, gas }, + variant: PassedVariant::Runtime { cycles, ergs, gas }, group, - } => (None, *cycles, *gas, group.clone()), + } => (None, *cycles, *ergs, group.clone(), *gas), _ => continue, }; @@ -122,7 +128,8 @@ impl Summary { .and_then(|mode| mode.llvm_optimizer_settings().cloned()) .unwrap_or(era_compiler_llvm_context::OptimizerSettings::none()); - let benchmark_element = benchmark_analyzer::BenchmarkElement::new(size, cycles, gas); + let benchmark_element = + benchmark_analyzer::BenchmarkElement::new(size, cycles, ergs, gas); if let Some(group) = group { benchmark .groups @@ -145,14 +152,14 @@ impl Summary { } /// - /// Wraps data into a synchronized shared reference. + /// Wraps data into a thread-safe shared reference. /// pub fn wrap(self) -> Arc> { Arc::new(Mutex::new(self)) } /// - /// Extracts the data from the synchronized shared reference. + /// Extracts the data from the thread-safe shared reference. /// pub fn unwrap_arc(summary: Arc>) -> Self { Arc::try_unwrap(summary) @@ -162,41 +169,7 @@ impl Summary { } /// - /// Adds an invalid outcome. - /// - pub fn invalid(summary: Arc>, mode: Option, name: String, error: S) - where - S: ToString, - { - let element = Element::new(mode, name, Outcome::invalid(error)); - summary.lock().expect("Sync").push_element(element); - } - - /// - /// Adds a failed outcome. - /// - pub fn failed( - summary: Arc>, - mode: Mode, - name: String, - expected: Output, - found: Output, - calldata: Vec, - ) { - let element = Element::new(Some(mode), name, Outcome::failed(expected, found, calldata)); - summary.lock().expect("Sync").push_element(element); - } - - /// - /// Adds an ignored outcome. - /// - pub fn ignored(summary: Arc>, name: String) { - let element = Element::new(None, name, Outcome::ignored()); - summary.lock().expect("Sync").push_element(element); - } - - /// - /// Adds a passed contract deploy outcome. + /// Adds a passed outcome of a deploy call. /// pub fn passed_deploy( summary: Arc>, @@ -205,14 +178,20 @@ impl Summary { group: Option, size: usize, cycles: usize, - gas: u32, + ergs: u64, + gas: u64, ) { - let passed_variant = PassedVariant::Deploy { size, cycles, gas }; + let passed_variant = PassedVariant::Deploy { + size, + cycles, + ergs, + gas, + }; Self::passed(summary, mode, name, group, passed_variant); } /// - /// Adds a passed contract call outcome. + /// Adds a passed outcome of an ordinary call. /// pub fn passed_runtime( summary: Arc>, @@ -220,14 +199,15 @@ impl Summary { name: String, group: Option, cycles: usize, - gas: u32, + ergs: u64, + gas: u64, ) { - let passed_variant = PassedVariant::Runtime { cycles, gas }; + let passed_variant = PassedVariant::Runtime { cycles, ergs, gas }; Self::passed(summary, mode, name, group, passed_variant); } /// - /// Adds a passed special function call outcome. + /// Adds a passed outcome of a special call, like `storageEmpty` or `balance`. /// pub fn passed_special( summary: Arc>, @@ -240,7 +220,41 @@ impl Summary { } /// - /// Adds a passed outcome. + /// Adds a failed outcome. + /// + pub fn failed( + summary: Arc>, + mode: Mode, + name: String, + expected: Output, + found: Output, + calldata: Vec, + ) { + let element = Element::new(Some(mode), name, Outcome::failed(expected, found, calldata)); + summary.lock().expect("Sync").push_element(element); + } + + /// + /// Adds an invalid outcome. + /// + pub fn invalid(summary: Arc>, mode: Option, name: String, error: S) + where + S: ToString, + { + let element = Element::new(mode, name, Outcome::invalid(error)); + summary.lock().expect("Sync").push_element(element); + } + + /// + /// Adds an ignored outcome. + /// + pub fn ignored(summary: Arc>, name: String) { + let element = Element::new(None, name, Outcome::ignored()); + summary.lock().expect("Sync").push_element(element); + } + + /// + /// The unified function for passed outcomes. /// fn passed( summary: Arc>, diff --git a/compiler_tester/src/target.rs b/compiler_tester/src/target.rs new file mode 100644 index 00000000..e0803950 --- /dev/null +++ b/compiler_tester/src/target.rs @@ -0,0 +1,43 @@ +//! +//! The compiler tester target to run tests on. +//! + +/// +/// The compiler tester target to run tests on. +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Deserialize)] +pub enum Target { + /// The EraVM target. + EraVM, + /// The native EVM target. + EVM, + /// The EVM interpreter running on top of EraVM. + EVMInterpreter, +} + +impl std::str::FromStr for Target { + type Err = anyhow::Error; + + fn from_str(string: &str) -> Result { + match string { + "EraVM" => Ok(Self::EraVM), + "EVM" => Ok(Self::EVM), + "EVMInterpreter" => Ok(Self::EVMInterpreter), + string => Err(anyhow::anyhow!( + "Unknown target `{}`. Supported targets: {:?}", + string, + vec![Self::EraVM, Self::EVM, Self::EVMInterpreter] + )), + } + } +} + +impl std::fmt::Display for Target { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Target::EraVM => write!(f, "EraVM"), + Target::EVM => write!(f, "EVM"), + Target::EVMInterpreter => write!(f, "EVMInterpreter"), + } + } +} diff --git a/compiler_tester/src/test/case/input/balance.rs b/compiler_tester/src/test/case/input/balance.rs index c13f87e2..8ec76d2e 100644 --- a/compiler_tester/src/test/case/input/balance.rs +++ b/compiler_tester/src/test/case/input/balance.rs @@ -6,9 +6,9 @@ use std::sync::Arc; use std::sync::Mutex; use crate::compilers::mode::Mode; +use crate::summary::Summary; use crate::vm::eravm::EraVM; use crate::vm::evm::EVM; -use crate::Summary; /// /// The balance check input variant. @@ -64,22 +64,28 @@ impl Balance { /// pub fn run_evm( self, - summary: Arc>, + _summary: Arc>, _vm: &EVM, - mode: Mode, + _mode: Mode, _test_group: Option, - name_prefix: String, - index: usize, + _name_prefix: String, + _index: usize, ) { - // TODO: get balance from EVM - let name = format!("{name_prefix}[#balance_check:{index}]"); - Summary::failed( - summary, - mode, - name, - self.balance.into(), - self.balance.into(), - self.address.to_fixed_bytes().to_vec(), - ); + todo!() + } + + /// + /// Runs the balance check on EVM interpreter. + /// + pub fn run_evm_interpreter( + self, + _summary: Arc>, + _vm: &EraVM, + _mode: Mode, + _test_group: Option, + _name_prefix: String, + _index: usize, + ) { + todo!() } } diff --git a/compiler_tester/src/test/case/input/calldata.rs b/compiler_tester/src/test/case/input/calldata.rs index e6afda51..023223df 100644 --- a/compiler_tester/src/test/case/input/calldata.rs +++ b/compiler_tester/src/test/case/input/calldata.rs @@ -2,7 +2,7 @@ //! The test input calldata. //! -use std::collections::HashMap; +use std::collections::BTreeMap; use crate::directories::matter_labs::test::metadata::case::input::calldata::Calldata as MatterLabsTestInputCalldata; use crate::test::case::input::value::Value; @@ -13,7 +13,7 @@ use crate::test::instance::Instance; /// #[derive(Debug, Clone, Default)] pub struct Calldata { - /// The inner calldata bytes. + /// The calldata bytes. pub inner: Vec, } @@ -22,41 +22,41 @@ impl Calldata { /// Try convert from Matter Labs compiler test storage data. /// pub fn try_from_matter_labs( - calldata: &MatterLabsTestInputCalldata, - instances: &HashMap, + calldata: MatterLabsTestInputCalldata, + instances: &BTreeMap, ) -> anyhow::Result { let calldata = match calldata { MatterLabsTestInputCalldata::Value(value) => { let hex = value.strip_prefix("0x").ok_or_else(|| { - anyhow::anyhow!("Invalid calldata value, expected hex starting with `0x`") + anyhow::anyhow!("Expected a hexadecimal starting with `0x`, found `{value}`") })?; - hex::decode(hex) - .map_err(|err| anyhow::anyhow!("Invalid calldata hex value: {}", err))? + hex::decode(hex).map_err(|error| { + anyhow::anyhow!("Hexadecimal value `{value}` decoding error: {}", error) + })? } MatterLabsTestInputCalldata::List(values) => { - let calldata_vec = Value::try_from_vec_matter_labs(values, instances) - .map_err(|err| anyhow::anyhow!("Invalid calldata: {}", err))?; - let mut calldata = Vec::with_capacity(values.len()); - for value in calldata_vec { + let mut result = Vec::with_capacity(values.len()); + let calldata = Value::try_from_vec_matter_labs(values, instances)?; + for value in calldata.into_iter() { let value = match value { Value::Certain(value) => value, - Value::Any => anyhow::bail!("* not allowed in calldata"), + Value::Any => anyhow::bail!("The `*` wildcard is not allowed in calldata"), }; let mut bytes = [0u8; era_compiler_common::BYTE_LENGTH_FIELD]; value.to_big_endian(&mut bytes); - calldata.extend(bytes); + result.extend(bytes); } - calldata + result } }; Ok(Self { inner: calldata }) } /// - /// Insert the selector at the beginning of the calldata. + /// Pushes a selector to the calldata. /// - pub fn add_selector(&mut self, selector: u32) { + pub fn push_selector(&mut self, selector: u32) { let mut calldata_with_selector = selector.to_be_bytes().to_vec(); calldata_with_selector.append(&mut self.inner); self.inner = calldata_with_selector; diff --git a/compiler_tester/src/test/case/input/deploy_eravm.rs b/compiler_tester/src/test/case/input/deploy_eravm.rs new file mode 100644 index 00000000..4a71666a --- /dev/null +++ b/compiler_tester/src/test/case/input/deploy_eravm.rs @@ -0,0 +1,124 @@ +//! +//! The EraVM deploy contract call input variant. +//! + +use std::sync::Arc; +use std::sync::Mutex; + +use crate::compilers::mode::Mode; +use crate::summary::Summary; +use crate::test::case::input::calldata::Calldata; +use crate::test::case::input::output::Output; +use crate::test::case::input::storage::Storage; +use crate::vm::eravm::deployers::EraVMDeployer; +use crate::vm::eravm::EraVM; + +/// +/// The EraVM deploy contract call input variant. +/// +#[derive(Debug, Clone)] +pub struct DeployEraVM { + /// The contract path. + path: String, + /// The contract hash. + hash: web3::types::U256, + /// The calldata. + calldata: Calldata, + /// The caller. + caller: web3::types::Address, + /// The value in wei. + value: Option, + /// The contracts storage to set before running. + storage: Storage, + /// The expected output. + expected: Output, +} + +impl DeployEraVM { + /// + /// A shortcut constructor. + /// + pub fn new( + path: String, + hash: web3::types::U256, + calldata: Calldata, + caller: web3::types::Address, + value: Option, + storage: Storage, + expected: Output, + ) -> Self { + Self { + path, + hash, + calldata, + caller, + value, + storage, + expected, + } + } +} + +impl DeployEraVM { + /// + /// Runs the deploy on EraVM. + /// + pub fn run_eravm( + self, + summary: Arc>, + vm: &mut EraVM, + mode: Mode, + deployer: &mut D, + test_group: Option, + name_prefix: String, + ) where + D: EraVMDeployer, + { + let name = format!("{}[#deployer:{}]", name_prefix, self.path); + + vm.populate_storage(self.storage.inner); + let result = match deployer.deploy_eravm::( + name.clone(), + self.caller, + self.hash, + self.calldata.inner.clone(), + self.value, + vm, + ) { + Ok(result) => result, + Err(error) => { + Summary::invalid(summary, Some(mode), name, error); + return; + } + }; + + if result.output == self.expected { + let build_size = match vm.get_contract_size(self.hash) { + Ok(size) => size, + Err(error) => { + Summary::invalid(summary, Some(mode), name, error); + return; + } + }; + Summary::passed_deploy( + summary, + mode, + name, + test_group, + build_size, + result.cycles, + result.ergs, + result.gas, + ); + } else { + Summary::failed( + summary, + mode, + name, + self.expected, + result.output, + self.calldata.inner, + ); + } + } +} diff --git a/compiler_tester/src/test/case/input/deploy.rs b/compiler_tester/src/test/case/input/deploy_evm.rs similarity index 71% rename from compiler_tester/src/test/case/input/deploy.rs rename to compiler_tester/src/test/case/input/deploy_evm.rs index d003cdd8..37a73bf4 100644 --- a/compiler_tester/src/test/case/input/deploy.rs +++ b/compiler_tester/src/test/case/input/deploy_evm.rs @@ -1,29 +1,28 @@ //! -//! The contract call input variant. +//! The EVM deploy contract call input variant. //! use std::sync::Arc; use std::sync::Mutex; use crate::compilers::mode::Mode; -use crate::vm::eravm::deployers::Deployer as EraVMDeployer; +use crate::summary::Summary; +use crate::test::case::input::calldata::Calldata; +use crate::test::case::input::output::Output; +use crate::test::case::input::storage::Storage; +use crate::vm::eravm::deployers::EraVMDeployer; use crate::vm::eravm::EraVM; use crate::vm::evm::EVM; -use crate::Summary; - -use super::calldata::Calldata; -use super::output::Output; -use super::storage::Storage; /// -/// The contract call input variant. +/// The EVM deploy contract call input variant. /// #[derive(Debug, Clone)] -pub struct Deploy { - /// The contract path. - path: String, - /// The contract hash. - hash: web3::types::U256, +pub struct DeployEVM { + /// The contract identifier. + identifier: String, + /// The contract init code. + init_code: Vec, /// The calldata. calldata: Calldata, /// The caller. @@ -36,13 +35,13 @@ pub struct Deploy { expected: Output, } -impl Deploy { +impl DeployEVM { /// /// A shortcut constructor. /// pub fn new( - path: String, - hash: web3::types::U256, + identifier: String, + init_code: Vec, calldata: Calldata, caller: web3::types::Address, value: Option, @@ -50,8 +49,8 @@ impl Deploy { expected: Output, ) -> Self { Self { - path, - hash, + identifier, + init_code, calldata, caller, value, @@ -61,53 +60,42 @@ impl Deploy { } } -impl Deploy { +impl DeployEVM { /// - /// Runs the deploy on EraVM. + /// Runs the deploy transaction on native EVM. /// - pub fn run_eravm( + pub fn run_evm( self, summary: Arc>, - vm: &mut EraVM, + vm: &mut EVM, mode: Mode, - deployer: &mut D, test_group: Option, name_prefix: String, - ) where - D: EraVMDeployer, - { - let name = format!("{}[#deployer:{}]", name_prefix, self.path); + ) { + let name = format!("{}[#deployer:{}]", name_prefix, self.identifier); vm.populate_storage(self.storage.inner); - let result = match deployer.deploy::( + let result = match vm.execute_deploy_code( name.clone(), + self.identifier.as_str(), self.caller, - self.hash, - self.calldata.inner.clone(), self.value, - vm, + self.calldata.inner.clone(), ) { - Ok(result) => result, + Ok(execution_result) => execution_result, Err(error) => { Summary::invalid(summary, Some(mode), name, error); return; } }; if result.output == self.expected { - let build_size = match vm.get_contract_size(self.hash) { - Ok(size) => size, - Err(error) => { - Summary::invalid(summary, Some(mode), name, error); - return; - } - }; - Summary::passed_deploy( + Summary::passed_runtime( summary, mode, name, test_group, - build_size, result.cycles, + 0, result.gas, ); } else { @@ -123,33 +111,49 @@ impl Deploy { } /// - /// Runs the deploy on EVM. + /// Runs the deploy transaction on EVM interpreter. /// - pub fn run_evm( + pub fn run_evm_interpreter( self, summary: Arc>, - vm: &mut EVM, + vm: &mut EraVM, mode: Mode, + deployer: &mut D, test_group: Option, name_prefix: String, - ) { - let name = format!("{}[#deployer:{}]", name_prefix, self.path); + ) where + D: EraVMDeployer, + { + let name = format!("{}[#deployer:{}]", name_prefix, self.identifier); + + let size = self.init_code.len(); vm.populate_storage(self.storage.inner); - let result = match vm.execute_deploy_code( + let result = match deployer.deploy_evm::( name.clone(), self.caller, - self.value, + self.init_code, self.calldata.inner.clone(), + self.value, + vm, ) { - Ok(execution_result) => execution_result, + Ok(result) => result, Err(error) => { Summary::invalid(summary, Some(mode), name, error); return; } }; if result.output == self.expected { - Summary::passed_runtime(summary, mode, name, test_group, result.cycles, result.gas); + Summary::passed_deploy( + summary, + mode, + name, + test_group, + size, + result.cycles, + result.ergs, + result.gas, + ); } else { Summary::failed( summary, diff --git a/compiler_tester/src/test/case/input/mod.rs b/compiler_tester/src/test/case/input/mod.rs index b87b2c18..21308509 100644 --- a/compiler_tester/src/test/case/input/mod.rs +++ b/compiler_tester/src/test/case/input/mod.rs @@ -4,7 +4,8 @@ pub mod balance; pub mod calldata; -pub mod deploy; +pub mod deploy_eravm; +pub mod deploy_evm; pub mod output; pub mod runtime; pub mod storage; @@ -12,7 +13,6 @@ pub mod storage_empty; pub mod value; use std::collections::BTreeMap; -use std::collections::HashMap; use std::str::FromStr; use std::sync::Arc; use std::sync::Mutex; @@ -21,13 +21,14 @@ use crate::compilers::mode::Mode; use crate::directories::matter_labs::test::metadata::case::input::Input as MatterLabsTestInput; use crate::summary::Summary; use crate::test::instance::Instance; -use crate::vm::eravm::deployers::Deployer as EraVMDeployer; +use crate::vm::eravm::deployers::EraVMDeployer; use crate::vm::eravm::EraVM; use crate::vm::evm::EVM; use self::balance::Balance; use self::calldata::Calldata; -use self::deploy::Deploy; +use self::deploy_eravm::DeployEraVM; +use self::deploy_evm::DeployEVM; use self::output::Output; use self::runtime::Runtime; use self::storage::Storage; @@ -38,10 +39,12 @@ use self::storage_empty::StorageEmpty; /// #[derive(Debug, Clone)] pub enum Input { + /// The EraVM contract deploy. + DeployEraVM(DeployEraVM), + /// The EVM contract deploy. + DeployEVM(DeployEVM), /// The contract call. Runtime(Runtime), - /// The contract deploy. - Deploy(Deploy), /// The storage empty check. StorageEmpty(StorageEmpty), /// Check account balance. @@ -53,39 +56,42 @@ impl Input { /// Try convert from Matter Labs compiler test metadata input. /// pub fn try_from_matter_labs( - input: &MatterLabsTestInput, + input: MatterLabsTestInput, mode: &Mode, - instances: &HashMap, + instances: &BTreeMap, method_identifiers: &Option>>, ) -> anyhow::Result { let caller = web3::types::Address::from_str(input.caller.as_str()) - .map_err(|error| anyhow::anyhow!("Invalid caller: {}", error))?; + .map_err(|error| anyhow::anyhow!("Invalid caller `{}`: {}", input.caller, error))?; - let value = match input.value.as_ref() { + let value = match input.value { Some(value) => Some(if let Some(value) = value.strip_suffix(" ETH") { u128::from_str(value) - .map_err(|error| anyhow::anyhow!("Invalid value literal: {}", error))? + .map_err(|error| anyhow::anyhow!("Invalid value literal `{value}`: {}", error))? .checked_mul(10u128.pow(18)) - .ok_or_else(|| anyhow::anyhow!("Overflow: value too big"))? + .ok_or_else(|| { + anyhow::anyhow!("Invalid value literal `{value}`: u128 overflow") + })? } else if let Some(value) = value.strip_suffix(" wei") { - u128::from_str(value) - .map_err(|error| anyhow::anyhow!("Invalid value literal: {}", error))? + u128::from_str(value).map_err(|error| { + anyhow::anyhow!("Invalid value literal `{value}`: {}", error) + })? } else { - anyhow::bail!("Invalid value"); + anyhow::bail!("Invalid value `{value}`"); }), None => None, }; - let mut calldata = Calldata::try_from_matter_labs(&input.calldata, instances) + let mut calldata = Calldata::try_from_matter_labs(input.calldata, instances) .map_err(|error| anyhow::anyhow!("Invalid calldata: {}", error))?; - let expected = match input.expected.as_ref() { + let expected = match input.expected { Some(expected) => Output::try_from_matter_labs_expected(expected, mode, instances) - .map_err(|error| anyhow::anyhow!("Invalid expected: {}", error))?, + .map_err(|error| anyhow::anyhow!("Invalid expected metadata: {}", error))?, None => Output::default(), }; - let storage = Storage::try_from_matter_labs(&input.storage, instances) + let storage = Storage::try_from_matter_labs(input.storage, instances) .map_err(|error| anyhow::anyhow!("Invalid storage: {}", error))?; let instance = instances @@ -93,17 +99,28 @@ impl Input { .ok_or_else(|| anyhow::anyhow!("Instance `{}` not found", input.instance))?; let input = match input.method.as_str() { - "#deployer" => Input::Deploy(Deploy::new( - instance.path.to_owned(), - instance.code_hash, - calldata, - caller, - value, - storage, - expected, - )), + "#deployer" => match instance { + Instance::EraVM(instance) => Input::DeployEraVM(DeployEraVM::new( + instance.path.to_owned(), + instance.code_hash, + calldata, + caller, + value, + storage, + expected, + )), + Instance::EVM(instance) => Input::DeployEVM(DeployEVM::new( + instance.path.to_owned(), + instance.init_code.to_owned(), + calldata, + caller, + value, + storage, + expected, + )), + }, "#fallback" => { - let address = instance.address.ok_or_else(|| { + let address = instance.address().ok_or_else(|| { anyhow::anyhow!( "Instance `{}` was not successfully deployed", input.instance @@ -112,7 +129,7 @@ impl Input { Input::Runtime(Runtime::new( "#fallback".to_string(), - address, + *address, calldata, caller, value, @@ -121,18 +138,22 @@ impl Input { )) } entry => { - let address = instance.address.ok_or_else(|| { + let address = instance.address().ok_or_else(|| { anyhow::anyhow!( "Instance `{}` was not successfully deployed", input.instance ) })?; - let path = instance.path.as_str(); + + let path = instance.path(); let selector = match method_identifiers { Some(method_identifiers) => method_identifiers .get(path) .ok_or_else(|| { - anyhow::anyhow!("Contract {} not found in the method identifiers", path) + anyhow::anyhow!( + "Contract `{}` not found in the method identifiers", + path + ) })? .iter() .find_map(|(name, selector)| { @@ -143,17 +164,27 @@ impl Input { } }) .ok_or_else(|| { - anyhow::anyhow!("Selector of the method `{}` not found", entry) + anyhow::anyhow!( + "In the contract `{}`, selector of the method `{}` not found", + path, + entry + ) })?, None => u32::from_str_radix(entry, era_compiler_common::BASE_HEXADECIMAL) - .map_err(|err| anyhow::anyhow!("Invalid entry value: {}", err))?, + .map_err(|error| { + anyhow::anyhow!( + "Invalid entry value for contract `{}`: {}", + path, + error + ) + })?, }; - calldata.add_selector(selector); + calldata.push_selector(selector); Input::Runtime(Runtime::new( entry.to_string(), - address, + *address, calldata, caller, value, @@ -171,15 +202,17 @@ impl Input { /// pub fn try_from_ethereum( input: &solidity_adapter::FunctionCall, - main_contract_instance: &Instance, - libraries_instances: &HashMap, + instances: &BTreeMap, last_source: &str, caller: &web3::types::Address, ) -> anyhow::Result> { - let main_contract_address = main_contract_instance - .address - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Internal error: main contract address is none"))?; + let main_contract_instance = instances + .values() + .find(|instance| instance.is_main()) + .ok_or_else(|| anyhow::anyhow!("Could not identify the Ethereum test main contract"))? + .to_owned(); + let main_contract_address = main_contract_instance.address().expect("Always exists"); + let input = match input { solidity_adapter::FunctionCall::Constructor { calldata, @@ -188,11 +221,9 @@ impl Input { .. } => { let value = match value { - Some(value) => Some( - (*value) - .try_into() - .map_err(|error| anyhow::anyhow!("Value is too big: {}", error))?, - ), + Some(value) => Some((*value).try_into().map_err(|error| { + anyhow::anyhow!("Invalid value literal `{:X}`: {}", value, error) + })?), None => None, }; @@ -205,15 +236,26 @@ impl Input { main_contract_address, ); - Some(Input::Deploy(Deploy::new( - main_contract_instance.path.to_owned(), - main_contract_instance.code_hash, - calldata.clone().into(), - *caller, - value, - Storage::default(), - expected, - ))) + match main_contract_instance { + Instance::EraVM(instance) => Some(Input::DeployEraVM(DeployEraVM::new( + instance.path.to_owned(), + instance.code_hash, + calldata.clone().into(), + *caller, + value, + Storage::default(), + expected, + ))), + Instance::EVM(instance) => Some(Input::DeployEVM(DeployEVM::new( + instance.path.to_owned(), + instance.init_code.to_owned(), + calldata.clone().into(), + *caller, + value, + Storage::default(), + expected, + ))), + } } solidity_adapter::FunctionCall::Library { name, source } => { let library = format!( @@ -221,30 +263,42 @@ impl Input { source.clone().unwrap_or_else(|| last_source.to_string()), name ); - let instance = libraries_instances.get(library.as_str()).ok_or_else(|| { - anyhow::anyhow!("Internal error: Library {} not found", library) - })?; - let hash = instance.code_hash; - let address = instance - .address - .ok_or_else(|| anyhow::anyhow!("Internal error: library address is none"))?; + let instance = instances + .get(library.as_str()) + .ok_or_else(|| anyhow::anyhow!("Library `{}` not found", library))?; let expected = Output::from_ethereum_expected( - &[web3::types::U256::from_big_endian(address.as_bytes())], + &[web3::types::U256::from_big_endian( + instance + .address() + .expect("Must be set by this point") + .as_bytes(), + )], false, &[], main_contract_address, ); - Some(Input::Deploy(Deploy::new( - instance.path.to_owned(), - hash, - Calldata::default(), - *caller, - None, - Storage::default(), - expected, - ))) + match instance { + Instance::EraVM(instance) => Some(Input::DeployEraVM(DeployEraVM::new( + instance.path.to_owned(), + instance.code_hash, + Calldata::default(), + *caller, + None, + Storage::default(), + expected, + ))), + Instance::EVM(instance) => Some(Input::DeployEVM(DeployEVM::new( + instance.path.to_owned(), + instance.init_code.to_owned(), + Calldata::default(), + *caller, + None, + Storage::default(), + expected, + ))), + } } solidity_adapter::FunctionCall::Balance { input, expected, .. @@ -265,11 +319,9 @@ impl Input { .. } => { let value = match value { - Some(value) => Some( - (*value) - .try_into() - .map_err(|error| anyhow::anyhow!("Value is too big: {}", error))?, - ), + Some(value) => Some((*value).try_into().map_err(|error| { + anyhow::anyhow!("Invalid value literal `{:X}`: {}", value, error) + })?), None => None, }; @@ -299,7 +351,6 @@ impl Input { /// /// Runs the input on EraVM. /// - #[allow(clippy::too_many_arguments)] pub fn run_eravm( self, summary: Arc>, @@ -313,12 +364,20 @@ impl Input { D: EraVMDeployer, { match self { + Self::DeployEraVM(deploy) => { + deploy.run_eravm::<_, M>(summary, vm, mode, deployer, test_group, name_prefix) + } + Self::DeployEVM(deploy) => deploy.run_evm_interpreter::<_, M>( + summary, + vm, + mode, + deployer, + test_group, + name_prefix, + ), Self::Runtime(runtime) => { runtime.run_eravm::(summary, vm, mode, test_group, name_prefix, index) } - Self::Deploy(deploy) => { - deploy.run_eravm::<_, M>(summary, vm, mode, deployer, test_group, name_prefix) - } Self::StorageEmpty(storage_empty) => { storage_empty.run_eravm(summary, vm, mode, test_group, name_prefix, index) } @@ -341,10 +400,11 @@ impl Input { index: usize, ) { match self { + Self::DeployEraVM { .. } => panic!("EraVM deploy transaction cannot be run on EVM"), + Self::DeployEVM(deploy) => deploy.run_evm(summary, vm, mode, test_group, name_prefix), Self::Runtime(runtime) => { runtime.run_evm(summary, vm, mode, test_group, name_prefix, index) } - Self::Deploy(deploy) => deploy.run_evm(summary, vm, mode, test_group, name_prefix), Self::StorageEmpty(storage_empty) => { storage_empty.run_evm(summary, vm, mode, test_group, name_prefix, index) } @@ -353,4 +413,43 @@ impl Input { } }; } + + /// + /// Runs the input on EVM interpreter. + /// + pub fn run_evm_interpreter( + self, + summary: Arc>, + vm: &mut EraVM, + mode: Mode, + deployer: &mut D, + test_group: Option, + name_prefix: String, + index: usize, + ) where + D: EraVMDeployer, + { + match self { + Self::DeployEraVM { .. } => { + panic!("EraVM deploy transaction cannot be run on EVM interpreter") + } + Self::DeployEVM(deploy) => deploy.run_evm_interpreter::<_, M>( + summary, + vm, + mode, + deployer, + test_group, + name_prefix, + ), + Self::Runtime(runtime) => { + runtime.run_evm_interpreter::(summary, vm, mode, test_group, name_prefix, index) + } + Self::StorageEmpty(storage_empty) => { + storage_empty.run_evm_interpreter(summary, vm, mode, test_group, name_prefix, index) + } + Self::Balance(balance_check) => { + balance_check.run_evm_interpreter(summary, vm, mode, test_group, name_prefix, index) + } + }; + } } diff --git a/compiler_tester/src/test/case/input/output/event.rs b/compiler_tester/src/test/case/input/output/event.rs index 33cb94c4..bc642522 100644 --- a/compiler_tester/src/test/case/input/output/event.rs +++ b/compiler_tester/src/test/case/input/output/event.rs @@ -2,7 +2,7 @@ //! The compiler test outcome event. //! -use std::collections::HashMap; +use std::collections::BTreeMap; use std::str::FromStr; use serde::Serialize; @@ -44,31 +44,34 @@ impl Event { /// Try convert from Matter Labs compiler test metadata expected event. /// pub fn try_from_matter_labs( - event: &MatterLabsTestExpectedEvent, - instances: &HashMap, + event: MatterLabsTestExpectedEvent, + instances: &BTreeMap, ) -> anyhow::Result { - let topics = Value::try_from_vec_matter_labs(&event.topics, instances) + let topics = Value::try_from_vec_matter_labs(event.topics, instances) .map_err(|error| anyhow::anyhow!("Invalid topics: {}", error))?; - let values = Value::try_from_vec_matter_labs(&event.values, instances) + let values = Value::try_from_vec_matter_labs(event.values, instances) .map_err(|error| anyhow::anyhow!("Invalid values: {}", error))?; - let address = match event.address.as_ref() { + + let address = match event.address { Some(address) => Some( if let Some(instance) = address.strip_suffix(".address") { instances .get(instance) .ok_or_else(|| anyhow::anyhow!("Instance `{}` not found", instance))? - .address + .address() + .copied() .ok_or_else(|| { - anyhow::anyhow!("Instance `{}` is not successfully deployed", instance) + anyhow::anyhow!("Instance `{}` was not successfully deployed", instance) }) } else { - web3::types::Address::from_str(address) + web3::types::Address::from_str(address.as_str()) .map_err(|error| anyhow::anyhow!("Invalid address literal: {}", error)) } - .map_err(|error| anyhow::anyhow!("Invalid event address: {}", error))?, + .map_err(|error| anyhow::anyhow!("Invalid event address `{address}`: {error}"))?, ), None => None, }; + Ok(Self { address, topics, @@ -79,7 +82,7 @@ impl Event { /// /// Convert from Ethereum compiler test metadata expected event. /// - pub fn from_ethereum_expected( + pub fn from_ethereum( event: &solidity_adapter::Event, contract_address: &web3::types::Address, ) -> Self { @@ -124,15 +127,15 @@ impl Event { } } -impl From<&zkevm_tester::runners::events::SolidityLikeEvent> for Event { - fn from(event: &zkevm_tester::runners::events::SolidityLikeEvent) -> Self { +impl From for Event { + fn from(event: zkevm_tester::runners::events::SolidityLikeEvent) -> Self { let mut topics: Vec = event .topics - .iter() + .into_iter() .map(|topic| Value::Certain(web3::types::U256::from_big_endian(topic.as_slice()))) .collect(); - // Event are written by the system contract, and the first topic is the actual msg.sender + // Event are written by the system contract, and the first topic is the `msg.sender` let address = crate::utils::u256_to_address(topics.remove(0).unwrap_certain_as_ref()); let values: Vec = event diff --git a/compiler_tester/src/test/case/input/output/mod.rs b/compiler_tester/src/test/case/input/output/mod.rs index c595c77e..728f7734 100644 --- a/compiler_tester/src/test/case/input/output/mod.rs +++ b/compiler_tester/src/test/case/input/output/mod.rs @@ -4,7 +4,7 @@ pub mod event; -use std::collections::HashMap; +use std::collections::BTreeMap; use std::str::FromStr; use serde::Serialize; @@ -47,13 +47,13 @@ impl Output { /// Try convert from Matter Labs compiler test metadata expected. /// pub fn try_from_matter_labs_expected( - expected: &MatterLabsTestExpected, + expected: MatterLabsTestExpected, mode: &Mode, - instances: &HashMap, + instances: &BTreeMap, ) -> anyhow::Result { let variants = match expected { MatterLabsTestExpected::Single(variant) => vec![variant], - MatterLabsTestExpected::Multiple(variants) => variants.iter().collect(), + MatterLabsTestExpected::Multiple(variants) => variants.into_iter().collect(), }; let variant = variants .into_iter() @@ -71,29 +71,27 @@ impl Output { }) .ok_or_else(|| anyhow::anyhow!("Version not covered"))?; - let return_data = match variant { - MatterLabsTestExpectedVariant::Simple(expected) => expected, - MatterLabsTestExpectedVariant::Extended(expected) => &expected.return_data, - }; - let return_data = Value::try_from_vec_matter_labs(return_data, instances) - .map_err(|error| anyhow::anyhow!("Invalid return data: {}", error))?; - let (exception, events) = match variant { - MatterLabsTestExpectedVariant::Simple(_) => (false, Vec::new()), - MatterLabsTestExpectedVariant::Extended(expected) => ( - expected.exception, - expected + let (return_data, exception, events) = match variant { + MatterLabsTestExpectedVariant::Simple(return_data) => (return_data, false, Vec::new()), + MatterLabsTestExpectedVariant::Extended(expected) => { + let return_data = expected.return_data; + let exception = expected.exception; + let events = expected .events - .iter() + .into_iter() .enumerate() .map(|(index, event)| { Event::try_from_matter_labs(event, instances).map_err(|error| { - anyhow::anyhow!("Event {} is invalid: {}", index, error) + anyhow::anyhow!("Event #{} is invalid: {}", index, error) }) }) .collect::>>() - .map_err(|error| anyhow::anyhow!("Invalid events: {}", error))?, - ), + .map_err(|error| anyhow::anyhow!("Invalid events: {}", error))?; + (return_data, exception, events) + } }; + let return_data = Value::try_from_vec_matter_labs(return_data, instances) + .map_err(|error| anyhow::anyhow!("Invalid return data: {error}"))?; Ok(Self { return_data, @@ -128,7 +126,7 @@ impl Output { let events = events .iter() - .map(|event| Event::from_ethereum_expected(event, contract_address)) + .map(|event| Event::from_ethereum(event, contract_address)) .collect(); Self { @@ -160,11 +158,11 @@ impl From for Output { } } -impl From<&zkevm_tester::runners::compiler_tests::VmSnapshot> for Output { - fn from(snapshot: &zkevm_tester::runners::compiler_tests::VmSnapshot) -> Self { +impl From for Output { + fn from(snapshot: zkevm_tester::runners::compiler_tests::VmSnapshot) -> Self { let events = snapshot .events - .iter() + .into_iter() .filter(|event| { let first_topic = event.topics.first().expect("Always exists"); let address = crate::utils::bytes32_to_address(first_topic); @@ -176,7 +174,7 @@ impl From<&zkevm_tester::runners::compiler_tests::VmSnapshot> for Output { .map(Event::from) .collect(); - match &snapshot.execution_result { + match snapshot.execution_result { zkevm_tester::runners::compiler_tests::VmExecutionResult::Ok(return_data) => { let return_data = return_data .chunks(era_compiler_common::BYTE_LENGTH_FIELD) @@ -195,6 +193,7 @@ impl From<&zkevm_tester::runners::compiler_tests::VmSnapshot> for Output { Value::Certain(value) }) .collect(); + Self { return_data, exception: false, @@ -219,6 +218,7 @@ impl From<&zkevm_tester::runners::compiler_tests::VmSnapshot> for Output { Value::Certain(value) }) .collect(); + Self { return_data, exception: true, @@ -284,10 +284,10 @@ impl PartialEq for Output { } for index in 0..self.return_data.len() { - if let (Value::Certain(value1), Value::Certain(value2)) = + if let (Value::Certain(value_1), Value::Certain(value_2)) = (&self.return_data[index], &other.return_data[index]) { - if value1 != value2 { + if value_1 != value_2 { return false; } } diff --git a/compiler_tester/src/test/case/input/runtime.rs b/compiler_tester/src/test/case/input/runtime.rs index 91eca439..afcbf001 100644 --- a/compiler_tester/src/test/case/input/runtime.rs +++ b/compiler_tester/src/test/case/input/runtime.rs @@ -2,17 +2,19 @@ //! The contract call input variant. //! +use std::str::FromStr; use std::sync::Arc; use std::sync::Mutex; +use era_compiler_common::BYTE_LENGTH_ETH_ADDRESS; + use crate::compilers::mode::Mode; +use crate::summary::Summary; +use crate::test::case::input::calldata::Calldata; +use crate::test::case::input::output::Output; +use crate::test::case::input::storage::Storage; use crate::vm::eravm::EraVM; use crate::vm::evm::EVM; -use crate::Summary; - -use super::calldata::Calldata; -use super::output::Output; -use super::storage::Storage; /// /// The contract call input variant. @@ -88,8 +90,35 @@ impl Runtime { return; } }; + let gas = if let Some(benchmark_analyzer::Benchmark::EVM_INTERPRETER_GROUP_NAME) = + test_group.as_deref() + { + match result.output.return_data.first() { + Some(gas) => gas.unwrap_certain_as_ref().as_u64(), + None => { + Summary::invalid( + summary, + Some(mode), + name, + "EVM interpreter gas usage value not found", + ); + return; + } + } + } else { + 0 + }; + if result.output == self.expected { - Summary::passed_runtime(summary, mode, name, test_group, result.cycles, result.gas); + Summary::passed_runtime( + summary, + mode, + name, + test_group, + result.cycles, + result.ergs, + gas, + ); } else { Summary::failed( summary, @@ -118,6 +147,7 @@ impl Runtime { vm.populate_storage(self.storage.inner); let result = match vm.execute_runtime_code( name.clone(), + self.address, self.caller, self.value, self.calldata.inner.clone(), @@ -129,7 +159,15 @@ impl Runtime { } }; if result.output == self.expected { - Summary::passed_runtime(summary, mode, name, test_group, result.cycles, result.gas); + Summary::passed_runtime( + summary, + mode, + name, + test_group, + result.cycles, + result.ergs, + result.gas, + ); } else { Summary::failed( summary, @@ -141,4 +179,73 @@ impl Runtime { ); } } + /// + /// Runs the call on EVM interpreter. + /// + pub fn run_evm_interpreter( + self, + summary: Arc>, + vm: &mut EraVM, + mode: Mode, + test_group: Option, + name_prefix: String, + index: usize, + ) { + let name = format!("{}[{}:{}]", name_prefix, self.name, index); + vm.populate_storage(self.storage.inner); + + let benchmark_caller_address = + web3::types::Address::from_str(EraVM::DEFAULT_BENCHMARK_CALLER_ADDRESS) + .expect("Always valid"); + + let mut calldata = + Vec::with_capacity(era_compiler_common::BYTE_LENGTH_FIELD + self.calldata.inner.len()); + calldata.extend([0u8; era_compiler_common::BYTE_LENGTH_FIELD - BYTE_LENGTH_ETH_ADDRESS]); + calldata.extend(self.address.as_bytes()); + calldata.extend(self.calldata.inner); + + let mut result = match vm.execute::( + name.clone(), + benchmark_caller_address, + self.caller, + self.value, + calldata.clone(), + None, + ) { + Ok(result) => result, + Err(error) => { + Summary::invalid(summary, Some(mode), name, error); + return; + } + }; + if result.output.return_data.is_empty() { + Summary::invalid( + summary, + Some(mode), + name, + "EVM interpreter gas usage value not found", + ); + return; + } + let gas = result + .output + .return_data + .remove(0) + .unwrap_certain_as_ref() + .as_u64(); + + if result.output == self.expected { + Summary::passed_runtime( + summary, + mode, + name, + test_group, + result.cycles, + result.ergs, + gas, + ); + } else { + Summary::failed(summary, mode, name, self.expected, result.output, calldata); + } + } } diff --git a/compiler_tester/src/test/case/input/storage.rs b/compiler_tester/src/test/case/input/storage.rs index 444c7c36..de22800d 100644 --- a/compiler_tester/src/test/case/input/storage.rs +++ b/compiler_tester/src/test/case/input/storage.rs @@ -2,6 +2,7 @@ //! The test input storage data. //! +use std::collections::BTreeMap; use std::collections::HashMap; use std::str::FromStr; @@ -23,43 +24,44 @@ impl Storage { /// Try convert from Matter Labs compiler test storage data. /// pub fn try_from_matter_labs( - storage: &HashMap, - instances: &HashMap, + storage: HashMap, + instances: &BTreeMap, ) -> anyhow::Result { - let mut result_storage = HashMap::new(); + let mut result = HashMap::new(); - for (address, contract_storage) in storage.iter() { + for (address, contract_storage) in storage.into_iter() { let address = if let Some(instance) = address.strip_suffix(".address") { instances .get(instance) .ok_or_else(|| anyhow::anyhow!("Instance `{}` not found", instance))? - .address + .address() + .copied() .ok_or_else(|| { anyhow::anyhow!("Instance `{}` is not successfully deployed", instance) }) } else { - web3::types::Address::from_str(address) + web3::types::Address::from_str(address.as_str()) .map_err(|error| anyhow::anyhow!("Invalid address literal: {}", error)) } .map_err(|error| anyhow::anyhow!("Invalid storage address: {}", error))?; let contract_storage = match contract_storage { MatterLabsTestContractStorage::List(list) => list - .iter() + .into_iter() .enumerate() - .map(|(key, value)| (key.to_string(), value.clone())) + .map(|(key, value)| (key.to_string(), value)) .collect(), MatterLabsTestContractStorage::Map(map) => map.clone(), }; for (key, value) in contract_storage.into_iter() { - let key = match Value::try_from_matter_labs(key.as_str(), instances) + let key = match Value::try_from_matter_labs(key, instances) .map_err(|error| anyhow::anyhow!("Invalid storage key: {}", error))? { Value::Certain(value) => value, Value::Any => anyhow::bail!("Storage key can not be `*`"), }; - let value = match Value::try_from_matter_labs(value.as_str(), instances) + let value = match Value::try_from_matter_labs(value, instances) .map_err(|error| anyhow::anyhow!("Invalid storage value: {}", error))? { Value::Certain(value) => value, @@ -70,12 +72,10 @@ impl Storage { value.to_big_endian(value_bytes.as_mut_slice()); let value = web3::types::H256::from(value_bytes); - result_storage.insert((address, key), value); + result.insert((address, key), value); } } - Ok(Self { - inner: result_storage, - }) + Ok(Self { inner: result }) } } diff --git a/compiler_tester/src/test/case/input/storage_empty.rs b/compiler_tester/src/test/case/input/storage_empty.rs index 63f69dc1..49f39c71 100644 --- a/compiler_tester/src/test/case/input/storage_empty.rs +++ b/compiler_tester/src/test/case/input/storage_empty.rs @@ -6,9 +6,9 @@ use std::sync::Arc; use std::sync::Mutex; use crate::compilers::mode::Mode; +use crate::summary::Summary; use crate::vm::eravm::EraVM; use crate::vm::evm::EVM; -use crate::Summary; /// /// The storage emptiness check input variant. @@ -42,6 +42,7 @@ impl StorageEmpty { index: usize, ) { let name = format!("{name_prefix}[#storage_empty_check:{index}]"); + let found = vm.is_storage_empty(); if found == self.is_empty { Summary::passed_special(summary, mode, name, test_group); @@ -62,22 +63,28 @@ impl StorageEmpty { /// pub fn run_evm( self, - summary: Arc>, + _summary: Arc>, _vm: &EVM, - mode: Mode, + _mode: Mode, _test_group: Option, - name_prefix: String, - index: usize, + _name_prefix: String, + _index: usize, ) { - // TODO: check storage in EVM - let name = format!("{name_prefix}[#storage_empty_check:{index}]"); - Summary::failed( - summary, - mode, - name, - self.is_empty.into(), - self.is_empty.into(), - vec![], - ); + todo!() + } + + /// + /// Runs the storage empty check on EVM interpreter. + /// + pub fn run_evm_interpreter( + self, + _summary: Arc>, + _vm: &EraVM, + _mode: Mode, + _test_group: Option, + _name_prefix: String, + _index: usize, + ) { + todo!() } } diff --git a/compiler_tester/src/test/case/input/value.rs b/compiler_tester/src/test/case/input/value.rs index 5e4b997f..32e150cf 100644 --- a/compiler_tester/src/test/case/input/value.rs +++ b/compiler_tester/src/test/case/input/value.rs @@ -2,7 +2,7 @@ //! The compiler test value. //! -use std::collections::HashMap; +use std::collections::BTreeMap; use std::str::FromStr; use serde::Serialize; @@ -32,7 +32,7 @@ impl Value { pub fn unwrap_certain_as_ref(&self) -> &web3::types::U256 { match self { Self::Certain(value) => value, - Self::Any => panic!("Value in any"), + Self::Any => panic!("Value is unknown"), } } @@ -40,8 +40,8 @@ impl Value { /// Try convert from Matter Labs compiler test metadata value. /// pub fn try_from_matter_labs( - value: &str, - instances: &HashMap, + value: String, + instances: &BTreeMap, ) -> anyhow::Result { if value == "*" { return Ok(Self::Any); @@ -52,7 +52,7 @@ impl Value { instances .get(instance) .ok_or_else(|| anyhow::anyhow!("Instance `{}` not found", instance))? - .address + .address() .ok_or_else(|| { anyhow::anyhow!("Instance `{}` was not successfully deployed", instance) })? @@ -74,9 +74,10 @@ impl Value { web3::types::U256::from_str(value) .map_err(|error| anyhow::anyhow!("Invalid hexadecimal literal: {}", error))? } else { - web3::types::U256::from_dec_str(value) + web3::types::U256::from_dec_str(value.as_str()) .map_err(|error| anyhow::anyhow!("Invalid decimal literal: {}", error))? }; + Ok(Self::Certain(value)) } @@ -84,11 +85,11 @@ impl Value { /// Try convert into vec of self from vec of Matter Labs compiler test metadata values. /// pub fn try_from_vec_matter_labs( - values: &[String], - instances: &HashMap, + values: Vec, + instances: &BTreeMap, ) -> anyhow::Result> { values - .iter() + .into_iter() .enumerate() .map(|(index, value)| { Self::try_from_matter_labs(value, instances) diff --git a/compiler_tester/src/test/case/mod.rs b/compiler_tester/src/test/case/mod.rs index d545431e..64e9f8b5 100644 --- a/compiler_tester/src/test/case/mod.rs +++ b/compiler_tester/src/test/case/mod.rs @@ -5,7 +5,6 @@ pub mod input; use std::collections::BTreeMap; -use std::collections::HashMap; use std::sync::Arc; use std::sync::Mutex; @@ -13,7 +12,7 @@ use crate::compilers::mode::Mode; use crate::directories::matter_labs::test::metadata::case::Case as MatterLabsTestCase; use crate::summary::Summary; use crate::test::instance::Instance; -use crate::vm::eravm::deployers::Deployer as EraVMDeployer; +use crate::vm::eravm::deployers::EraVMDeployer; use crate::vm::eravm::EraVM; use crate::vm::evm::EVM; @@ -42,20 +41,20 @@ impl Case { /// Try convert from Matter Labs compiler test metadata case. /// pub fn try_from_matter_labs( - case: &MatterLabsTestCase, + case: MatterLabsTestCase, mode: &Mode, - instances: &HashMap, + instances: &BTreeMap, method_identifiers: &Option>>, ) -> anyhow::Result { - let mut inputs = Vec::with_capacity(case.inputs.capacity()); + let mut inputs = Vec::with_capacity(case.inputs.len()); - for (index, input) in case.inputs.iter().enumerate() { + for (index, input) in case.inputs.into_iter().enumerate() { let input = Input::try_from_matter_labs(input, mode, instances, method_identifiers) .map_err(|error| anyhow::anyhow!("Input #{} is invalid: {}", index, error))?; inputs.push(input); } - Ok(Self::new(Some(case.name.clone()), inputs)) + Ok(Self::new(Some(case.name), inputs)) } /// @@ -63,11 +62,10 @@ impl Case { /// pub fn try_from_ethereum( case: &[solidity_adapter::FunctionCall], - main_contract_instance: &Instance, - libraries_instances: &HashMap, + instances: BTreeMap, last_source: &str, ) -> anyhow::Result { - let mut inputs = Vec::new(); + let mut inputs = Vec::with_capacity(case.len()); let mut caller = solidity_adapter::account_address(solidity_adapter::DEFAULT_ACCOUNT_INDEX); for (index, input) in case.iter().enumerate() { @@ -76,23 +74,18 @@ impl Case { caller = solidity_adapter::account_address(*input); } input => { - if let Some(input) = Input::try_from_ethereum( - input, - main_contract_instance, - libraries_instances, - last_source, - &caller, - ) - .map_err(|error| { - anyhow::anyhow!("Failed to proccess {} input: {}", index, error) - })? { - inputs.push(input) + if let Some(input) = + Input::try_from_ethereum(input, &instances, last_source, &caller).map_err( + |error| anyhow::anyhow!("Failed to proccess input #{index}: {error}"), + )? + { + inputs.push(input); } } } } - Ok(Self { name: None, inputs }) + Ok(Self::new(None, inputs)) } /// @@ -113,13 +106,13 @@ impl Case { } else { test_name }; - let mut deployer = D::new(); + for (index, input) in self.inputs.into_iter().enumerate() { input.run_eravm::<_, M>( summary.clone(), &mut vm, - mode.clone(), - &mut deployer, + mode.to_owned(), + &mut D::new(), test_group.clone(), name.clone(), index, @@ -143,6 +136,7 @@ impl Case { } else { test_name }; + for (index, input) in self.inputs.into_iter().enumerate() { input.run_evm( summary.clone(), @@ -154,4 +148,36 @@ impl Case { ) } } + + /// + /// Runs the case on EVM interpreter. + /// + pub fn run_evm_interpreter( + self, + summary: Arc>, + mut vm: EraVM, + mode: &Mode, + test_name: String, + test_group: Option, + ) where + D: EraVMDeployer, + { + let name = if let Some(case_name) = self.name { + format!("{test_name}::{case_name}") + } else { + test_name + }; + + for (index, input) in self.inputs.into_iter().enumerate() { + input.run_evm_interpreter::<_, M>( + summary.clone(), + &mut vm, + mode.clone(), + &mut D::new(), + test_group.clone(), + name.clone(), + index, + ) + } + } } diff --git a/compiler_tester/src/test/eravm.rs b/compiler_tester/src/test/eravm.rs deleted file mode 100644 index f9aa5878..00000000 --- a/compiler_tester/src/test/eravm.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! -//! The EraVM test. -//! - -use std::collections::HashMap; -use std::sync::Arc; -use std::sync::Mutex; - -use crate::compilers::mode::Mode; -use crate::test::case::Case; -use crate::vm::eravm::deployers::Deployer as EraVMDeployer; -use crate::vm::eravm::EraVM; -use crate::Summary; - -/// -/// The test. -/// -pub struct Test { - /// The test name. - name: String, - /// The test group. - group: Option, - /// The test mode. - mode: Mode, - /// The contract builds. - builds: HashMap, - /// The test cases. - cases: Vec, -} - -impl Test { - /// - /// A shortcut constructor. - /// - pub fn new( - name: String, - group: Option, - mode: Mode, - builds: HashMap, - cases: Vec, - ) -> Self { - Self { - name, - group, - mode, - builds, - cases, - } - } - - /// - /// Runs the test. - /// - pub fn run(self, summary: Arc>, vm: Arc) - where - D: EraVMDeployer, - { - for case in self.cases { - let vm = EraVM::clone_with_contracts(vm.clone(), self.builds.clone()); - case.run_eravm::( - summary.clone(), - vm.clone(), - &self.mode, - self.name.clone(), - self.group.clone(), - ); - } - } -} diff --git a/compiler_tester/src/test/evm.rs b/compiler_tester/src/test/evm.rs deleted file mode 100644 index 229c82d4..00000000 --- a/compiler_tester/src/test/evm.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! -//! The EVM test. -//! - -use std::collections::HashMap; -use std::sync::Arc; -use std::sync::Mutex; - -use crate::compilers::mode::Mode; -use crate::test::case::Case; -use crate::vm::evm::input::build::Build as EVMBuild; -use crate::vm::evm::invoker::Invoker as EVMInvoker; -use crate::vm::evm::runtime::Runtime as EVMRuntime; -use crate::vm::evm::EVM; -use crate::Summary; - -/// -/// The test. -/// -pub struct Test { - /// The test name. - name: String, - /// The test group. - group: Option, - /// The test mode. - mode: Mode, - /// The contract builds. - builds: HashMap, - /// The test cases. - cases: Vec, -} - -impl Test { - /// - /// A shortcut constructor. - /// - pub fn new( - name: String, - group: Option, - mode: Mode, - builds: HashMap, - cases: Vec, - ) -> Self { - Self { - name, - group, - mode, - builds, - cases, - } - } - - /// - /// Runs the test. - /// - pub fn run(self, summary: Arc>) { - for case in self.cases { - let config = evm::standard::Config::shanghai(); - let etable = - evm::Etable::::runtime( - ); - let resolver = evm::standard::EtableResolver::new(&config, &(), &etable); - let invoker = EVMInvoker::new(&config, &resolver); - - case.run_evm( - summary.clone(), - EVM::new(self.builds.clone(), invoker), - &self.mode, - self.name.clone(), - self.group.clone(), - ); - } - } -} diff --git a/compiler_tester/src/test/instance.rs b/compiler_tester/src/test/instance/eravm.rs similarity index 62% rename from compiler_tester/src/test/instance.rs rename to compiler_tester/src/test/instance/eravm.rs index 988867df..07c4a45a 100644 --- a/compiler_tester/src/test/instance.rs +++ b/compiler_tester/src/test/instance/eravm.rs @@ -1,9 +1,9 @@ //! -//! The test contract instance used for building. +//! The EraVM test contract instance used for building. //! /// -/// The test contract instance used for building. +/// The EraVM test contract instance used for building. /// #[derive(Debug, Clone)] pub struct Instance { @@ -11,6 +11,10 @@ pub struct Instance { pub path: String, /// The instance address. pub address: Option, + /// Whether the instance is main. + pub is_main: bool, + /// Whether the instance is a library. + pub is_library: bool, /// The contract bytecode hash. pub code_hash: web3::types::U256, } @@ -22,11 +26,15 @@ impl Instance { pub fn new( path: String, address: Option, + is_main: bool, + is_library: bool, code_hash: web3::types::U256, ) -> Self { Self { path, address, + is_main, + is_library, code_hash, } } diff --git a/compiler_tester/src/test/instance/evm.rs b/compiler_tester/src/test/instance/evm.rs new file mode 100644 index 00000000..d285d46c --- /dev/null +++ b/compiler_tester/src/test/instance/evm.rs @@ -0,0 +1,41 @@ +//! +//! The EVM test contract instance used for building. +//! + +/// +/// The EVM test contract instance used for building. +/// +#[derive(Debug, Clone)] +pub struct Instance { + /// The contract path. + pub path: String, + /// The instance address. + pub address: Option, + /// Whether the instance is main. + pub is_main: bool, + /// Whether the instance is a library. + pub is_library: bool, + /// The init bytecode. + pub init_code: Vec, +} + +impl Instance { + /// + /// A shortcut constructor. + /// + pub fn new( + path: String, + address: Option, + is_main: bool, + is_library: bool, + init_code: Vec, + ) -> Self { + Self { + path, + address, + is_main, + is_library, + init_code, + } + } +} diff --git a/compiler_tester/src/test/instance/mod.rs b/compiler_tester/src/test/instance/mod.rs new file mode 100644 index 00000000..751124c5 --- /dev/null +++ b/compiler_tester/src/test/instance/mod.rs @@ -0,0 +1,103 @@ +//! +//! The test contract instance used for building. +//! + +pub mod eravm; +pub mod evm; + +use self::eravm::Instance as EraVMInstance; +use self::evm::Instance as EVMInstance; + +/// +/// The test contract instance used for building. +/// +#[allow(clippy::upper_case_acronyms)] +#[derive(Debug, Clone)] +pub enum Instance { + /// The EraVM instance. + EraVM(EraVMInstance), + /// The EVM instance. + EVM(EVMInstance), +} + +impl Instance { + /// + /// A shortcut constructor for the EraVM instance. + /// + pub fn eravm( + path: String, + address: Option, + is_main: bool, + is_library: bool, + code_hash: web3::types::U256, + ) -> Self { + Self::EraVM(EraVMInstance::new( + path, address, is_main, is_library, code_hash, + )) + } + + /// + /// A shortcut constructor for the EVM instance. + /// + pub fn evm( + path: String, + address: Option, + is_main: bool, + is_library: bool, + init_code: Vec, + ) -> Self { + Self::EVM(EVMInstance::new( + path, address, is_main, is_library, init_code, + )) + } + + /// + /// Sets the address of the instance. + /// + pub fn set_address(&mut self, address: web3::types::Address) { + match self { + Self::EraVM(instance) => instance.address = Some(address), + Self::EVM(instance) => instance.address = Some(address), + } + } + + /// + /// Returns the instance path if applicable. + /// + pub fn path(&self) -> &str { + match self { + Self::EraVM(instance) => instance.path.as_str(), + Self::EVM(instance) => instance.path.as_str(), + } + } + + /// + /// Whether the instance is main. + /// + pub fn is_main(&self) -> bool { + match self { + Self::EraVM(instance) => instance.is_main, + Self::EVM(instance) => instance.is_main, + } + } + + /// + /// Whether the instance is a library. + /// + pub fn is_library(&self) -> bool { + match self { + Self::EraVM(instance) => instance.is_library, + Self::EVM(instance) => instance.is_library, + } + } + + /// + /// Returns the instance address if applicable. + /// + pub fn address(&self) -> Option<&web3::types::Address> { + match self { + Self::EraVM(instance) => instance.address.as_ref(), + Self::EVM(instance) => instance.address.as_ref(), + } + } +} diff --git a/compiler_tester/src/test/mod.rs b/compiler_tester/src/test/mod.rs index 52f9fa8d..6f78de79 100644 --- a/compiler_tester/src/test/mod.rs +++ b/compiler_tester/src/test/mod.rs @@ -3,6 +3,121 @@ //! pub mod case; -pub mod eravm; -pub mod evm; pub mod instance; + +use std::collections::HashMap; +use std::sync::Arc; +use std::sync::Mutex; + +use crate::compilers::mode::Mode; +use crate::summary::Summary; +use crate::test::case::Case; +use crate::vm::eravm::deployers::EraVMDeployer; +use crate::vm::eravm::EraVM; +use crate::vm::evm::input::build::Build as EVMBuild; +use crate::vm::evm::invoker::Invoker as EVMInvoker; +use crate::vm::evm::runtime::Runtime as EVMRuntime; +use crate::vm::evm::EVM; + +/// +/// The test. +/// +#[derive(Debug)] +pub struct Test { + /// The test name. + name: String, + /// The test group. + group: Option, + /// The test mode. + mode: Mode, + /// The EraVM contract builds. + eravm_builds: HashMap, + /// The EVM contract builds. + evm_builds: HashMap, + /// The test cases. + cases: Vec, +} + +impl Test { + /// + /// A shortcut constructor. + /// + pub fn new( + name: String, + group: Option, + mode: Mode, + eravm_builds: HashMap, + evm_builds: HashMap, + cases: Vec, + ) -> Self { + Self { + name, + group, + mode, + eravm_builds, + evm_builds, + cases, + } + } + + /// + /// Runs the test on EraVM. + /// + pub fn run_eravm(self, summary: Arc>, vm: Arc) + where + D: EraVMDeployer, + { + for case in self.cases { + let vm = EraVM::clone_with_contracts(vm.clone(), self.eravm_builds.clone()); + case.run_eravm::( + summary.clone(), + vm.clone(), + &self.mode, + self.name.clone(), + self.group.clone(), + ); + } + } + + /// + /// Runs the test on EVM. + /// + pub fn run_evm(self, summary: Arc>) { + for case in self.cases { + let config = evm::standard::Config::shanghai(); + let etable = + evm::Etable::::runtime( + ); + let resolver = evm::standard::EtableResolver::new(&config, &(), &etable); + let invoker = EVMInvoker::new(&config, &resolver); + + let vm = EVM::new(self.evm_builds.clone(), invoker); + case.run_evm( + summary.clone(), + vm, + &self.mode, + self.name.clone(), + self.group.clone(), + ); + } + } + + /// + /// Runs the test on EVM interpreter. + /// + pub fn run_evm_interpreter(self, summary: Arc>, vm: Arc) + where + D: EraVMDeployer, + { + for case in self.cases { + let vm = EraVM::clone_with_contracts(vm.clone(), self.eravm_builds.clone()); + case.run_evm_interpreter::( + summary.clone(), + vm.clone(), + &self.mode, + self.name.clone(), + self.group.clone(), + ); + } + } +} diff --git a/compiler_tester/src/utils.rs b/compiler_tester/src/utils.rs index d3df09bc..b43b049d 100644 --- a/compiler_tester/src/utils.rs +++ b/compiler_tester/src/utils.rs @@ -2,6 +2,16 @@ //! The compiler tester utils. //! +use sha3::Digest; + +/// +/// Returns a `keccak256` selector of the specified contract method. +/// +pub fn selector(signature: &str) -> [u8; 4] { + let hash_bytes = sha3::Keccak256::digest(signature.as_bytes()); + hash_bytes[0..4].try_into().expect("Always valid") +} + /// /// Overrides the default formatting for `Address`, which replaces the middle with an ellipsis. /// diff --git a/compiler_tester/src/vm/address_iterator.rs b/compiler_tester/src/vm/address_iterator.rs new file mode 100644 index 00000000..1eafdb4a --- /dev/null +++ b/compiler_tester/src/vm/address_iterator.rs @@ -0,0 +1,29 @@ +//! +//! The address iterator trait. +//! + +/// +/// The address iterator trait. +/// +pub trait AddressIterator { + /// + /// Returns the next address. + /// + fn next( + &mut self, + caller: &web3::types::Address, + increment_nonce: bool, + ) -> web3::types::Address; + + /// + /// Increments the nonce for the caller. + /// + fn increment_nonce(&mut self, caller: &web3::types::Address); + + /// + /// Returns the nonce for the caller. + /// + /// If the nonce for the `caller` does not exist, it will be created. + /// + fn nonce(&mut self, caller: &web3::types::Address) -> usize; +} diff --git a/compiler_tester/src/vm/eravm/deployers/address_predictor.rs b/compiler_tester/src/vm/eravm/address_iterator.rs similarity index 65% rename from compiler_tester/src/vm/eravm/deployers/address_predictor.rs rename to compiler_tester/src/vm/eravm/address_iterator.rs index a60a3856..1c3ec765 100644 --- a/compiler_tester/src/vm/eravm/deployers/address_predictor.rs +++ b/compiler_tester/src/vm/eravm/address_iterator.rs @@ -1,45 +1,45 @@ //! -//! The EraVM deploy address predictor. +//! The EraVM deploy address iterator. //! use std::collections::HashMap; use std::str::FromStr; -use crate::vm::AddressPredictorIterator; +use crate::vm::address_iterator::AddressIterator; /// -/// The EraVM deploy address predictor. +/// The EraVM deploy address iterator. /// #[derive(Debug, Clone)] -pub struct AddressPredictor { +pub struct EraVMAddressIterator { /// The accounts create nonces. - nonces: HashMap, + pub nonces: HashMap, } -impl AddressPredictor { +impl EraVMAddressIterator { /// The create prefix, `keccak256("zksyncCreate")`. const CREATE_PREFIX: &'static str = "63bae3a9951d38e8a3fbb7b70909afc1200610fc5bc55ade242f815974674f23"; +} +impl Default for EraVMAddressIterator { + fn default() -> Self { + Self::new() + } +} + +impl EraVMAddressIterator { /// - /// Create new address predictor instance. + /// A shortcut constructor. /// pub fn new() -> Self { Self { nonces: HashMap::new(), } } - - /// - /// Increments caller nonce. - /// - pub fn increment_nonce(&mut self, caller: &web3::types::Address) { - let nonce = self.nonces.entry(*caller).or_insert(0); - *nonce += 1; - } } -impl AddressPredictorIterator for AddressPredictor { +impl AddressIterator for EraVMAddressIterator { fn next( &mut self, caller: &web3::types::Address, @@ -56,7 +56,9 @@ impl AddressPredictorIterator for AddressPredictor { - era_compiler_common::BYTE_LENGTH_ETH_ADDRESS], ); bytes.extend(caller.to_fixed_bytes()); - bytes.extend([0; era_compiler_common::BYTE_LENGTH_FIELD - std::mem::size_of::()]); + bytes.extend( + [0; era_compiler_common::BYTE_LENGTH_FIELD - era_compiler_common::BYTE_LENGTH_X64], + ); bytes.extend(nonce.to_be_bytes()); let address = web3::types::Address::from_slice( @@ -70,4 +72,13 @@ impl AddressPredictorIterator for AddressPredictor { address } + + fn increment_nonce(&mut self, caller: &web3::types::Address) { + let nonce = self.nonces.entry(*caller).or_insert(0); + *nonce += 1; + } + + fn nonce(&mut self, caller: &web3::types::Address) -> usize { + *self.nonces.entry(*caller).or_default() + } } diff --git a/compiler_tester/src/vm/eravm/deployers/native_deployer.rs b/compiler_tester/src/vm/eravm/deployers/dummy_deployer.rs similarity index 83% rename from compiler_tester/src/vm/eravm/deployers/native_deployer.rs rename to compiler_tester/src/vm/eravm/deployers/dummy_deployer.rs index 18a1abe4..af55864e 100644 --- a/compiler_tester/src/vm/eravm/deployers/native_deployer.rs +++ b/compiler_tester/src/vm/eravm/deployers/dummy_deployer.rs @@ -1,5 +1,5 @@ //! -//! The EraVM native deployer implementation. +//! The EraVM dummy deployer implementation. //! use std::collections::HashMap; @@ -8,35 +8,34 @@ use web3::contract::tokens::Tokenizable; use crate::test::case::input::output::Output; use crate::test::case::input::value::Value; +use crate::vm::address_iterator::AddressIterator; +use crate::vm::eravm::address_iterator::EraVMAddressIterator; +use crate::vm::eravm::deployers::EraVMDeployer; use crate::vm::eravm::EraVM; use crate::vm::execution_result::ExecutionResult; -use crate::vm::AddressPredictorIterator; - -use super::address_predictor::AddressPredictor; -use super::Deployer; /// -/// The EraVM native deployer implementation. +/// The EraVM dummy deployer implementation. /// #[derive(Debug, Clone)] -pub struct NativeDeployer { - /// The address predictor instance for computing the contracts addresses. - address_predictor: AddressPredictor, +pub struct DummyDeployer { + /// The address iterator instance for computing the contracts addresses. + address_iterator: EraVMAddressIterator, } -impl NativeDeployer { +impl DummyDeployer { /// The immutables mapping position in contract. const IMMUTABLES_MAPPING_POSITION: web3::types::U256 = web3::types::U256::zero(); } -impl Deployer for NativeDeployer { +impl EraVMDeployer for DummyDeployer { fn new() -> Self { Self { - address_predictor: AddressPredictor::new(), + address_iterator: EraVMAddressIterator::new(), } } - fn deploy( + fn deploy_eravm( &mut self, test_name: String, caller: web3::types::Address, @@ -45,7 +44,7 @@ impl Deployer for NativeDeployer { value: Option, vm: &mut EraVM, ) -> anyhow::Result { - let address = self.address_predictor.next(&caller, false); + let address = self.address_iterator.next(&caller, false); vm.add_deployed_contract(address, bytecode_hash, None); @@ -73,7 +72,7 @@ impl Deployer for NativeDeployer { return Ok(result); } - self.address_predictor.increment_nonce(&caller); + self.address_iterator.increment_nonce(&caller); Self::set_immutables(address, &result.output.return_data, vm)?; @@ -84,12 +83,25 @@ impl Deployer for NativeDeployer { Ok(ExecutionResult::new( Output::new(return_data, false, result.output.events), result.cycles, + result.ergs, result.gas, )) } + + fn deploy_evm( + &mut self, + _test_name: String, + _caller: web3::types::Address, + _init_code: Vec, + _constructor_calldata: Vec, + _value: Option, + _vm: &mut EraVM, + ) -> anyhow::Result { + todo!() + } } -impl NativeDeployer { +impl DummyDeployer { /// /// Writes the contract immutables to a storage. /// diff --git a/compiler_tester/src/vm/eravm/deployers/mod.rs b/compiler_tester/src/vm/eravm/deployers/mod.rs index fff4b8d1..8a45f7fb 100644 --- a/compiler_tester/src/vm/eravm/deployers/mod.rs +++ b/compiler_tester/src/vm/eravm/deployers/mod.rs @@ -2,8 +2,7 @@ //! The contract deployers. //! -pub mod address_predictor; -pub mod native_deployer; +pub mod dummy_deployer; pub mod system_contract_deployer; use crate::vm::eravm::EraVM; @@ -12,16 +11,16 @@ use crate::vm::execution_result::ExecutionResult; /// /// The deployer trait. /// -pub trait Deployer { +pub trait EraVMDeployer { /// /// Create new deployer instance. /// fn new() -> Self; /// - /// Deploy a contract. + /// Deploy an EraVM contract. /// - fn deploy( + fn deploy_eravm( &mut self, test_name: String, caller: web3::types::Address, @@ -30,4 +29,17 @@ pub trait Deployer { value: Option, vm: &mut EraVM, ) -> anyhow::Result; + + /// + /// Deploy an EVM contract to be run on the interpreter. + /// + fn deploy_evm( + &mut self, + test_name: String, + caller: web3::types::Address, + init_code: Vec, + constructor_calldata: Vec, + value: Option, + vm: &mut EraVM, + ) -> anyhow::Result; } diff --git a/compiler_tester/src/vm/eravm/deployers/system_contract_deployer.rs b/compiler_tester/src/vm/eravm/deployers/system_contract_deployer.rs index 9099a1b4..21df6acc 100644 --- a/compiler_tester/src/vm/eravm/deployers/system_contract_deployer.rs +++ b/compiler_tester/src/vm/eravm/deployers/system_contract_deployer.rs @@ -2,11 +2,10 @@ //! The EraVM system contract deployer implementation. //! +use crate::vm::eravm::deployers::EraVMDeployer; use crate::vm::eravm::EraVM; use crate::vm::execution_result::ExecutionResult; -use super::Deployer; - /// /// The EraVM system contract deployer implementation. /// @@ -15,15 +14,18 @@ pub struct SystemContractDeployer; impl SystemContractDeployer { /// The create method selector. - const CREATE_METHOD_SELECTOR: u32 = 0x9c4d535b; // keccak256("create(bytes32,bytes32,bytes)") + const ERAVM_CREATE_METHOD_SIGNATURE: &'static str = "create(bytes32,bytes32,bytes)"; + + /// The create method selector. + const EVM_CREATE_METHOD_SIGNATURE: &'static str = "createEVM(bytes)"; } -impl Deployer for SystemContractDeployer { +impl EraVMDeployer for SystemContractDeployer { fn new() -> Self { Self } - fn deploy( + fn deploy_eravm( &mut self, test_name: String, caller: web3::types::Address, @@ -96,10 +98,13 @@ impl Deployer for SystemContractDeployer { let mut calldata = Vec::with_capacity( constructor_calldata.len() + era_compiler_common::BYTE_LENGTH_FIELD * 4 + 4, ); - calldata.extend(Self::CREATE_METHOD_SELECTOR.to_be_bytes().to_vec()); + calldata.extend(crate::utils::selector(Self::ERAVM_CREATE_METHOD_SIGNATURE)); calldata.extend([0u8; 2 * era_compiler_common::BYTE_LENGTH_FIELD]); bytecode_hash.to_big_endian(&mut calldata[era_compiler_common::BYTE_LENGTH_FIELD + 4..]); - calldata.extend(web3::types::H256::from_low_u64_be(96).as_bytes()); + calldata.extend( + web3::types::H256::from_low_u64_be((3 * era_compiler_common::BYTE_LENGTH_FIELD) as u64) + .as_bytes(), + ); calldata.extend( web3::types::H256::from_low_u64_be(constructor_calldata.len() as u64).as_bytes(), ); @@ -114,4 +119,105 @@ impl Deployer for SystemContractDeployer { Some(vm_launch_option), ) } + + fn deploy_evm( + &mut self, + test_name: String, + caller: web3::types::Address, + init_code: Vec, + constructor_calldata: Vec, + value: Option, + vm: &mut EraVM, + ) -> anyhow::Result { + let context_u128_value; + let vm_launch_option; + let mut entry_address = web3::types::Address::from_low_u64_be( + zkevm_opcode_defs::ADDRESS_CONTRACT_DEPLOYER.into(), + ); + + if M { + context_u128_value = 0; + + let mut r3 = None; + let mut r4 = None; + let mut r5 = None; + if let Some(value) = value { + let value = web3::types::U256::from(value); + vm.mint_ether(caller, value); + + r3 = Some(value); + r4 = Some(web3::types::U256::from( + zkevm_opcode_defs::ADDRESS_CONTRACT_DEPLOYER, + )); + r5 = Some(web3::types::U256::from(u8::from( + era_compiler_llvm_context::eravm_const::SYSTEM_CALL_BIT, + ))); + + entry_address = web3::types::Address::from_low_u64_be( + zkevm_opcode_defs::ADDRESS_MSG_VALUE.into(), + ); + } + + vm_launch_option = zkevm_tester::runners::compiler_tests::VmLaunchOption::ManualCallABI( + zkevm_tester::runners::compiler_tests::FullABIParams { + is_constructor: false, + is_system_call: true, + r3_value: r3, + r4_value: r4, + r5_value: r5, + }, + ); + } else { + if let Some(value) = value { + context_u128_value = value; + vm.mint_ether( + web3::types::Address::from_low_u64_be( + zkevm_opcode_defs::ADDRESS_CONTRACT_DEPLOYER.into(), + ), + web3::types::U256::from(value), + ); + } else { + context_u128_value = 0; + } + + vm_launch_option = zkevm_tester::runners::compiler_tests::VmLaunchOption::ManualCallABI( + zkevm_tester::runners::compiler_tests::FullABIParams { + is_constructor: false, + is_system_call: true, + r3_value: None, + r4_value: None, + r5_value: None, + }, + ); + } + + let mut calldata = Vec::with_capacity( + era_compiler_common::BYTE_LENGTH_X32 + + era_compiler_common::BYTE_LENGTH_FIELD * 2 + + init_code.len() + + constructor_calldata.len(), + ); + calldata.extend(crate::utils::selector(Self::EVM_CREATE_METHOD_SIGNATURE)); + calldata.extend( + web3::types::H256::from_low_u64_be(era_compiler_common::BYTE_LENGTH_FIELD as u64) + .as_bytes(), + ); + calldata.extend( + web3::types::H256::from_low_u64_be( + (init_code.len() + constructor_calldata.len()) as u64, + ) + .as_bytes(), + ); + calldata.extend(init_code); + calldata.extend(constructor_calldata); + + vm.execute::( + test_name, + entry_address, + caller, + Some(context_u128_value), + calldata, + Some(vm_launch_option), + ) + } } diff --git a/compiler_tester/src/vm/eravm/input/mod.rs b/compiler_tester/src/vm/eravm/input/mod.rs index d58d5a6b..a68e56ed 100644 --- a/compiler_tester/src/vm/eravm/input/mod.rs +++ b/compiler_tester/src/vm/eravm/input/mod.rs @@ -7,6 +7,8 @@ pub mod build; use std::collections::BTreeMap; use std::collections::HashMap; +use crate::test::instance::Instance; + use self::build::Build; /// @@ -37,4 +39,70 @@ impl Input { last_contract, } } + + /// + /// Returns all contract instances. + /// + pub fn get_instances( + &self, + contracts: &BTreeMap, + library_addresses: BTreeMap, + main_address: web3::types::Address, + ) -> anyhow::Result> { + let mut instances = BTreeMap::new(); + + for (name, address) in library_addresses.into_iter() { + let build = self.builds.get(name.as_str()).ok_or_else(|| { + anyhow::anyhow!("Library `{}` not found in the build artifacts", name) + })?; + + instances.insert( + name.clone(), + Instance::eravm( + name, + Some(address), + false, + true, + build.bytecode_hash.to_owned(), + ), + ); + } + + if contracts.is_empty() { + let main_contract_build = + self.builds + .get(self.last_contract.as_str()) + .ok_or_else(|| { + anyhow::anyhow!("Main contract not found in the compiler build artifacts") + })?; + instances.insert( + "Test".to_owned(), + Instance::eravm( + self.last_contract.to_owned(), + Some(main_address), + true, + false, + main_contract_build.bytecode_hash, + ), + ); + } else { + for (instance, path) in contracts.iter() { + let build = self.builds.get(path.as_str()).ok_or_else(|| { + anyhow::anyhow!("{} not found in the compiler build artifacts", path) + })?; + instances.insert( + instance.to_owned(), + Instance::eravm( + path.to_owned(), + None, + false, + false, + build.bytecode_hash.to_owned(), + ), + ); + } + } + + Ok(instances) + } } diff --git a/compiler_tester/src/vm/eravm/mod.rs b/compiler_tester/src/vm/eravm/mod.rs index d27b65ac..2972015f 100644 --- a/compiler_tester/src/vm/eravm/mod.rs +++ b/compiler_tester/src/vm/eravm/mod.rs @@ -1,12 +1,16 @@ //! -//! The EraVM wrapper. +//! The EraVM interface. //! +pub mod address_iterator; pub mod deployers; pub mod input; pub mod system_context; pub mod system_contracts; +#[cfg(feature = "vm2")] +mod vm2_adapter; + use std::collections::HashMap; use std::path::PathBuf; use std::str::FromStr; @@ -23,22 +27,27 @@ use self::system_context::SystemContext; use self::system_contracts::SystemContracts; /// -/// The EraVM wrapper. +/// The EraVM interface. /// #[derive(Clone)] -#[allow(non_camel_case_types)] pub struct EraVM { - /// The storage state. - storage: HashMap, - /// The deployed contracts. - deployed_contracts: HashMap, - /// The default account abstraction contract code hash. - default_aa_code_hash: web3::types::U256, /// The known contracts. known_contracts: HashMap, + /// The default account abstraction contract code hash. + default_aa_code_hash: web3::types::U256, + /// The EVM interpreter contract code hash. + evm_interpreter_code_hash: web3::types::U256, + /// The deployed contracts. + deployed_contracts: HashMap, + /// The storage state. + storage: HashMap, } impl EraVM { + /// The default address of the benchmark caller. + pub const DEFAULT_BENCHMARK_CALLER_ADDRESS: &'static str = + "eeaffc9ff130f15d470945fd04b9017779c95dbf"; + /// /// Creates and initializes a new EraVM instance. /// @@ -90,16 +99,21 @@ impl EraVM { let storage = SystemContext::create_storage(); let mut vm = Self { - storage, - deployed_contracts: HashMap::new(), - default_aa_code_hash: system_contracts.default_aa.bytecode_hash, known_contracts: HashMap::new(), + default_aa_code_hash: system_contracts.default_aa.bytecode_hash, + evm_interpreter_code_hash: system_contracts.evm_interpreter.bytecode_hash, + deployed_contracts: HashMap::new(), + storage, }; vm.add_known_contract( system_contracts.default_aa.assembly, system_contracts.default_aa.bytecode_hash, ); + vm.add_known_contract( + system_contracts.evm_interpreter.assembly, + system_contracts.evm_interpreter.bytecode_hash, + ); vm.add_known_contract( zkevm_assembly::Assembly::from_string( era_compiler_vyper::FORWARDER_CONTRACT_ASSEMBLY.to_owned(), @@ -123,7 +137,7 @@ impl EraVM { /// /// Clones the VM instance from and adds known contracts for a single test run. /// - /// TODO: make copyless when VM supports it. + /// TODO: make copyless when the VM supports it. /// pub fn clone_with_contracts( vm: Arc, @@ -208,31 +222,62 @@ impl EraVM { 0, ); - let snapshot = zkevm_tester::runners::compiler_tests::run_vm_multi_contracts( - trace_file_path.to_string_lossy().to_string(), - self.deployed_contracts.clone(), - calldata.as_slice(), - self.storage.clone(), - entry_address, - Some(context), - vm_launch_option, - ::MAX, - self.known_contracts.clone(), - self.default_aa_code_hash, - ) - .map_err(|error| anyhow::anyhow!("Internal error: failed to run vm: {}", error))?; - - let result = ExecutionResult::from(&snapshot); - self.storage = snapshot.storage; - for (address, assembly) in snapshot.deployed_contracts.into_iter() { - if self.deployed_contracts.contains_key(&address) { - continue; + #[cfg(not(feature = "vm2"))] + { + let snapshot = zkevm_tester::runners::compiler_tests::run_vm_multi_contracts( + trace_file_path.to_string_lossy().to_string(), + self.deployed_contracts.clone(), + &calldata, + self.storage.clone(), + entry_address, + Some(context), + vm_launch_option, + usize::MAX, + self.known_contracts.clone(), + self.default_aa_code_hash, + self.evm_interpreter_code_hash, + )?; + + for (address, assembly) in snapshot.deployed_contracts.iter() { + if self.deployed_contracts.contains_key(address) { + continue; + } + + self.deployed_contracts + .insert(*address, assembly.to_owned()); } + self.storage = snapshot.storage.clone(); - self.deployed_contracts.insert(address, assembly); + Ok(snapshot.into()) } + #[cfg(feature = "vm2")] + { + let (result, storage_changes, deployed_contracts) = vm2_adapter::run_vm( + self.deployed_contracts.clone(), + &calldata, + self.storage.clone(), + entry_address, + Some(context), + vm_launch_option, + self.known_contracts.clone(), + self.default_aa_code_hash, + self.evm_interpreter_code_hash, + ) + .map_err(|error| anyhow::anyhow!("EraVM failure: {}", error))?; - Ok(result) + for (key, value) in storage_changes.into_iter() { + self.storage.insert(key, value); + } + for (address, assembly) in deployed_contracts.into_iter() { + if self.deployed_contracts.contains_key(&address) { + continue; + } + + self.deployed_contracts.insert(address, assembly); + } + + Ok(result) + } } /// @@ -303,6 +348,26 @@ impl EraVM { web3::types::U256::from_big_endian(balance.as_bytes()) } + /// + /// Adds a known contract. + /// + fn add_known_contract( + &mut self, + assembly: zkevm_assembly::Assembly, + bytecode_hash: web3::types::U256, + ) { + self.storage.insert( + zkevm_tester::runners::compiler_tests::StorageKey { + address: web3::types::Address::from_low_u64_be( + zkevm_opcode_defs::ADDRESS_KNOWN_CODES_STORAGE.into(), + ), + key: bytecode_hash, + }, + web3::types::H256::from_low_u64_be(1), + ); + self.known_contracts.insert(bytecode_hash, assembly); + } + /// /// Set contract as deployed on `address`. If `assembly` is none - trying to get assembly from known contracts. /// @@ -414,24 +479,4 @@ impl EraVM { key, } } - - /// - /// Adds known contract. - /// - fn add_known_contract( - &mut self, - assembly: zkevm_assembly::Assembly, - bytecode_hash: web3::types::U256, - ) { - self.storage.insert( - zkevm_tester::runners::compiler_tests::StorageKey { - address: web3::types::Address::from_low_u64_be( - zkevm_opcode_defs::ADDRESS_KNOWN_CODES_STORAGE.into(), - ), - key: bytecode_hash, - }, - web3::types::H256::from_low_u64_be(1), - ); - self.known_contracts.insert(bytecode_hash, assembly); - } } diff --git a/compiler_tester/src/vm/eravm/system_context.rs b/compiler_tester/src/vm/eravm/system_context.rs index f867195b..79aeb98f 100644 --- a/compiler_tester/src/vm/eravm/system_context.rs +++ b/compiler_tester/src/vm/eravm/system_context.rs @@ -12,7 +12,7 @@ use std::str::FromStr; pub struct SystemContext; impl SystemContext { - /// The system context chain id value position in the storage. + /// The system context chain ID value position in the storage. const SYSTEM_CONTEXT_CHAIN_ID_POSITION: u64 = 0; /// The system context origin value position in the storage. @@ -36,7 +36,7 @@ impl SystemContext { /// The system context block hashes mapping position in the storage. const SYSTEM_CONTEXT_BLOCK_HASH_POSITION: u64 = 8; - /// The system context current virtual l2 block info value position in the storage. + /// The system context current virtual L2 block info value position in the storage. const SYSTEM_CONTEXT_VIRTUAL_L2_BLOCK_INFO_POSITION: u64 = 268; /// The system context virtual blocks upgrade info position in the storage. @@ -55,7 +55,7 @@ impl SystemContext { /// The default block gas limit for tests. const BLOCK_GAS_LIMIT: u64 = (1 << 30); - /// The default coin base for tests. + /// The default coinbase for tests. const COIN_BASE: &'static str = "0x0000000000000000000000000000000000000000000000000000000000008001"; @@ -76,7 +76,7 @@ impl SystemContext { "0x3737373737373737373737373737373737373737373737373737373737373737"; /// - /// Returns storage values for system context. + /// Returns the storage values for the system context. /// pub fn create_storage( ) -> HashMap { diff --git a/compiler_tester/src/vm/eravm/system_contracts.rs b/compiler_tester/src/vm/eravm/system_contracts.rs index 22a5a349..5bcb6def 100644 --- a/compiler_tester/src/vm/eravm/system_contracts.rs +++ b/compiler_tester/src/vm/eravm/system_contracts.rs @@ -10,13 +10,11 @@ use std::str::FromStr; use std::time::Instant; use colored::Colorize; -use serde::Deserialize; -use serde::Serialize; -use crate::compilers::mode::solidity::Mode as SolidityMode; -use crate::compilers::mode::yul::Mode as YulMode; use crate::compilers::mode::Mode; +use crate::compilers::solidity::mode::Mode as SolidityMode; use crate::compilers::solidity::SolidityCompiler; +use crate::compilers::yul::mode::Mode as YulMode; use crate::compilers::yul::YulCompiler; use crate::compilers::Compiler; use crate::vm::eravm::input::build::Build as EraVMBuild; @@ -24,12 +22,14 @@ use crate::vm::eravm::input::build::Build as EraVMBuild; /// /// The EraVM system contracts. /// -#[derive(Serialize, Deserialize)] +#[derive(serde::Serialize, serde::Deserialize)] pub struct SystemContracts { /// The deployed system contracts builds. pub deployed_contracts: Vec<(web3::types::Address, EraVMBuild)>, /// The default account abstraction contract build. pub default_aa: EraVMBuild, + /// The EVM interpreter contract build. + pub evm_interpreter: EraVMBuild, } impl SystemContracts { @@ -37,6 +37,14 @@ impl SystemContracts { const PATH_EMPTY_CONTRACT: &'static str = "era-contracts/system-contracts/contracts/EmptyContract.sol:EmptyContract"; + /// The default account abstraction contract implementation path. + const PATH_DEFAULT_AA: &'static str = + "era-contracts/system-contracts/contracts/DefaultAccount.sol:DefaultAccount"; + + /// The EVM interpreter system contract implementation path. + const PATH_EVM_INTERPRETER: &'static str = + "era-contracts/system-contracts/contracts/EvmInterpreter.sol:EvmInterpreter"; + /// The `keccak256` system contract implementation path. const PATH_KECCAK256: &'static str = "era-contracts/system-contracts/contracts/precompiles/Keccak256.yul"; @@ -97,12 +105,12 @@ impl SystemContracts { const PATH_ETH_TOKEN: &'static str = "era-contracts/system-contracts/contracts/L2EthToken.sol:L2EthToken"; - /// The default account abstraction contract implementation path. - const PATH_DEFAULT_AA: &'static str = - "era-contracts/system-contracts/contracts/DefaultAccount.sol:DefaultAccount"; + /// The EVM gas manager system contract implementation path. + const PATH_EVM_GAS_MANAGER: &'static str = + "era-contracts/system-contracts/contracts/EvmGasManager.sol:EvmGasManager"; /// - /// Load or build the system contracts. + /// Loads or builds the system contracts. /// pub fn load_or_build( solc_version: semver::Version, @@ -152,13 +160,13 @@ impl SystemContracts { ), ( web3::types::Address::from_low_u64_be( - 0x06, /* TODO: zkevm_opcode_defs::ADDRESS_ECADD.into() */ + zkevm_opcode_defs::system_params::ADDRESS_ECADD.into(), ), Self::PATH_ECADD, ), ( web3::types::Address::from_low_u64_be( - 0x07, /* TODO: zkevm_opcode_defs::ADDRESS_ECMUL.into() */ + zkevm_opcode_defs::system_params::ADDRESS_ECMUL.into(), ), Self::PATH_ECMUL, ), @@ -222,6 +230,10 @@ impl SystemContracts { web3::types::Address::from_low_u64_be(zkevm_opcode_defs::ADDRESS_ETH_TOKEN.into()), Self::PATH_ETH_TOKEN, ), + ( + web3::types::Address::from_low_u64_be(0x8012), + Self::PATH_EVM_GAS_MANAGER, + ), ]; let mut yul_file_paths = Vec::with_capacity(yul_system_contracts.len() + 1); @@ -229,7 +241,9 @@ impl SystemContracts { let file_path = path.split(':').next().expect("Always valid"); yul_file_paths.push(file_path.to_owned()); } - let yul_mode = YulMode::new(era_compiler_llvm_context::OptimizerSettings::cycles()).into(); + let yul_optimizer_settings = + era_compiler_llvm_context::OptimizerSettings::evm_interpreter(); + let yul_mode = YulMode::new(yul_optimizer_settings, true).into(); let mut builds = Self::compile(YulCompiler, &yul_mode, yul_file_paths, debug_config.clone())?; @@ -244,12 +258,16 @@ impl SystemContracts { solidity_file_paths.push(path); } } + let solidity_optimizer_settings = + era_compiler_llvm_context::OptimizerSettings::evm_interpreter(); let solidity_mode = SolidityMode::new( solc_version, era_compiler_solidity::SolcPipeline::Yul, true, true, - era_compiler_llvm_context::OptimizerSettings::cycles(), + solidity_optimizer_settings, + true, + true, ) .into(); builds.extend(Self::compile( @@ -266,14 +284,17 @@ impl SystemContracts { let mut deployed_contracts = Vec::with_capacity(system_contracts.len()); for (address, path) in system_contracts.into_iter() { - let build = builds.remove(path).unwrap_or_else(|| { - panic!("System contract source file `{path}` not found in the builds") - }); + let build = builds + .remove(path) + .unwrap_or_else(|| panic!("System contract `{path}` not found in the builds")); deployed_contracts.push((address, build)); } let default_aa = builds.remove(Self::PATH_DEFAULT_AA).ok_or_else(|| { - anyhow::anyhow!("Default account code not found in the compiler build artifacts") + anyhow::anyhow!("The default AA code not found in the compiler build artifacts") + })?; + let evm_interpreter = builds.remove(Self::PATH_EVM_INTERPRETER).ok_or_else(|| { + anyhow::anyhow!("The EVM interpreter code not found in the compiler build artifacts") })?; println!( @@ -286,6 +307,7 @@ impl SystemContracts { Ok(Self { deployed_contracts, default_aa, + evm_interpreter, }) } @@ -295,7 +317,13 @@ impl SystemContracts { fn load(system_contracts_path: PathBuf) -> anyhow::Result { let system_contracts_file = File::open(system_contracts_path.as_path())?; let system_contracts: SystemContracts = bincode::deserialize_from(system_contracts_file) - .map_err(|error| anyhow::anyhow!("System contract deserialization: {}", error))?; + .map_err(|error| { + anyhow::anyhow!( + "System contract {:?} deserialization: {}", + system_contracts_path, + error + ) + })?; println!( " {} the System Contracts from `{}`", "Loaded".bright_green().bold(), @@ -309,8 +337,14 @@ impl SystemContracts { /// fn save(&self, system_contracts_path: PathBuf) -> anyhow::Result<()> { let system_contracts_file = File::create(system_contracts_path.as_path())?; - bincode::serialize_into(system_contracts_file, self) - .map_err(|error| anyhow::anyhow!("System contracts serialization: {}", error,))?; + bincode::serialize_into(system_contracts_file, self).map_err(|error| { + anyhow::anyhow!( + "System contracts {:?} serialization: {}", + system_contracts_path, + error + ) + })?; + println!( " {} the System Contracts to `{}`", "Saved".bright_green().bold(), @@ -320,7 +354,7 @@ impl SystemContracts { } /// - /// Compiles the system contracts with a compiler. + /// Compiles the system contracts. /// fn compile( compiler: C, @@ -333,18 +367,23 @@ impl SystemContracts { { let mut sources = Vec::new(); for path in paths.into_iter() { - let file_path = if compiler.has_multiple_contracts() { + let file_path = if compiler.allows_multi_contract_files() { path.split(':').next().expect("Always valid").to_string() } else { path }; + let mut source = std::fs::read_to_string( PathBuf::from_str(file_path.as_str()) .expect("Always valid") .as_path(), ) .map_err(|error| { - anyhow::anyhow!("System contract file `{}` reading: {}", file_path, error) + anyhow::anyhow!( + "System contract file `{}` reading error: {}", + file_path, + error + ) })?; if file_path == "era-contracts/system-contracts/contracts/Constants.sol" { @@ -353,14 +392,13 @@ impl SystemContracts { sources.push((file_path.to_string(), source)); } + compiler .compile_for_eravm( "system-contracts".to_owned(), sources, BTreeMap::new(), mode, - true, - true, debug_config, ) .map(|output| output.builds) diff --git a/compiler_tester/src/vm/eravm/vm2_adapter.rs b/compiler_tester/src/vm/eravm/vm2_adapter.rs index e88f2de1..3bb6ab02 100644 --- a/compiler_tester/src/vm/eravm/vm2_adapter.rs +++ b/compiler_tester/src/vm/eravm/vm2_adapter.rs @@ -1,27 +1,51 @@ +//! +//! Runs the next-generation EraVM. +//! +//! Its interface is simpler than the old one's but different, so a compatibility layer is needed. +//! + use std::collections::HashMap; -use vm2::World; + use web3::ethabi::Address; -use zkevm_assembly::{zkevm_opcode_defs::bytecode_to_code_hash, Assembly}; -use zkevm_opcode_defs::ethereum_types::{BigEndianHash, H256, U256}; -use zkevm_tester::runners::compiler_tests::{ - FullABIParams, StorageKey, VmExecutionContext, VmLaunchOption, -}; -use crate::test::case::input::output::Output; +use vm2::ExecutionEnd; +use vm2::World; +use zkevm_assembly::zkevm_opcode_defs::Assembly; +use zkevm_opcode_defs::ethereum_types::{BigEndianHash, H256, U256}; +use zkevm_tester::runners::compiler_tests::FullABIParams; +use zkevm_tester::runners::compiler_tests::StorageKey; +use zkevm_tester::runners::compiler_tests::VmExecutionContext; +use zkevm_tester::runners::compiler_tests::VmLaunchOption; -use super::execution_result::ExecutionResult; +use crate::test::case::input::{ + output::{event::Event, Output}, + value::Value, +}; +use crate::vm::eravm::execution_result::ExecutionResult; pub fn run_vm( - contracts: HashMap, + contracts: HashMap, calldata: &[u8], storage: HashMap, - entry_address: Address, + entry_address: web3::ethabi::Address, context: Option, vm_launch_option: VmLaunchOption, mut known_contracts: HashMap, default_aa_code_hash: U256, -) -> anyhow::Result { + evm_interpreter_code_hash: U256, +) -> anyhow::Result<( + ExecutionResult, + HashMap, + HashMap, +)> { let abi_params = match vm_launch_option { + VmLaunchOption::Call => FullABIParams { + is_constructor: false, + is_system_call: false, + r3_value: None, + r4_value: None, + r5_value: None, + }, VmLaunchOption::Constructor => FullABIParams { is_constructor: true, is_system_call: false, @@ -30,51 +54,101 @@ pub fn run_vm( r5_value: None, }, VmLaunchOption::ManualCallABI(abiparams) => abiparams, - _ => return Err(anyhow::anyhow!("Unsupported launch option")), + x => return Err(anyhow::anyhow!("Unsupported launch option {x:?}")), }; for (_, contract) in contracts { - let bytecode = contract.clone().compile_to_bytecode().unwrap(); - let hash = bytecode_to_code_hash(&bytecode).unwrap(); + let bytecode = contract.clone().compile_to_bytecode()?; + let hash = zkevm_assembly::zkevm_opcode_defs::bytecode_to_code_hash(&bytecode)?; known_contracts.insert(U256::from_big_endian(&hash), contract); } - let mut vm = vm2::State::new( + let context = context.unwrap_or_default(); + + let mut vm = vm2::VirtualMachine::new( Box::new(TestWorld { storage, - contracts: known_contracts, + contracts: known_contracts.clone(), }), entry_address, + context.msg_sender, calldata.to_vec(), + u32::MAX, + vm2::Settings { + default_aa_code_hash, + hook_address: 0, + }, ); + if abi_params.is_constructor { + vm.state.registers[2] |= 1.into(); + } + if abi_params.is_system_call { + vm.state.registers[2] |= 2.into(); + } + vm.state.registers[3] = abi_params.r3_value.unwrap_or_default(); + vm.state.registers[4] = abi_params.r4_value.unwrap_or_default(); + vm.state.registers[5] = abi_params.r5_value.unwrap_or_default(); + let output = match vm.run() { - Ok(_) => Output { - return_data: vec![], + ExecutionEnd::ProgramFinished(return_value) => Output { + return_data: chunk_return_data(&return_value), exception: false, + events: merge_events(vm.world.events()), + }, + ExecutionEnd::Reverted(return_value) => Output { + return_data: chunk_return_data(&return_value), + exception: true, + events: vec![], + }, + _panic => Output { + return_data: vec![], + exception: true, events: vec![], }, - Err(e) => { - dbg!(e, vm.current_frame.gas); - Output { - return_data: vec![], - exception: true, - events: vec![], - } - } }; - Ok(ExecutionResult { - output, - cycles: 0, - ergs: 0, - }) + let storage_changes = vm + .world + .get_storage_changes() + .map(|((address, key), value)| (StorageKey { address, key }, H256::from_uint(&value))) + .collect::>(); + let deployed_contracts = vm + .world + .get_storage_changes() + .filter_map(|((address, key), value)| { + if address == *zkevm_assembly::zkevm_opcode_defs::system_params::DEPLOYER_SYSTEM_CONTRACT_ADDRESS { + let mut buffer = [0u8; 32]; + key.to_big_endian(&mut buffer); + let deployed_address = web3::ethabi::Address::from_slice(&buffer[12..]); + if let Some(code) = known_contracts.get(&value) { + Some((deployed_address, code.clone())) + } else { + None + } + } else { + None + } + }) + .collect::>(); + + Ok(( + ExecutionResult { + output, + cycles: 0, + ergs: 0, + gas: 0, + }, + storage_changes, + deployed_contracts, + )) } struct TestWorld { storage: HashMap, contracts: HashMap, } + impl World for TestWorld { fn decommit( &mut self, @@ -96,7 +170,7 @@ impl World for TestWorld { .collect::>(); ( - vm2::decode::decode_program(&instructions).into(), + vm2::decode::decode_program(&instructions, false).into(), bytecode .iter() .map(|x| U256::from_big_endian(x)) @@ -118,4 +192,139 @@ impl World for TestWorld { .map(|h| h.into_uint()) .unwrap_or(U256::zero()) } + + fn handle_hook(&mut self, _: u32) { + unreachable!() // There is no bootloader + } +} + +fn chunk_return_data(bytes: &[u8]) -> Vec { + let iter = bytes.chunks_exact(32); + let remainder = iter.remainder(); + let mut res = iter + .map(U256::from_big_endian) + .map(Value::Certain) + .collect::>(); + if !remainder.is_empty() { + let mut last = [0; 32]; + last[..remainder.len()].copy_from_slice(remainder); + res.push(Value::Certain(U256::from_big_endian(&last))); + } + res +} + +fn merge_events(events: &[vm2::Event]) -> Vec { + struct TmpEvent { + topics: Vec, + data: Vec, + shard_id: u8, + tx_number: u32, + } + let mut result = vec![]; + let mut current: Option<(usize, u32, TmpEvent)> = None; + + for message in events.into_iter() { + let vm2::Event { + shard_id, + is_first, + tx_number, + key, + value, + } = *message; + + if !is_first { + if let Some((mut remaining_data_length, mut remaining_topics, mut event)) = + current.take() + { + if event.shard_id != shard_id || event.tx_number != tx_number { + continue; + } + + for el in [key, value].iter() { + if remaining_topics != 0 { + event.topics.push(*el); + remaining_topics -= 1; + } else if remaining_data_length != 0 { + let mut bytes = [0; 32]; + el.to_big_endian(&mut bytes); + if remaining_data_length >= 32 { + event.data.extend_from_slice(&bytes); + remaining_data_length -= 32; + } else { + event + .data + .extend_from_slice(&bytes[..remaining_data_length]); + remaining_data_length = 0; + } + } + } + + if remaining_data_length != 0 || remaining_topics != 0 { + current = Some((remaining_data_length, remaining_topics, event)) + } else { + result.push(event); + } + } + } else { + // start new one. First take the old one only if it's well formed + if let Some((remaining_data_length, remaining_topics, event)) = current.take() { + if remaining_data_length == 0 && remaining_topics == 0 { + result.push(event); + } + } + + // split key as our internal marker. Ignore higher bits + let mut num_topics = key.0[0] as u32; + let mut data_length = (key.0[0] >> 32) as usize; + let mut buffer = [0u8; 32]; + value.to_big_endian(&mut buffer); + + let (topics, data) = if num_topics == 0 && data_length == 0 { + (vec![], vec![]) + } else if num_topics == 0 { + data_length -= 32; + (vec![], buffer.to_vec()) + } else { + num_topics -= 1; + (vec![value], vec![]) + }; + + let new_event = TmpEvent { + shard_id, + tx_number, + topics, + data, + }; + + current = Some((data_length, num_topics, new_event)) + } + } + + // add the last one + if let Some((remaining_data_length, remaining_topics, event)) = current.take() { + if remaining_data_length == 0 && remaining_topics == 0 { + result.push(event); + } + } + + result + .iter() + .filter_map(|event| { + let mut address_bytes = [0; 32]; + event.topics[0].to_big_endian(&mut address_bytes); + let address = web3::ethabi::Address::from_slice(&address_bytes[12..]); + + // Filter out events that are from system contracts + if address.as_bytes().iter().rev().skip(2).all(|x| *x == 0) { + return None; + } + let topics = event.topics[1..] + .iter() + .cloned() + .map(Value::Certain) + .collect(); + let values = chunk_return_data(&event.data); + Some(Event::new(Some(address), topics, values)) + }) + .collect() } diff --git a/compiler_tester/src/vm/evm/address_iterator.rs b/compiler_tester/src/vm/evm/address_iterator.rs new file mode 100644 index 00000000..e0f9126b --- /dev/null +++ b/compiler_tester/src/vm/evm/address_iterator.rs @@ -0,0 +1,72 @@ +//! +//! The EVM deploy address iterator. +//! + +use std::collections::HashMap; +use std::str::FromStr; + +use crate::vm::address_iterator::AddressIterator; + +/// +/// The EVM deploy address iterator. +/// +#[derive(Debug, Clone)] +pub struct EVMAddressIterator { + /// The accounts create nonces. + nonces: HashMap, + /// Whether to start nonce from zero. + start_nonce_from_zero: bool, +} + +impl EVMAddressIterator { + /// + /// A shortcut constructor. + /// + pub fn new(start_nonce_from_zero: bool) -> Self { + Self { + nonces: HashMap::new(), + start_nonce_from_zero, + } + } +} + +impl AddressIterator for EVMAddressIterator { + fn next( + &mut self, + caller: &web3::types::Address, + increment_nonce: bool, + ) -> web3::types::Address { + let mut stream = rlp::RlpStream::new_list(2); + stream.append(caller); + stream.append(&self.nonce(caller)); + + let hash = era_compiler_llvm_context::eravm_utils::keccak256(&stream.out()); + let address = web3::types::Address::from_str( + &hash[(era_compiler_common::BYTE_LENGTH_FIELD + - era_compiler_common::BYTE_LENGTH_ETH_ADDRESS) + * 2..], + ) + .expect("Always valid"); + + if increment_nonce { + self.increment_nonce(caller); + } + + address + } + + fn increment_nonce(&mut self, caller: &web3::types::Address) { + let nonce = self + .nonces + .entry(*caller) + .or_insert(if self.start_nonce_from_zero { 0 } else { 1 }); + *nonce += 1; + } + + fn nonce(&mut self, caller: &web3::types::Address) -> usize { + *self + .nonces + .entry(*caller) + .or_insert(if self.start_nonce_from_zero { 0 } else { 1 }) + } +} diff --git a/compiler_tester/src/vm/evm/address_predictor.rs b/compiler_tester/src/vm/evm/address_predictor.rs deleted file mode 100644 index d6866a73..00000000 --- a/compiler_tester/src/vm/evm/address_predictor.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! -//! The EVM deploy address predictor. -//! - -use std::collections::HashMap; -use std::str::FromStr; - -use crate::vm::AddressPredictorIterator; - -/// -/// The EVM deploy address predictor. -/// -#[derive(Debug, Clone)] -pub struct AddressPredictor { - /// The accounts create nonces. - nonces: HashMap, -} - -impl AddressPredictor { - /// - /// Create new address predictor instance. - /// - pub fn new() -> Self { - Self { - nonces: HashMap::new(), - } - } - - /// - /// Increments caller nonce. - /// - pub fn increment_nonce(&mut self, caller: &web3::types::Address) { - let nonce = self.nonces.entry(*caller).or_insert(0); - *nonce += 1; - } -} - -impl AddressPredictorIterator for AddressPredictor { - fn next( - &mut self, - caller: &web3::types::Address, - increment_nonce: bool, - ) -> web3::types::Address { - let address = web3::types::Address::from_str("9f1ebbf13029eaa4d453a2eb221f322404be895b") - .expect("Always valid"); - - if increment_nonce { - self.increment_nonce(caller); - } - - address - } -} diff --git a/compiler_tester/src/vm/evm/input/mod.rs b/compiler_tester/src/vm/evm/input/mod.rs index 37b0318e..c9435653 100644 --- a/compiler_tester/src/vm/evm/input/mod.rs +++ b/compiler_tester/src/vm/evm/input/mod.rs @@ -7,10 +7,12 @@ pub mod build; use std::collections::BTreeMap; use std::collections::HashMap; +use crate::test::instance::Instance; + use self::build::Build; /// -/// The EraVM compiler input. +/// The EVM compiler input. /// #[derive(Debug)] pub struct Input { @@ -37,4 +39,71 @@ impl Input { last_contract, } } + + /// + /// Returns all contract instances. + /// + pub fn get_instances( + &self, + contracts: &BTreeMap, + library_addresses: BTreeMap, + main_address: Option, + ) -> anyhow::Result> { + let mut instances = BTreeMap::new(); + + for (name, address) in library_addresses.into_iter() { + let build = self.builds.get(name.as_str()).ok_or_else(|| { + anyhow::anyhow!("Library `{}` not found in the build artifacts", name) + })?; + + instances.insert( + name.clone(), + Instance::evm( + name, + Some(address), + false, + true, + build.deploy_build.bytecode.to_owned(), + ), + ); + } + + if contracts.is_empty() { + let main_contract_build = + self.builds + .get(self.last_contract.as_str()) + .ok_or_else(|| { + anyhow::anyhow!("Main contract not found in the compiler build artifacts") + })?; + instances.insert( + "Test".to_owned(), + Instance::evm( + self.last_contract.to_owned(), + main_address, + true, + false, + main_contract_build.deploy_build.bytecode.to_owned(), + ), + ); + } else { + for (instance, path) in contracts.iter() { + let build = self.builds.get(path.as_str()).ok_or_else(|| { + anyhow::anyhow!("{} not found in the compiler build artifacts", path) + })?; + let is_main = path.as_str() == self.last_contract.as_str(); + instances.insert( + instance.to_owned(), + Instance::evm( + path.to_owned(), + None, + is_main, + false, + build.deploy_build.bytecode.to_owned(), + ), + ); + } + } + + Ok(instances) + } } diff --git a/compiler_tester/src/vm/evm/mod.rs b/compiler_tester/src/vm/evm/mod.rs index ee0f5c26..fa13e798 100644 --- a/compiler_tester/src/vm/evm/mod.rs +++ b/compiler_tester/src/vm/evm/mod.rs @@ -1,8 +1,8 @@ //! -//! The EVM wrapper. +//! The EVM emulator. //! -pub mod address_predictor; +pub mod address_iterator; pub mod input; pub mod invoker; pub mod output; @@ -24,14 +24,13 @@ use self::output::Output as EVMOutput; use self::runtime::Runtime as EVMRuntime; /// -/// The EVM wrapper. +/// The EVM emulator. /// -#[allow(non_camel_case_types)] pub struct EVM<'evm> { /// The EVM runtime. runtime: EVMRuntime, - /// The known contracts. - known_contracts: HashMap, + /// The builds to deploy. + builds: HashMap, /// The EVM invoker. invoker: EVMInvoker<'evm>, } @@ -40,15 +39,12 @@ impl<'evm> EVM<'evm> { /// /// A shortcut constructor. /// - pub fn new( - known_contracts: HashMap, - invoker: EVMInvoker<'evm>, - ) -> Self { + pub fn new(builds: HashMap, invoker: EVMInvoker<'evm>) -> Self { let runtime = EVMRuntime::default(); Self { runtime, - known_contracts, + builds, invoker, } } @@ -84,14 +80,14 @@ impl<'evm> EVM<'evm> { pub fn execute_deploy_code( &mut self, test_name: String, + path: &str, caller: web3::types::Address, value: Option, constructor_args: Vec, ) -> anyhow::Result { - let bytecode = self.known_contracts.values().next().unwrap(); - let mut deploy_code = bytecode.deploy_build.bytecode.to_owned(); + let build = self.builds.get(path).expect("Always valid"); + let mut deploy_code = build.deploy_build.bytecode.to_owned(); deploy_code.extend(constructor_args); - let runtime_code = bytecode.runtime_build.bytecode.to_owned(); self.runtime .balances @@ -120,14 +116,11 @@ impl<'evm> EVM<'evm> { &self.invoker, ) { Ok(evm::standard::TransactValue::Create { succeed, address }) => match succeed { - evm::ExitSucceed::Returned => { - self.runtime.codes.insert(address, runtime_code.clone()); - (address, false) - } + evm::ExitSucceed::Returned => (address, false), _ => (web3::types::Address::zero(), true), }, Ok(evm::standard::TransactValue::Call { .. }) => { - panic!("Unreachable due to the `Create` transaction sent above") + unreachable!("The `Create` transaction must be executed above") } Err(error) => (web3::types::Address::zero(), true), }; @@ -151,15 +144,14 @@ impl<'evm> EVM<'evm> { pub fn execute_runtime_code( &mut self, test_name: String, + address: web3::types::Address, caller: web3::types::Address, value: Option, calldata: Vec, ) -> anyhow::Result { self.runtime .balances - .insert(caller, web3::types::U256::max_value()); - - let address = self.runtime.codes.iter().next().unwrap().0.to_owned(); + .insert(caller, web3::types::U256::max_value()); // TODO let (return_data, exception) = match evm::transact( evm::standard::TransactArgs::Call { @@ -187,9 +179,9 @@ impl<'evm> EVM<'evm> { (retval, succeed != evm::ExitSucceed::Returned) } Ok(evm::standard::TransactValue::Create { .. }) => { - panic!("Unreachable due to the `Call` transaction sent above") + unreachable!("The `Call` transaction must be executed above") } - Err(_error) => (vec![], true), + Err(error) => (vec![], true), }; let events = self.runtime.logs.drain(..).collect(); diff --git a/compiler_tester/src/vm/evm/runtime.rs b/compiler_tester/src/vm/evm/runtime.rs index 161b9f4c..797abc35 100644 --- a/compiler_tester/src/vm/evm/runtime.rs +++ b/compiler_tester/src/vm/evm/runtime.rs @@ -121,7 +121,7 @@ impl evm::RuntimeBaseBackend for Runtime { .get(&address) .and_then(|storage| storage.get(&index)) .cloned() - .unwrap_or(web3::types::H256::zero()) + .unwrap_or_default() } fn exists(&self, address: web3::types::H160) -> bool { @@ -129,10 +129,7 @@ impl evm::RuntimeBaseBackend for Runtime { } fn nonce(&self, address: web3::types::H160) -> web3::types::U256 { - self.nonces - .get(&address) - .cloned() - .unwrap_or(web3::types::U256::zero()) + self.nonces.get(&address).copied().unwrap_or_default() } } @@ -230,10 +227,10 @@ impl evm::RuntimeBackend for Runtime { } fn inc_nonce(&mut self, address: web3::types::H160) -> Result<(), evm::ExitError> { - self.nonces + *self + .nonces .entry(address) - .and_modify(|nonce| *nonce += web3::types::U256::one()) - .or_insert(web3::types::U256::one()); + .or_insert_with(web3::types::U256::zero) += web3::types::U256::one(); Ok(()) } } diff --git a/compiler_tester/src/vm/execution_result.rs b/compiler_tester/src/vm/execution_result.rs index cdb5c6bb..ce1372fc 100644 --- a/compiler_tester/src/vm/execution_result.rs +++ b/compiler_tester/src/vm/execution_result.rs @@ -10,33 +10,40 @@ use crate::vm::evm::output::Output as EVMOutput; /// #[derive(Debug, Clone)] pub struct ExecutionResult { - /// The actual snapshot result data. + /// The VM snapshot execution result. pub output: Output, /// The number of executed cycles. pub cycles: usize, - /// The amount of gas used. - pub gas: u32, + /// The number of EraVM ergs used. + pub ergs: u64, + /// The number of gas used. + pub gas: u64, } impl ExecutionResult { /// /// A shortcut constructor. /// - pub fn new(output: Output, cycles: usize, gas: u32) -> Self { + pub fn new(output: Output, cycles: usize, ergs: u64, gas: u64) -> Self { Self { output, cycles, + ergs, gas, } } } -impl From<&zkevm_tester::runners::compiler_tests::VmSnapshot> for ExecutionResult { - fn from(snapshot: &zkevm_tester::runners::compiler_tests::VmSnapshot) -> Self { +impl From for ExecutionResult { + fn from(snapshot: zkevm_tester::runners::compiler_tests::VmSnapshot) -> Self { + let cycles = snapshot.num_cycles_used; + let ergs = snapshot.num_ergs_used as u64; + Self { output: Output::from(snapshot), - cycles: snapshot.num_cycles_used, - gas: snapshot.num_ergs_used, + cycles, + ergs, + gas: 0, } } } @@ -46,6 +53,7 @@ impl From for ExecutionResult { Self { output: Output::from(output), cycles: 0, + ergs: 0, gas: 0, } } diff --git a/compiler_tester/src/vm/mod.rs b/compiler_tester/src/vm/mod.rs index 384ef8c0..ce3c7b06 100644 --- a/compiler_tester/src/vm/mod.rs +++ b/compiler_tester/src/vm/mod.rs @@ -2,20 +2,7 @@ //! The VM wrappers. //! +pub mod address_iterator; pub mod eravm; pub mod evm; pub mod execution_result; - -/// -/// The address predictor iterator. -/// -pub trait AddressPredictorIterator { - /// - /// Returns the next predicted address. - /// - fn next( - &mut self, - caller: &web3::types::Address, - increment_nonce: bool, - ) -> web3::types::Address; -} diff --git a/configs/solc-bin-default.json b/configs/solc-bin-default.json index de77783d..6c0700a0 100644 --- a/configs/solc-bin-default.json +++ b/configs/solc-bin-default.json @@ -463,12 +463,6 @@ "destination": "./solc-bin/solc-${VERSION}" }, "0.8.24": { - "is_enabled": false, - "protocol": "https", - "source": "https://github.com/matter-labs/era-solidity/releases/download/${VERSION}-1.0.0/solc-${PLATFORM}-${VERSION}-1.0.0", - "destination": "./solc-bin/solc-${VERSION}" - }, - "0.8.25": { "is_enabled": true, "protocol": "https", "source": "https://github.com/matter-labs/era-solidity/releases/download/${VERSION}-1.0.0/solc-${PLATFORM}-${VERSION}-1.0.0", diff --git a/configs/solc-bin-upstream.json b/configs/solc-bin-upstream.json new file mode 100644 index 00000000..8db4b2e5 --- /dev/null +++ b/configs/solc-bin-upstream.json @@ -0,0 +1,478 @@ +{ + "binaries": { + "0.4.12": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.4.12" + }, + "0.4.13": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.4.13" + }, + "0.4.14": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.4.14" + }, + "0.4.15": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.4.15" + }, + "0.4.16": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.4.16" + }, + "0.4.17": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.4.17" + }, + "0.4.18": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.4.18" + }, + "0.4.19": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.4.19" + }, + "0.4.20": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.4.20" + }, + "0.4.21": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.4.21" + }, + "0.4.22": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.4.22" + }, + "0.4.23": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.4.23" + }, + "0.4.24": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.4.24" + }, + "0.4.25": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.4.25" + }, + "0.4.26": { + "is_enabled": true, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.4.26" + }, + "0.5.0": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.0" + }, + "0.5.1": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.1" + }, + "0.5.2": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.2" + }, + "0.5.3": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.3" + }, + "0.5.4": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.4" + }, + "0.5.5": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.5" + }, + "0.5.6": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.6" + }, + "0.5.7": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.7" + }, + "0.5.8": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.8" + }, + "0.5.9": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.9" + }, + "0.5.10": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.10" + }, + "0.5.11": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.11" + }, + "0.5.12": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.12" + }, + "0.5.13": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.13" + }, + "0.5.14": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.14" + }, + "0.5.15": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.15" + }, + "0.5.16": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.16" + }, + "0.5.17": { + "is_enabled": true, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.5.17" + }, + "0.6.0": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.6.0" + }, + "0.6.1": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.6.1" + }, + "0.6.2": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.6.2" + }, + "0.6.3": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.6.3" + }, + "0.6.4": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.6.4" + }, + "0.6.5": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.6.5" + }, + "0.6.6": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.6.6" + }, + "0.6.7": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.6.7" + }, + "0.6.8": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.6.8" + }, + "0.6.9": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.6.9" + }, + "0.6.10": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.6.10" + }, + "0.6.11": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.6.11" + }, + "0.6.12": { + "is_enabled": true, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.6.12" + }, + "0.7.0": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.7.0" + }, + "0.7.1": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.7.1" + }, + "0.7.2": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.7.2" + }, + "0.7.3": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.7.3" + }, + "0.7.4": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.7.4" + }, + "0.7.5": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.7.5" + }, + "0.7.6": { + "is_enabled": true, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.7.6" + }, + "0.8.0": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.0" + }, + "0.8.1": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.1" + }, + "0.8.2": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.2" + }, + "0.8.3": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.3" + }, + "0.8.4": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.4" + }, + "0.8.5": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.5" + }, + "0.8.6": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.6" + }, + "0.8.7": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.7" + }, + "0.8.8": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.8" + }, + "0.8.9": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.9" + }, + "0.8.10": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.10" + }, + "0.8.11": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.11" + }, + "0.8.12": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.12" + }, + "0.8.13": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.13" + }, + "0.8.14": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.14" + }, + "0.8.15": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.15" + }, + "0.8.16": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.16" + }, + "0.8.17": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.17" + }, + "0.8.18": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.18" + }, + "0.8.19": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.19" + }, + "0.8.20": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.20" + }, + "0.8.21": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.21" + }, + "0.8.22": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.22" + }, + "0.8.23": { + "is_enabled": false, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.23" + }, + "0.8.24": { + "is_enabled": true, + "protocol": "solc-bin-list", + "source": "https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/${PLATFORM}/list.json", + "destination": "./solc-bin-upstream/solc-0.8.24" + } + }, + "platforms": { + "linux-amd64": "linux-amd64", + "linux-arm64": "linux-amd64", + "macos-amd64": "macosx-amd64", + "macos-arm64": "macosx-amd64" + } +} \ No newline at end of file diff --git a/configs/solc-bin-zkevm-candidate-0.8.25.json b/configs/solc-bin-zkevm-candidate-0.8.25.json deleted file mode 100644 index 7e331a7f..00000000 --- a/configs/solc-bin-zkevm-candidate-0.8.25.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "binaries": { - "0.8.25": { - "is_enabled": true, - "protocol": "file", - "source": "./solc-bin/solc-${VERSION}", - "destination": "./solc-bin/solc-${VERSION}" - } - }, - "platforms": { - "linux-amd64": "linux-amd64", - "linux-arm64": "linux-arm64", - "macos-amd64": "macosx-amd64", - "macos-arm64": "macosx-arm64" - } -} diff --git a/configs/solc-bin-zkevm-reference-0.8.25.json b/configs/solc-bin-zkevm-reference-0.8.25.json deleted file mode 100644 index 297ce19e..00000000 --- a/configs/solc-bin-zkevm-reference-0.8.25.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "binaries": { - "0.8.25": { - "is_enabled": true, - "protocol": "https", - "source": "https://github.com/matter-labs/era-solidity/releases/download/${VERSION}-1.0.0/solc-${PLATFORM}-${VERSION}-1.0.0", - "destination": "./solc-bin/solc-${VERSION}" - } - }, - "platforms": { - "linux-amd64": "linux-amd64", - "linux-arm64": "linux-arm64", - "macos-amd64": "macosx-amd64", - "macos-arm64": "macosx-arm64" - } -} diff --git a/coverage_watcher/Cargo.toml b/coverage_watcher/Cargo.toml index 18274203..5f2e5089 100644 --- a/coverage_watcher/Cargo.toml +++ b/coverage_watcher/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "coverage-watcher" -version = "1.4.1" +version = "1.5.0" authors = [ "Oleksandr Zarudnyi ", "Anton Dyadyuk ", diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index c7cb6dd0..a1b3c6f1 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -1,29 +1,8 @@ [package] name = "compiler-tester-fuzz" -version = "0.0.0" -license = "MIT OR Apache-2.0" -publish = false +version = "1.5.0" edition = "2021" -[package.metadata] -cargo-fuzz = true - -[dependencies] -libfuzzer-sys = "0.4" -era-compiler-llvm-context = { git = "https://github.com/matter-labs/era-compiler-llvm-context", branch = "main" } -era-compiler-solidity = { git = "https://github.com/matter-labs/era-compiler-solidity", branch = "main" } -zkevm-assembly = { git = "https://github.com/matter-labs/era-zkEVM-assembly", branch = "v1.4.1" } -zkevm_tester = { git = "https://github.com/matter-labs/era-zkevm_tester", branch = "v1.4.1" } -anyhow = "1.0" -semver = { version = "1.0", features = ["serde"] } - -[dependencies.compiler-tester] -path = "../compiler_tester" - -[dependencies.solidity-adapter] -path = "../solidity_adapter" - - [[bin]] name = "demo" path = "fuzz_targets/demo.rs" @@ -37,3 +16,20 @@ path = "fuzz_targets/optimizer_bug.rs" test = false doc = false bench = false + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +anyhow = "1.0" +semver = { version = "1.0", features = ["serde"] } + +zkevm-assembly = { git = "https://github.com/matter-labs/era-zkEVM-assembly", branch = "v1.5.0" } +zkevm_tester = { git = "https://github.com/matter-labs/era-zkevm_tester", branch = "v1.5.0" } + +era-compiler-llvm-context = { git = "https://github.com/matter-labs/era-compiler-llvm-context", branch = "v1.5.0" } +era-compiler-solidity = { git = "https://github.com/matter-labs/era-compiler-solidity", branch = "v1.5.0" } + +compiler-tester = { path = "../compiler_tester" } +solidity-adapter = { path = "../solidity_adapter" } diff --git a/fuzz/fuzz_targets/common.rs b/fuzz/fuzz_targets/common.rs index bb2463f0..e0a4a313 100644 --- a/fuzz/fuzz_targets/common.rs +++ b/fuzz/fuzz_targets/common.rs @@ -1,9 +1,6 @@ -use std::{ - path::{Path, PathBuf}, - sync::Arc, -}; - use compiler_tester::{Buildable, EthereumTest, Mode, SolidityCompiler, SolidityMode, Summary}; +use era_compiler_solidity::SolcPipeline; + pub use solidity_adapter::{ test::function_call::parser::{ lexical::token::{ @@ -23,7 +20,7 @@ pub use solidity_adapter::{ EnabledTest, FunctionCall, }; -use era_compiler_solidity::SolcPipeline; +use std::{path::PathBuf, sync::Arc}; /// /// Fuzzing case definition @@ -127,14 +124,15 @@ pub fn build_function_call(case: FuzzingCase) -> anyhow::Result { /// /// * `EthereumTest` - The Ethereum test pub fn gen_fuzzing_test(case: FuzzingCase) -> anyhow::Result { - let test_path = Path::new(&case.contract_path); + let test_path = PathBuf::from(case.contract_path.as_str()); // Generate Test objects for the fuzzing contract let enabled_test = EnabledTest::new(test_path.to_path_buf(), None, None, None); - let mut test = solidity_adapter::Test::try_from(test_path)?; + let mut test = solidity_adapter::Test::try_from(test_path.as_path())?; let fcall = build_function_call(case)?; test.calls.push(fcall); Ok(EthereumTest { + identifier: test_path.to_string_lossy().to_string(), index_entity: enabled_test, test, }) @@ -159,6 +157,8 @@ pub fn build_and_run(test: EthereumTest) -> anyhow::Result { true, era_compiler_llvm_context::OptimizerSettings::try_from_cli('3') .expect("Error: optimization settings incorrect!"), + false, + false, )); // Initialization @@ -183,11 +183,12 @@ pub fn build_and_run(test: EthereumTest) -> anyhow::Result { if let Some(test) = test.build_for_eravm( mode, Arc::new(SolidityCompiler::new()), + compiler_tester::Target::EraVM, compiler_tester.summary.clone(), &compiler_tester.filters, compiler_tester.debug_config.clone(), ) { - test.run::( + test.run_eravm::( compiler_tester.summary.clone(), Arc::new(compiler_tester::EraVM::new( vec![ diff --git a/fuzz/fuzz_targets/demo.rs b/fuzz/fuzz_targets/demo.rs index 691a448e..d458a768 100644 --- a/fuzz/fuzz_targets/demo.rs +++ b/fuzz/fuzz_targets/demo.rs @@ -1,13 +1,9 @@ -//! -//! The fuzzer demo. -//! - #![no_main] /// This module contains the fuzzing target for the simple contract. use libfuzzer_sys::fuzz_target; -pub(crate) mod common; +mod common; fuzz_target!(|data: u8| { // Fuzzing case definition diff --git a/fuzz/fuzz_targets/optimizer_bug.rs b/fuzz/fuzz_targets/optimizer_bug.rs index 8e01dd16..ef012e57 100644 --- a/fuzz/fuzz_targets/optimizer_bug.rs +++ b/fuzz/fuzz_targets/optimizer_bug.rs @@ -1,12 +1,8 @@ -//! -//! The optimizer bug demo. -//! - #![no_main] use libfuzzer_sys::fuzz_target; -pub(crate) mod common; +mod common; fuzz_target!(|data: u8| { // Fuzzing case definition diff --git a/solidity_adapter/Cargo.toml b/solidity_adapter/Cargo.toml index 9caaf456..b496e2b7 100644 --- a/solidity_adapter/Cargo.toml +++ b/solidity_adapter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "solidity-adapter" -version = "1.4.1" +version = "1.5.0" authors = [ "Oleksandr Zarudnyi ", "Anton Dyadyuk ", diff --git a/solidity_adapter/src/test/function_call/parser/lexical/token/lexeme/keyword.rs b/solidity_adapter/src/test/function_call/parser/lexical/token/lexeme/keyword.rs index 83808cf9..afe1da02 100644 --- a/solidity_adapter/src/test/function_call/parser/lexical/token/lexeme/keyword.rs +++ b/solidity_adapter/src/test/function_call/parser/lexical/token/lexeme/keyword.rs @@ -41,8 +41,6 @@ pub enum Keyword { Legacy, /// The `legacyOptimized` keyword. LegacyOptimized, - /// The `code` keyword. - Code, /// The `bool` type keyword. Bool, @@ -155,7 +153,6 @@ impl TryFrom<&str> for Keyword { "irOptimized" => return Ok(Self::IrOptimized), "legacy" => return Ok(Self::Legacy), "legacyOptimized" => return Ok(Self::LegacyOptimized), - "code" => return Ok(Self::Code), "bool" => return Ok(Self::Bool), "string" => return Ok(Self::String), @@ -252,7 +249,6 @@ impl fmt::Display for Keyword { Self::IrOptimized => write!(f, "irOptimized"), Self::Legacy => write!(f, "legacy"), Self::LegacyOptimized => write!(f, "legacyOptimized"), - Self::Code => write!(f, "code"), Self::Bool => write!(f, "bool"), Self::String => write!(f, "string"), diff --git a/solidity_adapter/src/test/function_call/parser/syntax/parser/event.rs b/solidity_adapter/src/test/function_call/parser/syntax/parser/event.rs index d600fa5f..aeba98f7 100644 --- a/solidity_adapter/src/test/function_call/parser/syntax/parser/event.rs +++ b/solidity_adapter/src/test/function_call/parser/syntax/parser/event.rs @@ -339,20 +339,25 @@ impl Parser { #[cfg(test)] mod tests { + use crate::test::function_call::parser::lexical::token::lexeme::identifier::Identifier as LexicalIdentifier; - use crate::test::function_call::parser::lexical::IntegerLiteral as LexicalIntegerLiteral; + use crate::test::function_call::parser::syntax::tree::literal::integer::Literal as IntegerLiteral; + use crate::test::function_call::parser::lexical::Lexeme; + use crate::test::function_call::parser::lexical::Location; + + use crate::test::function_call::parser::lexical::IntegerLiteral as LexicalIntegerLiteral; use crate::test::function_call::parser::lexical::Token; use crate::test::function_call::parser::lexical::TokenStream; + + use super::Parser; use crate::test::function_call::parser::syntax::error::Error as SyntaxError; use crate::test::function_call::parser::syntax::error::ParsingError; - use crate::test::function_call::parser::syntax::parser::event::Parser; use crate::test::function_call::parser::syntax::tree::event::literal::EventLiteral; use crate::test::function_call::parser::syntax::tree::event::variant::Variant; use crate::test::function_call::parser::syntax::tree::event::Event; use crate::test::function_call::parser::syntax::tree::literal::alignment::Alignment; - use crate::test::function_call::parser::syntax::tree::literal::integer::Literal as IntegerLiteral; use crate::test::function_call::parser::syntax::tree::literal::Literal; use crate::test::function_call::parser::syntax::tree::r#type::variant::Variant as TypeVariant; use crate::test::function_call::parser::syntax::Identifier; diff --git a/solidity_adapter/src/test/function_call/parser/syntax/parser/gas.rs b/solidity_adapter/src/test/function_call/parser/syntax/parser/gas.rs index 25b7819a..98441b9f 100644 --- a/solidity_adapter/src/test/function_call/parser/syntax/parser/gas.rs +++ b/solidity_adapter/src/test/function_call/parser/syntax/parser/gas.rs @@ -28,8 +28,6 @@ pub enum State { /// The `gas` has been parsed so far. Variant, /// The `gas {variant}` has been parsed so far. - ColonOrCode, - /// The `gas {variant}` has been parsed so far. Colon, /// The `gas {variant}:` has been parsed so far. Value, @@ -95,7 +93,7 @@ impl Parser { .. } => { self.builder.set_keyword(keyword); - self.state = State::ColonOrCode; + self.state = State::Colon; } Token { lexeme, location } => { return Err(SyntaxError::new( @@ -106,24 +104,6 @@ impl Parser { .into()); } }, - State::ColonOrCode => match parser::take_or_next(self.next.take(), stream.clone())? - { - Token { - lexeme: Lexeme::Keyword(Keyword::Code), - .. - } => { - self.state = State::Colon; - } - Token { - lexeme: Lexeme::Symbol(Symbol::Colon), - .. - } => { - self.state = State::Value; - } - Token { lexeme, location } => { - return Err(SyntaxError::new(location, vec!["code", ":"], lexeme).into()); - } - }, State::Colon => match parser::take_or_next(self.next.take(), stream.clone())? { Token { lexeme: Lexeme::Symbol(Symbol::Colon), diff --git a/solidity_adapter/src/test/function_call/parser/syntax/tree/literal/alignment.rs b/solidity_adapter/src/test/function_call/parser/syntax/tree/literal/alignment.rs index b1a88232..52d9658f 100644 --- a/solidity_adapter/src/test/function_call/parser/syntax/tree/literal/alignment.rs +++ b/solidity_adapter/src/test/function_call/parser/syntax/tree/literal/alignment.rs @@ -15,6 +15,12 @@ pub enum Alignment { Default, } +impl Default for Alignment { + fn default() -> Self { + Self::Default + } +} + impl Alignment { /// /// A shortcut constructor. @@ -29,11 +35,4 @@ impl Alignment { pub fn right() -> Self { Self::Right } - - /// - /// A shortcut constructor. - /// - pub fn default() -> Self { - Self::Default - } }