From 757fdc925905464d801a54fdaa5a69c692fa687d Mon Sep 17 00:00:00 2001 From: Oleksandr Zarudnyi Date: Thu, 9 May 2024 22:02:51 +0700 Subject: [PATCH] feat: update to zkVM v1.5.0 (#8) --- .github/workflows/cargo-license.yaml | 4 +- .gitignore | 18 +- .gitmodules | 4 +- Cargo.lock | 307 ++++++---- 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 | 151 ++++- compiler_tester/Cargo.toml | 14 +- compiler_tester/build.rs | 10 + .../src/compiler_tester/arguments.rs | 8 +- compiler_tester/src/compiler_tester/main.rs | 99 +++- compiler_tester/src/compilers/cache/mod.rs | 28 +- 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 | 15 +- .../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 | 550 ++++++++++-------- compiler_tester/src/directories/mod.rs | 61 +- compiler_tester/src/filters.rs | 24 +- compiler_tester/src/lib.rs | 151 +++-- 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 | 131 ++++- .../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 | 169 ++++-- .../src/vm/eravm/system_context.rs | 8 +- .../src/vm/eravm/system_contracts.rs | 141 +++-- 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-system-contracts.json | 2 +- configs/solc-bin-upstream.json | 484 +++++++++++++++ coverage_watcher/Cargo.toml | 2 +- era-contracts | 2 +- fuzzer/Cargo.toml | 13 +- fuzzer/build.rs | 10 + fuzzer/fuzz_targets/common.rs | 19 +- fuzzer/fuzz_targets/demo.rs | 6 +- fuzzer/fuzz_targets/optimizer_bug.rs | 6 +- rust-toolchain.toml | 3 + solidity | 2 +- solidity_adapter/Cargo.toml | 4 +- .../parser/syntax/parser/event.rs | 11 +- .../function_call/parser/syntax/parser/gas.rs | 9 +- system-contracts-stable-build | Bin 406613 -> 819088 bytes tests | 2 +- 120 files changed, 5603 insertions(+), 2051 deletions(-) create mode 100644 compiler_tester/build.rs 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 create mode 100644 fuzzer/build.rs create mode 100644 rust-toolchain.toml diff --git a/.github/workflows/cargo-license.yaml b/.github/workflows/cargo-license.yaml index 5fe13905..b4f4f8cd 100644 --- a/.github/workflows/cargo-license.yaml +++ b/.github/workflows/cargo-license.yaml @@ -4,5 +4,5 @@ jobs: cargo-deny: runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: EmbarkStudios/cargo-deny-action@1e59595bed8fc55c969333d08d7817b36888f0c5 # v1.5.5 + - uses: actions/checkout@v4 + - uses: EmbarkStudios/cargo-deny-action@v1 diff --git a/.gitignore b/.gitignore index e58896eb..b79c463a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,13 +4,6 @@ # These are backup files generated by rustfmt **/*.rs.bk -# MacOS -/**/.DS_Store - -# IDE -/.idea/ -/.vscode/ - # The LLVM framework source /llvm/ @@ -18,10 +11,19 @@ /solc-bin*/* /vyper-bin/* -# The debug and trace artifacts +# The debug, trace, benchmark artifacts /debug/ /trace/ +/**/*.json +/**/*.txt # The dependency locks # /Cargo.lock # /LLVM.lock + +# IDE +/.idea/ +/.vscode/ + +# MacOS +/**/.DS_Store diff --git a/.gitmodules b/.gitmodules index 649dde8b..ecd35e88 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 = main + branch = evm-equivalence-yul diff --git a/Cargo.lock b/Cargo.lock index 563e43cd..9370a1ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" [[package]] name = "arbitrary" @@ -63,14 +63,14 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" @@ -107,7 +107,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "benchmark-analyzer" -version = "1.4.1" +version = "1.5.0" dependencies = [ "anyhow", "colored", @@ -197,9 +197,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" dependencies = [ "jobserver", "libc", @@ -235,7 +235,7 @@ dependencies = [ [[package]] name = "compiler-tester" -version = "1.4.1" +version = "1.5.0" dependencies = [ "anyhow", "benchmark-analyzer", @@ -256,6 +256,7 @@ dependencies = [ "rayon", "regex", "reqwest", + "rlp", "ron", "semver", "serde", @@ -265,6 +266,7 @@ dependencies = [ "solidity-adapter", "structopt", "web3", + "which 6.0.1", "zkevm-assembly", "zkevm_opcode_defs", "zkevm_tester", @@ -272,7 +274,7 @@ dependencies = [ [[package]] name = "compiler-tester-fuzz" -version = "0.0.0" +version = "1.5.0" dependencies = [ "anyhow", "compiler-tester", @@ -315,7 +317,7 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "coverage-watcher" -version = "1.4.1" +version = "1.5.0" dependencies = [ "anyhow", "era-compiler-common", @@ -393,6 +395,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] @@ -453,6 +456,7 @@ dependencies = [ "ff", "generic-array", "group", + "pem-rfc7468", "pkcs8", "rand_core 0.6.4", "sec1", @@ -501,8 +505,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#94d110f5061e8ef9b14db964ed32ee974cceacb2" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-compiler-llvm-context?branch=main#bde99017add2c53a34a6cbcf2ba39560a3e72f53" dependencies = [ "anyhow", "era-compiler-common", @@ -523,8 +527,8 @@ dependencies = [ [[package]] name = "era-compiler-solidity" -version = "1.4.1" -source = "git+https://github.com/matter-labs/era-compiler-solidity?branch=main#fab586681524407644c155dd22e36cd65b9fdee2" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-compiler-solidity?branch=main#e214b99e6014a3e5a7d2d50d8581111a8e7dc646" dependencies = [ "anyhow", "colored", @@ -546,14 +550,14 @@ dependencies = [ "sha3", "structopt", "thiserror", - "which", + "which 5.0.0", "zkevm-assembly", ] [[package]] name = "era-compiler-vyper" -version = "1.4.1" -source = "git+https://github.com/matter-labs/era-compiler-vyper?branch=main#8fc8c76e4592f7b16619ab9b76a8f38521f54e4b" +version = "1.5.0" +source = "git+https://github.com/matter-labs/era-compiler-vyper?branch=main#9d5d903c054581273dc9d0f9cc47c29fba205768" dependencies = [ "anyhow", "colored", @@ -571,16 +575,16 @@ dependencies = [ "serde_json", "sha3", "structopt", - "which", + "which 5.0.0", "zkevm-assembly", "zkevm_opcode_defs", ] [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -759,7 +763,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -811,9 +815,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -864,9 +868,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "headers" @@ -1102,7 +1106,7 @@ source = "git+https://github.com/matter-labs-forks/inkwell?branch=llvm-17#c0821d dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -1190,9 +1194,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libfuzzer-sys" @@ -1216,9 +1220,9 @@ dependencies = [ [[package]] name = "libmimalloc-sys" -version = "0.1.35" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3979b5c37ece694f1f5e51e7ecc871fdb0f517ed04ee45f88d15d6d553cb9664" +checksum = "81eb4061c0582dedea1cbc7aff2240300dd6982e0239d1c99e65c1dbf4a30ba7" dependencies = [ "cc", "libc", @@ -1245,9 +1249,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1273,9 +1277,9 @@ checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "mimalloc" -version = "0.1.39" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa01922b5ea280a911e323e4d2fd24b7fe5cc4042e0d2cda3c40775cdc4bdc9c" +checksum = "9f41a2280ded0da56c8cf898babb86e8f10651a34adcfff190ae9a1159c6908d" dependencies = [ "libmimalloc-sys", ] @@ -1324,9 +1328,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135b08af27d103b0a51f2ae0f8632117b7b185ccf931445affa8df530576a41" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", @@ -1338,20 +1342,19 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ - "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-complex" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] @@ -1367,9 +1370,9 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -1378,11 +1381,10 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", "num-bigint", "num-integer", "num-traits", @@ -1390,9 +1392,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1415,7 +1417,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -1433,11 +1435,23 @@ 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" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ "arrayvec", "bitvec", @@ -1449,11 +1463,11 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.6.9" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ - "proc-macro-crate 2.0.2", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 1.0.109", @@ -1461,9 +1475,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -1471,15 +1485,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -1488,6 +1502,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" @@ -1511,7 +1534,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -1542,6 +1565,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" @@ -1567,12 +1599,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "2.0.2" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_datetime", - "toml_edit 0.20.2", + "toml_edit 0.21.1", ] [[package]] @@ -1601,9 +1632,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] @@ -1731,11 +1762,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", ] [[package]] @@ -1863,9 +1894,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hex" @@ -1884,9 +1915,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.33" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3cc72858054fcff6d7dea32df2aeaee6a7c24227366d7ea429aada2f26b16ad" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", @@ -1897,9 +1928,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.11" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", @@ -1928,15 +1959,15 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "scale-info" -version = "2.11.2" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c453e59a955f81fb62ee5d596b450383d699f152d350e9d23a0db2adb78e4c0" +checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" dependencies = [ "bitvec", "cfg-if", @@ -1947,11 +1978,11 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.2" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18cf6c6447f813ef19eb450e985bcce6705f9ce7660db221b59093d15c79c4b7" +checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 1.0.109", @@ -2007,38 +2038,38 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.198" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -2139,9 +2170,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2149,7 +2180,7 @@ dependencies = [ [[package]] name = "solidity-adapter" -version = "1.4.1" +version = "1.5.0" dependencies = [ "anyhow", "colored", @@ -2241,9 +2272,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.60" +version = "2.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" dependencies = [ "proc-macro2", "quote", @@ -2303,22 +2334,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -2372,23 +2403,22 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" [[package]] name = "toml_edit" @@ -2403,9 +2433,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.2" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap", "toml_datetime", @@ -2437,7 +2467,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", ] [[package]] @@ -2502,9 +2532,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "unsafe-libyaml" @@ -2577,7 +2607,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", "wasm-bindgen-shared", ] @@ -2611,7 +2641,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.61", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2682,6 +2712,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "which" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" +dependencies = [ + "either", + "home", + "rustix", + "winsafe", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2700,11 +2742,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -2871,6 +2913,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wyz" version = "0.5.1" @@ -2888,8 +2936,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/era-zk_evm?branch=v1.5.0#6119ce908ab714f2f99804794e725b97298a6b11" dependencies = [ "anyhow", "lazy_static", @@ -2898,13 +2946,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", @@ -2915,8 +2962,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", @@ -2934,24 +2981,27 @@ 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#109d9f734804a8b9dc0531c0b576e2a0f55a40de" 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#488ca0782fea1eb9af7ff82d252d54af3471f33e" dependencies = [ "anyhow", + "ethabi", "futures", "hex", "num-bigint", @@ -2962,6 +3012,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..a3eed1ab 100644 --- a/benchmark_analyzer/src/benchmark/mod.rs +++ b/benchmark_analyzer/src/benchmark/mod.rs @@ -23,19 +23,162 @@ 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; 119] = [ + "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", + "PUSH0", + "PUSH1", + "PUSH2", + "PUSH4", + "PUSH5", + "PUSH6", + "PUSH7", + "PUSH8", + "PUSH9", + "PUSH10", + "PUSH11", + "PUSH12", + "PUSH13", + "PUSH14", + "PUSH15", + "PUSH16", + "PUSH17", + "PUSH18", + "PUSH19", + "PUSH20", + "PUSH21", + "PUSH22", + "PUSH23", + "PUSH24", + "PUSH25", + "PUSH26", + "PUSH27", + "PUSH28", + "PUSH29", + "PUSH30", + "PUSH31", + "PUSH32", + "DUP1", + "DUP2", + "DUP3", + "DUP4", + "DUP5", + "DUP6", + "DUP7", + "DUP8", + "DUP9", + "DUP10", + "DUP11", + "DUP12", + "DUP13", + "DUP14", + "DUP15", + "DUP16", + "SWAP1", + "SWAP2", + "SWAP3", + "SWAP4", + "SWAP5", + "SWAP6", + "SWAP7", + "SWAP8", + "SWAP9", + "SWAP10", + "SWAP11", + "SWAP12", + "SWAP13", + "SWAP14", + "SWAP15", + "SWAP16", + "RETURN", + "REVERT", + ]; + /// /// 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 d012ac35..23f344d5 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 = "6.0" colored = "2.1" serde = { version = "1.0", features = ["derive"] } @@ -28,19 +29,22 @@ md5 = "0.7" hex = "0.4" sha3 = "0.10" ron = "0.8" +rlp = "0.5" regex = "1.9" glob = "0.3" semver = { version = "1.0", features = ["serde"] } itertools = "0.12" once_cell = "1.19" -rayon = "1.8" +rayon = "1.9" 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" } diff --git a/compiler_tester/build.rs b/compiler_tester/build.rs new file mode 100644 index 00000000..28dbae4b --- /dev/null +++ b/compiler_tester/build.rs @@ -0,0 +1,10 @@ +//! +//! The default build script for `compiler-tester`. +//! + +/// +/// The default build script for `compiler-tester`. +/// +/// Is required for `rust-link-lib` flags to work. +/// +fn main() {} diff --git a/compiler_tester/src/compiler_tester/arguments.rs b/compiler_tester/src/compiler_tester/arguments.rs index 5109c95c..42779a12 100644 --- a/compiler_tester/src/compiler_tester/arguments.rs +++ b/compiler_tester/src/compiler_tester/arguments.rs @@ -77,11 +77,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 9d8d6535..ed0efc90 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 @@ -100,9 +103,13 @@ fn main_inner(arguments: Arguments) -> anyhow::Result<()> { )?; 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")), @@ -115,16 +122,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 @@ -132,7 +148,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, )?; @@ -142,22 +158,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}"); @@ -182,7 +225,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() { @@ -190,13 +235,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), @@ -207,15 +254,17 @@ 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, + workflow: compiler_tester::Workflow::BuildAndRun, }; - 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..dbd2b7fb 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,19 +93,19 @@ 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()) + .map(|value| value.to_owned()) .map_err(|error| anyhow::anyhow!("{}", error)) } /// - /// 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..fc56a399 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 @@ -8,6 +8,7 @@ use serde::Deserialize; use crate::compilers::mode::Mode; +use self::variant::extended::Extended; use self::variant::Variant; /// @@ -30,6 +31,18 @@ impl Expected { Self::Single(Variant::Simple(vec![format!("{instance}.address")])) } + /// + /// Creates EVM interpreter benchmark expected data. + /// + pub fn successful_evm_interpreter_benchmark(exception: bool) -> Self { + Self::Single(Variant::Extended(Extended { + return_data: vec![], + events: vec![], + exception, + compiler_version: None, + })) + } + /// /// Returns exception flag for specified mode. /// @@ -50,7 +63,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..55fc44fb 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 != "Benchmark" + && 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..5c26e15f 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,321 @@ 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 Benchmark 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/Benchmark.sol", + ))?; + sources.push(( + "tests/solidity/complex/interpreter/Benchmark.sol".to_owned(), + benchmark_caller_string, + )); // TODO + contracts.insert( + "Benchmark".to_owned(), + "tests/solidity/complex/interpreter/Benchmark.sol:Benchmark".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]; + if full.starts_with("DUP") || full.starts_with("SWAP") || full.starts_with("PUSH") { + continue; + } + let exception = full.contains("REVERT"); + + 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: "#fallback".to_owned(), + calldata: MatterLabsCaseInputCalldata::List(vec![ + "Benchmark.address".to_owned(), + format!("{template}.address"), + ]), + value: None, + storage: HashMap::new(), + expected: Some( + MatterLabsCaseInputExpected::successful_evm_interpreter_benchmark( + false, + ), + ), + }, + MatterLabsCaseInput { + comment: None, + instance: "Proxy".to_owned(), + caller: default_caller_address(), + method: "#fallback".to_owned(), + calldata: MatterLabsCaseInputCalldata::List(vec![ + "Benchmark.address".to_owned(), + format!("{full}.address"), + ]), + value: None, + storage: HashMap::new(), + expected: Some( + MatterLabsCaseInputExpected::successful_evm_interpreter_benchmark( + exception, + ), + ), + }, + ], + expected: MatterLabsCaseInputExpected::successful_evm_interpreter_benchmark( + exception, + ), + 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 +489,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 +509,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); - } + sources + } else { + self.sources.to_owned() + }; - let mut address_predictor = EVMAddressPredictor::new(); + let mut evm_address_iterator = + EVMAddressIterator::new(matches!(target, Target::EVMInterpreter)); - let mut libraries_instances_names = Vec::new(); - let mut libraries_for_compiler = BTreeMap::new(); - let mut libraries_instances_addresses = HashMap::new(); + let (libraries, library_addresses) = self.get_libraries(&mut evm_address_iterator); - 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 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 +546,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 +567,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 +607,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 e2d075e4..16d9049e 100644 --- a/compiler_tester/src/lib.rs +++ b/compiler_tester/src/lib.rs @@ -2,25 +2,20 @@ //! 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(crate) mod workflow; -pub use self::filters::Filters; -pub use self::llvm_options::LLVMOptions; -pub use self::summary::Summary; -pub use self::workflow::Workflow; -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; @@ -31,8 +26,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; @@ -41,8 +38,16 @@ 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; +pub use crate::workflow::Workflow; /// The debug directory path. pub const DEBUG_DIRECTORY: &str = "./debug/"; @@ -119,7 +124,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 @@ -133,12 +138,13 @@ impl CompilerTester { if let Some(test) = test.build_for_eravm( mode, compiler, + Target::EraVM, self.summary.clone(), &self.filters, specialized_debug_config, ) { if let Workflow::BuildAndRun = self.workflow { - test.run::(self.summary.clone(), vm.clone()) + test.run_eravm::(self.summary.clone(), vm.clone()) }; } }) @@ -148,10 +154,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() @@ -164,12 +170,13 @@ impl CompilerTester { if let Some(test) = test.build_for_evm( mode, compiler, + Target::EVM, self.summary.clone(), &self.filters, specialized_debug_config, ) { if let Workflow::BuildAndRun = self.workflow { - test.run(self.summary.clone()) + test.run_evm(self.summary.clone()) }; } }) @@ -179,49 +186,61 @@ 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(), + ) { + if let Workflow::BuildAndRun = self.workflow { + 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, @@ -247,7 +266,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, @@ -258,7 +281,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, @@ -268,4 +295,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 c12a307e..0809621d 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 @@ -146,14 +153,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) @@ -163,41 +170,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>, @@ -206,14 +179,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>, @@ -221,14 +200,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>, @@ -241,7 +221,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..9a6b79ce 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. @@ -74,7 +76,7 @@ impl Runtime { ) { let name = format!("{}[{}:{}]", name_prefix, self.name, index); vm.populate_storage(self.storage.inner); - let result = match vm.execute::( + let mut result = match vm.execute::( name.clone(), self.address, self.caller, @@ -88,8 +90,39 @@ impl Runtime { return; } }; + let gas = if let Some(benchmark_analyzer::Benchmark::EVM_INTERPRETER_GROUP_NAME) = + test_group.as_deref() + { + if result.output.return_data.is_empty() { + Summary::invalid( + summary, + Some(mode), + name, + "EVM interpreter gas usage value not found", + ); + return; + } + result + .output + .return_data + .remove(0) + .unwrap_certain_as_ref() + .as_u64() + - EraVM::EVM_INTERPRETER_GAS_OVERHEAD + } 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 +151,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 +163,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 +183,77 @@ 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 evm_proxy_address = web3::types::Address::from_low_u64_be(0x10000); + + let mut calldata = Vec::with_capacity( + (era_compiler_common::BYTE_LENGTH_FIELD * 2) + self.calldata.inner.len(), + ); + calldata.extend([0u8; era_compiler_common::BYTE_LENGTH_FIELD - BYTE_LENGTH_ETH_ADDRESS]); + calldata.extend(benchmark_caller_address.as_bytes()); + 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(), + evm_proxy_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..f64ea55a 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,32 @@ 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 published EVM bytecodes + published_evm_bytecodes: HashMap>, + /// The storage state. + storage: HashMap, } impl EraVM { + /// The default address of the benchmark caller. + pub const DEFAULT_BENCHMARK_CALLER_ADDRESS: &'static str = + "eeaffc9ff130f15d470945fd04b9017779c95dbf"; + + /// The extra amount of gas consumed by every call to the EVM interpreter. + pub const EVM_INTERPRETER_GAS_OVERHEAD: u64 = 2500; + /// /// Creates and initializes a new EraVM instance. /// @@ -90,16 +104,22 @@ 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, + published_evm_bytecodes: HashMap::new(), }; 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 +143,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 +228,72 @@ 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.published_evm_bytecodes.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.deployed_contracts.insert(address, assembly); + for (hash, preimage) in snapshot.published_sha256_blobs.iter() { + if self.published_evm_bytecodes.contains_key(&hash) { + continue; + } + + self.published_evm_bytecodes.insert(*hash, preimage.clone()); + } + + self.storage = snapshot.storage.clone(); + + 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 +364,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 +495,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..27cf88e2 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/EvmInterpreterPreprocessed.yul"; + /// The `keccak256` system contract implementation path. const PATH_KECCAK256: &'static str = "era-contracts/system-contracts/contracts/precompiles/Keccak256.yul"; @@ -93,16 +101,23 @@ impl SystemContracts { const PATH_EVENT_WRITER: &'static str = "era-contracts/system-contracts/contracts/EventWriter.yul"; - /// The ETH token system contract implementation path. - const PATH_ETH_TOKEN: &'static str = - "era-contracts/system-contracts/contracts/L2EthToken.sol:L2EthToken"; + /// The code oracle system contract implementation path. + const PATH_CODE_ORACLE: &'static str = + "era-contracts/system-contracts/contracts/precompiles/CodeOracle.yul"; - /// The default account abstraction contract implementation path. - const PATH_DEFAULT_AA: &'static str = - "era-contracts/system-contracts/contracts/DefaultAccount.sol:DefaultAccount"; + /// The base token system contract implementation path. + const PATH_BASE_TOKEN: &'static str = + "era-contracts/system-contracts/contracts/L2BaseToken.sol:L2BaseToken"; + + /// The EVM gas manager system contract implementation path. + const PATH_EVM_GAS_MANAGER: &'static str = + "era-contracts/system-contracts/contracts/EvmGasManager.sol:EvmGasManager"; + + /// The EVM proxy temporary system contract implementation path. + const PATH_EVM_PROXY: &'static str = "tests/solidity/complex/interpreter/Proxy.sol:Proxy"; /// - /// Load or build the system contracts. + /// Loads or builds the system contracts. /// pub fn load_or_build( solc_version: semver::Version, @@ -152,13 +167,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, ), @@ -168,6 +183,10 @@ impl SystemContracts { ), Self::PATH_EVENT_WRITER, ), + ( + web3::types::Address::from_low_u64_be(0x8012), + Self::PATH_CODE_ORACLE, + ), ]; let solidity_system_contracts = vec![ @@ -220,36 +239,52 @@ impl SystemContracts { ), ( web3::types::Address::from_low_u64_be(zkevm_opcode_defs::ADDRESS_ETH_TOKEN.into()), - Self::PATH_ETH_TOKEN, + Self::PATH_BASE_TOKEN, + ), + ( + web3::types::Address::from_low_u64_be(0x8013), + Self::PATH_EVM_GAS_MANAGER, + ), + ( + web3::types::Address::from_low_u64_be(0x10000), + Self::PATH_EVM_PROXY, ), ]; let mut yul_file_paths = Vec::with_capacity(yul_system_contracts.len() + 1); - for (_, path) in yul_system_contracts.iter() { - let file_path = path.split(':').next().expect("Always valid"); - yul_file_paths.push(file_path.to_owned()); + for (_, path) in yul_system_contracts.into_iter() { + yul_file_paths.push(path.to_owned()); } - let yul_mode = YulMode::new(era_compiler_llvm_context::OptimizerSettings::cycles()).into(); + yul_file_paths.push(Self::PATH_EVM_INTERPRETER.to_owned()); + 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())?; let mut solidity_file_paths = Vec::with_capacity(solidity_system_contracts.len() + 1); - for (_, path) in solidity_system_contracts.iter() { - let file_path = path.split(':').next().expect("Always valid"); - solidity_file_paths.push(file_path.to_owned()); - } - for path in glob::glob("era-contracts/system-contracts/**/*.sol")?.filter_map(Result::ok) { - let path = path.to_string_lossy().to_string(); - if !solidity_file_paths.contains(&path) { - solidity_file_paths.push(path); + for pattern in [ + "era-contracts/system-contracts/**/*.sol", + "tests/solidity/complex/interpreter/*.sol", + ] { + for path in glob::glob(pattern)?.filter_map(Result::ok) { + let path = path.to_string_lossy().to_string(); + if !solidity_file_paths.contains(&path) { + 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( @@ -259,6 +294,13 @@ impl SystemContracts { debug_config, )?); + let default_aa = builds.remove(Self::PATH_DEFAULT_AA).ok_or_else(|| { + 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") + })?; + let mut system_contracts = Vec::with_capacity(solidity_system_contracts.len() + yul_system_contracts.len()); system_contracts.extend(solidity_system_contracts); @@ -266,16 +308,12 @@ 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") - })?; - println!( " {} building system contracts in {}.{:03}s", "Finished".bright_green().bold(), @@ -286,6 +324,7 @@ impl SystemContracts { Ok(Self { deployed_contracts, default_aa, + evm_interpreter, }) } @@ -295,7 +334,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 +354,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 +371,7 @@ impl SystemContracts { } /// - /// Compiles the system contracts with a compiler. + /// Compiles the system contracts. /// fn compile( compiler: C, @@ -333,18 +384,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 +409,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-system-contracts.json b/configs/solc-bin-system-contracts.json index 4f247eec..1451b735 100644 --- a/configs/solc-bin-system-contracts.json +++ b/configs/solc-bin-system-contracts.json @@ -1,6 +1,6 @@ { "binaries": { - "0.8.20": { + "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..42e9534d --- /dev/null +++ b/configs/solc-bin-upstream.json @@ -0,0 +1,484 @@ +{ + "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": 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.24" + }, + "0.8.25": { + "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.25" + } + }, + "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/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/era-contracts b/era-contracts index 4aa70061..b8098420 160000 --- a/era-contracts +++ b/era-contracts @@ -1 +1 @@ -Subproject commit 4aa7006153ad571643342dff22c16eaf4a70fdc1 +Subproject commit b80984205b4f453ed1f21d7b8d497f6f6988574e diff --git a/fuzzer/Cargo.toml b/fuzzer/Cargo.toml index 3f095ef8..fa4bdc4e 100644 --- a/fuzzer/Cargo.toml +++ b/fuzzer/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "compiler-tester-fuzz" -version = "0.0.0" +version = "1.5.0" +authors = [ + "Oleksandr Zarudnyi ", + "Anton Baliasnikov ", +] license = "MIT OR Apache-2.0" -publish = false edition = "2021" [package.metadata] @@ -23,12 +26,12 @@ doc = false bench = false [dependencies] +libfuzzer-sys = "0.4" anyhow = "1.0" semver = { version = "1.0", features = ["serde"] } -libfuzzer-sys = "0.4" -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" } +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 = "main" } era-compiler-solidity = { git = "https://github.com/matter-labs/era-compiler-solidity", branch = "main" } diff --git a/fuzzer/build.rs b/fuzzer/build.rs new file mode 100644 index 00000000..11799de2 --- /dev/null +++ b/fuzzer/build.rs @@ -0,0 +1,10 @@ +//! +//! The default build script for `fuzzer`. +//! + +/// +/// The default build script for `fuzzer`. +/// +/// Is required for `rust-link-lib` flags to work. +/// +fn main() {} diff --git a/fuzzer/fuzz_targets/common.rs b/fuzzer/fuzz_targets/common.rs index 1c1500fe..4c628065 100644 --- a/fuzzer/fuzz_targets/common.rs +++ b/fuzzer/fuzz_targets/common.rs @@ -3,8 +3,11 @@ use std::{ sync::Arc, }; -use compiler_tester::Workflow; -use compiler_tester::{Buildable, EthereumTest, Mode, SolidityCompiler, SolidityMode, Summary}; +use compiler_tester::{ + Buildable, EthereumTest, Mode, SolidityCompiler, SolidityMode, Summary, Workflow, +}; +use era_compiler_solidity::SolcPipeline; + pub use solidity_adapter::{ test::function_call::parser::{ lexical::token::{ @@ -24,8 +27,6 @@ pub use solidity_adapter::{ EnabledTest, FunctionCall, }; -use era_compiler_solidity::SolcPipeline; - /// /// Fuzzing case definition /// @@ -128,14 +129,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, }) @@ -160,6 +162,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 @@ -185,11 +189,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/fuzzer/fuzz_targets/demo.rs b/fuzzer/fuzz_targets/demo.rs index 691a448e..d458a768 100644 --- a/fuzzer/fuzz_targets/demo.rs +++ b/fuzzer/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/fuzzer/fuzz_targets/optimizer_bug.rs b/fuzzer/fuzz_targets/optimizer_bug.rs index 8e01dd16..ef012e57 100644 --- a/fuzzer/fuzz_targets/optimizer_bug.rs +++ b/fuzzer/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/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..f92c249d --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +profile = "default" +channel = "1.78.0" diff --git a/solidity b/solidity index 30d78788..cfff77cf 160000 --- a/solidity +++ b/solidity @@ -1 +1 @@ -Subproject commit 30d78788941b5f891c96fe8a1c7b924c32a1478c +Subproject commit cfff77cfb8908a545325ad73ef4cfa03173c4af0 diff --git a/solidity_adapter/Cargo.toml b/solidity_adapter/Cargo.toml index 9caaf456..0e89ff7c 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 ", @@ -24,7 +24,7 @@ colored = "2.0" serde = { version = "1.0", features = [ "derive" ] } serde_yaml = "0.9" semver = { version = "1.0", features = [ "serde" ] } -regex = "1.7" +regex = "1.9" md5 = "0.7" era-compiler-common = { git = "https://github.com/matter-labs/era-compiler-common", branch = "main" } 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..105f4cb6 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,7 +28,7 @@ pub enum State { /// The `gas` has been parsed so far. Variant, /// The `gas {variant}` has been parsed so far. - ColonOrCode, + CodeOrColon, /// The `gas {variant}` has been parsed so far. Colon, /// The `gas {variant}:` has been parsed so far. @@ -95,7 +95,7 @@ impl Parser { .. } => { self.builder.set_keyword(keyword); - self.state = State::ColonOrCode; + self.state = State::CodeOrColon; } Token { lexeme, location } => { return Err(SyntaxError::new( @@ -106,12 +106,13 @@ impl Parser { .into()); } }, - State::ColonOrCode => match parser::take_or_next(self.next.take(), stream.clone())? + State::CodeOrColon => match parser::take_or_next(self.next.take(), stream.clone())? { Token { lexeme: Lexeme::Keyword(Keyword::Code), - .. + location, } => { + self.builder.set_location(location); self.state = State::Colon; } Token { diff --git a/system-contracts-stable-build b/system-contracts-stable-build index 36c6c75e4e00007bd9407ddcc091c173a54d3565..9c108481e26ea952af225320a70bb37217adf53c 100644 GIT binary patch literal 819088 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%0A#5s?LtO`hx_t%_}B8~hp5y? zq3Zk8#}X9k5^gi}(;)*qL zV2uDNN3&sSZPe){te<6o7y7Nz(rKD=74WnHZxo4`$~ieCJJXu-g&0Hn3mwHV_znJS zPz(p19B;qUr^WzVAAst8v5A|mLw+6Ru0<6tKqJh{#IyE9Dk~G!*l#ymFaNP%)&@Hj zCh0-sQI0uY4`G=y^~?yqcs)Hy?yuQN4C_e0x33XQiRPdRl-Xi80$4I zLhr0e?qF@by4mUU#WhFB7M*t-60}5RBJarg7uJy1f1N^Cp(bQQ*tY~5uDf9)>Z*P= zmrGTaIF^y%$5P+V9VJMd5(0f*g#%A)DXRMN7ZnSf!b@A1)$NQ0eq3dP0U7YSH@B2nX!koTni(^A8sTg1|AKLYHvm;w&IAl;cK}(6i8a z(4Y+nWcjT{MnItVS9sS21TDx{Z>{PCB$?fby3H*L2qDlgw8Vt*?c=N8knu=BP^N~n zuMMW-Ey@^XB#hC#?_=&+x>6=t&R1$8KAP{WNDwJdm-Ycy!_G31F1>%ds9Aa%RM8dd=+1p zSyAc6#v_WWVu*}>h+)O^&uh?2<8%(Z zSMr@c&TqJO%@Mq}tzs4p3>Rxrwq?Us-boyyo()@q}OQWugB zEL<=oQfABAp-JQI5;*s~Z$IOf*-oory-796sEY{A*9RRXxp4|Q%qt1iBIjqHP_3gZ z8@+g6Z8LyIG6YRYgY`2;HwvWUr}N3Os{XF5SXI#|LV-WJ5M7xGF447rQ;0#9tz@&Z zX%@WPn}tqFsH8?WJJNJ7#A)P58Gosfktb2~R21g+do^s=ktdC7bu_tLQm!FvOV4J- zPF|-B8Jej7h`0uD;ogrq1K8?^QrD%haU;_{Qbj#!VQOg7I#A&1mno{_!Z#>a%`!Ra%NTnAW@*k!n29n|e!^z*UJL#t0vy z#>6%)ta;QcA@bm1*&704L&3i-}8*l1|Xt3)f8lB@zP(HXIoN!$jPfxp4BI>*WV z>`(>krI3RGVvKjT3&2#m3BZ=@3;z+^GaT-7t}8Cl$M+p$`KZa|NY~!hq^Pc*9&zc+ zk60F>h(Oa3aI-)b72LU+gIYADq*{XSeWuc+6OvM)Yg6oHUN7Fcn;bi5{AP0YGNV?J zgjjH)R?zIiejo1awO*qhg+uJXbxq%*SH($*6a$m8_Dy$I7|l1(dZF3s7OaPgT}REu z@G5Ra{V-#f>p=De%cZKi7vuH#>F6n3ig#5kU65&(E>u6y4F`*xiJYQ;BF>76m11pr zq>>?m4!vs&_m2;@>YV|Q=>IUrWPjDDH*9l_zJ$)K_p@+@#fnCUebdkFbIF<#L_}a4 ztg$tKOwh@~de%s+iGk)6(V3eLt12T+=~lk!-^$t4wmFQUKtyQU)aQ<+7Ri2r<40Gw zDN@I??IPIJx2KtUEDP*1PigQZ-4|nJm=)9RT#BPgq=4eDjccn{NtA51`^dK}N52L` zm5j0y;E5Wmzf*;6{L=V#-ltpU+C-CxM8Hi!O~ zh}RjZC#P!YS`2Nq};Dy&$vL&HY3)djt( z5Y|*q7ZO!MTt&lN=V0qCtgOsSRILHMM382a6fwKZy*#R)659cF*sKM~A||dom^WQg z1vO+SLAJGM0`Z5sc7^D!Gi+27dTi?I^*XdwG$>&xmn%vS(z8p2Bjou@G3K$4Q|}hfq*Q4YPuf~qwyEvMazrO(Y+B0> zM|7y$$fTW(wSJno=U}_e3BPQ;1-h5I^F269A$jD=Rtbx&?r^P5@AK1pu5%>r$UX4~HYGQQ-|=6yQw+sa0dCzK`wDwNQCw!xcKC~#{zp($H*|_2!BD_hhsaK3^gYwf|>rG2( zvDDf5jB{P5_gZgS)(iIvqK_rrnGrx3!Qc9Ihr5=c7WPOeLTcB7GAkbvw+KRp>({c* z5`5}b#%b!1AyOHv>Q2T%5s#rddj@+3W2#esHcmV(MyoMgu!5gr=?U#?OlI^6j7u=* z6fR7RL0N@sAt2rC;JS4>Mt&5kcpU)lL7bk#7M^~lg{Y?o!|NyJTj5M-SIM7)Jd*y$ zTZszlIM7Iw5|LshD)eZ0nG_2R6A2@&;3W)q%z2=!sp2XfP}HNGE#AYj)S&*T(j{a< zgalU%;ieQLX(}y_fq)K^P2x+hE@865W3A2$iN3C;pB_x6`-*NFw_$04w0}Q%xC~ep{eq@jxF{O z4H+&tHdr9Fw-s7dTP@14Wh&$}7u^dJf~Mhe}G{B-o*@y6DCc<)s5y>HlCD>q+|kdr4a)6bbZJ8vORGxVDO8qD)ncz=)I&78!*Yh zv(P6_2hg|?JU`skY;sXHkr6t&7+F65LRwNso~3Bm@Nw3>pC0%wj)^)%Th`Hv-7Ome z4TAY=Cs>_c=uQCCWfW-?sw}#7A+OfD7b%3_O9iS+t!gT!(}^mzZVo`D8HtPkJ_9f0XX45dbizIY^k<*thI|?*b7OGJOWir0%f%>I~m52So8fvSc zNodHTZJj`|YP*x>Md>eYtWnyJZq{yms>7OTwT;3G_KU{ z{g%pO;(?Z>H_^s2&<#_y?M%M#Z>WvDE^Qyv)IBr z1Q#OJf{DtU*aD%<;$k~OyIXAKn9FvAXE^7zgt{RihjY{86%Dr$jRQo z{PHn-xz5V_W^)=3EM;ODt*{JZnrh)8dL6so#HKqe!M$`djzCe?dBM4t&{<|$wbrs8mviUR@yH5VT2}N*t#-i$S<#_ z*Y2T6aA*BilkOoDGA4p zOU!vG=Jo3~z@C+%xBbh*l(I?|*xG7%yzAH233o%MZ10!27l2|MY~WNp2(_)EG7JF0m} zTH&eF7^Z4#OHR1mfQ{7z|zl8<5-N$}Su126^-a)XSl8&6+$9q@$5%232k0?WhrgI=6ZG*phL~nXVZdEAG^n$PiMO1Anlj!IS%ltvZ?7|(xNh(aA zq;56bV(DULgRMx7sG>5xe^CY~n9D$=^8@!DoRIE z%*nz|CStyaOU7m1TZ%rET!d0(F9Zw9EVP$9jw0f72{w792{t1$ShdJ`Cdecx))zC@zw_<9F0D|myC{pFTv|-gLX_gmBmXsnnSvZVvN&Q?KD`oW{9V)uv zuGYShN3}k~sG3~|)MLYfss(j_QD_BWThTS$TIrhJEh?o8DL@Pm?03C>0T)YU%RFGJ zRMbaDOr;cWMh(uG+=51%Ycn@{_AVJCt4#Aar0D8em&7^4xF&RNnNyJyHuS0+A+t)j zK3gLY9wmJ-tU7GE(iC@-p(|?LT9b4-NHZ&qzLpqz7$Po48wjHHwyVPv^QYdy(@7PI zqo5V^_^d}D*T-_;%?MomqqQ>7Ud(zVks?rkuwpgv1;^iShdJ}uu)2?GaU8P5V2GMu zdfrsD5{~y#mD`C=Gf1+*N)u-Ez{PIx-^yyg@woyUUquyys;0)E>v9O@&P=)&2w{`2 zQNxM`-+Pu%%kWwxCt8;t4`cMw_~b<#TKu5DG@Y>BN(hKlO~s&>>cLb|EM!s~zN1QJ6hPNxlcN47e5kab6kz=L9Ht-2< zac%LB9>=Zl1nNMga#f%8-s&^=EAsU|UG(?cHR(B`Qgsx_bYjg{%Kx+>Owg?I?iso^ zinr=MW|WPDnWj0a4iOfLWopTFiKsTL^nK@zhFptUDz9yK-D`3>p%O(TT(1G%EWov> zwZh<1uzk(d3z{GXxhAY;`JO<#eLp=F?46|s4SS_Dx(rQo<=i)fx;PW5N_IOeB^Xy` zPQ$&d@B^D(Ve5@jO9mH&5GJx)%>#~dQib&*ID6M#Hrv4>V-<&@|Eo+uS&*he*N)#P z+nnB)96K7To_C=&QTNby?L3YTrf<%C%1!LL_TA18Ca3)}54?JMqg~mEC1bM~Nq`ez zvs^+H{cDahEN{t<;;l06AJ{M^HuGHAkPFRSW3|h>s@jK8>uyyR91RD#k?RgCUwPvT zootdcw{&t+^_QA(LK+l-zz$I3k!W`&cE|?iG(IglSi-n{3b>9CwZ_-t29JUf zN9N7Le>Hh?-N}JjL7+h=v`U5rbc)xfYA4({RLh~-0s(X~VJKwGPdCz=L z6Mn=n%udYlSS1lYW6Lrw)&x+ao&-N9hV%g?*7d|$id*x#rNSB62HqPCqE+mCC z&f;Uw3Cn(Z*K&%Xr}`|}7;P3>P+y5{vH?nX5<{)wiYV@x30an1$2p&I-9-3vJRbL!e8J24GtGjWX!*J14)ZFVeD?XbUTOTY%ppH=PZsTIQX@%}sO-YGalHk^Z zZLVr+isWeEoUd7|Q`Ke@*8{U#56Nm`*<_f@bO}SYz7lIAKhF(hd)_sUr!8?Oi>QiTsO6khZXIxb^|D~ThR@fszQJ~zc zlQN8nC_%p^pK7jUkg8jm855IzWT9C?nT75wf=yIsh2^%KcK*!gj?uoL+~^ZLTf`i; zT~eYE)y$$eLx+c$Gm|C(`!)=Yl}ym1tXxtYwOffuMtcdv9rGqmjg-@R zAho6k_Qq>poP(e+n>} z5)WQ19OmaZhw*da;WD`#K=hf<9fKQ|3lk#N?D9oK9O*?r!uiXG#H;6y!6R~-veJ7o zF&EMhSJjgh6OB5A+r%#me_H(V4ad;)f%w&)J0`?`I?gLGDWnL!3&_d1!?1srKamTTvZQA9D^pBa8N0T=co1Dx0}9LRMqK#1PDOvxW(|zW&^6FCaIfrC5$^fm zNvNQ|q?4Tve`~`E4x?WOfE($6OKsI4F)I>r`P&C%ZRq*@0iz{h>bGZUI;QSmYqSOfo+#yEUUiUwZp zTJv+Qrg|uu$w`x|dRM)ptLa*N^5vae2baM4>QKgOLsXTkXrT_E80qklO&@vRFDEMSLQgE?f;~$(|F7XIT z%Skk{4#PS#j_?ebK}G>&*zr!qqpg=hGn++Ou0Hi7m6X`(Ftd`IY?z&+@zPX4tmyeH z@#Qkyd;Y`BFbCK0h7BL*FthnVpB`*P+pRTev#Egwoqx-L$L0|Op$3;xq-nKtrn#*r zEbBHI*ZfXbUdrPj-=&IGx-t1k_EeYFoBV2>`R6s{U~IFxc4=SYY-2VJqpRhHMK5sc z-owmr6aCuR%ERY7%Wqc`y$Gl~n5PLuTU-c3NLO&ryoMLSJoDgAVTsHPZ&E-%iMWq!hJlqiiW8N7a*3E(N zEoa!Qo{xH%ne9k3_gU~Uh%ZlKsZWjp{IZ?O(p;!Rt3o&p?Q%?6g7jG&$e8+ff1BqY zz5VWgKIYNoUJL8GRe>nV3DsCS3R+T0a>mAdp;9zn-->Zy7Z}C(Q17x-b(mS1@(@X>&E1p-)O&whqq#7t zt3J^A#fIKzI?PNNkef_2?e79IML&$t1Pog$%(VOEmBQN9vRo;wn%;MFm(T;lG*d0; zaV|-6`%tVU6(pF*YxGG4_6EixiCv_YJFQ!+mj-hdujk2dR=ZlT-C=NfSC@k)&N-m^ zSs=Q%oK6g$DkBmsTHfT&3<#k}CH`^g#ICkmWM%v^; zX=$mKb`7Srq6>!5&pL4K!vrbb$2rbSX3}EjJ90j8oLMC&kQMIVb(|R$ z^s{Isb%1I)90oRaS{b+#TG=yltHKBs8&JCCIJ4S2F{5zD(7bS`Zb=DlT{zCHRu&$j ziOL!o;MiOSDpl7FTCy@_3MEe*XSQxAWFp7dbtJ`%ju;C|P?ggKPaS7glw5>DQ7;6` z4q0%oR-=g6U&2jZX*h86ab~4i5ucvBix&;}$`U`Dz%ZW+V~rp&HASFUQNKfxD(5X& zSawQm@gi29_e#HWtTT*(dx5r%v8uyrPsF%!`3h4zUW96&@i?=p0_tp0DP5@LNY>f! zdi}(4W~=(r%oTh_4bGUHsmn-yrBZQ1n=!I3d&+70Q24bjiF1Z=5#5<8Nd@AKDs&K9 zxKUL%f@YQQoAwH?M#<+s&I~t!qJ(VQQ%tVLOLAM%MsXDX0L6d)Q=O}!FOL3W&1p*L zC5{L_rJ|K^Jh96aE&R3ZOP3;qH#T?>n=c1Se<{bAN!^wP9T{^!Agq{<^d5G(SJefG zR87SkI5)x6YAObUwd7)O6%br(vGKC{86aChRj8SWSqEFJ6%Avt!EXX#Bdt~BdLiu8 zD;ajGEeW`ZfAlzRg(px4DwP$E`fL{cGxwWS$~4WBv`gKUvdvCYU>#UP2%K28q2}E) zbZw|~lN(bhI-QM(lF;N9GbkZhEV3V)i>Gfq7>gPWi;Hy)W8{~@4%=|8>s}L~1*Ika z~7St2_)iGT>Llr^21t?(aOaXH0CS z31LGllZ-T6&$|zycJnwhxY;aCDxDN)1P7KYr9y~Jl4k3zHA zUcb_=Wv#&03^3ovx`q!t&J4I<`L#PIS-Acp<304b-U#Q-jR~Hm7rdDnbwhI9v)KL4 z&v$6(zzTr5tTRgA`^0=V$z*tc;s*Fw-5B#BQUVSfW4b#wM zuC1{N&q%i!YSXf_odRV8Axo#xAppJevEhd`#~AL`Rk`ucax#&P^C7-2B)D{&#mAoQ z>rjH4^?@@h=W;GXUmF@zpLO;1i??=)fEu8ub!tHQia)F4%$UWm2SI+oac1yqC+o7V zT3F{=SBJC4sauaTE2=HF=9_*pgYK;4u>RUuMr^y!dN7pOVkbjMpA0WIW46W$x*?vc@ z>!-6yvp1FYD1HBIjJ*)_A8n~n@g|>~vBz9Cbp4#Ti;O*eR-la;@Ri1XW}H10m^IV6 z3I(3Uuf8v13tUSVTj+k`pWJx{5vD?o({~@Qr)Zw}cY z^zz}|?|*#rKf2S)q*j?K&GOu%{M+BZM0_vo=2ws3e)qj4esVB8?DC*6c`StxH9u9K zDJ*RIE_teW>8V9BDx^DG6)c#S6%^(ft5hghWqkDduFVfrDz7zCcQ&tEiJ+d!wm*L& zt&2h8^UaRZ8k96z9{GITN1l>V!4Ek5UP>crqXjWH)GGqfz>9Z(_(4d(^9L6#TvbsVTg`dH?Ydt#y*K}{0#!prtFjX_h|?*>g2 zahfviQ@FUIiGt8nmUj0f$!_RqGo|lqqqaL(=VEvJ3XFJZv$EX*^Ho<7qq}R1|kvo*ee);XI%B z`~9@vu}tNB(8aXl;>3T?0tjQeSt&%=1worWWI?GBvpzCWJk!+w94-tlQ; z>y>~zo{aM_?T%;m&e;>eb+!`}#1mm}KA}VVdEh`1=TT+fo=LO)=`=DV_eYg~EO7R=Dkp|%IG^WX81t>lis3NMB7mq44hQo1xJ`TG%DXiQWcSj_?+woybtI*aec)to6LE%`rF-%9kAiCeN z_sQR?O2Jl+-Fc55h8V}njq)h;Wvtv7_QQM_4rg|Dc!iZ46JM`C(Bn`T`(UIKcpT0< zSjafv-+>qW7^L<5J3YWQ{yoIM#|N6@`V{{%-%*4y%+qu{P1Ei~ZRf*bI1)rqmQb&9 z@yiK=I6x)}BJDoR$0OHh6J17@r}KQ;9pDPKaz2b)ALjj@53KGEnAT~YC(LOY57X&{ z4Mg=-67>Bz9!`{*u|4=p;nNvQ-JMTtgW65gVIu#A)xyWq8U3B7-Ece~C;o57ruH;R z4MobD8>A1%huQ^nbmonDocYfLeOT50bcTr|?}Htvo%C{|5vl?(POyKT=6S@-=s%*E z562_Gz_v$Ykn(5z*au8aBnzWie{hNi?Ch)TMtu&@zCWS==iPW7Q0hGt%`C2>;Tcpq z!&~&|K#vQ)IO16VX-&F-X%cPW#(~49qHV`eK0;^LOhuHo$N4aVO!I+0>`l;M+vzZK z6U7ZH*w^x39wg4AOmaN!5c+^B9+3vZo)2()nBfV+K?Kxv*qveIema~+tV8@dAJ7Pa zB;pE9-Mc0pADnK!2IX{rfH8|V1pR-v>VZ* z5l+q{B+O8I;`%I2Iu7Uozz*bLc}HyAvk@kJGj^y!{LMu!G_~7AJHNpco=XL z7{*~Yp+IySr*gzn&f|VB)Y2c!8)vi{#+(n}<^(%Y#b~&K*@Q1*mF0wjqMFD33@%g4 zG#sg7H_TWB-80soPRE_(G96H!GeE*qSPrNik0VMHb$cI<4}Q6lhfClD4fr^AkZVB2VQVPZ98?iz`^-6R>z@biqJ0&C3r9Fply z?-)L09~KJz{s4$ccnhllWBd^y!;cIyFg?`g#A(}L=>*uE0T`4BKd1NtF9N1O=Mm6F zWzcWJ8%V@Bh`5k306$}6f?L;MP36JTNpNmpml~~uYPrEjGVNbh;#D%JY-Wl38E<_; z6KN3-bEYw=HX+6V28%FhE!qtu_6MmvDmw`l5cUz>hRT3@OZmFmPzma;kP@Urzc7*k zXrwy)#*Dh-?6`-n0|`iT;O+>g;RkgiB^(NG;pvcM^syoUAc6LYB|Af3QB z$~zALP?rh-TD=Txhkl^rfLlIbhF}xHuewQ}#~Ie3y5o3+$s=|F4(;hZq=Di%b2%b7 zgulo8Mtjxdiyr~cguMtFje<5DLgCgSl_fz(VI#7F&Kfb@q4YnB8ae7ncTx`mVmk$i$N1E_isyqmr0Xun;y+W}O<)wOj6G1f< zgHs30d?JW~KSTj&&b;G|Gr9*B0Z2GRv32)w@ZL>2?428j_!mN40H=_cAI9%iR_uV- z4>Skm&QelfmuMsHM&A}T0R~LbBbPlrswM@YupYMx-8-ZR{vl4}G~%s*{u4HN9&kh4 z#M00$M0FgwO?Z3KAi|yi9H@HqsHm7*BJJ@bIQXS-58sJ*CYXUMJIn(RN0E>cMk46J z+MtDy>9iBGAPA(ubrERc>Hu_%buNlpFfSQ4I4fzlnkE=QG;;#kPa5e_p(1g`k%Boz zn;@G4A|7J&rvt61J=`@Ij2F{rn1CkD$(uk*EaXRr#$F{VD_PL&XlLxN8l_^U1AM0mGHbCC(b*BPH7n`TivmF_sq%4&z$go$<} zC*~t##RPDJ#cm$Az+8qs)QlY+aKD5)_!z%nh8!A2q|_azBv$7j0hN8CJMmf1_o;i z39)oiKMJwrop?7bNy`*y8TI#`5>2J*inTlJefhItrh!dqE zh#|zm0V4jx0h=d`!Oak!5RWSa!=M=?5VeADDclMdj8G&ZP;Dae1^3Wh5MDNngb06V zfc}Afq?B-)1PQ`Q+E>t^NDdDL)X-**WNhg}L`m|N*e=nm5D55&J|KpugqRf*0dt5; zAP@Z0aGNm?WD_{iLygRdz-Sj`!fst|r$miVW0>ZQMS%PW0m6E$jNk?E)qo#|j6YQ4 z051uNsH?&ZI1ESWGv3R>MZ#;Y6u2H4PS|J&>?9y0PtO1|kT`&gU zi8Ujeq66z(iQIH)uYv<9zYqk&0Q?;wMSZf5|19x#aN_#f4$5?)9OMs@m3W;X zCqgPl1<`P|2GX@~44p@tmDWe+6_N@C0Gtdh=BnU~2M{d!kN04tN!2hxz(FuAMOP4o z1u7AQdO|z{IE{TVT?~oj1!jcy>BkTQqq$lG0S28!Jq5#x`;npYcQh$Q$M<0Wba)4u z*p@QR#2Vro|K+ZQvP64$;X?@BKCbLS$Ey=(gBifOay@7VLRUUeNdh1rDUn2yR)Y|v zr#0#TMZjhFgc<@|XtEq`MisW!*=zsu0KO|qz|rA2G)~5e!CxUD3Wg}nU%(f*AwwZ# zf*Km7mr`0F1&AbKEIMUhE{^Ku6fj9bNti>%fw3NGS0Nt;sB{}lChegNGhIPWxN_*Q zJO!e}5GD)~v?MYJm9>?`!J7!X*)DB(e6HGDV`+?vR+*0>uvU7Qeqjx;p9leJH0D%dS-BU+Qy79KYNS7$7rf zCWe8%@?KahDgaJUG%V6iUvqAAv!8;YjbKqV2HY0{$5|nCIt+fGODJwP$_AB+oondG z7>}-_UF36s1(_Qx!lB|0mE}R{aMv(YX(0>&P9Z-G6=lTZ;@>nT#>Y50IEh+4nyDa^ zF^dvYB$fov0(H?=UZ@bxkXXS1(VRx4AC%aRARd(}N-`V^~dm55r(!GNI}%l`xjJl!%fOz>$C%yAx6a+sGSW zqe|>epvQl?2Y!L<%G0CN*oQnC1BtA@QQQ*85-kE~xJz8IES6eu*Lq`&xe8milOAWO zP>51x_f5W8GjdWRWC50e)c`X{&Xfe|f^q|VXgZ7lU!-GT7Ov1$2~Y+%iQY)3psPCr z*C}S44(*In8F;@pSeQ_!$POLsQ;mU98b%!9B%^2zd`-YODMBHVJc<~Z*+sC-rb#2Q zA7xj_=`b!Oz5|SK^{5b(c~HyPtg1J29Yig%1|({UYid*`7PA4H2@DW6VS~&YF5#LH zIYcm%AlaZx$V%eF8JjUvd$om?#RG5>(6LNB`8bE%PSNl$VL2X!|Cn78A&{<6R*6LV znZQLeEyS-7h&dAXzyxTO(^YPg|7q?N2vZWoRfnueH6UdR87N`I47`{*gFhOj@h`C; z*NWy)5NaYq*1Rl*nOy@_0YiAAKtODXIaum$uuE=;5t%ZPgb5Oe&r-0mqXJf@``{yx zqFf(M64TKkPrp#QvO@_Jo3A4DA?_qNf;J6e<-WloLUU!;ovTeQNW)s$EECLtBMc*I zAhcABpg|+9o(CiSz|YVpCLu`S5g04dmuaK%OcF5WfjD!VP_@Jx4S6q)vO~V6SwW?uawKZTOA1MBAQ;3+;gwAH!6xD% zaGh}s$vE|lfQfM~S^>@rwuu|Xpi@k&aH4N%1b3_HE7>V9ppFO71GncpE;^CC0y%VUip{5R}lP6PX6ym0h6an#yIy z8hu2em8Jusl{mpqk+q;x#DW?DAQ`z$oP{$?A13r>nkb_fL=j|LRdmckQk-YVCZ0YO>ZzFZ>Cxks;(drHJ3JOE(@r^@xUm&4U9k= zNGF2Ms2BXj(SZ-R7$6@iQJ3E{nPv@`9 z6@mqiVC~D7kNcOH3I6uHEJzk2oc;q=uj;mG0Td4BvFoj?BL|LZk<@#5X%pWZP( z`Q|&mhWgk4IQ-Ygw{IW6|MrpXW^Z2qO>_O%6#Dk{o3CDf@$m5Y{ktFkjWwS3FH6np zLUKV$_r>h;4eVU+np@KR1F~8@%fi__A+D@E*Q$I<{=k{fwh&ftQ`Q!+`5EB3-jXjg zTrV};R7&5#xL%h&seQea`hC5WJ{*3%l+HE2Ug~sHDV-8@y)J!k{CX+QA-$>CZQLJj zWB+g)|A*TIFsvlNivC8+ay=y--O|x0nWgLq_gI!Z=7wfAN!U&kKe5B=S)c12M^CUS zq3o8>_5zs)Yros0EV}&xJhMX_zQF0Ks26XxTTt4!zUxtL*o|WpcMsajT(@R0Q&`JF zb-T+L!u(LgJlKl)m|8eVEWO}H*`x9J?je3Z{E={$^GQpqfEZ+{li2#IlaR*g%_2N@ zSy+j-jZjgx46FCT2-ufkAmUuW+Np1Jh`%}9U9XTXx%DcYj#=9BR zZ0yu$U|*YvV$I1TSsRXXRjfG)hhleaKKW1o$(P(D|Gr)6g&H=IH}Y7qUlLrfWsEbi zop7#ZGrg7#v4JPVF1p|R%Zqn!9$vrv>e2VN{O#ZU{XhKbxBvLh5C8D1-#q;0@BjI? zzxlOJ-oQriXq}NX@=b$^O~@FKcC57))f|uZzD8oFzDS3&wb*ZOK=3{`0wJ;yY0pY| zt4(KX1uQ6Q%$IA{yWUl-McUNT7hk_L`t(L36EAc=MelJ>wCE@Mc{|pHu4yauMOyS; z(4v;?3aln(hlsoyWIum0n39(mgfwm zC)+CP__2j#?fEBC0fXq!A#qE_c)7*}G`2Psk4&wszHalS+^uc3bbm`D;6iNWqwD=v zzWWhO0dA4!3c}PMC$(7Z`+2r(V(n$LKGBrT+1CT44z7CADMFwbaqu73nB4^8ngV|L zI}5Si)=!-}u;QDJ)o&KZobM>!avyaLTQCKbl;W+mfAgrNt#m<1`6}Jov-Jv+{Hk9L zs-kcejq;)mCP7=LH7add!%$R=<*!wn)5bUL`cK_H-wx0&zzYE-liQEcynXxb&5s;< z8MKPqS*(0(Nf`ZmogBh609_KmNtmvp;{5>6+u#k$`~(|#kOtWyKGR$=Uz zr6})x(uMkzJa4&>#+KkznFu%?b?$joWwe!mo!Q@YqtcuDzP;D9+DcmVTI46T!Ri$& zpAc&v0H*p=N~P{&oFsDDmE94PkLpkug9F|gx=xM%E!esa$`@yu-tViFVHVduUB{YZ z@IPY5Y{k2oiZrxQ>ym7eiZu6?GN_Cgw#G#3!Oo|3FfmXtOm<@B=<^!_=fp_(d!X0BBhqh$81%hG7DkJX%r~29Q~Me(IF&WB^|VN@T*l zmu4%n7dsuC08L)OU9kXe>mVd~GW%|de`6sWk=v-ADXU>2Z4?ywVnrTsW|(cm3NLIZ zRnUN`Obeo+P?T8VKx7S3tZ?my+OoJoMFhI^v;!vHlRLFS;%C6N5oJmjZ}}-Gy6`&v za|J4G*JK*how%0EsNIS2Z`2Iy{h_YC>3WT5bnJ*#qw11XYBxp?wwMoju9H-?+re`? zOjPn!n1Tj4hH;I)(u|8lij*QTEY!t9$@D%_O7~Ra7wGGyV;JA2Nus6S%yk&@O>!r1NFl;Eu|!TwZQtNq$nHVG)p_y!Rz z0vOM~hZU8^EexCU_5aF)^z}2GG<}#tNPV z5Lx={t*nNZ-2god4$@W!Y?bJusDxUk6Kc+m4v#mTShX(!n`tvu$7KuO7HdQi)08k2 zFK?zIcn{S#eyV!a*cMmYmeA5e3j0Gf6nrS$?HT_G+MgaXtctXJKg@1uuU^ERU+p0q9 z;G*iPQ0eOM0)70Bn|G+uC66-piEQzm0pqN<0^D@V`8IZBL)!`Q>r3LG)!8A?Ph1H%Z29wZ}V2^bkXvruy>)I6-ftX z78DJePCMINe*6|2mt_m4=j?^l1wP)L5z>;TMik;}j@yIyX56kjaI6=(4h}M=8KELJ zhRb%agNsB~qKH3Bidx0WjQetwVO)p-Y&(`UrYj1Hcg1g2MX2jgY2#FXZiJO#`()=C zYffE@!g^fg?Ya}~-dj8HaPXPYer0NSNqYbn;+Sv4_RA=S?x!v6ZZe`SzscT<<#6A|i2 z_d_R4dkc59mBYgA+Uv+sbgNaQFDcCX*OwuV%7yF^1#nHbj8T~d@=glzelOCH>5 zHv}GH!CN2cf-xzTYk^4_dkhh*SWL+rs3Kx=TZ3r#g*455rK;?H;6#?srH#N-yDmiLyusocgonk6-_CmH_0XhAxG(4X z!^>CyQc@Jw?B;6Pu2}j%$w~Gl9#P$j|5O)Wg!Lm|x8g}LsS&Q@unicbowBkrvIyeq z{U;LAq!^u}lt?jt3y-c&k3xyBf(HeyThMt^>( zkTrYjxL%h}HZ#oxJBIKjVQne5480@b=zV}n82A}=3LAimItEQs&*)Aa)f-w1;IlHi z11tKth^6=5&(I8w3UpGat!s^*L2O2Gjs9dwjq0S-8T%za=2)3@l&QEbt6y@-k+C*k zX9-bq@Y#aZrJEE|&=fxd_EmZ2yQ4CnTfmE{X7!Qh*2fYkvIiw5caqD!r_xu+=|GE!+s#u^#=T}6MyWrh=PZkA(;>#q5*nQoEHYwSJ7oaL zbxq8Q_N4kiO?o_I z6BL!Gt2}j%dZ)~Of)o=9TQO^IB6@1R;Z-W})N0F_4_zUF+gKX4ptPc-)CIvUjXEJK zjk>J6v?=3mJc&|wW)gUGpRG*keChhg~If8&vgPtuCk0P9fKX8FAATvWGVgLZ1_C5eUS zhLP%2Z94iLr*}kRU`U|A6FEF`C#W54>qFZX$(CLdJ@h()*^PnkVyp9Nx z@Tj=gK8_u-t`sg&lW3E{w!@DMLsdP`tdatHaxCJ<0BkZjfz`E27$jIib4VY_oQ z*9&sgTIjZ<8AU{D&1R>1r3(p8+g{+sJ8`*k{W{blC^Yz^ep6Oh9xT!705#i0{PBdD z7=P7{Xl%H6YV!HIol$;avU;YKsK(9GlJwq+r@WIiJBiUXTW?4`5cwr;stD3va}zv? z216}_{knOD{q~+y=j@Lgj*n4+(H^=RUA0l4Q!Y>s%2lwdW`jw#1Flw2hwP$tkN#WL z!cPN7)gSg-;+9TH!U~FLDKBPo+bd8{bkUjY2Tjg)RW)PKpu{bv32NvI+^{r5uu?q= z7Fz$jkCioU{dF+aSgJSDRi@H4aT#JvPZLh-Qoy!qL*+NxCX&?|@QD%j z*4R1+jG~aY%^2$Z=+bzke^q0nQE63~XAR9-8Wy zn)+H|{3(Yaw)`kyLhV|dQOo;w5r>b|D4X$?Fj5hVAh@AAvY^can=WvFnfp-(DZjQ& zc$=}CRdj8^nv;xHHl^2Ftb!(+N>|evL08&?cw>l4IC6Pse)qST1ZmSHikV9guUByK zy;hG=sYNmkS4)ymsf^}+x3Sq+%ya6H5tdM02({#<%5qjI*DdnF2>59HftHcXIV=DuXLu!cX;otWyG*(mtlfmwpmN=1hNMB`36xba>kX# zI=ZZ-LLXi>MJbSaTh`LzM9)&Ht658NWt@`dYzsx$GCPR70uaKy7{nDkjM21x?7TX; z&yb{?b2Yr3$Bd-^gQp$Ek3^-y4?c%uF}WSz*Dz1^9(ohVMu5pZNjuY`r4kOTai$h# zM5c1_5%DHZVeVUOe6B>LdY4*N^0od^apftMuB)sFG&2P&y{I1*H%?I>3`j*VSFiio zQ^R;Kyy>p#$s2Hoq z7AHBCCRIBc(5W%}D+XDj{pe20xMV5mMQ?s3OIfmY?wT5E>;=~D=u?lo2aQL)X|YN^ zrX-7^Hv-shSX0Lo6t{ObhcD3PB~?$Da}GkSA=u#486r*{SL-E)M14D0`mn{MT~$Ai zS75f7?z$N+`Re-RC*0}{W0fW%oD-q~oL6cM+Vn>uNmIA$|u*%{aW6SVOoBzqu@_{VhVg#zlo~T48&bE(QeC z9bZb4qgBCuA1&UU@a5z{`42jtQVz}1;$q!y$gooCHDsm!*^ZyOC!YRaMz>TaF|Vnj z-{%rzhXUh;`>jGm4UutJj=btF9uX^%wA|hT#RBb|8s|}iKJe4%!h-GJ8@0T@=uc&gEax`^NF^9sVb^!stES%rmIoY|OB-Fs)?bRj=*Nq$fw3RhU= zdS!A|Op-d@8dRv1TJ>6NNpfSNjY>sa<9E98Zw7o)x)$Jum_!Qw6OqX5!?pmg0Jf5LO z8H^wgM@^2AOm8wIl^&GxdSYabWyB#yqu~linw-WU+CX z_!v;S00>T4Hi;HB5Kf>L1>yrYOfA)I;YCSAHx`k+_VR6}B5M!acFN%kPlBJ<=tB)t?pG#1ktuM42btvNRTvZZ?(NZg1sGg-wW_8w0>I0Q3v7rkt4_!#jWldyhICWBTNd+%jhFGL{GW+MV&ejlc@Qzi_$<^wmU#QxVf&)T7NkgL zTi_b5BG(2kg|&7ov#k>z_SVqp-S5A9`-S=Q;`)c^mKZ2Cq57hFsR0sm1zVk^>MYXz zc&7vl@s7ABdBo$zV2rJjpv@WWrJ9Y>xu`5> zoRnJDA)gT_EQl>fp4ol*U8_`egun9<%f;T-BMSZZgLFh)^ysu(40hrt_8$6sfv7#ipx=?D0NPDg0E&^Sy8j=K?1MTOnSf$J~yit&;96nEUps@i1-%j6>+ zT2bDPe4@ie%SDBmb>oAAX@~UmTXlv%Y?dxW&8G69vzKZv(`c)jB|!@ouGG*jPwM-t z8tyxv03vR%yA%R?wl-b)_6^fjWDsH`Z&RFuO7~bJ66}r}bA))8jQP*#e1cGw63Dxu zrdIl{j<8o_5VA=|I@_^nJ=aZcm`xYlAH$~8h>IOjQdVo{aK)QjJAh@iF^q^cmoj+- z(rgVjOXRLgFc2op)2J!=iKM2}E|Fp5Ox5h2j~=NGz(xhqqW!W>tmsZIEmRF$uN&<& zs~Hbv;gUhq7bctWCmmH~F$Uw{Y9GMy)gK?$N2W~F1w&Iqi&Ram%j!Wt!{xn1W$5A^ zjP9gR%WAf@_@gs12kyLFkf>o%1K{N=rH_P+_c-j^JoZIxXWr6@&Aj5$meDn+&$ENQ$E5!?sTQg37x%Yzto;-7m8x zH9*TcR(uHtW_IR+;)U?CRKZ15O3E1cP(kewgVA`T7$%IS7;*`4~bs z=$?;4f-XJvMLkixJ`Ef8ULTtSG2|9)jARVss?*4z%9?fTQO)HYb*8bGtimhQLtzDh zYMH)^F8r8tpUHweqc*nmsu5moW|czEO+Y0R&$pMICBUR^Ttq2#vAj96M!ybvu&_4y&x-?^=%uJ+%~Q^S+~^hcNSIl3 z-(S8pD5m5zaM(%L0o}{wbsD$S#9mS}p{_ozG-_q~HmjsY_1L;(t54fILikVz=#07G zs0~5+LtP6oRi6sSwFY~}UR9{)rg2Vd$SW7`6jUq4^ddBxa*OSOk~LO1#sDZEU5vR=rc@dKR{29~M)=rH&UaOAFGgrfQ2U+^V|ei%(6%xT$!x?h02i>g|Qf zr&||FoYP{1P_~L&b*5-Vgz>l0-+*G3X=P=s4mp?|^Qx+H&Y*9}8s1xKZBPPbF)7Pg7HMgSg?>t4QOHoy&w2mwJWZ6JUyiAEK$Ym}+E&d5WiQS{O( zQ_YlB%er>ksn;|ly|ijogyP04QJE_shaB0o0jn3&d#zd;A|S~!EwM2XdU;wKKN6i`TPg(uo) zY<*I5Vq6hsFeL6rKgtv(ViYN+ORCJTKX^S8A6)i&Rj2-PJyV=ymyRHiKqPb;CaVYh ziw&x;x`(04YT{=F;|ws=lKxC!AcQVtaXeojM1GTpZ`c-uko`EZON7vnyvH-_xuZ_4 z1jcne(S64PBqSjOyto?++Njz)rH2Man729^*DWd*q=<@&2h+7Wo*pcoOYy84RmWpP z3bmW7nd*icaajsUXBP-Lx-HglNal`;6*CHXcU@OiGB}G3nz|+J)C+&Fji<%w*4SbZ zfZj(rjjj?Dcl)ne%1b!g@4s?t1Qx{rvafZR;hGygDbaexfb3&Qk!2O7Q9dZ$Rni@! zv&xI7A!ofAQAWhuET9+p?_P|?yl6pdTqRxcGaol_++l2#S>xN1QP`}Qc(gG%1BfR ziN-cVXn$9)257k$bs@@lZ-L(XJO#){sU~zOq7%79ugM1rky($vcGQ`4n;t|N@1och z;Nr%sryl(|wCbT<2h_&3`kJgRs2vpbe6p}ZJHtR+k_lXoe6x!D1%X%Gkyd{upn>RER`%$9~zXMEK4y$1mbpyN};HjOm zQ79??5B-96b&jSf+67}Oy&3R{*LsHnYe1AhS|8NJP#bNy_~2n(RnkZ>8c`owu%^Tc z2cHP|GQ$+=vp_YDg6?#o2xG~t#+mYH!c2c$7A_I$R{zB)+{J2$ zX6|kaj;N$#17tN!YfkkjU0=D;hBsntkG|P;gaT21=xn^O?s_qv?bAPGGUkp|fyh`p z@8HSu^pA{tZQuiesWatN5HYy9pGOBV=x+a$5hRdg0Zjq;obT?yKhRl;Uk_SH%*C<$c5=$)08w zQ2X#eb)V}n<6&g5FF9HeEctZ&`T1Co&XSq{J=hkS@a)He42wWN(lTaoQl-qoOXuvS zOjI|aOCTLTW2_^^3xZ{?nZz-)(1peYJU7TG!*NK69S;8K0pXKrNkE*HE*u#M^(`&W zkMxp$u8P5tE17YtJ=``ME!FIm5w;GAX8lDzd02cuBSt!fA?iP`7JjIox`!JQKjNqJ zLtT!Hk&5N0$k?F^b&%%^cI2iFNYntJup>Zi$qq28AE)7XI_lI)1Y`!F!PYwRiHd#X zqo;rzPrFREjb=o%3iDijRi6TlpY?Q*VJe4AdTQGczvOhHS>r=-+2t4z>mt}vlK|0o zk8RZhsSAn zj}qVq`}2Mo&j19yIU1|oGB$Bge2-de&9e{!7A_EAj9Ymy#}oj0E_Trl)XKP-#;cM{ z%{;nv)D*T#pnm!(-ZI3MN>NzkWZQh;Jm9wf&JrZ5v7xn`ruF8m>#f!=oFy{Fv{cZs zBKksCo4xj1jjhfS5!by5@7lpU$&y2EZ0*6|((7XQ!8Z#@sV~_m7GENNWxlF#!%!B+ zm{r2^#}aIf%uf7lhlI<1aJ265QyXe+1J zJPwZ{c$S(N7KLwJ`OUa*`lTAJ));i?LJXmSz60`_d36&LyNCrcS~OYl=Oxr>BDE_U)oG$N@> zIoQ$~_rsqw=b*UI0cDDupDA)EdI^kJ_ix5fJ^s`zQH5@2Zm#DX_>zdnoL={#NsPka zg%R-~5mSy%7UGcjQ3k(#et0unSc;?(;4=B){Ps4gk=tF74z{GG`ei%wn0-sD;@Wyt zxHX1eM*CaCE#C2F6A4yGl$E%`Kuxko`3$oqjhdSK7xE${*QY^6Q(WniCBqCLlom;< z5Y|cpq3e40-&0yd3MrjHwtrSB5uLv#b@(|YVjex<%iPdYC#4P;oY?k+jRH$)APthF zHEVlajML2;r5pN-D0`Uu$VJg&1oP&pKSfGp&RE9Q+0`6?tUE!g`TiBp*?nfotaHF2A9N=*K^xso}eO-Q^USHcOji-;8F zq@uK;mZ?R?CIb8tD3);H&Q*XBt>ZbK_axQ9&h_n*9W`dHQI(C-goE7bJ>I|pJQ3!q z!-AyZxDU-4%&N<{;GZrvf7|((L$lS72MS)rtZ1|hQ-(ejE>8cPnK9j4#%VuiW@4f$ zRH#L(l7Z>bt?VQJki?8_ao$eM=!=XppG=ONW39$XtD!q1G`+S;!l=;mNGRNyn5oH% zuIZAOsb?v-NudjQnM3nYCycZXDP_u#cp)!yXqKpLy|$f~Im9|Kd5o>MmU)@804NH# zTt4KvSpK~v$6_f(fNBM-v0v6K3~I`~|L1~>s~tn%mW#ty$2zylT3o9ZWz;V&~GWtG$S) zR;e6mZwwM8EmegL`2b263TSw4l$9AF2*^~9GM-*q&ctJnWj}V}t7(OWN2;~*$dfhP z%g(9?5k`HuTTfPRx^+D)K`p7HyhhUL9mRrT8loyYzRMGA>)4T}>c8{`p|yGggWC07 z>M5G#ZF>VI=Ips#c~Qei-#IpoO(s>P+T3J5iyl#9w+t)P*Xs$_C32;RZ8eQoa}Dq2 z0^|ctlEn!xaa9_45o zEef>IjM;`jt||&jW#?0s<$6nMaxm+kYDyPbfb`0I#1P)5IJKOQup_v8H-Sl|D>VW| zD6g-aSlm}<)Q|KYHp8F`X;MUn$~O9UCWbtWd_UBW5z7Sxi1!vbXk}6LN9k9JS5p&d zwbq{dyqfQ`YvLWzqqn*4`W7hmQRgEp4E9bGv<}1m@$p|De|+~s>n~q^^@uZsk$?Ml zfBz4^`t3jd^TR*<>NgL+`TPI&_rLk)FqCqD|Kj$fpksjZVrp+{%Z<>2l!{-r6GhyKaHn~twfNzIN@WkxeCC$h&*TZwt=Yx_S{>mdtd8t- z0d8;F7hkDywX?-V?(1E2uzdB7;@jM4LN^-q&z*%sHG<-+cf&*QcTuS^unCa$s(4n& z>hpvV;0+Iv5tJAxE^7Z`R<{;~4O6GDzG^x;)9XUaiEmBh3=a_~=|wa`I%8kFlTvg{ z3LIR1HOWn#Q+osBtC zFJbs@L9E7iM0}nA5jCM#QV!#X`Y8hHRy5sNV9f9^wu|ky7oJ0FT1%tmX?ai1>Oi#? zQ`YcLd5HvP@!_LS>WBhCu6im;OoADgB#*-lQ^rzW`d4l@St-``tPJh!wQ&q}G*2n5 zuuz1`EIcnErNofIvZ(^syqPRE7x})Ci0DeyDrh&_xCJ^)6deQB>L9%;q((P8(sVB{ zxXrB87&?^K49T=oc$~{x_0X;fnN36edNVwZiqPPGJ&x)ULhCt{C@G;!RmY`=Ku)F0 zy{z6dQ&tgAjFcsG-0DqjMA}Zj1jJ*;$gV^gjwvibwVLs5HZ8&P+hR6}F|LiQs7=qJ zf+nK&W?tM8vEw&T(~4Np>{iSNt7}Nb)f)oiM8KH z)ogQIqq$G3{`IizDb{LAv(qkN=FyV4B8UcCFm5Av}*eo!Rg5RM5ciL-yeV^JW% zR#ZzIFeHGO5?+TI7Nd*L%`!;^Z9xTbOYFev2*ER`9SsVTkQZLGqh|_wp(c~|_c=7eicQ5(phw z@4o$BhePiEwEIN{dKzNU)QeY-uV4Rir}B$}K=wlp1!-61zPM5j3Exys{kWw*Y1S>} zB=}p(Nfft~LsmDHLsmDHLtrBjT6Sc%IJ3(|A7U$?id5qF}KJ5>C$U5`c zC~=O|=DeRLaY*%?=H2c%?dNeg?e@px{(LwcPU6(g`ydUr$}4AT<)`5QbI&KWblPz- z^WXDvzdNdm6C2wm(y`O-aN@wtf$!`dhut`xN0u~FC$gnaR(WOaj8M<}>2S>U>68_z zTpmut={z6LLvqtXq+s-tjfdeJZPhl;yXnXdzB3wv_&Lf4#vhNj{ZaUX@`n^HN^wcUWgFa=0I(>=(U#SmCDLL5Rp9L{tZl{uwhYFzI%Oqd-< zp-u>ok18Jb^Q^kmHSvNqdu(2nVeAhx%-*3X^CZTf*)_vcSayaNNBR|tVR?W{U0_+G zvBi#j@IrS;^t40w2WZ=!=?kO`X8-^-JtLxd3?5qUAPLx^k{IXNbOUPl7!w-3o9F3- zI>0ba@sR#yu=Um3M6D zp9XG&R;Uq&tcJ2#4#hi#-O0wV5Kw|jX9NIZfMUb$l#pOar{FMpH|=QW#E;!!k3~vi z`x&CPT&)oxu4)R`Z%BIDzG;5fI81W#feK?jPr4ypjeG)5A0C|wmx9L03THH zCM`;&bn^hnj6zweMygc1KSM4IJUE~wXMhYt9kFuW$GXul`B6abD9Ry=o*xb;e8TB~0C$M{bUF?6 z8->S~!9P?#nyC&!1Z9D?HnNMt`-XRr|w{-oySK3MZrohYt8# z`iKOuWl_VYIRPN~b?QY=qvC2ZhY_)6dsW+~Gq}x5Xf9P@tAIburtLEyO+YWPZkRU< zT!H)vsKYXZ6q6c8^w5JRm9Nf5t6m+5Z73yoCB%XNsS)}^gn&5bBmWPt&*u}kODRa< zzG*gQ1xm~_4-j{wSXH4Q2dktBzH%sM6UcP~XwCyWc_0u4=2HF$KPT*B2XqmJ zjT5Rk(2MbmyEub>r~`Bk7}A7hVXs&qZcow20j2~F+GM3y%CM87C*`Mp3~Kn23;(8KDmL4EO>Dm^0@q7~1J%12dpTR?rCTzzrw^d=MYP9qA@) z<4sfw974(ABQnEJ@6Aht@&p3Bxr25>1H?6lr?9Eburg4Mp3?*o5vGNX5s@NpG!Wkp zwKEYC(!xFnA}p|=e)NBa?NUHI6QDpWwaY$KI{<8fNCc_C7UTeeaTtIAeAy8@fFF1$ zshmW*!}I_~!wakt)e?fC4ieiwP}RO0pZvd9&!7hQfg6M`04nNCM26yF0>S@kI=DVW ziip7wBU#2MUJu-q$5xR3*fgKG_Gui(Gn{GXDOg+ zOc)>VzmqndF^uypQKFrS*(B!jL91_`&q>};F1O9%bH zdtny947QI=;o9Y8U;!}$P64wys1HXpNRh_8$6;f5+$PS#n`NwesN;x(01>>B!lSup z{)D{}p3eM>8N!)8J{v`r_s9P!R;5=!6&?Zqfx!^yh?r!@VQUQX-ou1Y(?jEGpa4`+ z;5aH!MRasufI-};@t%xAqa2uoO%Q6pa7H=;Xk?8fabyg4=uW7|p;{O!1q%dg*cfK4 z=#-EQqk=c$588W1?FIO73IBoPL}17S=3{dcPVfL+!L6*l`Xy?IrLdZz7Xg$E2G2vq z2Mt^qkx5q=ej#Q&6fngSOWJ}C3$C$k$m0LN(8M0wgX|iD-+^qjgzyZ(0~GUMRsfCE z22~i!tT+>NAzoL=3FFY01AZ0x3HgY>0r$e0YL4)Z2msW@3dL~(8jO?9D6)r@QWm(1 z8p|%hAc7pQ3{hZ@1lLFl?Se3@E{y%qnC0C*4yeQkff@~FffZsdC<1*rJk_xOP|fno zXCIv$n`{Me1lkV}f&l5~4h9?*H)V=yNp z4qG)DpbYEK0R@ww1gOO5Ps#;~$TGo<^9*`0xB!U(cpehONxGm6IH-}_3F}5MtP?cG z*qX&q8HM2ZCaBOaUfvN*$myYUyo5#&Bq2U2AsV81mQE2VC}5e1LZKStnz4ys`>|Ro zVd;zwQXV{vwyUYhA1YLe2h~cj3K&MLIBBdwa7v(yb)ZQgFK`dL&@0+TneYdKe*vw6 zAP)=_i~ymE53}~_hmev++;joG*WextKWUIl*2P9CcyF*8#Kcw!ItVIg0H?#4&W5YJ z1GX!CPE9*jD;dBKLOSG)?I9QqVF&|g0shjFGms%9CV<6|6w?6}7ARvbuo6Kt)L;Yw z)*SCOCk7G)+Wz!a*xqBgByI@t*+LQ;w2s zwVyXL5(Q#nGxaO}0-XpS2q(CSF6WEYQ_0CO3@`NM7LNr#u^U9&` zu@S8Pfc9W!I2PhM!UPZyY))i8RYL&P(;L{D++al{a?nPqg%S8N{2JVbn6Q^qH48{& z^g}5RS)m8J$4yJ6z!2b2-kOMyI0(N?NhMn3IvBGfCHY6v9e70Ebn)BjRVN0R!c!7* z!(|e$pcP&pwZS?Rtuq?LiOS>|8UXnq7$yKmr3@M<;u;u|NvyzK)ZUs!4l*zsRi-?O za%ps1!}xfiYP!4zTt?suMi?%_Lhww1t89tpXdTOk17yL(7aS^M3{(Z@2NMX@F+Qa& zGA=n)U3WVYkWw#oJ(2w@nGZ~DWT0Vg74$aJO9U}jcYu1qp6HS7t9Az8(WV558zw>^ zIfd+i5274&%?1t$v~#1qGEBJ@jRTa$Lf_#s-Vw%NnIJq7Ax11r;|F$4EPyqD^av2} zR$dy)DLnnvfBY-|-|kPug;)PaOmhBJc>`eE?Y{aN1^$Z2@$gFV&a1D+Z>E=z=l!e0 z*8u4^DDtc0tHvV46nXEoge?7|IKUq;>EkiKfNQj_|12(AHVqP z|N8dZ|Mv3rA0PkeJ3hDd^4%L&JG^=QH>PmW4h1&Wip~J z(v+yafAa$~#w^PJ-~Z3wl`|)DZqXOX%)Y?fLbBDz^hM{rzkQD42hUP?#iTrVX~UoSP^R7&4AyIvRP zGv8F~Htr9%vERP?cmr*>31C=Bfcf}F%W}evj^^srR!5^`ma@gVF=;)b7B*Qfh1c>4 zTfV?WS*rZ_?je3Z{E=9jvoEcYx-68!?eZ42POwY^AFFa3jUTxsUMEDI@5eQ8=emGNu& zLwVZnV)7=&X4yz7X3Kq|i73{uxiLz}%y`|RB#Jdl#d!bzbFBb3Uuc;Epq8G$62Q9W zV;s`pUEpdq?eMa}Ob5Q;$6b5Z#xxB0Y$uqbb&uqESBEru8`0)#R$;~-?F^J zbuswXd~>W(;CH|O?rmyMd|E3i>sgZLT}3}XP-)bYD@rpoia44X7SN-0#E8cmKn{~xtE=AURZv!ZCM^I?Tkd%+qB|y>2YOCxYzWc*>d}iSF zYhUQp>^}SLumAgh`SstqZC~FP)X73!YB}H9Q`u6LE8Dkr2kaigy!0lvDlFRC9k6?d zrGqMmKGyYy;yf>zaB^MfhwKj6J%lx?vRuMl^vu5c)#_QUTrVf6*-F*oKebAgix1o# zuzQ$-H(L%II;EA+nqfv`OzG;M+#Rrc`243}=ONgWk~5A-vSalud0aFqkHGdX#N4$z z09I0+X^g7YUu;zTgy&w%Y*MvKsFKHC+Z;^KfDD@|1Eg0!rQHFDMY+JbfEzz3A$4dY zVPWoDPC;HoK(w(Ix31hRk+4jAqjX`aZ|sTIjLq(ixdUCf&p}Yc`yO2#ZfE_*8XU|gB$U-eSK@&zc`3} z!8Bv@dN`YSTZ17FsEt(tSGMh>HlZ<26YIJN;>?|(%$T@cP?H*#Eno0*Nl{CuX)0Xdzwb$?Ka~D(#Jywu7 zCMhB-0-!nq3@4lJI-P7@7paGnjr-NfVi~4}W;iqe(;T5xQa3~1;-3EevfYBmoz8B< z$?E+?!fhujHRI()h%ONoCo2)A6Ns+sp*vksr<-QnWn+x)ta!*;4)12|28-YcoRurA za4U+SxiDla_oI81S#6Y&mc+XfRCLaHsSDAS{3xD*6^lm4q=8fAdobEKQ#^z8ppqKh z%($T4tyRb`%+ballRPxQQpaLrpH=CbXH)>j*hxH0HKmF(AZ=M+3GTxhMSg)jUKBIJvBzyY#!R<^-*LNq&&v%zD=DXOgsMmBar z%LSvMv~?}9u|xa{Q;aIg!Bt@+al3wdEYF)P@@7WV&8+_#?6+qf*blgl+Wxe-;^axWg4mRR-O*-EZ7sHY>pv$){# z5U8b>DS3C6M45B5hBW*4sl?XSsTOxgS2iSG+m(ep`wY?jof2jhRFtLl0H`|%q1Wjt zOvS9pEy@(}Qy*oYC)rP$GjhO_wuO!NH2(z7v45^(>}@r00mkh;xDB*RikP2yn43!Gn zf{I0|raNZ>F1y^UhHmTKcy;R?C7jCSV?mK6m6T&{?&nQzwyO_Q z%FTK(&SrEjP-rEbP;5ZfjPa)a58z zI`JO6eVV0Ptnp@50xN_j)?#V-HOmdptdN>m*vzaJ%R{wtgS8b}K{>JJk5y6H_`_N? z7S3pGEDNhx!VVd^S`T&$AaU0o!?8m{Zxa&isQ$6euc@4}{L+efT?WPJ=PVzmxLE8bW|qop3K z4brwJ*3Y032$4nctovcr2y1Cril&vlTI|WX5M-Sz_?n$|&$tV)>Q=4vgZC{qYp0%n z^NW3^z%^FMf4%>Dm|wC4?B(MtRtHYsQ2J|@m;HbLYjdI0KmFU=caMMgzutVW z1D->U5TEK?zTzK>u!Do(5{WD$)?UjZpY01bA z)cbYKNseLb{9DN^SaGV#A~nC_i8`XR?)3CHPIYPMu==f7*?VA8K=vv zW;2;thC6v4>b|aJhyhFFKqK?wgQHSfENRKequsYlFNw}`mfO-LM~J^$R^E`p@Xkmn z9Hs6YGJd<7{-?AL(8Wy~M{K-qqpFjhMv1qt-@Ig46&V`|T2X@~04bz8m6Ph$<~Yyt zb6io+eRD6w0?*Kq5MH@n^S@#)=j;A`KfQ)0C?$ z#K06Tq+0*s@!dwNyeIPQ<`R(0@#+4>O6ienmQxm>a%>hyq3(Ct{N5(aTH|R}@6bwb zm|Bq0Sz3NVT0K~o=1yO_G5RcBc*I7HW$}iT_c3DunN6lhL(v}VOB-7BQhTb2vIeh^ zn1~;8Yc-3CxO_p+^iW#Eoy97zA0PkkKYsV)#b& zo9*->KLBlZu+%xNY3xfkVP49tXc0TIm&F^U%i{Mq1NF(j)$kKhqq_dN%h z{m;k$pS^dBwJpu=yWH49oN@w*A7aEs(EWfRp-$HQV$#rMciSYVCRr&`@sWTC)Ac;F#`1VjRXa#J1%C^CegL_ka)Lc~o%LLe*>2%rG3kRTA^_aB$< zGUr@tuXXmW>gvSR-ac#2`CZ0+jPEwS3w9sAye0wM%NG|qOf(^l_lRGJeU#FAwXD8` z648@TirJDv-@aknNa9tkBZ{jE#zIwOZ24K^uysgTe9-c=envZz_gssRDT+|fN|c2W zEmdlTby{5nbC~_C9zv?d-&kEN_j@PQxnnq5@6GN=rWZ{BnlfEgfb?6v#w;T2Y#TpE z1#qGwPq5TrYpV}T8|y3=j6Qzs^7ZxCzx>hrSGTX;d2#cVXYb!)sNVlzLXyV(3XmL){`k3!w zVI2euMa@F67br%UDj7LW1<`QqHc5%BbT-dPCE~awYUCcYaAsU6?@>qX>M4pe!7`W7-@z!S#^4pI~{~& zmM&(8HGyIA#!A7*UO@9=pq*xrQ^?0UBK|vjOC*;)k0Uw36_PgWEZ4!2q;x9Kxe1Pp z`N1WzK6l*|ZZxd5VOVsevaKH&7(?=?e*F@UGYaMeU|zq&e3MzO*fdtTlh>uIZ>VcX z6@qiuVA95AE$?BPEWH&Xt)k@J>MA|Kolk-%$@a8u4YT|Cl%AVu+j3W|z&ADs71&|{ z@!Rie`ZH`^;OKTGE!Q~An2-qHSIP%<(DL2e^039fx;+XJvd>mN>aqeB(#eZcg+!ee zY@)h|G@i6-MewZ1eL3=$d3&CS2CTi%k-%jjM~er>*Y^Y$BEcZ|NVVCg!`Er4PPUg5 zBgdjQ(Tq{D>}GC@AO*(U{VFzhtLX&-P?e!c{s^!;gXJo*nG9*zaT8sBN|H7`U2M*nVU*nut85`wzWCzrig7D2fD@Z$OH9Y?RL0Jt3YD-HBv zd$`lhs`Rl#HT@=TL5LaLrM^*n;3L|*1STS3xHw)UE_y(7ZIoX1H|U$e0K_#t&yyE5 zh;G)7;G^-RwyrxkJI|Bb!COQ2$d*Y>2YXv@n4)r>r^5!BxVwh_6sfhwoU&YVLT_CM?vGFjxXY#Bdj|@r< zd7GmNHK)oVmBced^eiXG^!ww;ZZ3<1+oX={wMQ&DGWK{U=|~VQ@N9erwMB)8`+PPC zJsiw?RJk%co#4l)(3aQqFxe$d9%!&j6cLh7NSpi6=^EN@mJLiw!;pqdH~DrBFE>oo zvw4CxE=xLwevAx3oj5qMaxkzqcAj>slVH}UlO*!J*2ZcU4^`$yY$~!9?RGLECoUDo zxaF|OrnMYNgs=*Yc8JM`+(%B2nuD6KH3n=6tjLs@@)Eih!-DA4N*7s z2wQYy2LoHC6{?VW&FxwQDN!Mha!&`C>aGl?k{r?eY4~Zj`PYKE53~~QoA5%1L*}K6 zMR&vDNVsBlD}Wwvp||c21}#ufv}7ZadhJJ*OP^3#Z!n$Fq|A zDDCAD*Ch&s4K-yZ}Qf)|5sN+N@O_DaP z9<^72H*UoA*a9JOGpvMu)<@+Nk|@TTTBBu5u{6L{-Ge*9koIO2W8g-cjVYR>Nz@+E zvofl*5HDw z4&=Ut(nkHQpL3nx$AC4kbyqe{+O?m6gl*|gA`v87n^@87mIcGm%=$sO^*XUYNH+1# zASv8F%bYMHf7G6cefJI*2P5{F)%}^pkFg8n8>cMd#~hnX+gLn8ORFUh2{YDeMn-Xn zm}dbaDl~2)CCo~xN+(1EQnztde2RG=*w{^V0Eka@nAAkO43nIP>#+3E`FSIu^&^dh zatjIw%LwFrN6r?o6!3Ae|HnUi^@=pCzvH7T9RU4>5BNQvzKN9ujq@vi<-?3q;{2JM zQWckvj#FyVCZ&v1(Qf5b%#FSgr!1dQ+S4~2OJ?;q4A<|O(9bRRL7Q+C`8G7jTtsn1 z9KG`iG0PfX&DVVa&?%?49~c0U|NR7!)dZf6ujFUpzM(e`fW-0EjTHh=g4siO@GHcX5b2^09}0$jK9qaUI1#1atTD!2Kvg*=!mnMU|$%r0l%y)tVPI zzMST8GPDZwo6qO1GebZ)#EB5;j9{%?60MlzK(I!V^DZ9pa^q-?o1D6G=f_QU`@?}P zhUQ?Hr6kbO4X?KVMNaH98r)qt{N+dy$RE5S)g07>ave|7yAi)nMj^7-o}}GHuTVHGOj>VI$r2f<-j= zm3L1z7H2&sUP25LHQb~1@Vnq;(ijp5_Ys_KG~|`FsA2&R>1V8 za5IYgyL)oO>9Xm z$7+YJuSuhaQ8{mqU5O5L&Q-F|!K%nIKO>r?xq=)8QXdt3dqlu=2 z(ezX@^a{6rKFgHNv@cqj)V|P6Gkpd3fw|qq4nr(r1m!~}RLZv4wREkPVIF=mN)}p4 zFXZQo!N?bxT71ewbRdsSLWojp66_Up1mt280?;O=@xD)y%f+B*$;Qn4-l6P5qK<@~ zty><4o;fxX-0h(n6S0W^Vb>cJFE$>k?fP{%FV;fh06%m*LoD@}LpmMJzAr-BO~l?4 z5z=ld_Wl?MFnd%2EQSlf6B4jFjQ|fFtn1p;YAnN~m>i|Om&p3asj6NWfRfd`7)z;R zZ!C+EkfA9CRS#ohIkp07kFv6&rs+JQoQ)MNI;$3YD{3~v_E1ESc+YA^Se(JSL(Z{H zx8KD24N%2FP$}kKgR&}Qq8}igH}`6p_teCr*rD-J;2hP}F1AzpHMT?Ber)^k*&!{} zG=wg~6+#hoX?Enrhruw|2C^o9G2iNP<#4y5WW!(k*d49>)J?$dK1G3{Vzt&xgm#%< ztr5kN^S}*1%Vh>)+o2shQz1dv92+G>%+xg7*{LuRf=6bnouBv{E2^|z5Ib{h=a3qk zCepS`EmNd!>}6y7%JF9yoaS1eW8>pa>D<&BiCOhF)UvLis@0Zb>#$GZcRUs@o3;p7 z>uSIuW=mX%-TN@Q8$v`0GXUllDege#Srx_pbx*Q_#Sum2M^p?K=r_k$tMRhgnyhjf zfSaq7`N`4vFqT%qJoZ&$X{Avylx?ZX0XbH&Xrd|#r;JS0t7D`=x1Jgp^HmFD#dy~o zUV>lp-ObW)%XQ~wu@l9YCt4M{j+0^2yZw=Z-L3z=37y4r1G0%r%KmqHZ)!D>^i-{O zr>JU`Wr?Z6WA`jf4Uk3`a#$E}ltIJc78}f*bag-+pTepE;TboaY=U8`3~kSznbt&} zXfh0J&RR8^`#n5`jSpNlTANjIgpBeNA@+7^Eo36NnOF*$STcF=AZw;YVA0V9+v43; zL^sV6Pmz(zaNl?|uGJtpRm%jsskKa-Mq$~+oVVpBSKlwW%n`!@Fn4bR=I&fD(?2W& zEK7=Bbqwf5%CRQO@f4E~mPDG^l0d)Zd#32+EjF5{23waDpVWo}amJRwhic+(5FeL0 zCIMN!XzWkg<(yD|{CPZz8Y=e7Z#>no=JM*6tddAzNfh%F@BIA5&wuGFU;X(nCs)!o z_y*{P$WF&?L&zy=ACR%_l(oKB6}HVJ{ZwZ|RoKf#AD4qEE7gk9r$(~2I}$CHfxQAEvmz1^9z5pC3ZvjJItn`xjoIL}i{dhHwbT>M^oXjX!9-Fm9Flk5 zHb#0E+h=XBY6Er|CWCA{?GmwO&aS8%A>WLUI$BV>#+LM4L%t+vfTRZXGvFRT*xqS$ z@1|7lgt78hx9>fB5zCYldog2ma()3=#4nNue&bJT{4apvPAzpauY6z*F`cAhliz-xvTaEA#Y@tlUmSrowfQLt1NOP8FO7mE zE-Vcy#Sl$n;-spG9BRf878i!iq@b3*jWQv2t=X-I5_;uuj(2MgNI<_cS*j(D=+Bj@ z&C4#euo~*RDZ-S7JsK~Iwg70OEvpA=%jz+-1rRSIO$L|hljhR=9om9^ zavyGCL$L>)@aUunCP&}W*pDsi(}o)?apE5Mf@t_bUWX73)c*7Xvl&*vWikIX8P2bD zWUmtehVsK}K%aV1hX_Jxo8|W2+dwJdO^x6NKSL_vq058!Y_imPAsBTCljg2(pFj6& zC-1ws&|+u0cF$q|A`+0$uLN5iywH$6Lb{x9uS>`l+z03v^a%;C`S!UN5pKK%#b(~Z zf=j;I|NOf`1^>>ygt`lj+rC!I*?h6x0}sDvJaBK=a=+Nj*-Extt#({AxaJSbrVog>5qPZOZLJsEbdb3|~BMnrX zU1&7zUsf|Z+pPe`zi7g7@Fn_bt{4=b}+{^8P+uxSUeO#p0 z+~l`hhzwl)7T8)Q%>X?=1%S5MGqK`2LGBLZR$K>Tf)v?TLyN_Xiy^s;E&Hm((3Nvr zt{o(^syA4vL2>xCUNGlrpPD=7%BMiNxgZXX!_nn#9ep*oyOqB3TDeB5Hh=*rjNQGj zDo`+3Lc86BDB3O@r$+gdwHj)0J>8Ov_!e2KQiH{Am!@^})lh>QGNIFIn-ErNpjorc zYQ0KHvkBk;W47FKyWcj&({A$4Pe|u{vEd#7!^?8^4n9R+c0qM~nu`>x-7K}Qp+@!< zz%Lsu0fzN%vzZ4|mX#V@21X9ukF(GxB@3jYjS>0R11+zA+C``V>$;K=dphGf*wu`i78iT_r5l2mvD0LBErp8u?yYoilD>y)Zld``MKUBA*@1r8fGE)mI25qvY(;L*mL{^ zri^p8+}60+taez?s$AV-yWu|D9b$r7Y_^O2ZboW+=T>F? z8Er#cRDHf*qU~F@lkWFB4wUK6R=VPH-e?=?p%b5b3ywbeg{5{>E*eM5xGi?S#1jII zeC`}kTX9A3Y{`AUYpx;PaN8*N07E^pXs^*x2qjOw#pdFR*E{UPPEO8I*fa{wt-^B6 z2nLs~&JbqqGG5@1Q6}AGyTh>{Jlt7JMvFB+`80z>z{78R)qFMIa%1Qg=?o)d$@Ls| z$Op8<2CxVhzlIsO9Be6&R&H4;1g`*&u3MX>u9)2r>!=`*14`+j%J|z#wh)Z5LwP|n z?&by39fnFY+wY+d7aJ><01_^0-H?Z2wO*n_*iM+ug{N~ogUUmT`EH3U&Q=1rbXvu{ z-l)9aV8M19>EC9v*zGYSrF^wZ=ZGLzv_gNbTE?>=)qu>+!T^O{5M^bERK*(sm|^oE z5HS|_KTcm?pP!AJ8>Z*~0?DICzbzMn3m4iFWhu5|jv4Fw5x2cE>%;a1~9WIOTD_t_qg*%UfS@ z&o=jOLm%#SMoQ2*K!>OJ2pJjrMNLoxf?q@gBPD^t6fe=b1$X6Qn263{6>=1}RKe^i44co5#fYfg=4}iU;}8OCZjU_9_Gk^kIX4W4i?ws=4m(hw!79wH zPyz@+=a~p}Cnp+mvu%t$F)}@@xSg1mDZ~E-%vI3e0A@4aC=NaqDj*?nh7ORDXuYb% zihJ7$$DlflM_i#Bgn`AYC5&6Gu<%3xFmF3UB6iq3ZrFx#(q+gE{m=}g02Iq*Jt({dND$mMx%-xQ)8rr6fC$v8qMAVIRb@R z@V~8`B1x|^65UlRj%C0=lSO67gV4b$ zb&CwpN2r~hS`Fcnqurp_Tv88R)I0{CORYCtZ;oQnF+zuWZRizefDy%8U=qyW)87sp z@l!iRy)Fj3Mlr^)qg8WRCNu2*E{S84v{Dgy9jx(EBuE~!*vknqV?4~?d$L= zRh)246ECn=mH;<}WxyC=Y|S(*?Mn7NVub_{CnC`3JU&YURV0Y25Z{&1HWP#()0CNA z*$pU%&D6LSp@k*jLO4nIfi0F}WVnMl)ktKn2_PgCJ}o>LL`1_o?W+>Pa^!?Y@S&(5 z>pn;ioe;!=Fl<6~c3W&5z8M>aVN&o3K3EtYm^DJFaPu%Wh0U&w2-qz^#z#{|jz)F{ zq9~+;uxK{42RJ%nvWBn%@=CvVOiuweFhO;(32TzWP1_3Mt&13PF~oT|1^C2#59ZEb zp_BO%>q&!p3y@oh)EZ-nFUGKJh#;f}I2}r5Xt=LFuzD%n6VBMQSCP7+8^Z8lRMo8N zrI4P}!U%?m{2vyG=mB99(-h3pL$qEsi!cUTr;EgGKmqvp5JW=(6k0i7GFnD_D8ir< zMTPqWsW1H{GFwh}vl^=8T0v=XR zaXduFMr_e4I@@BBi4-!Nz#r5J2#R2W38@x9!-LE`juye^Q3eZUW1>+BG-$@?LLM2m zoB5WMi#nKVB%;g_Rt1DCjHWb>pm&*Uh1d!Yp^ln!bZISyhZQhWU3y(EXNC6IZUgJLvl@`)vZZ5@fF|-FlLOR4o;Rpi}#14tVV_?5gV%5mz8>fiqp|+95xm}(FXKYv_nWHJBZDJrZg>8W}L8v)R+e}K4cV9Pe6aNg2|WQYzQTm zV55MR&IrcXVIRb)8isLIw1i8?M{J4D5Crf*CkdJq-eGtlH3(`j#4}T)Xr&AYjDlp4 zND3x0mFBC8*_=p*Y8W*HLFS6^2n$801Z@iohnXxvTu=by5;icQimJ$D5+N&gfjr6D zW}>_fGvwWYKye^^U=9GyN|h&YHyyBjD^P%~5%$cYul>t9VbVVPJZPFX#fz#zQ2gZ*w+KxY^-;~OH9LN+H#W5dvegjazCg7b zbXl~`ev1Ops0{29HXB>2b)R%r=!R((^@un?3z=f3hDZ^lZSeYizxn(8zuC938~6T) z>ArlY)KD}IrFQeX#p3FUIk>ya_3h#sK5nn?b~l%sOCssptNH8>i+Q!XTV5`1u5WK} z$+Nr7_4=Rr$GoK{Pha1D`!(~(ch5Kv|2Z}kU4D4`m6zYReev|em(PE+dHx)Q-h2M? z>iLt43(}sx`VOmw_HVaPC}O^N`GM~w{4Jk>`E-l3qQ76 zq2)4ZVM=-&!_3GarZK4EB4bdsv>Zu;=4m3?ik>__GpjT4iTi=V?Uuvp7}unjPKH zCy8=s+fngcVoCtdC3wZ{$haD zK*yO~fSP2rlD(NVS;(4HuI0t_;;mpE>=V8}(f6{?L?DxM);X8O$n3;%w$RqzByXxz zyTyxBY$}}Ud#Xj2h=qb!(u$UDeLPf|Au>((Q_X}pD7mEEqgtJ&J${aZ1JOXoWZLj7 z1BaGAhs;|!sL_}=ewt*4Q7M9375uo>OIj7>U_(YiXln?SRz0H!<+L9)biEIxwh_MA zm2yRTzt^pl(Y!axx`t|yV7}d(c%t!=0eVy81usLkiWo@B#hHGK@$7<4XvlsJX+nsK zd`Wss$g-F^y{bmTRu-olweCF$gmY+)whCz3wA4s-+$g9XJ$UvWy=>FHTbahURVs9! zB%BWM8Rx(k4?G8MTcLs{C2#^jQ<2_;9W_s*s5pWm4N$YWLsp~Z&p~q+3<=+6;Pvep zA0+UT?yta5MwW81$x2nh?gfU<=?Iw;pdr)djoTQVN94kBYuhAEqM$>3GOATllwXen zAFp(tPp5q%l(QVSalml+y*JCjc$LK<_5{iPeS#pGGr_b80zo5*_RnA@Iyl{EE$Cug z5n*HrEtJqh>)|HoFI+`9yd zO%Qr;r@HzBr>@7Ki`Xa2A7Ls}y(;VFT~ni=NFWWg_7Y<~2x}Ve6=JpbP{?-C8TI1C zRtfcjs4+oP%w4uDf;d7$lqF~uwr1y0rNc}BK}#=W{NP2HI>#@#<_ACZAfNtq&V8#% z)v)Kt@e$7=RJcdD9RqG_U3KGdsGhxRFC5R>%Ad5;XS0?LrUi_)=g$aiWF|ydGg0N3BU)?hMa>6#t ziix^erkc>OeAa>>##5uO4ws(!Zap~@)c-DQo0g~q)hoSJ2ayxj462?r8|+5LazxqW zu(3nNu$d}0_NqeK(K5bbiNh^~O;DMa396Q|1ry6CYg95TB5XxQ0VwJ;*q8h#F<`aL zrzsn6a`BewmvwDZ3F<1%lApVBI1=>y=0xU6Rb3NSO&l0%9MI++EDyfQxdI!`r@%S^ z&-?+?eH$%1g_N2~N|Mp&-c=en;lQTnsjI9zjdH~49>kmLV~ynH_prBU<+d33Pr>*s zNjp}P?}oELnyRx1cWBlmWW%k$45ZvoJ;)f6C?c0Q79;G%n~&u>9#t=%ga_5MkLsv= zG+$R_-lcUJGx|}`1MwVXWFT&l7iOdJKD|M{)=tX)arfB{0 z2#zh?=e#(q;s#zE4(BBVi!qit4qj+H=SX^v%2S-HV#+X(@t$RD*%en4kh3-Esb`|^1(bNo#Opj>tx%EtQ^92vPt}u9qPzGCa1j# z@q-44aO{rgvBnzRDq%v9-b<2t(G5*#A zZ$5=>GdZbd5xDe_v3qYQ*vf7?U9Wd4#w%_2wDD4NEfUszv}GZp`DGax8ACjUdmV!W z;59L!D*A|(w8@6%*$ao7p}sLS5lxk;2|bImYE6aXIj1HZCf6;aLFUubEgiU5B z#lu>O`KK~x#Yc04>OlzU`5a4Q^_mXF|1dp_>Ry>wk9Qu;n8d3mFI7`v3Cshw%pJ@0 zB=W$P8IplWaWa=5UOd0O104FC>Os5~FB}j^wwe8C1H}z&51EkuhN&UrAl$~mDVoHvsDQOj4=XrsKchiC%>Y!UbUJtYI z#3q-cvZQa)81TIxv~hWc+k)}9d~6GVq{1r3f@cSs-X*36e-!dsB5!E3>$<)Qe5taq z(not{nz4Cqrgo>zf>1T`MO*m{VJ$^v>?zqBQc6U=yi^j=RZXNG$7gvT)&`DOb{KvA^Ap}5Yd~+L&D3ePn3+kp>A`|oH3WUR*3*(&W7W9G%t1c=@|u}HWZ)_$D}`lu;g%0IMcuJu`FxaLQ%SyXujvPN*DrWrXt>mII)?w zl;gG#UEKr{$?SM+XDqyI(8swe`lJt4BhgSTsgDfeeBY)FpwvD`bq#f`rf8E>F}A@l zn=7jp>Kt^O9%VqwN!cK{gg~uind)@&Zd04Z5|Sa!#xz6P+R(sBU>P#q8y^scz~?x| z%9-Sac&U`0u>wvZ35_N_jLwozJ$U?M9jP!h>G{X$`3MQ@NNucv%!LO52Q*p4 zv<3MbiuFpGjQQQsL=CoB=s6~JNrRx)%CxguMB|)!k^-1x#!Ged(VoNJ#IqL`d8Bt` z+Bup!bOsi>O2E3!{hpbYK4)wI1qz9Sd};HXolak*=2JEO z%<1coV&FD6Y`Y$$kbF1G zP`9=%iUm3)u31X%3bK&(txa?MvsSsH{{Qv4nk} z3Rnu!Xe#U^85Gnsf9Z~@h^}~bpW`LJNgtykgG9Ctqd@UCGDLCZ@PNe9%9_gQt<*3_ z8Ro)xeqcbd%6r&WS_E}W1d_Os;Y*b?(ia*gbr2MycPK(3|DGdaS$~Z+#_V8*t(%Cj zP{<6UuaoJ`xO4K^IvLYY#rhPPmWrnrg<36_9EoyN?JR-Q*z8pNtbtru2=P3Oh?6BL z&i8?x;*?onS=auPh$%9Kh^zxx{Rra7#}TZqqflWXBu8~g?loipWlTPjC;@$8NSe@9 zKMEBxGRhLYG66KYJ#MYo0xd^F}4%F$7g<7dszg>KUQyVnV)a2x<*S@iMN8r5|vSg$DQ% z#mRD5F<}=&(6W0VB)^T~!m6u{(X@88Lft0`r(2cjwFI}x6p|~TU9)P@4BUsZR?97DlZslKIeZc<~hHRl_?V^YBEcNRpctP z&>_=83Z$)9b5dRDwdB^Jszxc_c(iGql?g$WGCLP-tBjr>y2o>VVb%$tWt$8yfhfE{ zv<=S4r=5rikp}Z1410c^K&2%C*pz!(CrH+bZnxsbQb0dxznI#^ z;J~4YS;rJK6;NxKOyAgtw8*D%uKG9h2$n z@TEXF1_7e!FyOQcd8RG}BK4e=OFuG($CA^jsbUi)F!~$9LmQ+aJK>feO9hv=V;sEI z=ke~;5hXoA9C5y~`6+m`bWb9WeRAeG8q*TCpT2FtgsTSZ8^OtMTaHX_L+i5ZpZmQ?7vh)3CLmDG~-kQ4}fDjUd7 zND4_3{F9HHERsq(PqNA=*6?JQn2QK+(KoSCp(=tERxrdI0i#6KfN;c3@yG`A3u z8+t>kXeDAL>$kF)ljD;tgaEC4?IdO-9jG#0l2sGz$wA1)KjfH(aF*kjOvI!x)+H9? ze04ONi;NIJa#@lrkt~KJqf~BE<*T-&ugV^*l+N6=LHb)#o03|bG@j(vWR07Yk#@NN zX=>M`;3R!A5sj&z&DNhy7v;h}mbx+8t{VI{8CoTE#_x|eIi3N287hRB(^6_D9M+V zo*3Og0xVBxh_2;ba;lT@mgY#CO1e(VL9AS(9FSF_S-a8-3qm4GbAklA)5Wl!Uo^WR9CVBy?nKC9SJ6^OC|} ziIXY7C#9?=Co7jrzy$P}6sJj_f?TtR-1c3f}4p*O}qAxtI%1K6V1z z=5DVO^3%fo#@_2Pg~v@b-P{>gWbs(0>1CB&Y%ywqd@I>DS+~uCAcJ%-rb#UrF7*Iy(*SeA%bLoiDGh_Se_sz`fb=tH0p?cTguD%>qgn%(R!0j^Eqy!!=Sm-{R!Wl{S=gJ8wLIZtPkWg4Yvc%4DCjSI_C zT?{2Pj_c;(>ih92fx17(#-N&Wv*I3o_fyEtpogEnM(8IZHDc4NcG9lNHk#AqbrZ>; zY;1N^Gg-DwMUOJ;HaYX4M>~^2cGaAM`D~D&Nqz)?{VZ37xE#ICGG*u8{v0Hgt%qxD4?o^l#9x3QH1zF5_^ z(=4(#6;GP~bSXQ1>mzxo63(bOcee|-OU5prq3r))i%6d2CQ#4y%Y7V>>Q zNn!JW;67Z`kzU(!m>+n`r4rM3eTz9SUz*OZyq}!uqVPaPW5R!H`%1c5KQyD)m4zle zPg#ogLN)tpXMTlq}Sn)T8rQHn=j!BD@k z6k~xc25@ZXialZcZM-{p913Zr8^=$Yno8!&6?E!W&Qva^&QRZ&kKg_-sz@vVZ@`S2OlY>5_OX=U#=h3pG<%o@kF5u( zJ03&=Ls2B)Sr*Ek16D7rjskE(Pac}mZqM>)$iZs9)T*l9$z1$Z=>k!->0;O}8pYfn zSrGflzq8gSe(cAvU#M-`)^R=&34jqkIl+q&;XC@c$b7unz!mI;C_1H^gx!#?*LCg| z>kEgM3DWk3M~P>{x~nklj8H#Jx!SAkyOPi*Xsw5J>Z*D1eX5JiEHwg9EWs+8Li1v&&o#H=n1NX1nJHTVoCp%qg80fYPii^QRSf56 zX@Wc*Q$s_gXCs56WdCX@0*7XAZFVTQqr3Z0P-e#WsC2#(X%_UbEC{BPv?vLq3f&>2 z$}xcm-Fiu6;>=~dSj^yI7^2XRh9=`3BKp`go_knx`-6sihcTqP;9ZF)Q>@-p+9!mG zUJ`^R3>?H)tRq~!7V*Az`Re^&c>ePB#34I(+ES(1|6*54k9Z^rOR~5uwTi?WYsalP z1TY^W7JAu!Boed_Dmh;wP&Gv~d7chDi<_X`YWG9t9f|W!RM<7y0ZFk=G;TuG@~cs? zGh=hr%rLfu2SFouu1AQJO#Zcyy+}nHf0V_2sfaZHo(u*g&o*Oe>Mc2pklpS#uVJua z%}+LDUq$F@DQR5$G6EWf>?__>SR?oj*W;3=ccqYd4JVI8@zWWyuVhq@hwSXzGTt2w z+1(~MezFDfsWL=nH1seC*|DJ`H8(RBQNPt7j!st;jyBDpKelF&H_kdUV?dCGzl|qq z2{n{3loF(vY~6Vio6E77U6ri!P-63lgT&mT8v>;mVTY6`Msm@V4wu+W5=>Sz`h{^d z2};Q<5Jj6!Y;G>Awb2iZ%`JIjF@~eeu(00gDoDb=^l<^O3ijlsDjW$bMvUWn*p@BF zqpX#7sRR_pg`kE2h%l`WL(Y$~a+Ul@WNL$YP52K!HT184p_KTCc%+1gfO0UjgV#9lt`=J z5V#b0QA-9_C<-Lm=%Q>YdsNxnOlKQrjJdg&TA}_Dd@~znJ{%L#G1WS>P7TN;R;Sk} ztB4fkf&dTF&b5quir>_NT0l>?{Tz)thZvHphAugE8pE|sUA5CDUZEYhHgit~zg|P7 zsHe>B#-WnxQ)r7RE*kM*!W~;NV{(XQkO^xt#44J!o61ab?Y)>(o4it+Z%l1ge>IOy`zH}k^3$XI=*J$auS3T3`IUF9EHBg>ZIHoQm7-|qfqKh zl2kNHnjl$^Qz;d!F}6FOZAQvXF)WU!&?k34p+(77VO_c0#-2%I)7R*!C`#0l?K6du zu+X9Vn>3g<7b)Wv9rS~SO|)s&juO$f4kkD3U*uxX5pc@64GDS+PSZ}SzeS9)yophO zGm?osuOO z={4$}`sRLYmY#d zYIOR`ixz%=Y~fF3vbLi1NSRbk^FkYUDr6g3(b6s+~3UlLtd|if**H z(;X1eSf;&$Jws8G{OZZ{x@w!WtFm_9UbPOrj3DKlTwrX0tdvn=A>qo%R;(O^(HTto z$-Xo?RJ5|;^cxrJnr`nY69FxUt~!xv9i`f77-XN)Dd?L7oTG4FUu**5v4DeFM}=l} z$zeS`aGhUFp8;*+*2j}?I_vzSFdiD;r>H|iYP2;fXITp! zXKm*+<`)wym%=B|yKi#h- zJQW$4+$@vuP=GTtHmY{9`3?MbJd75mvaIkDLDn&TuKu34>D9G@W4;FQf8f^#f_b6K11j*|l% zq1(-7yCvJtXG;!2IMmHaw#RHecFXH9oVlEa(2svyK?m!sm&&x0jx99D?%w8Hl}=qi zuf=?)Q!@Jvye;}Dq6CUDYdg|u$x z8=Wd+O_Sp+OHS8toGR4|S+thwvCA{gPMvlf&XVGV4s*zgN}NpElc9<;Dxw?5p*Xt& z^hu2>>&wSKu2gk|4rfrw$;)9UoxI`1mJT=qpY#%ja*z>LatsGu0A!BZF_p&Q4JIEs zD8ivN&QQVPJPcJCQVyVSR+3{JI-SM&8=V_L&C1Cv*<2@0IgB~OvaHrSz=HdT4ZUsi zaBOorkF$JEO883=j*f6V3jV|09Vf@cRknFnZF)?d&2wdq7CoZ9iKAT2k-LJc%oeA& zU>5m3IkLuy9nNsM1Xsq6EF{PI&O0>&J!iB2)C_Fd@2;-l`|>` zQI!u^R6w){pHdM^+raDHFA$D?^y-D8%MYLZ;%&}#X(W*15aQ8nT#IqQVCn*Ry1_v1 zZjiBcvL}@xdk0kzQ-~$c4e@xN@(>Jii8rEv4Q&}5WhW%(u#8l8VB5>4b|ig@Kv+&( z6IDZ!<{(&wRWt=FpN|Xz8^_dn=mccxQeJh4sSe~$j=8$N*_yDrRDB~%v@678PXm}3 z*7Afs(o`Cm1X0xI)k>=$?;!xmKqnkPb=Kg_PCv-4s+Fl}z)*$mlUU?q+HOL$@Yoi+ zRS0OxLS8eO!t2rG$B_#SG!nko$hUiMa>i)Z%wtWfrK@9yoFq0~$&pNd3nxGwqvsxR z+jLaSbf{VKC--3}dc_dbA22CWiCEtY-b}$U#JiNwtyjhT* zf($-nv{-V6e#@|w843Mwc3wEn_T%%N^%*Bc1nXWhpu91VFq!br^T~0 zds<7k1wcTq?e#wB=GnIhQRn4ESwQ*zN6(+EY;u%t6LNe|eMgw}>h}8O`yV`ee(Q0i ziitR8tMw3*t3COKf&IBQAdf1_sGT2X^xN9bGGKjxFrEuO$r82}?EWlXVF?~@nzYVk zl*uI~q-JCuQ8pvSwxq#ODiu<+FeO3)5++2L=pfTY&JPq*HZ)IlwyBKO$tZ&@*^${L zYS*cYqf;q9(b3FAFtMf-F(mSsnn(G}zwwW}|F7TtJD>k6?|k#W`{qyn>N})qHb#hdH|??b6NcaxuTUC5INXJhxY~)xY`!lZNR_*V3JDky+z$4O2uS zXmrti8)LYF%B++KH`Wnla)@_o#nx6ShzYwhs;KwGMzvy@06@c4QHD&>u!fOO7CIB4 zHyY>q8y;jJ6pocacAXKwS&CmSyD66RHa*U=7Ew2jHkt&s;b81hHUH|mbEveF*hMK zQ76N7vXjiwcg>h;9;cF~Uzj`DDt~H>DHd+U)clUjYy8g>u<4Bw#cAz}Og~K1M<&2J zKYU8HGOyg&S4=AT-AH()g%08|tTPkR%$kx_aJ05nCZSoHpeSn!ERB&8ZGqJ%Z2Qd5 z{OQ}z|M1`Q_x*5W?Pu4wi%TYack}Dr9Tt;pcEEH?-mc9Zi*47}%jNZYf4RQC z{`}uOX*_@7x4-lFA)TpE1}!LGGm9z{se$fb&ZU-iaK zeoR1xw}@tA7%{BsyUJAe`3_LSQ>c`W~3b3HbBQo^9` z^*DYQhF9{n1COj1kYIaZd2bGgU(E>~nIB_Ah?%ladUB_3cZQuN)n#mRs7~n2#_JeSNLuy*PJ|Hm{s_~uXl>HqDI zeeQSu^S|+5`P%>Zi{Jl$|IuIl(ZBdt|H^OrZ~y!M@Av-R-8;Yf(cRe&ZZmLjEEryr zHg`F@VK{I-yS`cNZ})d(bzE)kSYRYwf0P+s4oJ;Dv9p$y|lnvOhY{sNX*1Z|!+=Mm**cKHZ_OIr=i6E!=WGyDY7h zF_B2rmMB4lm#Am*nh*pc7&%MEJ>8$lJy>zjHZdp3NMB+=^>J)2I4`rufohnV@YvZd)XaB92*Boy3$ z=i8JOZoZj+R`>uz)${>|TCZ;^1|ut6JL;|Q zkzN|^w^rEorxmi1d0;Z>Z1Z4qJ{ecbXO*~`u&JW+04r>ppF6__<$J@n0J$;b6dM{g zlj(lf)nwS4J;n*idG>YtM#DbMWYS@Vkj9Fhst<>W(eGWN^e+ zw+=F{Ch9{6L2{BPqB@eGbTehFP>ZM)xtY#jq&@^18L@0V8^c)=|H4XeQ$wA0uk2cr*@8POWRH(V_Te zII0b`d}Mq_GZLO5j%tNk^o~8XhiJ-uW#Tvjav*HpL@0WE6Q$H5gs;dA5a3%8L@VEx zjdp}sP<_-+3bb?3!wxa~fU~O#IwFKV9jGNF4(MtnUvK0Dul~H6Z?QF4r>%iLIVnbP zMJ-XO8`KEiz}nx(phoWxhCL5ftQxPG=y5TxSFtZ=}*)Fd6z~efu}lFSQ{ZJ~ZcLmuI>-jU>>% zsbUxjLGR`H5oYC{*@p(4kqS58I#7WIij5qniXX6L)>4A0hvQK82D_tTW3WXvU(-oj z82fT7B8|aTX=0Q5E%;&f)4v&#YQruqMKh+boJ&2z1dmL%sg7G*$+ADHJb(9q8YVSq z-hmq58c;`QH1!IvghnTx4azF-Son5kwS9oECZW?lDimOfPPK!P*czUE zhVw`-4Pj+C^(Z9(YnP$4IBD%o?kExG?6vSPutX50il59(8q^ZMa&@A+M&oa7Ot~4n zst^>=S+&eTafjE$7=mI$3*|Q0ERV#{sZHVV25ho$xFV3uFJ{8g6GVf?Uw-uZ z=k9*CDBDlGeD(eT`RWGLbX8*7P8p6%w`dspHpCn8oZzY|=r8SG+Cxgq?nQjJ${4ZE z!wL=o3C7jeg!47tlt$C}s%g5ZrNjAVY2dEk27`^XAqk!oej>uKrcB~SK1H9rhnRiJ zZXNT3K!$5g>BqDm1TxLOfRv+wG*dvTspxbRA9yW#h)7pJT62z6#A<51xFK=x5F_+W z-Q5#f1S7IEyg!H|-%R@z1zGvSIC{|$BaD*PL*S?vYU&;!gApc0l%9%z)QbMPZy`xd zJwre=v>m(?$YwjqP$8@GU^K+EE|>9O*@12VvXz z2l?wG5kk8@&QX3WFCu8v1K9V&DQ+L~07Pr#<8c+`hf#|o=9TQ6>(B>S#6-mtEmbwh zimfIpnXozRCps;G=&1K@(_*W8A)5d}Dy&A+gz34$otY%hO%IPQy| z_Q*vX2(M(xl8iD3(G|)rradD<6elRH8c}QF>-nN!hAHfw+O1sswfN;rKk?32f95L} zU;Xmee(K9#bQ+x(aD+NUB}*ph^5E7%>*^Cs6dOWpOrOP~o-IYqdHtYpEFfzGb6ZwY zyC~`7E-|(H{wE^FQ6@mQ%z?eYNrzo>{59fUzcpJ2L`m!%_^W#_+|J33GZ((2o5J{1B<>iSS|9zcVgv{oEe5iyCFi~ z)6k}@^kLYd&EVJLM2@k#wSrn5PC(t@gstsxlDJCpJUiyIys$m&37)L_0q`QaDd0t$6`GhXsxP8O z51t)LSYFf-jHZQBSqfHul#NpJB7? zUstZk#KlGL*ITxuvn!nxP`e#z-#GL>=W)N?EjZu4;PUqw z*}=HXoni})$?n#>%)=F(RD?*<#!03P$FlaM6l425$>B&V#UXi;wvoh*lw#{Ssp80i z$A)_4@7r#a6HeRmwe4OP!>>6!%W>H_Dkhn61UR#`-_AINuY2Uz)Mtl2hro1dcS+u| z^@gNbd!4rhSRe(J1$p9jV9YJ{oa`r2A*szabVKK5yS-5j|HHpHhCoQ;lgFo8&KY~F=4mZkf?33 zBFh_3tKAA=*dq>WrD@~6QYGw{GjjAnZ^VTUw6Tq3XR@8%IGkQ1JhYBDkYy5qfZ-dm zH@NgIoxhlN%Bafis|}*2w1G2D6LNL|$tj1tWz`(= zlBI1rx!-60!Oh?Pr9b;S|Iu z|D#|2pU*y9vF*=R%s2b()pEAHSuSp8H_PSKjC^ey{%?LqYR22mHS$dQ%iBv*>MbtC zw-w0}S7b=sJpIyvvlT>GZ+^ByQ<-rx!nYA%w9U-D`vqQ?bg7!{PG=pKzI6(=%|6@F z$vTw@1Fu69dc=C?`3d)uqZLAVIgGP4tU%Mo0`ALd*=CY4%a%<~%{hzlgajU8I3Zzf zsNz3`Hz3K4#d!clH+Iw`JuM&v^}xEEE1|1Ib>L_r6jj`@3tex!xV2e2BYnHqLC!J( zaF!<8NC!z?#m24CT85Kih1`1YAiI6VK&W7RIGYvJ^5ESbXvUiByto?g0EwwPmDC@z zULzozZ|23-#*rzvrqdyi*3xi>@r2D*g2z1BJGx z;UF^lT765pD4p}BPP!*H-)ir=(5y(F8V@QD5w?D(Yc(^R!&<>(0jd~~+wWW>YDe)t z){&3&BCsJPP1yz|e-M81TpMC50i!VRAdVV{xilw&0 zXfHCIp<*xf;HTVxL1yRm(-ZRNJs`1-@-#Pi%^%W~BGxkTBfnMl7DJ;-y9f1QW9A~I z4I5H(Bn0~Fx@^h?8?B7W=@>@C4Kl=;WLc6&H{WTD%#Rr&X*J|aXs;T=FbpcGKbo%J zLeraXB8d(DB+{Meu;PB_D(izw~@Sr^WD=wW=ch~_|4WbK3Fae|Bl-DT-Oy&`B;*t44{ zxp254z!VIDlzjvrW3A}rQ(0rUlEO#Z0t(-@Q(Euf%5$b&Tg?0z5ACH3h1PlP(Fq)E zRL`-V?M&}j8#c!x7*%i`>lxX8q%}Y5XRJi|I!MC=NDIS>56w<0=!8B>=ML1W>KK!S z)=VuQ_|-U!Vq&rCxy-rYE`+cTQ^wb1#l9k-BlBx;sLdkKW~_=p7qjEdOjDlfog?YQ{4&Y1NDNBBf=#gyjp0iC~(Au zALt^OY$~bW2I|9_ktCZ$BzQu@UW7NF_f%T0F3odO%Hh!BC*vrz34iNH&v)YE5dIG z#Ll{pJ#q5M#oDY_S0frUWigBoXpGf78f6+I;CL-ryGVEYNkVMNPoa~ z!PQEv0T9k{q(D5A>j)>#8*5ltP^N<_dRFNK3P+sMIV_zkCn1JYhB`Rcf|)!Ov(@sX zPc95X-%XrU+FTuQbs#rk`2jm(ZCNcxrNGp+lSCIX*$}v^U%e9XY{Jed>4kdTTzGJS z;z>g!;`-}#q?%3Xr#Gy&_&Yu(FyG&3lPUz5sj1co@2W%$oQg*2^<#)j!kI&O4=M!b z5mOsT4SkTc)%Fnp2r{+U0T=xnMf{uF4K-pFrWqaWObMGcW!*zuJ*cUp@samv`YfFQ ztxhIFw(jpz?G!^tLm@NIX(xAVuV4Tp{-$FUC3bS6dYd34cQ2gXwhoYn0gl69SPE6x<63o z!=A!;br`oJsIIxhrE9J*-#}0dGZB%S=l6(sgh&Vh)!hCGmSHmiX4j0s6eLoaB1Bfg z-U!>nA$^rMm%C0Z&1Aa!Xr)ojYJf9R&MMy1QO1~FMWv1yj+_-5+Dg@>nk$VSipvWE zr&;%I+mdExy?UdCwZUGbjWNP`%I35mO$6L0NerDCeOo4)Y73T(=vNTvK8CclAH5^P zOUyXn(Om|dqeJ2u*ewV{6Y3(<24JYblt>FMK8cRQP7LI>lYu*(QmSN)H~AP7n5j>K z#trGn(;QGU2}GjSL^20t)y!!YAEp2^!;(|+gWLCt6NiC{+%Lzf;HJ|`|)_ML5NC5!+t1(IW|b^)iEULW}Qxx z+x=ol!ca4d)07P|oNFkZqUEneAOpeOFTTdeXg;7wtqK?^_3wnjTLZ&y=|$Cpe{0C5 z)@X@0EqI10j56>ALf2VNcH372$BkGKfClI%Hk(~bLZ@nu12Tg_&^Zz5)*1+4r$oH% zbV%~RXsVi#Ctw{`vu~+l*0ms_@MJ>{;IuUk4yXJZ`5xRBP@Fm@3^MxNKfVVkoW0|ytdLwXJ6rj;c zyBwaEU}|mOWygYWnip1dp>{&qqI$(MxMcWKdMcW1z8MYFhJ|9&ylFbU*A7S78vE$Q z0ViJRS0yF&&qO=1&*--it-#(aP!ub33-oBFz)a@>lUd2C#)W(gq@!W$5^|KKsUytQ zCl8^=cpPGnP!W)wriO=2X1l=nU?cjNQi5n^O17*eV=8u74emCK?$`m(MrJG!ot8z^sW33M{IQDL%=*zquvWo zkIRFn?ME{s(iwAdsL~XcrfJiRN_Cs?(`KBeYgOd}J3Ux4{2SxVz|H_oTcTp(r~WA5hMI3iNpVi?V3kJgB=`q}O(YGI zK6F|^?I{s8AsR3u=x0by3%0T7Kxc)06@j2ygMc9`%8LL;SbL}suVt;MRe_2WOd+UF z!X*yL5}fx|M0xX{MvUq_Jou<&Yybm`sF)!ch6o- zZRa#Vg(}S#-#=<>;<)O>*3d$_+EyNYFHKj94B$Pzi{X5+qQ%S$o4@nUGlAqj^iB6} z=6P2R-@G*eV|PMS`(Tk5OoIeS3~O}vQ*&y!Zt|^?(V?M@wT^}~Nob%4?Q$xou3Vx; z+H!6;3Wman0;-P99aMGGYHTUzdR5ntKGvhKHl?}f&zh=5Y@W4vT-d44 zvOz<7=$2G5zQft97z&0w3L9F-uy&o2==NoF%=QFP#T6rqhMWjSWX#4tt^>yJxy(2I3epS8-cjYiGn%Ny%t49` zML1M3P)_ex4zXuxxKO`^m^a_dFej;;+ZjEfUw&Rf#iP^kY2_5Xwbb@7@V!(U^`7W@ zlg=1HAib*bY3W^b=sjxby;8aEtD>kK+?QVE;R6$H(H{g2VO7*~7EVi!d3&qcKtJfv z*}pntkTf*fbsPy@NW4ov0OJ&$q6hU`Yk2&d0jM@4tk9^TNja+AbQ&BfXQizdYITDv zsbX0zQHiRHvVW!B09|5R+6^HPG!8jZYlP=T#EvQ-;cdizAV@LMNZhu7{qTfbG^o-7 zz?qZ{hJ;mo1PFYaB1@?E6!<1|E#c!#h%gW~Q%p%L`DRQJZqdb5@6k-xd#Ho8WH1M= z;nb=eL=bFk&`tGE0$f1yrj`!iMOOh7;k(TeB+yMrU(U)m-wYvRANA3^+IC2!+SQ*! z5)Gb=@IqZz{^7^vS5Y6ZmIWJjwVA({>o z7EHf{rg9th8&O4A{-WGScPuI?hQqXy(lHt28XIhCz8OoUE)t90loxuNj=8lO5-Js~ zn&47PGKdM8lI*Hipr!|Frj`%X!mzkm5DQfF9<$IBLF7`UP7i_z284ezB8cWA@v#P` z#V5*k_%-<=-1{7IRE0`Ht9O&dDC?6eSqOlYizP${IU>SCkksxgJTHSOOF_cAxo z6^@3IIa3PXe6xncvI=+447m`MKj5AWyJ>le7CXg>leXf*0s18~56I?$S|%0~rY$k` zYOaxn(X?~~qMdYvu3JlCT=ZLT9?`r5a7HU#z99k)QWjX2kta_-SX@4T_TGypu!I}m z-)hh06P;Y&K7amX_NmBILR{S$S(Wt-?_!yF-4D*yYLS4p?$K=(4JSCYOrRJD<``7Z6Oyw!zHd!*`tajVooD?VX z{gSjeOETeXxY2eso09{Fv^QJJhr`{rbKT8KmLJmLEV!S2yGZu5)@C!GDn|Guc}zSy;^c7`--eEtL26yEZn-wehmx8h{zBFhTF|{xzXM9N|Zv{Ba#JiAuyK!uQ%Lb`%aa_{GM<9hX3V{{9FI~FaIsO|Micbm(gW6o89yoU1p2*-R1n6 zgh=~qII$%u(e2fYt7oroU!9lN#RC9*kOEF(OP>d_>u_US#?+B{@>wRStM5JGmwXX%W;@5h!6#f? zPKkxHYG;&YOqk?&ETaahSdsU9CeDjkC~xzx>Q=34qm@E}XjRYh+VPl~6Rt0xKfmR^ zT>!fkZJyped-dUKA7An=f(f~BYb(0eEY?5z71BP;-hHL~u!1Mj6U3HCOilIJKcES;rjAF*b&_(jj%i_EX$*nlz>^@5jt)SGwr!LV1eP@DZyF#? z>5A@#YtS!J+B6Y;Nps`xRE}sypo{GBGuYOk7g$(g9My^QcQwU@UWPE3yp&Wvu}P|u zGv{h_r>|^<$xfo?AD^CeS9^!J8q%!7NvSDf*b#Mz+n%0uL37SBne&Sja--nRB{su4 zPl1HIS&-*XOy4$7Od&T{MN7g7yjg5`aQq=bF&}$4}R;v`1!ZawZrqpzz(lSVul;I-e0b-cZ;j* z#m(&UdNE(@_sgr>8>}wAXmfkZ#pQQII(Ik9561oFSF73I|F=!r;U9l_bNh3zF0Y^8 zo@a*JtIKnh%f_yugpFBtosf0y&2qf&^pJP9zU7IzE-e?!Q?-A8so6=TMybFidz2}? zzelA=zQ5G^q*9xcN^MUnwL7WQ{-jd#)5tnMjYsp-$Ui@g{`1oqFc$~Rw>NIr3^7v@ zZf>xOjfaxe)dW;YFIEkwUu#csv&`}Zd@C!mW?Sb^Vw_!@iJ6ILCO!Upmmgj{zrB-> zFaF)TZDum?XSCxxd%ZU+?GuK3b4G5}U%m>M`%Iq#V!7CE15{NWtUO;_0RQV}*9t)H zS=E+zJzcRQWGOy3X9jL-q9kF5Sy+lOG6xi~_CKp>fSj2zR+VH^3#Ul+nGxqGg7*n@ zRn;H!PMvl}xFAO7T#MGt+ZkZNVbzTx%wr`OwAH)rI3~Z)VhM;+R9qK_36Xk;sq26l zhLkWYpOZ^3TL-1*Hv&Z^6lZ>&qXYV=Htm_fuzBv#Qk_PA<=~mYU95#}#4XU&LmEA+ zfZ7+X@r>-)qq@*%Ba63d8!Wm-UP%&#dK{UEGYnEwx?KQMr6rd|Z)hN82946S{1~qDr}7}h9@s*%iyT-X!N^KSmb2|jsC`D552QngI&JovxWgDM~sSf z)t^8uV-fNF;|x>PsRHzl>U1io&kC^TrW+H&+{l*J0F37JXG6@HACcj;@cej13&wRw zrf6wg=v{v@qSdBnyGOJZ1o&dmLW78hh*mQgd8>7E-Eudw_w@DqAE>XpOHT&-)IFjb zMYK#o0=EHJxfE52dMJiXYFi0!DhFLfo#BC%NqA_3d7#4+-x2o$k`Huj;(KC(JmcdN z%K_I(?O}CJDu>HGsT?EOlggn=CzV5SPb!CqpHz-Hj+4r1UgNZalBv36afEZHvSfZL zPqgjkw61%uoS6Ro;~hWyq^#7(Q22Y7nrz)MOG@r;60noxn|aaYY_lW%HmTruB>tW; z6-vT)61~?WQd)xVbe!0B9y1OAs_;$a#yS$rS&sSHQoB8E>ce|d= zb}Q0jUtQnsZ@%wlx}8PL>)CD9&?l7CWA!p+p-{i>6(~v7+ro9wj7E2bn_Z9Daw)tN+HK4I;e{UQ*gdOF zPfMXZiS`Q8#HfW)uMIjW+R#E_aNA=ft2R;x;@Zk%QK1^6u#ekSA}hBI%fLAen+(E) zVbRNGmIF+%AB4wx)}~uyvZO#oiZzVpYQM21D8$9;vLi~5`m0@b&_s2WW{hfsgIbyv z#YDXTwVvEUUT=aHyIF+QTBa%UO1G~s;`hZz2s2@Keb*{jiHt8Q_PC#{FPK7!p(M4R*t}@gkwf)l7EHZ>Rkn$;MMd716Z6d`&0F5 zXvwpW;d?b3$v|W<#1R$E^ciY4`3flCwkgysG<404oVYle)t4brje<#v4rO{eiqRs1 z$wtiVbyO5&2)?MO&qD_u`lC;9(c13x2g$7p<3jJAAc;fYoyc}ztB|ObKqgs|0l3n{ zY>EM@dPu7V3^1O^+{cPY_S7*T+UpERFd(}Mw|kHQNu*BGqCvAUQ-qw)I#V-`5KA!1 zfhdmj$kGuQx>~-P^Np|TBw4BJ7w@hjZ@iRy3?K3tP-oAsX%qscqawW84lxs4{AT;+_7_BlOWXXl-DY zLD?>*o0pm!s?zd61wKK47mYv`H6QEmvR^(vTQQfmH0r~v2Zd!dwr~Lsw-OPX5p~+F zLO=)h;{DjOz4k{`#P1f=Ms< zQ-N6($<)kfzOQ;%Gexgrdr~u_jb1Yyoj_SCHUh8&W^?l@!Jkg+D)LWUXjBLZ=nCl_ zk}I`~0at3E%L*{4h{!&IVb5W?g!{t}a*#^sH=IRO+FWNCl)~uT-^7gu1pmA0< zx-%5t_+(M2$)fGDNO+}?%`S_8M^c#l6IQf2+D^eLlFadey%ZIRLKvUc(6o0)T-uLG zDqdgov#ZT6jD)PnM#U?tdsWSa#VnWCHoGcPyI`pukoP*TH1UVH)eW2rU(j2uxKcUdZKAN3e>YRZ{7qr(jQyd7K5Y<;)KSROO*Ilmsg>;_E6bUZ zpQ6%XoRdHAeqrPtr^@C)(sn8;wC~zn$E2uaFh+h)HvTXw+RSA_t?ED9`B!PC&?vjL zWHN%bBD?;o=}N?T69%s{zk&lk^yXJbi;ODn;G_Lsb-j#o;rJ=D4rxee@UG-Cp* z2q{x`77eV;|CQZzp1U_HAuD^8X~hqC4fkmVwW6m3W%jxgoX6J(P(rszO3BgjUf zauZ=2oH7$~kwy^1hU$eN9gaqjg0KfrMxSWIK3StM$%WZ?CMYutj-|H=XaXt)!d^P* zF=p~c!d_SzXm?OrCk-x9#w$rDV&26!-dbrvX***DvkgM*jW)-&Z(3_zcG^a3@WO{e zRz2Ix=FHH#(KoX2-KgfUIu#VMP6pmZ81`uz(v_^!13~v*h4*2Av!pql?AqqpeV?## zs$sL)E^(>%O)-a{5B#M>moY!*7Qx7jmcFZ>EK`7=FK9E&l=Z5{hG5UR%(ZpJO;o4a z+87#0NL1b0H9yev-1xGg7whxx5ZvtXhIxP&NWum|i)OG!<-4#l_k% zQ!idjq*s4UpWSMMlxAsR0VTw29Ex#9wLnKypCOnTu*Rfxaf(Pu`@NAH#wh`z$|lQ8*ph}#%x>K%F++NfBT-AV93`fr z7Rjb0#v>HbZ{&`*^qqaV9ixz|2Ae4;Cjxuxe~l@Vo!v6RZB(~ zKP@6wLu>TSRvV*)i0W2m6Z1V;)f}%VSlUUAa6Nren>Bsp#7r8IYIR-pLeawN6&_No zXqvU{f*V_R7PAOTcN&(fppP!2vBFMqQt!+KctU3PvbjheLRK+ty=4i!Bo!(d>(`B> zxiq==ljxcF>1CmMG?*O?-Xt zwwVf62K{BVT!2|WjdqBff)0448~W^^OJ_TOq)$+~w?}FlcOo?r$LRbG@Pr?^o*f|*TW6@C<~L{lL($&GO| z=JhJg!;hNWK+&G)OMo}9p4gE7kcOb9VrhJ)0>svXz~bA$X{_B6#RS$?x^4PBEXeNi zBBJKryfyK-XnM9DIYl|SvIL1vg`Ml6=oKil&n66#qr&G!#+Ui2UA{mD8=GnJAWH5#E zcg`WbFmE*^JX6#)j@JTFn%yUJiHR``Jd1CP7h_oiU++q=2q6@{7Y2D8W2oAoL=Km< z`K>{76&8E1g<>B!*5GVB3>n&$>u$^)K7X}PP3(#2`n%!~@DH=~6wAoB*i z5qLl*9f$4PGUI%FGhoT_?Sj8rwf+u?Kn{C7c zz%Ew7G=q1r$*h3?#0I1v$~E554z-kp(ikp=lR#RJ$XoyivSM75ZB(_mecnYxr zJw>%pfuvgOY5i=^u!hVjRWt?FHASV5daE}%R*wojlJi-J2j(lp!exBlZ?Ce6pKK8r z=PB&;VBdlx;(9dj(EY%(Tp9PRHsS)EjRb>ttvn|EfVH8Y2|l=XDDFlmcJnk@ebr1? z*<8*D)yB>MO=@emP%~wGgi&KFlV`;;&ZqMwM9A6jU&bc`8fuR$3z$4`F2`k3yjGZ! zl(D6xR;)@uF<${kodTkw1mnPB`>q)Q$e7tsrxNGDV*9?K`c$oUy-=Mm#WqPWg{1>| zJR>qAupJWNDAFaYr1Ll~NtdLZpW}!pK6LC|hFs}e#+Sva*b-k~ydNYEtO?WZ9!!4r z$wm#fHm6QIFnTo#hkt7J3W#SS=ox!C&BBJRdtG}IO!O{R4bMs!ObYwul@#OMfaE!w+ljg8L5q?kvgPT)>lTTTe+*rq4|jc5 zF8X|8`5EHPz+#2Pth@d3V>E(yQj4mC6sT(xV1eS#7PRD;L9a?g9_)$kM(@?EN8R)x zu~I>oA!}1z=npt-%#}r;KoGD4IlGDBR?hhqEE2?I$!P4Gq_$lWhI6BfqO5v=Qw`lZ zIGQ;*IyHV4NnNgM2^y3Q!BsKpxr(ZqobV9; z2&7e)8PaDjzTvva>uGE|j%qT;%X-En45VX`gK>hcHgqhi`Rx#9iUJDXTktg*&$kBw+Yqg5;d z@q{f+r8YT@&0!sDP|ei5$Q^rUg%$Fg!2mrC$&w3YqKn(fZmH+~;zgsQHpa26tMLjN zrWtepQ|HuzsA55QBArH%O_V69w83s1QK6w3oUWf7^}Z!cGK_9^Yu_Oa>deV)Sf>e9 z>AJ3_LPOZ6>o>52w;|>oge2y=n)(TH3FTB>5!=~iUG{|%wn_z`+*i?POQctS`lBgH ze^nnZvP>tC;iuv-ftp)+N_@o)2#@om5f;x63#_F$5uK*^CMmFcCo|J~1UozNR>F|{{&;#aKIVpw%!(H}QfruO znwy?Q^$2#^>Dl;LIxV;hWClC18FW{$!<$dcE|i<(pAM01LK`9w!`=>UGJj$#eM{sW z!kkg+1;XBhu!=AC;`>Km6LR+W%tIu)w`y94S)qkctaT?BS_`A2V^MEvbU1{>2#|ol z#EXE~rgp0kc7Uvh43MytGi-( zcVcA{u@DiEF}ywWGh-nF7kg!O4)Pgy;Z^vGKf9wO`bp^IP!;6g&@L7&PF*Y0aE%Dx zCPBrUIL)n=@GDvke)XGw&{sI~@ljicFPXLQhvwweQlu|SxvSu>g<aF*9i+YEsvtTg;ln#gv*F3Lp5$k&&;k?{EMuXrb-V0stt zlD7-vG6aUT5pUq~HW!cQcZ+unWzMb-r4Rrsp2r;c4LZJ4yh~VC+S)Zua5c^674Kk( z)mk{n2GS}}3s{lpTNRlnt;9zrCmrke>c3xo82C%M4N1_FuS~?hWUHJf2xZGcai%Ve z7X$5Tl{e+Z=IeJJ>B`;?eL@!(AIml9VPa?|SnxNZ5?UcDK;k^AEZ0+01zoJ2Vd~W( z6Hrv%#;VW2lZ0N-=yz~(?!l7qcK)DkY}JEFSZy#9C7N*nOWM?jc$R-gH1mQkrU8lM z)1WZ5a}&qv`#ho8Yd!p0O3A$^I!tWc`wc|dT5|~OibF6Oab}+&5y1XeF_5>2fwdI~ z^U}|XThlYNrhr6GUJRaq#QS*3v#VKo%rV~+0u2`Mp%#%ZR3Y+QcP1ELSVs_X@sJ0v zt+iQJCD91p=oHXm5qN>poNrZ3{RUKs#9eaKi2nkCF2y1;0T`z4l1Pvp>v)Y+#PJa7 z#BNVI)b@}%v|qZtrL_U9Ig+T^k0KRFPp5F;U3O-ovdgX;`>Ui8_U7tsvGNnP8V^j* zbUn6MKc+aZ2MH{#*T=2K?|bdUt3PZS)4t|Ma$tvCP?Dewr%)kidF!C0w{0Xa7TKZ~ zm3hP9h8M+5K_5*ODh%sg?u1N1aA zvmo1GlxoWJ3BHh8oLm88!7T12cGKKbJ_|5wPI*2Jw4ROazFQ*BX%|3)L-r~!b*sZn zmpTYoOM(98Y)j-N4{Ly3&DqX1SK{K<1Dwn|ZNTUSa518?XptG;swSGe9G{}*Y$#>v zuPu;vL0*mD%fevn_@xQi;oxL4lBpC8f{~>l!8IR)#=f%;8W9>$-zcPVm$XPX^8Bc3 zl%xxdpdgO?Fojb5=_Ob3IkY3`Z97NGNh>^H?g({M$imCg_TX%EowhJ>d1~pk=xvqt zfDpma1u7n)o}6P=^T+O8*2~e-hu*_dlUU?rOt6%%EwIv>V00GaJvH5#n(jv4T9WSe zBLF7JX?C>VVI`^;qpFy$h3P^+JUd;P9gJdn`v!V#@!tn9p|9DIo2w;*kyYDcritV3 zRV)voA%sn@LQeye6_Fw_>m&d@!G)T1>|{lsD6rE*G}+&I8a>yp$oRs2+an>^L*}f) zL~9%#upNWM@baneW^`56fi7i zRhD7agVjhZ+|)ZjBhSBP2r+5ald}6O*>X4@2W2Lr=msx>H`WE2WE-~oxmEofbL%1e zM;yCK7={=BW!Y0Q@uRU&C-Y=v8rMqWuf8b9A}Z}E4o-Acisa9~B3S@>$VziP9Xi$F z;Hw5ay33T>?3NJ>{Iv_Up5)+;wpEF%!FH)RML@za4mHnRscNVchv(rg!&=aUdG^MR=uFb}#$H`b>9>|9$UGl{e~8 zc#c^OAx~y`TN-{r{H#pOn{A*L>9yMSa8n!V2AZYM{5|1T2PP(n5dn|W@fMA>qK-_z z17(qdLYEQn_cR6P+he$KrPsr7rhhWrH^iV5dCl3AEj@TK7#%CEZkdb5tnh6-fPF(gsrs$-vHrT?|Z$a z^_ohMtbK^v`k5`ZuK=-#HoXcqk=_P!L#;Ddghwt9$6KcGG?_!%(FAa-(s0GMX8QQH z78?&N!*LH+d=qvfj-Yj-Y(bYcLVGXzCMPp8J&g)KPWONrfr#FI)=CY6@)_y}L0L;m z$l`vyrjf_Hu+#e8$!jP>?is+x8zAd#szj2{stHC zWgg{^d6rJi>n~v)>`HU8jGb+&^Hy!uw2BpnYz8Nc9+IY-R*PUg-z_XsU_ z%zZ2v(ka>yU>qKIBq_9r+Xg*RUIvDG@vp*`KW$qzOQN(;aLs#$k@_&JMu*`X3w^1i zkq7dSYO*p+KLTG=j+7+h&W=If%Yg6e0iaaL#Uf}+fzOrsVIfQ-zJx*0Th^hsOVz_l z4)*C_pt>~U2d{$MSti2!(3Vsd#5Htpd4-5VvP(%r8WL2-S~|zuGqiJ7AE#M*2^3B6 z>8CL7xtlxIPgM*+3(GiK!ACK`cx9i(z(o95^h0##=T-bSh?# zQo*JZk4aZjvbXp_Y2ZN3IWp1^&$vt`_14@0?+`&8dIh2F`yB7!K>v`2mXbVVPJ#mj z$K-)UIyOVm##xs(1tZ((*5g!G0QDO0KBFwUDHJ4%@V)e@P(*Llx!9co<2}-MQa4kXmca9d7{wauN0`wyX(fB3fj`BZd z5Y@iQPeJsjAlktfFWxt@rH}h5h=z&82O;}G^e`p&owh#@qVc=ty;&Z$@qfi2dMKxV zpwjkEB-BmM8AJ~(jUy)h6h!ABT17P7*_3y)9QwPOPN_D5S2s09YEs|pjMeKQ56Z+w zPpw}^R>LJvOQb$z;d|W~>eN!msP9Fl7T%{vZEklg{VIZ)T0H?O8ksSG}I>4Y@~ zChMR%Q*0&p^GD2-H%RCIq?6KpG~_u;mu`%OW-sd}mR4?f%nGR(i`u-;S@CHiWZ^&( zk_-UjnUE6jr}1zV;sko3@)Mb{vuDvJoYl(1k+S`>4T>*+{m-Cv3o)+ z?t-tMu(nKCXtiol_UMvXX!%;4lY%6;k6kN2TS_u+HKzClgWBjo|0vU}_1!YB;hFTw zL}(P4^RG3?AC7kf#FXS$7LH)BTJ%2DbF&?$tOWrr-+%-9f@Snax#(Cf&mV7u0Z(-x z_nz<;DP#FXKT}(0W|Kn^`gousL4}t2>Fwv-XUOM;Hpa-UNeQmq8xzeHr_ZHpTcPi#*_ALHkSC}fOss-AfnXQ&J@M!+11 zQyLn?<+6Ac)kDb>^~iG1L&-vlVnyKeN{=rOX17Z^Wp% z1_^{ks1Mycs$ccL;nc?x-xqdbiidry(*wgG*3KXZI2l>yqbr zI8r-Xd{m#7V#ddkr~AdL^z!u(uIxh<&%jhPnkQ}h2ibmjcz^T|A}Z}E4o-Ac$UT#! zjMZAmO45BgbUG~s_8Ktq&~l3@wYlVpV9HlV0+G-Vnp4c=2`Tt z1_G^pn&1v^xp%g(Wf?Iii~~Kvd^{^K^%4$emU}!m!#-!*Gqh-b+jIZkzt{D&G&<mZ~NVHP`9T6*})O|VfA_*?fP(KLzVUNHZ`nv}oI1}N| z7847hqo0~T=?Ki>UQ4MEOXj&&C&A z#&&a=oxdr_q~Akz_$@wT?B8)4cjRk0`W2q-I9C(=IJomS4Mfq=0*I*k6}3 zD{=`Hv0$RbV(zr#cwt4glaD%if0D?glVJ*}BmgU-)B#DIS$MXRN|Hj`HXqFja7ZV} zHh)8pHjW1C2#Jl=6)REc)A&LKL6~{NeOTX8+{U1@hlkY_EK~3tN-^z)bw)ua*bt`S z!Warg({2)huBa~w14HrYRbsySgVsh+y)1Q;pUPPR?k`l-$+8QV0YtSdj*7PAL9!!- zJ-mvDG%GsoI&u|jrtgQc51M8*sRJiKjlkoT2aizye)nE-<>>jqiJEfB1&2tnmbr7u z1ZsI5#he;>dw^XMjkAAvB*PV>XN$V|63lB*_=6QNQO&+onv4^qKDJAhpp_Ce6q z5F)EY&AnwZ*jX#D$Tz3SD+QwKIDPWa4e0zE9Eo(2nI}o;31-y1WAPPz#^TGXKh{<; zlg6Y-a`^>Xk9`t~!+Opv`DBWYk$8yQL7|(^RcJ3B?FrTFPQOI$e)qF{V+&BLe!Ii% zDlG9t4y8uzVnDOF)~m20*CNOtO>|o=3ftivu(HKgEp2iD(QO%-$~>K^F9z|bT}O;{ zmCW6}O|$h?#y)@=qMPkbB&2$X(DjPBz9tZ(iQ9iAi?%dk62DNV)k_GWT%7sCiySb^ zYjv=PTW$OJ=XWBcm8jnxJ+078D;a^9o~x{7*9dQ6HpRIV7ATC-0@GLR19=zob!nNIA}w(`}--B%16X^ zpA}nN%It9aQz-pXzpk;d)we^bKj-*!3Z?oiCVc&UoJtucP+7`I9rWu-*?*i%`6-k> z3#F&J&Q7(LDh%7plWSGxM&cunKjm$%UuD)avA*oBGMy^t#HSdYpAnmL+2(RYM))P7 z(560feak_MBqGOR^MHz_yb@%`{CK~~$Ml=CsTBE366#IN0#OZj?~&-L3^k`4uk~Gz zu67rXKvS&P9=8aa8O7aXU}PnaCq1%#+Tlt&{hp^3Zbg>By-_TFdK|$iGn|DrA1)bt z^}X|L+{i;SmZjn>qSj0~Ud^9WFg(^TO1n%*j=6CCoCkTlM2U=Y`$MuJhVv?hQCyHB zm?3BuPy{~GaUN)-gsvb|3LlU2N-Wk3W7B+^sg@1T)?x5U!#B#g`5*v{df8P5AjoaJ zX)wj{iPPJUj^LeW$sYA~{&)#u#vWQG{Kl`H!0|;qtI5nW$k;jttuAekNB=C(6mDQ- zEs|}aeJZkHdgbAGulxu95M4-y>>*+wPLB{1c>zFK`ozMGIl!HHRzD$h3a|0qE1rlU z@;X~m)2kd`{HVc)(W~Ni?@8@~9&IU-SS&CzJmN478q)rr z%@2B0Ua8FwUivvVKl0ha*DFNdpF1WXabKE|faw6N>DfzOz~=Z~5oNj|nkpXJ&|{9? z69bZ(%(V_2r{iow+PU*DXdwXE1Q>cB1tTqJ@vA`q%N6gr?nky23Ix|0Bqyh@=e#%J zL@rGrV50Hi(WZiI%&mauY1A>pXO~gp*_)N%mbyM%cfv}sKC}ZC;n=t)jS@13S=uc; z`gd*;mc|UvxS2+y#aQdb;9v%M8p@sq_pSb@4@QCu6gMi$7dsDPC++pzzeuG1t*|f_ z7?RSeVguQ9<0(0-LA!C^d(qZFmk@V|pXUe@Q8;9x6#j$ggjBELpZu)|Qi$Fo;YY!6q;rW|*%~ ztVEJqm!XN?RulqE*R9kkimR!r3lUR_s*z;_1};duIe+zwz4WlOqP?r*AJB>o5$~WY zU0(U&zQAwC*vs(TcmEc9jjCRj2MQq>VxIXYmhs?UJ2cYC<{2i`9^!k3M!?D&ghBt9 zp>YaR5Ly;6xLZh+SCJvD7PyGF4jm=$9$2f<(~OIPzvT%W7)gVy$>L3hGw%di1i}6X zIbq1{LLnjKG6Gb|X^Mk1wfD>1f}za0ZBLIsErPd5Mi@;dGJQ4EDgme^9HsZCLpl3!y|c5YrCdV}Skaz-st?IB zOX*i{oV(8!-Sp?75DdeKZ|P5O#8`#>F1x6CpV~ztQ-F|mdFvvcr`4Q{ zIabibAWWVI)K3;Qn?gL!rVu7P@{g&SBNN@I*LvA~`qHiCT&YWt!d2Ckhd8GLMiUpD z97C*TDnpt$T>-hSkO9?^CFiPmW%yCQd{Urlarqe8LA=u8auSuV9@mSGsxo1R15;zu zSk9mjj`cQRD9AX7)I-)Q>|yi{82nw;4Eui`AceuErTxqCn8juh^^ilCY+BWh^&yb{ zP;Y#W*$cbXd14$b2RH#}?RF5KjEWy65u^|btnlHzIX`(z}_h~PBC>Xm%x<;U~FO`Y&p_+mcm z^rX$$MnXg#;*x$k&0djCur1wB{BrrTPSQRgY1w&}T{_C22vaO9-4UbwCsQFM zKUfyh3O|%%;UT>Ue@^y>4vCeay8tqra>l9lrxPO#S@@-w0>$(JCztGapAB}(F5Rik zNc1=(t0y>m^h#}K+@V~!%c|DzWt@MXJjVq(ycQjXdhm>a&3KzmD9-dgj7?RDGrA9N zO9k}Fzra4ePCiAl^!Pft5@)3!UZ=jtIgy9AX)<1NKfIJ)KyfJMmf&KtITtVK97~Jq zbS3N}n;aD)hmrl3*?E{3^VdA*o$u^hM|K(EUvr*rC6a<>lyk1YC=)6=;*#fC@)d-{ z5LZGNps+K?dk8OErA$(y ziaCQX|ME2syN=)-3bS2Ga|f{58)_FSxa~yIerVepzxDaJsCz?TVMqwm|ZR)B+*efs{jHvZ!q z4s33@f8r5N!H^lAXu(Od@$LylElyj+G}Y!7QH%Imz2fn zakA&k9K+Nmg(PQ`T(qY|FXR~csrPhA#%Xe5+2y|an3!x^oso*ycsY1rR%`A4lzChl zfkZXd8qwcnlvgEmv(g{W;GlmHQ5685Mfzm8xd4hE6sqz=r)dim_bD^7J_ zHAvK|H;&f0rJR|*fS^MedM=5FNqbU82w~IEi|pgM)qEVn17pvcy_@1w;jj=$SF~m_ zpOcoJLCA&L%Lv_5O8vx1xWMKiOAOPZV{#JNDOzk+xKWuxMYT7A85Az7vKEQ74f z+wPwyBf6%Ulne?6W+Nh4)AfQdFe=BeOp@_@ZJkH%-G)Td7c{0&)=#m3nM-^ZUWie0 z(2FY4ranZW{4;z#{xZj+#YuQ@x^OLSjXLn9!h=7vHIC_Iiu!B#XT>Jul>*w(=d!$Q zv}O_y7OdgR#!S84o%{*|pY$!(j;kffJ<^dtJa-f@MP`-KKp&RmT-!up*ESC zPv6M{sq;`hAUG2g6Z2T;J}%?HFug~1y4O-qpn~)11OXHk*cda|6SP#^gcv}|oy5vh z&nRQ%34jZ`&Z@7T5f0oaL|;JyBBb%=T;CvYx(Rl04wYxJ>N9NYn##X`L#kzafaZD4 zcrcr_1^utsd*%KYTZPR_Zz{8(7swD_gJrRMLq1{2I1k_{JeMPV8tkeU$7`Le@^5nm zogfOGWwA}?=`NVY2zn+~WPW_o&&#CZKK*bWTEvxL^5(Nv3EW{j`bBB86$l%EQ^}JUIDpGP|Suy1U{X6p3cm>nGvy3+J34!(YdHae#+?z<<6z{J<%j-^-RWmQz`$5&fH;BP zdt+yCw!frjzG5Oma6iV<*J)wwemwMRM2EwOFMivpKofESSOUbc^s=qyjZc@%W#Maw z96U1#A)q|;Xi9cErNs^HHdX$wR=%B=K$7>E*=0eDtIpW=TI7~;4%-`^jddU<>H3BiDlgrQY3zSpMD65pF zJC6h5>;u!n=WxN0#63?{+YplU{F_c^{+{1ieHQ?!2ifV9uVvL2aP#>UtfxY3 z+^|8*qiO4V$kmF=i2Wm9v36l3_$pyryZ|Ym@=F3g;v&_w#Iqf`(OQPKq4I#4R`NH@ zw7;#apX-uIp}a6e!&j3!y%0@W!Rcpe>i`Q=h}bYX9zcNQ5%bgA&&rXe=Y=-L$ffTt zzj`)}hd`cj|F!(eXPYfk*UGXA`yyl%o##3)rsrli9w$=3y_b_CWmY<=*)vvX%5y$Bc)=1`ZCV8ClDnbbSkc ze)q*8R(`Xcti`;?9VF4P#Nq4@--Fzx!bfjw(Y62=oI7SA8!0EI-2V*`{L|=%BAh6B z;~K^a_@JZFt5V-!*{X4bs+>&oyLbaAVkhlme0o_LNyf7>?NoYZiCDcd$?FY=I+9dMHmV%?{mjXqZNk9Z zK(M@R#-WGRZtHb>a%al9aY$hjAmP}Z_QO_3+^?jGinhnf_YQ5lrEi0tX`~@@9M!Lyg#zu^SQ&G%ADLDpC>LD(rMBd^C~hFQboRBuUXtS;fC z6gejxwga}FBO|?e8~AQUA2bI|rLBj&R_pXSPSQT-8DH9&L?%nN`Wb3?UU=w++^{+M zySvH${L)OosuZtgyL5YdV+i4&xg&R3dJQ)>Fsp5CNe%jgn>m2GuZRW=}vi$2&EgC+&{1UVb8g&F*@DOzLoyfxWs#}fEE z#|f-N2MtpPl)#E$xbvn-3f}|NqY(FPqhTVM_{2dU;FdN#+f2!MSB=RJ7AH^TLT(~u z<8J8#Un9E7f z&Vj)$hfdfb-=eZ-&J{#dQZ)>8AI9=L;d#{*X31x^1uqs^ ziiu_^oxo?vCXK8XMhp`QpS;x7A^no0CrUE*d4K)H#gE!>KqK#DNPTqfJT=ZKiM(Hiiw3-3r3t2%CV&Ac--94|5o#U+}bszugUs zm?c+q^BEsWK$OdcV^+)-mAd7sUne{dWiMwx>Wxl#{R>5U9=KIT<1dVCyup{0dK^_p z*Q9+5UuY8~Cw>Ylu{!D>!SAs4V>gApYJs|JM&M6l(a_$KYylB!OytcA+47F*% ziiPE~zf1{-G&416e_1f2&Y3V=nzJg1%3+j=AEc!>J1avDhv{#kqVM?q+EUMOJ|9&B z3!2&Y^#Ju0gEwjrSj0~>q5oE0bsK7+2N+h0H*7K`Ij+|Dkb2HFX-dff*S1Q|ypY5_^wCUo^Ys)EC>E4r)HyuDNHTSkxl4R>4 z0+>T>`Y2~V21=e3r6NCO0C#X1Ddj0YV-*>cbfP~nKwL<;`&h2(N`>^6{*HAouh5`K zelsBnm^|!)oNkkj8b#=3rtbd(Zd+YSBIi0^S~k~fp>6(MDIg-&yS@AY>wkcgYaC|@ zwMOm)FkMFJ0&_YzPK)q|*qmd$4nx{a$~K!dDM0EqSIzIGf8;HCE#J~+U@Hg;M`TVC zyu{axNg}`6F)b+5cSXpBNgB1W16}*Ngxu_vBU2lv#hHv=EOo@CYY@91p?M$Oduu;O z&&Nd6q_hMxsp`!f?Q>rPCPfzJndzBE73o4tQqUIE_s0W{@i8}Eg88?ZMy|$~EV%8t z%b@A)gBce39`5_&C)`ChJR&(m9fIyU5@L=FX4j&Xj^jMYrc>cIxDaPSm{V;CE>D7_ z3Su2jTzRI)wxYzyV0;|?0F7t;VurFdpA5{2leAV+faqUl&PXDqY1JC>QyuoC$=F^~ zZI^tt2Lvz5k9UD*d*mZgw$iKr4h5xVz1%a)v#+@21ua|w*KyW^7V&KpMf&T4B3V>K zSn z6M|FxKA8KLauriYaMbVj1Qq>dz%1$H;Ah6h(vyd!ZOV6EKBbpnm1BvBHX#f7>BsiY zbL0d82UnY>AtvQ5UXotmBxazp9kdljr+tGwS?1%IG_2j$cOhX(W*EubyKaxA1s6LtbK z`j*?Mj*jkZ0G(GjHoITAo>A`1;Jk&w^Mr$wc$9BTQ&nHxH!Ll1pfimO7$JI}j;BBX zV8|jQ1NDZ8iqzqECj@2!reN)U!pT97GoCY^vom3Q_RDY`xEKG%(aq=;j-=@uYhWXs zE3&w*(z&E4TKgoJ@-p83_1Gcx<=sTd(4u0%2;V5ip3>BC2c4~#s`%~jCB)q6X#lSn ziDNPY_DDgDq-!0+D>_ugAU4&^8&Do$yg$M-79-+a)$PGC){hujZLB&~T#esV8*5)e zcOn{E0JDYR09iSydJ)=FBOg#mG2-bmZb)TrL zwfnVB<1Jc{gFZ8`mlB|f?=E^RS~2L;LVHayVtc8GwDJc(tJ$BHr({2JR`aZ{b|Q81 z7aXK2-eyCtiaH^01RBPbVLG1UCDK#WVPu?^*}hEkFUfV}N722mWv+Tar1COA3z?*Q zq+NX;&x290ox?)*NBsh;Wk>8uZ!p;D?FiD>%39SiPFlu|1i}iXu>A{6IP1Xumg!!O zged+@T0Ze;nFf3JoY9csxbohon(i6wv2X@kmcy6A3^sN30AAG60!M%*44*`cdTc$C ztOE+$`BVuaei^NO4*_2J#qcyd>))QEsoXmmmpQKFti^2k-bN54E3T{PZS1Ke`&{_w}lhX&DUW83+ejs>&PPtBX3;~2ST=t5-MD7o}~ zmHe5H1Kl46i<`rtzE*M*FVaxU6Zl7PF$j$Zd4}^Z9@$)+tR;kA4a_o)h)0Avb@=1A zk7rK8E)SDaNqiHhgAd05WPnUtp3XAp-H<_aeDqdy+Hp*XEeT|6z3>&6!?8G7rIW@> z?m2mW;hc!EqyEQ#_~N@ie);>ae*Lv{MeV=%_kZ=*fBAR+@o&HU>%aWvm%se0zx}&k z{!Kde8zsPCA36$$%VtKTy-AH1>s1$?OIBS0&Ddho15}lz{d03@K^*0B^2U4hhgZ?+ z#eR%3^sZcmRY+b~0a%j$Tg^K0I^9~^$JsZmVxOSpTSFl35v2A(V6n?zk=R{L4k7H{ z*jZAGy~{T)&rcW^arTB&3UHNF?fL^=LLLmP<7IF81N)Dpp+9YLUWwJpZ8+lw0%T=S zcGyal43(yH24Ek!pmW-1=;CBMPogOx@EQ?R`jEe%>=^_)z0(`>u@L5pbhOOpfHtA` z)6bgtXV4~r_4ma1_1S)_z=1C5ot1ODbQlKHU{dW&MX3+|+YMoKqmsd#Z7gL9Y9x3d zIn2b)KNWg)#1CgW5JcK*$Zp!qXF1|HYJscc@VZX+%Ss=TW6)CHVmu~Y=MeqP%s{XN z-cH1)|AzzAmzvCyPOU=|8b@2wo&2^eqSy(>u(eT3vJ$h0b|AdV2Fwk>Rgbb;F?wMQ z?5et`S8aViIHQ8p%u2u`=JOy1@Gm$%iVul(4#!&L>y{}(KwdHcK$C1-Vy0Z>!_42L zc44Q93pE{K4vgmq1%r`60KM$_GgJ=NMuQ8MO=T^K z7k~_h*saI`or`=i-z#>{6z>{3?P8grQyqKw(;pL}5 zk@f-w9S2n)HU5R%gN9!&YI3l&zYG|CATjm+R%Ot-eWqM>(zPn|!=c|>m9&%1o(U8+ zo1NJ_HBtrjd>3hrJF9V#(`Ef?#&Su*6K`KnVt1aifoPWCFFJ|jd_mc@@&y#%u{^`^ zv>KyD6Pw7+=g#_lGwV#NLaxTnFH}+@jxuWo5@Uyjr|)X-0ll=z-_9!-V~x}@yxCvt zkpR}KGvMpJG3(Wt5GcaOBu%oRU3lwCZ7-V*-v9!ojEOY@JVz(IYrWUXo+vdnel;D8t23-+;HNjxi`|(ZDOg^b%D&p(&{R#^*ucdn%rx(NsNfzf>={) z3RBtseft9|ee7C2Bk$kEOn}je39Fo%-pkB1FnjTLnK=xq#ylW*ZV|lh9@Ob4d|1sUyhsoxRRvM-&)NucEgTuZnCt zD=mlR>SU%w_Td?PYn7L%4)`mzh zMs`tO?4GC3NI|jA8iV$bkJhLIq^&Th!84>1q&G_wU^x2l@jX}}l1bQM6oy+p#koz+ zcBmM6s0-y54M;p9u_+Y21mGdxt{|s_$E81=yVW&k1<57A_1hHZvj!Zzfd%R9_KsKO z6k|OwOec3>tARWop7T6ha9CjW(Ok>%s+9HvN7BiA2e!aFXe8`86zY@DNQw^?Y#zW# zDvN8Z5Sd)sH6U^20ZtXSB6Oy3M(ETuL;J)86go_4lqhioZ9*=Dz+eEEj#9vxl$6IE z`G-7)WMiJSq5y*7c$9C$IU0hs%co~p|APiqW`Mdd7Bq3b7K0Hl#rEfxBS5DETkqmr z_+ljqf=js8_z6W*+==;_GRCe1D|+mS&;vg6%d-^SB3lf_p~90c4F8NT&xUeDyg@cD zk=bOu08QpVTjhP1TKymEVEPiS0kUOP24cKigs){sf0OR^Vrv-iG6AAXD6AiOtU1SS z1Ee_yjGcb_*oU>WbSt2rCL}i)%?bFYZ~2!AH(h1Iy~-9fkx=xSANtxH5;3xLdPag} zEug8Q6@SBrc>ct*0FPB0T>$2IY?#2!4-U^qXZz+f$}uYCsv6J_hbC)YtSy~fxfkhe z*l@*;jDT6C5`}UWJdj_hvn^|spN?(yAJ|1aADOMu1)?Zhu%j)DV2iA>??c#$K$*Dd zZGQ6@xJr=AGc*sn(j%42p)W7ZNTWHP<><=q!M^r;%-am8vQ91AjQ_)`Z$q!7l>Nkp z&7lEl-CO4b&aDMi{c%YX8^mU$vQ}YCP1c9ES~!YTs$U4$okmA=hkr3{{sM06O>$Mi zcq#jy+ufz2UHSF^e_%we*hZx(SEZ|pP*+LP?7IFDF>NI`$q9Wak7VWMA>VqQ#_b{w zn{VOen;+?+XQy=4M-O8vq$*n8vqBrBN0P!%d^XA_k zc_tEU{D3B>+?b@CccI$R^?5u~}jy>v4L&Sj^u z+nM5y&~cnfYuU4Moucf-28X4U;6f>q7auPHfmLwcvJz@)SykxD!O}%MX)syl*)^E- zz>`j9xW8U2+@H$X6ir>x8b}2{Dz}ByLT|e`oLoc0AqiBw#Wm-8ZV;fwYoqmQ(}0oL zW?*?(9O8r%nGtvjv)*xe*nEe@0trq*Sk{%{lJg|nGQDUC$uC*0BYns5S{S7U;N2PP zsHwpNv*NfvVnk7bAj>bwwtV}vUrB_YU%&~EXW%o$U&-oCoKkj+rnC~8yOB`!iNXS~ z{BDZoulDI4pV6dgAWfh`#p+76vs9>jJ{@3Ib!z6H&mqhZlrN+WhBh%izvLH{1r2|_ zw!qcqVI4b;nk@6DNQ3z_Af~j(7Oid7?n*Dyn!+c0L@7@@?(?~49GhascAwmvz|MZB zPiKj0*jL&1wd$B>mXFS> zC3!9fdh$@?mF?L!^dKkO#v42VvdR9bz)O{RBLa>O#wuj#wFHtslUj7B@&1>zAvVd1 zSTN@(b1ixTP!37m<&a@l6+@QM`=w;Mri7Ds7CNGgqQ_9geE{?XfO>0oyXDfe!2MY* znqJlOU#}LO9y)5#=3RKDu7;+otr+25=Z#Zc-o*+sJY$jQ(?slgOx5U-vPWR9B>`hJ z=dP94%Enct>eT-GVNsC2(tabL&hEPR&ylkn-dArWBemr!#~G`>*?ypN%k{8 zWwW?ht3=uO#F2C;J{{kFyC?(ECyQ~%zaR(qfGos{@Iaf@xS_ccXm=cs&{Dh`?)y^i z+do$$!(qFg7&ObH{r6c3YT)lrh<4ekIU*t#hK5<@QW!MIsUF=b^pJNIDD!KSEyMP< zDw~%S@Xas+ba=npinNv@*?5!_@n(_LvmmQPrM&j?Pekc>358Z%I+< z%}bu?K<%iW^_~@h0cCe5RLop#zHNW+AaY_m2^M`G;Iu-1WQfB_n80}NaJxL<<+3_( zLyIK0(*mRLtDV>1gGh`WDxOH97#@%yr(2A85E#MwBN@FvhOmo+S1Cd()X3-`+=n>> zl6oV#bg{?7A>X78b<=M?g}U7)3j)(!A}=pgzwC7(i9VtckeS9`bQylaqh`75@`9No zWB9~XbUO<9GvGTB`r%g5u}0b~qwNWcd8;!H=NMiVzKQQ(^{|d6e%e13b3-|nWi%8$ zcyolQzO{;mQaz>#gane~^|i*diY|k6vR#S#@NO~RcouAHFSymBE7V!U+vexhLb=Tx z&wVMrohVmxb!4?`k{a_#W&Qn)-}Mf zIhHtViEu5A-T$RP{6m)A<&BEHhO^wMTU!_!hSs>w^k&Yi|^#pOae`P4+E zb(8sk?Y`C>3X8IijWr&c0GA8G%RT4XfztAxHNr(O*iKi*c)&#uxd+A3e+`088$~v= zJXr)5FE{ph$du#~NAZTk{s3ndS8R=7lDuf(a&VqMVqhxAXILYke-vZv(|dh3Vrz_p z`L5QyX?61kf~~GM<{@C;>W{1gLIIsTli?uZYz{$V9> ze`|HTtPt3H*;1IG0?=S8Et_yP$v$0N7=ZO&@3YN8qkPivl~4-Ws&4^%3rm}6d-9d3Z?$Q z+zuC;X{sz#Usr_Mo~E0sfR*q(d>FL8B-)RA@Q@+(zECKuIAIkV3q+CCe-rmoy&>dD z++&_S(kg_X#^!Q4hEkD<5>hQ9IG6^rXMJ!>CAM*Ob;rHbjY*W!952=oHM;EQJi3t2 z(Hk~F^5<4PJwdQco-$aK@~e|49r#dRs}~qxM3V{j$(}?e3Myc48g2GI_4`P&N)gH2VPI!v0T{`2-#AyVJ$L{OSrWI4-*~fr^ zLo-csk_#-R)N~K+fR(iiG0lNrreowChFQ}H4__EiT|y;BjWrMS)HD*4vT&OX5SofY zQ4K5S>sVm1y06Dj&j-$mS+ylk2!W{^4ZvR2Yt?|hz5%Ia4|r)00%n_Uqcjy&zOy%x55EzFT=2Y=D@8R==6DRNul}(*g}0uf#J-PQa~sR8HMAn6cQkA z10Fr}xoS^r6$$98TdA`CG>ks%5vZ*C2tJ`h(mU`OIB%;L{>Y?g*kd2VYtqt#D(L1EP4=;Wc3C`dXud+ToNm-a z-C2m$7aKXQzG^m zbPBs24k(YOIc|ITM=7>qK_hg%}YW$O8G3rSB++leh**V8TK zUcj#MvPoOxORr3DEG95W5&)myUXB(C_N(9g!_R-!2c0KqtL{f4C1UoppKT~a15RuB zT>zsvwuwSos0OQUmqoX^R#%(F-m&_!0Q_I!iORmm zf&c8DvRbw5wNJJ@0=1+=T}ra4MI7!&C-bo{j(dbbWKb)eH!#jCjNvtcxWraWnI?Me z+rpGjE*go@)EA9R1GNrUnlYV)lqgnz*6Aj=GC1|gp#!*y|AQh%PMd0vsf3h=f~3^t zf;+>eX{&GyEgen^O2%?o{zG@VEc$6HHJ_HHfY9?&z#cltSa_1^Y1N6*ECtY36ugOp z12l*W`|24*zNms2PKs`;KHeUhbs>(TOhaJ4I>hRlP%I=`b5at1>4?J1W@Vj+1cEd? z35_4Q1yW((Il}FrrYT2o*^Z`pm6Az<-=#2#!(G*UzklZmLl-K?QcGqDb}$20WeG?o zeHpWsWL#VtNdmoQ-1LaJ)Rd$|ir_hdo#nrBQ*%xcXj(W>d2n)nwVcR<378>$OFUD_ z5RnjpE>g+J0ybtl+(YC6wPnGG$d{RzUdre?+!V-If9c6+y%+LJL+6<9`9cuk<^$mo z^k=3z?I<0zlXN!-j$Xd=<)1oD8IYL@ z4k9Y&8jB^`A8hX7gFvhs1?V`tSwErhur~X(-^|>ih7X=~X%+=-^UxHN^Bb#}+gkEC zPpg240SCv|^hJ@NWz0s9YJdFDbYurhe^8Ygi)T)vaNvNTw>?VycmllywduT6h7j#= z{CK!tkzS7e#Oo8P?46vm>a;Q5bUG)pBddgP>yRikBXNlZ73h~Ocgl0+xMY?WdTCdt zTbXiw@dSjhjQDw=r0D(bo$=&o3J|y?_@O4T4%Nf%!$%FX$ykdSM&kEy@0YV4(@rNr zOQJ)P`{T>1V{Xr@v0FM)j=Y}cp1j7CD(>OFKc1e@ElNeVRV*e*yereQ=L_W{(<_vP zNs0>w(n)Bep>&#Y3(-fS$KdRiwI2otQ6u-D#}r7si_6GxGjZ7#a(dP;W{BEF&M2A` z(0PE+kp5N~q>E))Z%D8FlJNh9s}fmoE#rW#8ov7d?VeC#2re){k0XsKNoXRt;IIj` z$^>gwX@~i%U+D46-+cA^-~aZj@4l)#cN22cWVdn&MS!Dcw5p#S1+4e#!Hzfb(uCnx zIb-Goo&(%_d$v8vMV=(_8V4k5eIh2VZtGH?tLl#`@mfb>jUuIaI&}KX39>cdF%&Tv zNM=r?-w0eBm(4j9%N}^=@Dz2=aSiQ2)NoMav>WO5&TksRFgl2TY0P1MImmHD>wZ{j zw%TyR{;ZOrj=5vKgum-x7ujN%Re2KW-pbr-e~D33q12)@5;huBU&o)FNl%AqbxSUydKTq z&=F|_Q1UrC*NI{a+9d;C3zoav3+h?=)Qg9FkA4|ac5gLG>rif)RoGA!`r^~w4@Fs$ zd@G|Gy)2)6vy*&Y!kqF}JaeWyw8NP=0E?x4dghwdeHqysDwJM7G{o#EC9;Z}W1g!L z`eM*X`r&w{_}9KHy17#mST5>WdCcdf_?n=UU|5LJPIcdyh>n+rTAAlYbj~-gb>E?w z^#OzHv)~T~2Uli;tLfU_`u^gF?<0O7d#kh7SD#}}<#;amg)ww1v0I^N5V2uyap&zY zx73NxF*n$RHSM08`99`0&MRXcaDQuU_>x=-jh}^0uQ6o8Xf*aiMu?wq<^$%;`DlZO zSaZB-gNLl%N6`fql`Mc+3wX`+F%x{{NT)p~pb%E27G;7&_4I#hUclmom!9jbmG-r^ zrmu%#H^y||3=ic6$}U79mRbw#Ls|87))~y7Kr8-i%@8@fX@x%d4CzDS(2OAEnWNEB zuQ&l8U2q5|m+~$~im~=LU4ghBDn|JLb7;p$`2xX38I8yl?C=vN$aPPQrWDER5eV zwNqt6KK=ZjLY>Q*Jc)I6f%5eMm%)u__h)Eo=Q?EL0tS+PE8pz<3fk`@jVx(YQu=hU7tvZN zq{}kQRX%U5^|(s&J<~haTByrAbC|;(1=rRv&fBgnkpxB9IoBzjw!PBSj&k2W&D2GC zIoq%m5Zz1iH2p?9xyG8N$};Oms;)jy`5wC-ZjdW(nSJ{hB(fO8#_~wCVP>b9D0QTo z;Sk@jJMnQtS=}Lbo$ipE_~PDOZ@h}`5&W^cl9Va2m#kuq504GYOQi@03dST;b@4np z?C0Ie3Jem*#O0l@<53ZY)8Wa*FrbwTi|xR2!akXDy8$v2owTYxpOmP?tO|pG*a`H6)L-+I6RzUGouF1V`T)d;w*O4u*bf6(%O$rwdd82Hj9FPnGYm%+3PBy z`HVxccDr9EUd7pkWG0R^c5ls(<|~ZukXSyGBzxF=(g8(JT zVrsr35iX6Nwp}%G1i#p2S0`F#^_+HlcgDjiuMmCMIrV|P0{&#qC*V-Q(v%4uN^u4k zM-b?xCg6E36OVAJa>zG-z2Td$zGcU9`tkL@`P=Ffg@IvBI*aEq z7{KYZ(`Du@Kint$3h(WQ3o*XcABO{%=8HV0#e(evD7#il#ou zCX9mh={M1c{*ep>)%+>99}84_zO6+gmR$Tts*?DpXRNdC+ucz!2rrEZhdKx?MrXUl*>Lz z2!mx_dU}W2O!8`2FFjZ8Jf+31igDH5-C=}2As8wGqR?3;+jO4pxg0b-6DzVLa68wk z@|FcGpWN}xf+r!ZF5t$tX+%A**Xf9z>|_YMLdbb$i$~^vw|B>Y&;Sy0#LQT=$O5e$ zdP2cs-a1Q0zswZ3$HW|{eHf2U37Sq5?1UDFSzu}UAf`^#p+*Sd1NMlSGegZ0dbytD z!iCts4fszJ#deC!2id{T_+B9C7{Le~1M8^xjy&G_B_ zb)}+KQjVi^H;UuPB%gh3E^Mi;qI?9EY~K?lSazNazrfNBdeF;pVmV(%$=MJ^S14h4 zpkMvs$yvHm0sE<^XY#67YnH6^A?72u2M6(R>={){-xxfd4jy-Lr}SU^#3ERr;_)yE z{AcHhbz~aRsxR8ZP^60JELE#)y0pz9IbjQ|^3l!#0F`q%R`4u*hf*liSbbo#{^ekO zTv^_tThxKc1`h~4mBD4pr3LyCTwWR_< zDk5$e{JrgJMg^0_IkT6UI_}2C1;I584(57?C;XyaG%&<4li2CS^`3Xahz?B{#LglV z1~Z>#9yE{}ds&k`-RgUGB`*!MR7FUxDBSAu%uusq9${vt(2eDQd+9(g@Tw0^AvqL) zrnRryf8;~($FvSJz7+3zg$VwM=pW$_{3eGi4vSiIxOMbY94_X)lZ+Oi5>*4by<~ z_V0fAHw6MG^X=FF_3LlH%NC#hgN&*%*vIO*YYW5c#6n+_F5IuwX9>NV75VHd6pMo% zpIqAoh3ArjSgALVK=sqprjkfz6cLV@#ZE+Wn#-TzQIS{S(872Kngk&>p1*vhOF=ZP#Y&!LAVQLSH zPWiwX3kX5>OKV1OZI)j80=R_CYv^Nkx@g4og^pK!;Va`SZ46nnTG6}Oa%2B>T537` ztJfm`Dl!5g98r9-RIcIu{_VJemU+3Q77}&n4DdO1Ew^Ca&Pob38Otp?C1{pgJc68> z5?U;`l6!D-!d;K^a%)RX`y56K;-04<=d=;4OK>P0htEs{5L26y_NY+!Ek~=;5S@sym4b5lU`t5w`;?H;FA^*r=jmhVO%ql$p?n zFh}IGwxoz6xcP9f;aU|EK{tdvXsJb_SE|(T%w^#45V{u(@c2|!a>%S69P*5|s0eK( zUxS86jqmeLkH0o^;|G{#6W-)0$bL+|3OuyJ%G>Uf-~65Y-YCA_;$ZnZ^){}; zwnr~Nr-gx^aR{{S)!*D+3r$}%jYG)=j9=QENwhl`h=}2KQOFdI3Zrl*C&&Y<18Qw1 zb3_T<#vRUKx`?_8YeLz*M?kE5s5Ab#x&`2wIG)R?#{ukin*g>0uPa0BAe09(hL_6ZDOi*$!;p2IpeQrME6B!#wWe|lOjbT-YW|Htf;ZZWz8^Zqh5PkHPtSzZg!kb4msNu>K*7HJ9#TEWm*;JC)Z4T;7BM; zUQBj)xwPa)S_4ejP?QaY)#3fs4AlkB77^Z-W$2vg@m%%hGxg?Sh3HWfwr3keq1>8w zU<)h_wWHtxEV25PvA1)c<-NrzdUuEJU4qsPQXuXTr1n8z+V^?=2%8xsny@ZoCxU8Q zE7@-2Vvc-%#<)p5r}}x%+qur}{EL<7>hQTL5zm%qwY0Pdw)!u%u*Oy=Tlt5V+L@n| zi^k+b%w8~%z>Xp|n|cr0M6mc&co$7%qBszR&JSzDeDIMYqJ z)C6tXCCh1?RSMZu9DW%vCP|7yQ_P$Cg|gByFCY8{{jU{4bggYCP#DmC@CJ0e;O8AH zk6>DSNQTM@vk+7T<@-h29=%}DE^BY}0dy>Ostgq^KrL3M$1y`=vZ)1>s>FHeV-h8q z|}%YYh#`+a)g0O zdhlbFus^ed>|nLL9XDZ8luv#eELUA76!N!*9Me15IEzpM%G0Vl+j zPh*?azxft9U7A2wt26Bj2ho9K^(k0iI*h!=yW-NQ+pSgU(A6PIA?{13`%4Vb61E>U z5w^wOM23Eevdm_I^7l*A^XzSKjkk^aR9QryriEsLwE^`)^RCZp=7r{JcuN5=$IGI; z(Bu)V9cKEZcP}(4vNC#FY0@O_!b1BlJtoRkdA8fp95rp1Vdy$)&+eA+=L115>;@B} z`t0!muMtb|!mCViOH|o@IA+Zg&UMWK2|x%JTmqFTC;}1lvB+}%sRji#VO>DT1x_qL z4YFUSfIR=IzjGZj$AA&oMAkm-u1qOyAr=DQsn;Ap5n`kMoDEKPtPX?HQGQ14OiC)u z>|XVmS}d8S3qQ?MG)ej9O4jY+#?LIwNBZkAnWU!Wbv3TS!uG;X1nhqr2bSS%F-KZ! zjbVK(&67eZAMp-Vh(FQnAabiKKZWlILh$MdRhcl)I0xj^i>Ok;zPw^Vl|%ryU?R?! zb1`ZxS}F%kQaT6C=Ws;<7Bl<21F_~+i8#1YGOg|Eg)s9nxh*z=OC~niZEWbNSEfqp z#De=_f%it`JH-@A!T2)L%7AYEy@U`ZOxPFBxCzwnk)j|)8UgNReRE}YrOJ+>vGg zt7)2^XsX3$oTq)uIH3jOoN22|x{}sZ{9w!z>zv%wV(XFzp5Bn)-mZ1tS=ogI!X)Y0 zc~OWdI+R`Z9=ZTdcB!XFyJT+1_g=!dMK)}^EReC>v4T99r!Itx&S7J7r=e)vCkPQM zKoeBdc_1xtJqk*GdT0T|QbL~!HFqqd+3d}A8a{5N9$s0(u+q9u@OB^f3EHFW(t3Zv z19~;ad-oAZla=1}{*kPB*v8uZ`iYIKX2NzJg8M)touNm zx?6AnMDmvYs`y=@&E^MlzWXrXk7~}EB58WflOLG>b8}vqv}ewtBEy>XGcCcixD9Z` z;qPnA&AxvDTYjyQZ)C`}C|2f91YNLO%%m#}YiK61Rsb$H=fgN*oS96=VG*l}M$Vzx z2ZotQ?RLn0;mVzF&oZ{WA-8o=V{qY?((DWy6a5r3eyyv~!;ELYU^AxS<6+su#fdfG z++Xvw$NhD(Ixsb#9s5F=Bf~%|0a#w#-t2&RjG_+StKcArX^5$f5w5M5bzndWa|eT=2bu9yZJz zL&rfF_4#-}Zsz?+HXMN^{Li6_-C@2$mI!DQrpAYLj?}e(S7DH+KDi69Sa4WQn6?er z4P}(s)mw_pQow1EG*n{|G7(}EHg=S5XH=;rxqX9hcV#MYPCzPux*{rSm!v;=Njw-m;2Y;OAsX_ zUv4NTCnOjdIe$!Uf1@_BqxX|28l`2;XW;C2Y-NEsT}INZEQ|-clEiFm3e+#Q-*@!ci!10AAeTOQ z$@8pn`AoTl*MQ@7*V|eI%XN!L0V3Tk#T%*5(2@rA)EBX_0-218hDDYR#PBbdpzsMR!zR9(W8qw1r5G|iez;e{Ot|y_5O2Ko6^_7feZh{8%nSs2T z6_w{g*=I~X!%Y{bOe_zeSXMNmSlyL*_kD}u(m%+W6wHQo%z@2v zSPD|o(yXIVx2KiVsI59NmS3dmIr_AwuG?bU~4jv6v zdb;3CS2u^D;u&Npu^j`eXcWJL5BfXxG8`8VU?*R&D(d}G$@+CCSr-$xgya;l{|d-Z zNNFQTIN2!7V^4Xk9fPX$Okgy7H%9w8Gl6^#eKqXGkMSDqsxz}Z)1mxWzAp_$E9h?5 z!qkmp=M}lFq<21};GQ9!B`l2}u@vEf9q=^w?3!%JNF>*fc479f&`9J-laF6Q_B_Sx zk#=w$RmkrR&05orgqQRV6*G03!Co%hEoiST!L$hB*Iqo+kNJhU;nB--mQJU5knGsI zVsbxC=?4SSgvNJ0_iM%S*iVl##$>@8<-5UU7Ox=}flQuN0-_z|!2X;G$Z(;{i4m!S zK|eb#W^R?fgjbE3r%m5_9movoQ;$k=lRa;2>EYmH3axY&mT3|Q4v$yoCR4XVnazsQ zW?!LenLHe8NuM#GmH$kuG+&eTrJMc=&4!SurMP+89-MTLG41F{rP(wTw7!WBR5WX~ z`{$t_(_{{|#On0G>&qyECfaaoqcJ*;Xa?3?QRnhRdoP$BxsE-I;`L~u zPZbm|b>iM(5HzrNG?ZRu(=#I}3-VlV{ocvFPKn=rc+>u1A!im~YukgZiC`7x$ zLtEcS*Fg(_<%~uM>w_4)&MuHzxOA3Lexi-Ew9_XqP@(By0f2CcODJ=(C{^DUH(ZsJ zqWlpiXK|05y^dYn=VKS89Xli~{shw1=Yb}*4;s_I#3_ss+L%KKhkbw`G*wj;9rE)U z+8!o?7m`~50UJF?+?lzTCKB;@$uk|8rJjXCd5^^CG2-1?XKibRT$_0-Ea^TBnwHW} z!&)phu~-wYk;Uy1&F;$Hti47zHE3h6I_47gV>n=tYG7mHS#WJKABkZ%3P?7`S!di} z?g<>RKD`=EQ|JdlKfW8qZn;5zuZ!Ho2Tov;v|oh5&eHPD>q z7+OU`UBz;b0~T$7sVT$$P)IDpFEr54vYhET#ctKGQhlDG<O(0n z{|veyGRJyNtVanutQojaqxY8nC|lM|SK!#B8TVrOBBOr@`y=b7bo}m`vnZ4a@0&-to@*L18 zjR`*Le+9K;4LoA{Qb1V5vv3RY(J&A-W6-_8x<5Cevv6Gf3o}GShJscx@O(n&IL@&4 z>7+<5zXUgSpWk5I!NmbIbxoyRFwp5113aTWZKn7{b9yf;HRSj_`VNL zer{Q$0FV?-Cq6#PB4F-Or1n8!!+-oPPX-+FwULOVd4WFLV%q91>m>I80ycUWR>~+G z5lIm*tyVcpJ?niXMXAuCWMI_79_KwUFJ!c}(iyY9RfV~KxPoS!#?;)&aeEd>>dXnm zz&ksjN2%yS@6WHG*~emnPh&OyJqwyM|3foM5h7Vz>a4uNukxkKUe)vyE1G3XS@zqd z>8z7b47RjgZX>2avN2omkSEiw}EOKvk4oR`Xl;ddQo!LgOCfNSPSqf8{J8ONwZZ3wT3KUpYO8 z{dln+^zNj=_L~A>hn+h`Iaj$>Llkt!Z>Db7IvvBIlS>rSItw_tL`mXiuQ99}sZUE$ zmmAEvXn$T|jh{JVM(3qmybsNvN@Oo&#E{$tU39@yX2+qIrtt~vkMUBwvgKL zs*dJ}u<2~hTa+!lqGM{sLYmsgb0tjiE1|u3DFq67TLa#=-`OiP6Cpjd#%iL+{iG>M zZOJUVif)l0-Z$=JmsqTwR^##l=2_uE_WI0(EvQ=F04H+)xtt_qEU3wJxdK^4(W`W- zdQEG}zPzR@N@cU4mpS!hpYK?#58$-LhOU*yBCu!?X32dfVa*YKG3wcYcdu+>h$OVt z#JbPs^<}!y28@oolHeJW)(4=ssneS^JNf{5y!5k{rqeUzdO~&GEjICzv4P$wTxlZQvBA=% z*u}iL?I|X7!3Bg7@^%@Ueu>ut@sLpgp2W2Dq%bizS$UcfkIwkgOH?L(=Ag>fTntdZ zaxVZ)3B2iwL#s>79eQ8^)0W23jb}|aY*i1TU#W|p=&hf75jV52ufN6)8F)USZ(P@s zeMBE>+DTL0j`I3)8O8lK7J-@`zj8{xh|XLw)2lT>^-Uo*mkJc&JT za5&@Y#K*1Qt$S-l$*Amqlr>O1Q^IpwZ|rimjip+I96@Ndi?q-#=;m{D&N|7kGTy=f zibcHSS%nl^?DUM4@yN`)wo_u}fg)>v^Bwbm_ggFDWu<#_u>xemgmTc>R|ZT^m#6{e z+3B~~aF0R8rATN?G{n&X{or5Jj{f|LMXx{B@wZt3p&F$LKYq1nwg39EiQ|E(LQ&+o z=V3PFQ@WIYBil<^R=u;{g$^cwydozDebPOs-~5+9{N~HA|L|L(^cP=#`I~?G>dWu` z?{EM3|NO6i@rB>~`s=^{-5+pQ^|J-lUv282Kkc~mR@LQ;@BZ_jpi!^Ci56< z`#o0`A zLH=wH}NsGoH1L;a*sAL_^M*T*`3 zR3fh*6$#%be{AZH3gz`(sg!oVH*+Yrf9!VY`iDP$_w^V5$KU+j&z)V!D8Bup&FFvs zPygZNOKj5w{oB*we$vV0!~OgheuJZpH|OL1RR>j$$K&bv@_M-5&pe!7^l;UG-w&79 zGsRl!};aEz} zV~jVi`lYPP^>li<9@XP~e!bLaJYMwq`T2UgUtZMl^>#VmUhdcH>#Jxz>C54lllXKO z(+-!*@pQi*j>m(3tAT&7x~%Ya)z{eK+WB(6ob&@Scij^n3?H(172Na7>*aVo9);vV zpEVy|ju-t_rp~LsUJf_0UWfe7_v7I#Uh9snfG}ise>q(a=gVEaFJ}$9ilU3=I||P2 z<#;_E^s`?Ad40W}&*1ZV(LD}lvG;P21hm-D&pZhFQO9Qj_K@BA^?E$t&Id_Y_ZM9C zGgFu2?Q%N39#wTZA5Rjk)a2#$bh@67;)}>X9dvcu;c}Lay}s)A%wNSA5r5L%jRAd< zzuuo-UIkcxycnG5KZu<|?Q)S&Zr98GcoX7xO?^G+M~|e1N6DDkMYFC1xt(-rf%G|{ zPdaoLuGgzHTC$MJo-_hLLS4erZw{WtsKep)`tl-%iR)K&(5vICFcIETGqFjp?y4mQ z>O&1xbvp~h^>lvKElxM-f|z*{gTx9AE?S3nicqP=@p6$$h$K+ZRD!O_kFQtF|N445 zr-u4N`c%IoqB@`S+hwv>u~G*;C2MJxlPJ z0GwD=R1EzuM{r03^lu!+yl3$a9>}JTVwJR4b3yV!hFqrXExN?>aMA6AcWIs!O-gmV zUwOE`z(@VJgz+MYO3&etbVNUbcGYi9zM`7i9g{ifCrr&||L90UJ)I@ni+Fpypu+N5 zaye)1k_wItO@<|yR~eX0Q2%q6@b1V{oRF=eujj)FZIni(>3G-gM*yGf^*&#M*9E;2 z%FE$;lx$^RvbE!jXgJ>FE0EMh|8B$m@v-7s|VRofCW2RY`mq<+w^!pc?iPQF2WyrQ#Ui?sCedeI-3*X!XThk^ZQEu$kw z0ex7*P!=i2QM#rYirf@o=WvuWxSa?usHaRyMFi0zJC*gwnO@JRQKLNkyzb=|kEOO} zK@n1C`JtoqPG)nHu4%hh<{GW(6=g&pkYz9efSZ#{65(Mf1L5zj-6OO} zRwoHVI(rb=a`Yz|gyiVZD>9_-`V2sPm5s~PiHUNtFDH3&1tGb~`{8<#|CMUr6J_t< zg?`{7qyTc1xKZ5Piz1o$csVJWrA+)|B&n^!;zey!)5nB?JXTV%Upz z4tKQete}arc@eI-K4}zMdlN}=$oewG8g(48Z4+xx+;f~AoTB5Zd9mrF!BCjutr8M7D%ndKm|PUK|d1ns<10>|DvdJ z(P(j2_omB?W#e*!G7QP$bVh$<^oLiOpu}{k7=9eKCUWj_D1>13mb+CBkn9__L#&0b zno`y$$0QTVPQwT^iLypwwnDNl9VEs`S0x+StxR5IE2P^0$U^l=f!v!Ch|59#;-th$ zilf*e3p+}_34K^Y(n0DWHIqBCzm#%_`h&8A zvr>eB?GN*V!yV0DKtMQ3O!82-n{eZ2$*oP^y?a+8a?uiqk~x{zUEWQRMYw8eA&EAi z-!c!ocm+hctb{(YD!CwOg-jkDe3eby^%=2fQ?@5X5$k00cWFO~qcSUTT1blx65U0L zA)h5jsiaU|vh4meEa4%kl{qI0L!Tcz9SYA81LT1T7K9a{AdjGIP-rDfc9L@Rk_#U2A~VOe$=20q_(EHrLjGQq=|76pQa+I)6;gBzUx*J) z=))4)w+gmQMQQY14p0~?gk8$Kyg!({=e>sv%miC;;eIBP~U=PEn>N zy;iO$7AuyX|`=B zeYqW_cN!>lPyjh1P(>>R0+C9Zqh~Tq;UQRZdI}OsNF-Nj8lfgw?@hacUaXX-yvy&P zB@&&yk>cok0^|^PuYsR zp`yxB5k@{!itdmo{TIii9$Jp*FD1;<9xZ#MaLN`W++xk}^qBw=Xyqy7oE7l2B0Y&_ znXxcZlo9m`Op0Q3mg5$Ow49S)Q39=yDc&QF^!?{yPl72c7B5QP!x&_YatxPyC#l{K zONyn>0xRc;*OlQa&(fL`<{cR%>ZBm7ep%j#Mp9k*42ekInFY0?3yGjm*P$Z&Y5(d) zfd7=3ff0##nYPw1q#$wyB#H`p5_ozL@4N-<2Nx+(Cj!Hje*()T_7|;&P)$%F!fVAT zM#zsTmCn_Px8?$Yk1ZK%+|XfFEM(?N*OfT26q4#IK)))Xmv56pCht+Smy#%FI?0dA z@MH~g)+{dZJ5n=69(1}i!QV2LNR^<-e8oM5B?Uu?RbEALL03A-`Jh9}-lfw72r0Jo zL((L8vpi5fDosc8mFrdL^|oGumyc8ehojVwq071yL#d=3Wjb=M(m~}CDE2{$jFoBK zjJ+zHi)`7h(tM}RnzV8le}$jm6=RjDYV|C16+@I5$#sehiYQVB>8aEMwG%T)w^ZQJ zKirG_I}#M>TFEP)x$A@CP{?5mKbN=D58cR--xP2V@{8P*a%W}5Xto$fQY%(yS*EZ` z_AUj)3BJe;%K<0~sF0@i=FLcvemHH*kmil1U<`{b7X|Cps4)G26|l+!{PllMti>6L zs~A8$R^XCBNSrJzS-mK~me*0xzzxfLt5~Nnt;}6+M2V`32Z{m;qVmNhkn6~XelF>% zPNJm`*|VZFl^*$4!lL|^5*rn(Z~_7>)+-ikIUr9dN37&ZE=c;P;DmEm6bpI#n)*Qg z!AYn-nw>@-^>dbK$|@A!0iAb8Sv!fi2vG8;NUJb@lTci|(qt56;B`VD z2J^r`nQ(R*de5p?fl~`iFCG;rHLb8x?Ld~SupwGhw`L_GDUqVy-bM*l{!(ab(W%5sU=1vQhimUKS!CIbC>Ark`9320VM`C#=)^Od2>|Aa| zJ|e@Tzh(~p{hXvz`HqC7lv2wivOID-c}<+G+^&M2A~uC&Ib6jkQ0Jnb)-1H42oPJ1;X^yS=iFhwh6DXajsy3lA&m+ zfW)#hIDZ@F^D)yu$qy@S!$T{bRDR1UTxx~g32PNDyj)G_BU0J7${h*=#Rb`^oVgZ8 z!ca=pbt8Y1oVqCO&@?PmH4o)3`C-v1LY3ExCdDyou}b$?pRj09(y!b_!jp@YS~5f} zYAs0(L!zdP=Y64;s>DTMf}BTvwIyE_IA2Pitd}DacSWbtIi=>Ln)2`RVsbWG6-a|r zcqOb#H}yjKwvtNqk?a*CWy>N}I4WpWjm#e<2Xv~?;)^&*jhOTZ4Uyg{dnSkxW96cx z)>>kBRSaDTOpni(A@}yEERTHVm>;FTrzOz0#L%~|YkWA#DSuPW2g}Yp?Kq&>2Kcv{9 zGM4_(d+bR&_@*mkZb5|z3+ppy9zq)@VuYLHfB>(m4{@dS@ zQh)X9(^tRvfBz>#`q>xXef>}0X*cVe-|44z{@q{w*Z=$<{_x$`-~LH^h+lvEpT7P2 zPv8FWH(&qbKYsn&FaG(z{Qlpm-{1fKkH7x?&%V@|kMF+yXB`mm|7x3Y9QVL={^5^* ziZe|A%m4PP1HYl>E&bZ}kAqaY#%}(6ierO&-(LOcyKlef(K@^0&CB?lsJ7(=|NUOC zAL_-mCGW%H+L8Br=`!mN^x|rh_hDT=)a&}8UbheRLTc~hrSDlj)a_&3*D)oV{jvuE}C$KU<8uj#3iNj?cUVA12tZ+`#P-~XfD`z+c2 z$KJc_$aW-GqGSF=3+Q2sI1w57nw5Z(00}}3gsQ6n(I9xMC^gYk)dhfmb-tDi#e^Xc`3bY*0xu0ivASfKyKmw+ zQya!zH4JM2FB#@=JdC?)7}m#KGR*z)Fr(|Z9uGsQesmnmK<*;&J828!^7tM()Z#&U<4h+z*;@6Zu$@?l7gm&bn1CjcVRD~X;K}*X`RLv_6h@Kos9pQ*-~Yqk{o$W~ z|Lq@s_pkr>yWg5cu2+dvp0S>~vM(xqxemP|C}>r6*hDLuv=V*SFwTfw)(iV|JiWcS z)*a}d6oX50S)EQypgK3|UR$Xym&rOr7T~HJoDYNoV+o2X#`mcJdV$fB<%(Ppy3X*T zYrv)Tp@r6%SJ!%pV#z4|SMyO5uUcS~>k<;qf#+#e901JM&Zhz%pKoOQz-W};@fmGZ z4a}wbnbp9#a{jDp;22kR==3c?rIXAK$1;?KN~}3b99zOHW_e+y?lFSjmp%A9h>@#W zQ)y$AY$brvv$bw!whS@HmaP1gW;_KdtN*!9v>3{*o!cf46B)?b=+eb>2>wQQB%5?I zwU6#fsB;Swf38uI6b7@9l4MEzQ-aG>A0Cq}MI1Nf?@33}xz2+Sv}D9$S)9#@V+G8K zL|Y<*C_?AOGPKkMlTNq~{%EW%Jv75?3=QTE*8fV3$M(sbM2DYO8QEj2tep$yb7y2f zCnG1HFBv($)}eZIj*{&2x8Lk_4P2-zag<)62^9q<5G!N={N^X}5ntv38VnF(nn&$_D!u=V=Bbx3(74-%y1iQpneZ_d^yT&y^t}7D+n#y!?@W@ywGV5}|xmL0*k! z5gm4-e&MhodOWAg2mFB}Fps`kn^OUnMrh<6jND>+oYX?kv&e-VMkb9dJDJxwqKPE6 zW$1?NOh8YOP(GTb0FLIqWciK0jVgmi(R|2{RbUI^ zJZV>r_=3H73X1=BCq1(hNA^rwDj)gUe97i~_V+;Yd_M2e$6N8ngdM9R!o&u8#Q*zn zZZdGg7ih31c3q4@ia07LDWM^A^Eog*u7$~qXN5Tryry=VRkFyIyex-A-YDijF(y`& zUR{VnNc-@WX_8~vgxs>NWmlMnjJ6k-Tw3(1~YZ66wbIy(nM5gGG zhKm4TuA@v55aMHqYssRXCP435*|JJJu$Wk_s{`rLnQoI8i!lgPwy*CaF5cJq*W`<@ z7xqVED{K$FV6B;+E@~{q;wp1WI7opL{1Zcdw5RJ~NWXOGW^FXGN2alVMh8kBFJq7- zE3Q888(Koqk;rKS(VNnBBXznrYv|x+?>}mF56;!zrpLVv4Js4)%<-{e>oy@4i z+GDF`LU`!Ri zn7p!pdP11~l#EQ6g_326ro|Rv(jYi~GsY?Yg*Fg~tP4y|L@*0C+6R8+>+~pHeE+>8 zclMg-K;(i}5-HA3oQWk&TmMcv=qLYNX*UnZNf7pnW%$aRY7$|K+azKlC3zCf&gz<* z0zbk(1%Bd&lXBN!X&A*S;;GRkDAxQcIMdNcu59S;@}xpcoZ%ua!|6xVEI$`7&|)Gt zoL?gcDC^}_?lW-vY-vjgCl4ib-zGdNYUO*NmKQnQaAsMj&aBe~*58sgcvrskOGnn~V z0u@(z9xmuCQ@wqH63}@W#Jbb^_4NiW&C8?p0MC%=Ru-JGSbm2=7VgZ7rKW@u448KF zV*{NhZ(SHh#WG_B)&pFbVg13Y#(DR)!avSBj1xuf)mJ&Y-GlORniK%)}=eOdxdd=KP z;q^x!z8y)H9B(p_%CEqhX-0A?|L{Efd#ELs0io(l>t#!|9^osvlMhn1HdZn=?D(rE zN>In8b=Nbz-E=K--2hh9?FKeui%luUNX*Hd!=!*FC$rwiFKwKX2GG_ zh`U1^0zq$TJ9l#-0$JqEE5?RvtNm$ty+Q~RntDb4U@NA$q6Wu}W=9*<$$?1e6n65K zIx37QQi`!CO8kR;1OSmKX;QSNUrqwRWa6;(F zQ<P^nss} z<11C07NY{BDU6iDD}b@bOFBaJhaYVz%BW?0D+)F7rah-`f<}M08D$(#O3E}ftNFOJ zgl)x|MQnpM5!%?PmvvgQU5%3I3!D#IaAw{vtRj}NwzQ664QG=Y{JYHW`Vvi(F?hre z)P18?1uR7(@;5Hm01;c7R5y7?timRIj~+`nlG-X#X=uf<3!_zcyx1sRg4VqnamJ)^ zfpm_ z!fBk3qS20Q-X)r3r?ldYnuK3mSY`PvpLidd1fPV6AWUCDSY`Rc>x4QYR4Z7t3fU|< z+{ioc4p*Axk0JWzo&Q-*@IHu+n;nAax@csT==VObaH;u-i1S8S!u3e&vZNb1-i`UV zPphmOK~~YK%RH`7QE8N6xC0G(k?tD9MCyM2mG4z9B)*m1oZwTABK4SzS&TCf&WSRM zvhZ-Vh_Ckg&P%ikSCZqP%Wx(ABHC|eU4~O7Z!QMFy%Rv}DNrZS8ebM- z!_-h$gmCz-{RUd?{=E+y{Fw2t?#^hMWJJb&NA;7alIJ;&__b(~$}>e7VxvuM-Qm)O z5o^3;&fQi?uHvY+VM8oqUfo}ynuBmS=>2(K0#iQ5arz*P zC```C-dRlLc(fl**2jhqAqbFf^;66|+>2BIR2pO89S`_NBO^}X+1zN)G`ikX>9)?i zX`tHkw~a}7ZvorYkrb-1s`|F6V1tO(Tg4MFI-m>E*q)Hs>0JP%4R;QAU%H~n%Tuuu z`|{7^85K=@fhpBUVBwkTqElD}p_Gdqb5M{RhH14x7`0m5-@A+({bUuNv? zd8vXH&*c%bXE#}XrJFwVHyT&+W(#G`iBAZ`V60g)XT|s{6@UM-Hhpj{SFmr&T5kG0 zE9QN}O`qoy99k3Yh4f7yQhTV=;0$sB4bs@iz*S2W=`2Q0k*;M}5e(fU4S>xW1}1DB z8ew{V^Ua`Vrr(8trgj$V)CPIjsj#>d7O1$6CVdDjmCQLcorRhUwU#Ka%SE7@yYd1E zR@|H@bRo%sI~@7K1dtje?(r^1tWqkrY452XV^ntFj~G>}Uj*H4sk_Jj8G|fPuzSe& z0Q+drKEP8p%*pC|vbZ%YG&-)X0$m2{oKW3TQ{LbcNr+YR-V6o?nb(F{CJZaw01Uwr zSq2b6t!SlGfDP=*u0E-dBf9-rgY&$ytM9&AzAfR|arP_YxUa?`MowU{sve%Vs{_g) z*bIL*RRE7fJpTaLl@)YD)4`4NC>HHl-p&S(_(M^)q>J?7bZBUoMj9dB3`(`0Ari`I zk|>n4nC05a0o=rCAac!l82^I;gVMfKvS>OfR6?ox_m{X|26y=m?CZQ67^^Ay{d8{bMya8Q%9tb2#!`- zrAfq6(zsXwSG%VoVGmA&Stj+TsRO&Z_`q$33+sW0+rg#g=|%piXP2h3B4oiwyd(-F3Vhl z2)qS!kinh7jkwj-QxWX)GY#iV-BC8%woGd%-PfZ$ib-Ukah0sih3?6@5^CH7PBa)d zUdzit9aK9FwXSbl?D3z$bvuDh@w$>0rGGR(3vGtZE zW4Kq_$kWG~8d@tcj*ISZ5V$*Pgrwq&vo0=k&f&KNZd}n4R_1Jx_S>I5s+6Lb8VjNt zisRms%!e`Hf^Bq?2Ysj*_dZ`wAzWvjC`>VZNtK9u$#eKE3h9$b)(={**10sR&D(i< z3WXSxJT?mSfKuQ?0ran#<4u#Gp_o+dpjAHCbtowXFbYS1y3SUzrw; z*+{828?V`DDmyvYQ|K3~?DNyDBp2LQ<0J{rg8f2Cf!%geK&T)*4HI9E49KG;f4p4a z&ntR_Y%&!8GW?D@v^Na=@%@6Z@A~rAxd0zEUvzxm^N|63D`F+UCozVmF;PHoxi=M; zbfp~%k6sXzL)Sk;xstHyVbvJ;)_`fh5VYlyfr<=PtLUtgw<3c#z7J8skrY|rqnQhQ zL%UtWv|Ju3I8i%Knpva*vq_Q-Rp32)A5hDp)XDrH#&|3XmGU>C){i z?WxNk8#mFY)}#$MbjCKA-i!V;+3s zjw1DT@jNCBR}&y%W-t#_2Dy{gGJU*?Me#k)gpM5JHUnI~gq9Cymk-?2V z`(o{RZI^C!U#({Iz8aq+y>QhP09ZpVl`m29A3lhDJ=8C?KN$?TSpC-KWQLjel;ME*CC!-( zws-wIK`se2!?2Oy3r>k2CPT#)>=Kz@5nR3T;-YE?n04JidtQ5&{z z1W_R-y$fv?0$FVI2`Yv5^Tq?A_=LCY=tK#z76@T2j$V<8m{>n^%MQMwY;3aw|I)<9 zT_$g6V)v=Y_sq$iGqHOu?p>$M?{vG07P?_#j|#-nXRnGfi$T7xYk9KnDSxQi>mz67 zIQd>jZbKuFGfR0RQw~}+N#G&0MKCV$1rtH1DwjCUeInyJNp&{K&FLck4fQ%JH6)j9 zPFGPi?h)z{x^SZX8+;0TSUV}O3%m+=i^M@UVwb_Q2>Dm-^PWPgv1gZ}?DJ{?2KYn- zSe1LH$+}(yud;xt6nRaB~ z2{L_^GuE@kqS|Yg(mwyOIeA#p^w1}%&`%bFM3042Hh(we**P4kD0#DA?msfi->;R$ zonmQNu&q0GUfOonc zN41R{0Mn}YQDCz_{xCy;LhpqyFo+rnPBy@w&dunf!G6JBJWtIZ-AT_0nPVY3!V$5#9)aLS{VYw#ztCjnz%Cwqs>0ld!e06W?7Y5|?7+SsB=KVlRY% zA8=4kz_1j}dC+|*wu|3D!3isytupa}m=haRkw-dT!SDFzWna5Gs;e+eD zkzq{kzWmt4OI5!R%g37ctIYzKkxX5!14&dYLLc!9Qot`2tZhJjwqY zL4A`28zRy!4LLQ-^u?)Wmmn(f)U;QK_Lc#F84`9VIuvv%Nvh>k=%H$0A^3uZ>%4`) z6+Nzgw~3MRm(gQ?{B2r@ob@UTk&t06oQZ4C$wI@#_m2!bmBuJw($OQ&pAy+zMM_ixeu^I7#F%AYN+lviIyDe`_eB?P_#T$b(1yp9n;Lc>> zWMAqj{^2>6U)~t&SAy&8!A!D7$-oT58k*{{7H&KElfrbO19w1`y7WzvC6iiYChS21%{ zKhE-4TBJ%k*!oOt4z{1o+=17Igqc%ewk?>z#(-PiqHLi|0UEI*jb&Zp#uv;&&`smN zIZ7rW*omWq0829mNCYgKvzae128bD!bu&k}SIwNd#ta+N^+r6|2=@+>4y_gnc2Bw* z=?1VfIWrlswy8r8)_d)16A8JmEvXZ{iOT_OKjfk|wMVSzZhI3i6R%b8 zUI-u4?DphRe))vKiHUfmpz%Hj8(G{C`l0+=!Wr5Vb4hEPRElj{7T3h7OWN*c!Vpqq zb02Ah0W5nTC1b&2KmYK@KYsV)kN@Mp{pPoS{||rnhkyS4w}1HEzy9Ozev9dQdKJAz z=_>h++lc3a+`2F+E-12G5s>8ND%S~nk?%p<0j1_y@s|RnE#+2z9Id|buALhT!`QjW z7xIsUH}r^5ZB!-TIhZ-q0EayW4gZKU00nY=v@AUH9r_H{hcE2fC|kve$P#o@%Rk}^ z_M(5pf4h^O6EaMj{!vbb$;EnX&UdcP28hEyveu`o;O2?;E`;KX9F_|dXl-iEBUa{I zo|nHjmS<_A+Hs)qp2`p_K|I#RJEtMa1bQy38OwqojmeH=D)4wDKI{?*koa&Z6aWl= zs|4l{7`3}JTx7ipV;1x94yT3zV(N$#)ln7;t=)q5Y24}~C2kxHNuN(@h@#lHX;8kT zq({A6lXRIitCRtgi$oJAc`7Cdk;{j!G&PQvFHEx6AYb*W?I8AIo4p(9;a)yP3j@XL5nw~wv{(OJNr^NnIm9(*6RN0wRR;VH>Ca#yI-bdrCP}hAmj@zGy z$GGe8_}?0blq6-&!sF8OKF(3!yS4iCV84_`x(?zY>AMXz^AU=JII^U4mLk{~b&zQ; zW>1!4Fznt(0)2WPRW^cM6@;lEparr6y=~igC{wxmM9A<#^orx9!8`FC)5Ct5U~{JG zuvaiI(Nf+7n-%z>&R{GOO0PDk3sduSv+#+q#nCUo4jud_YdhnNoh z9Ai+4qY(jG?R)x;Db~OMvwLpfGhwAXx6tRrq@>`R-+%z$e)q?pfB3KOzb;2X>bdyF zVs^wgeoqR*jrifu|9fjlG+dj^!nfOn?dtM(!YMD%|Mr`GIh0h|UwrT|cGhl_7d}fy z+VpXBbGurMG2Yc%uL3nKT=E!1zD^3J^Sn4M=8b=>(O`~!5#wTA7cNtr$q91&C z>g4Nz=K^6Z{PT(`tALxHHmGQ7IenC- z`ftBc#?A3lT^A835Hu?}n!yMIU1FL@h)f{wDnUs%OI{v*E(7I}@QI``QUi9xH8~}d zeFNBgq3tqYdvv)8?DQdvD}f!sQv$2FBaAgLTL+SoJx*Y@93HgY1h&4$aXn^jRT~$$ z283xAoC$iOTi$tc8`k=E0zd_2!Z!q4$;qUa;d^DDl(OQu!60H52wP&yUnChh6<6@& z@5LinZs5e>TyRwr0ke85`kl@El!%PpR)<~IiUy0$oso_@D?ik9X98b?5gTHxl^M$~ z$Bfa)E7Wpx)MeCcGL;LrUvYKZSL3+-S=VbnyNCkFeKk%l;4D0I##-aJuf|EjogHV+ zhiRPbODnb5FQ{|X#37Tj#JZ~gdCGuGYYfMaHp zvxxux_M6hfW@ih@arVS4yQPyIAaH3|Z4;RBy@D`Kh}lUVq6HE=L-&m9j_goLVmKP! zG}Z6;!NhAnw5&^w|y-zAz!7{15z)JDcbiw9q^fs;cd7s)aR-0z~M+#6k^ z3*R75QX4bCX`|@w15QoHKE6Ix?*5{Yxn>PvYKFS|m-<|R37~1c2!CN>rRY8jj1Tu! zNOSr5hQqX(?sdG-?Vk7JBANx3eY@vD-z_(eKbLD)0tZ8IR)uauaH8)(X^0Qqs&+mB z%)QM+t)c|&5;glBB|waSvh%o3wefy*Cifm?Ps2_G)M2#5U*5T0R8qm2UqVEvHU_0= zh8l!$rd7FZ8^1}z24v$)tkh~~6kVh^?Qhyhr%T4NlW1fi3wY6cfUDpr8WHuj7|Vc| zMKRIH3Sw=fTzF33?QwJ_eCsoO`IzZXS*|(zD@RTs*>R9hpWam)QqP?83H-kGJf4Cq z+AZjpqin-xyz}$8yv8Aucn>=C^M*S=4?fp-c;qE%=aP7j)Z>DV0#A;hel2kM654vG z1S-(a?E1RHV_hOdZDH*uTe8^s(K5(f>l$Upc>in&_$%KHsEh+}SX#vR{A{$y?YjXm z`XdWjnJ^dP#;}ro@(k*CqPQ> z?H0Y_2K8gjO!xe(n$q$9cYOue;gRR8wa|}LQ(1!MY{g}J4GXzFz1IM~KF<13;Cp7F zrPN?Vz}l)gGuKnah8eK~9a^`tJ{u*Qlp+4DS$|01iCw&&_(AVk{U7Z$bV0OKXE}3V z^}lP9(AMet{G0x(!u*tWm~ zqyOTMl_Q;IZ?T_(i*N%Zw6`%z#rXn}#&q6}QLHKsRGWndXIUTsaUX*_>6wf{DFK?Z zo}91r+EUEsd`>?^-x#no4SuO;h)t;#Ot|Peo^iW$4wx-C|M34%ujPq3QhR^-L30}4 zc5}pPA`FvD(XFPv&t%|$@$J4X19wdlEm(evD{FIO%01*gMW*Vo0{xyM(!aYP8=D)s zb1_X|cg5btG)(hk!Z68`q4r|y@?-*i@usW};YEvBCs_cK#zQdIiX#OYtxeDNE7P;J z{PJ6FFfy+d*Gl&^=F75`&+-N%;y8_KIa*NnKwiC<(UqM^uI!fw(VmG?i3j@uiz9^h zAKHz5UlQ(AGfmuA^~`BqF(uqZ07}N-I|aj*fp&m>W6)+c(PCvdr8M0N^Po{Yh%fSi zEKqKwZA0gc^ii3()_ZFE1~SjO>Am$rwpY>!oEnKI^fAxn@0BblEn(+!Z4!E}d~d(N zp&db|7V4^RH&+m>DQ*swP?V<*6!iDXxN}8*KTm7~-ZJsBlBG0_MKJ;6jzeH#W|3hp zuR7Z(!dq*fP37eok@WUi4dK2TCkb_~3S&2yFeokNz8c49JP*%&zDDD?uf{PSoFC`R zhQI>T%c_vUfX!xt|Bi#&P{v2c&lQ&ul)fbA$4QA3r@$Sw!=2 z3*c$a_xJRpYPOI2o(kr z>!D3*FtEx3njJ+(j=KG{|g;@9dl(0Rs=nbuPrTl#y z01E>y5s)w?S>_cHfPIoO>^|^NAcbZfr?-nZN!Y6zvbzJ#lA-9>Vi+u0zuQZdQI;G_n zUclw}OLhr;p2e8&gBR|Th4pEIRWmu&YJQ5loQ{k6Otfuo95s=kt)!Y=Ap05OUUfed z^?KWt5pQ}}{(B6o`nR>|GEtG!uNT#UvDrBrRek_#7^W%J)MDmJc~EHhn?2Myc_4qu zptIos9k)Q0A;>F{G?ayYX$Wq}d&qFL87uwp(%(Tju~D4y^2sZdbaBj=(R+NQE0pdZsxls@JK|n_(D=Z`=eabwfUArqfLj_>wzJxZ-Y z1hNCf$D%?mwm^+!sTZ1(=U80kD>`E^<}0tc@#TK2?)Uz!>wYnzGdP#j{oelq>VC0? zl{B9WKt;|Gl6QsSkPs9n%UIPF3&k@}Ld6DHYZKD)&{FHlXcmmrf8VVYSKEb0|m^>RH!BldW&GSqYS^>l{=VLp(O0yC}Kiu!11Rk14 z%&9@CC-{0aG<-*ktlWXu4C_m?$<7H;eh{cHz^Fz*mQ!Wx1@58YMITwkSkK+vhkY|L zUP+LQ&AblOWc;;NWgWyr%bDdwdfqAIH4{Vze?VdsF?~tneOel)N|l?GA$Zc<4o+z*4k)z7SNo$Pxd(O4j4#U7L#_Y)J9U4k~D0g zKJJrT5;&}nWsms9G7Hw+g7kZtXyP4hq_q-v#XDQ$EU?9`O!w6|9{)T%pZPU)H!|JC zkLK%Oz9mOR%U{-BZJE#197{PfQEBK6THh+QPl ztzOrhl(HvZuBA&$JxMK5XLAiFy?bn3kd5HduN&tKUbRAg-rMGGf&GLjv$2*??2hV@ zsObAIgLh~S$IRE@sxW=PuyOYE>#YSzAa*gS()BHag*_#8x0+|xWs5o3ZrpM1}2&I z4o&*yTDlu<_rLgBx-d?q>#H`10UaaHuT(#tT5v>J_i?r8(T%o%9T9Dg_R6=?)vneJz>ZicfqjKr>89L&Id`~E zwU@Gw*xJSu1~Xyu!4Op&d?vTg0oQFMI4^Im0*gx%DPchyKe&6o_g-9Or(mr8@ZN5v zn?7SR2#u9b3i+Jp^=rMAj%o$U$c*kC(70DPrTJHUDcw*ImLwbR=I$>Pci*9NN{u@p zcH*0ywdD0IrK9pStQ87MXsBp0K#|$Qdgf`?n5K?$Suq&W_i|6&Qx>E=x8to}&i}mr z`5)D;RMuX%c&Tb|z33}Ui}KwWlX!z6P35?3s_}fL2;2x_l~$@prI}-oNvN6Z$E5(| z9CoVXw~`TYU$IFt*|~9Ml*>@+$YaPz(l$D#B zNqu1TDxLDq&KeHmi?w>apRHDTrh*xb6WZScNGWOCsoDoz z*7E*U%G#}7Bt~-n(X~K};-nFt**J%$dSxyLBQCod8+k zk33U|YrhpL=9$80hgT2#cD;f;4%#;;J}J`0i41uTxHJ00p>Fu0;pzSaZlnH2p9Qcp zwKhz+^NJa=dldG?E1<5m2p1$gco_yWv5xb3>Nqjz89_51Ybq1Y0hHR(=y)PXM#Ii^ zc@L{c0MB6IFdZ&1Zf?Bm32u+wy%^`90-!aB(=X5`)x7>o>9&8NTc0Qv;}}lSaP#7) zI-2v)&jely^$NFP_S^2%TH<+-aKRXfW_@ib(LAmmI>j1wb9Rz+O$L%(C~TebqjL!U zTHhueRx=JG?aXsLEXU14oJle%EIL-9gqXaJjt`buMiZG*TG_CO3^Lky)>2S-4p*JJ zgR@UyHPs1Q7*Nq!E>*}LoH`Q-!%|ucW!?V6fxISRR!rcMe1XmW_`?jNBwwz&8wo5n zz@K&@U+1bSfXGNedrEhMU%1xt =4Az@2c3ol`Je{@wTR|kv?lVyK`3u^DZBZ1@~ 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 delta 82249 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 Jb00S8{6C(}2AKc= diff --git a/tests b/tests index 7e755e60..02ce0188 160000 --- a/tests +++ b/tests @@ -1 +1 @@ -Subproject commit 7e755e602cb3c7d2492febf7801c60b96d5351b6 +Subproject commit 02ce018866be7d5ef14399f4932af31a170b997b