From df8ebb61ec75fbf3aa2debd8c4486b9b812cc5ba Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Wed, 24 Apr 2024 18:51:19 +0200 Subject: [PATCH] Integrate benchmarks and differential tests against an EVM interpreter (#7) --- Cargo.lock | 444 ++++++++++++++++-- Cargo.toml | 10 +- Makefile | 20 + crates/benchmarks/Cargo.toml | 32 ++ crates/benchmarks/benches/execute.rs | 185 ++++++++ crates/benchmarks/benches/prepare.rs | 172 +++++++ crates/benchmarks/src/lib.rs | 24 + crates/differential/Cargo.toml | 10 + crates/differential/src/lib.rs | 154 ++++++ crates/integration/Cargo.toml | 9 +- crates/integration/contracts/Baseline.sol | 7 + crates/integration/contracts/Computation.sol | 1 + crates/integration/contracts/Fibonacci.sol | 10 +- crates/integration/contracts/MSize.sol | 2 + .../contracts/{sha1.sol => SHA1.sol} | 0 crates/integration/contracts/Value.sol | 2 + crates/integration/contracts/mStore8.sol | 2 + crates/integration/src/cases.rs | 119 +++++ crates/integration/src/lib.rs | 362 ++------------ crates/integration/src/mock_runtime.rs | 11 +- crates/integration/src/tests.rs | 267 +++++++++++ crates/lld-sys/build.rs | 27 +- .../input/settings/selection/file/flag.rs | 3 + .../input/settings/selection/file/mod.rs | 1 + .../output/contract/evm/bytecode.rs | 19 + .../standard_json/output/contract/evm/mod.rs | 3 + crates/solidity/src/test_utils.rs | 54 +++ 27 files changed, 1567 insertions(+), 383 deletions(-) create mode 100644 crates/benchmarks/Cargo.toml create mode 100644 crates/benchmarks/benches/execute.rs create mode 100644 crates/benchmarks/benches/prepare.rs create mode 100644 crates/benchmarks/src/lib.rs create mode 100644 crates/differential/Cargo.toml create mode 100644 crates/differential/src/lib.rs create mode 100644 crates/integration/contracts/Baseline.sol rename crates/integration/contracts/{sha1.sol => SHA1.sol} (100%) create mode 100644 crates/integration/src/cases.rs create mode 100644 crates/integration/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 189c268..bd11682 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,7 +68,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", "syn-solidity", "tiny-keccak", ] @@ -85,6 +85,18 @@ dependencies = [ "serde", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + [[package]] name = "anyhow" version = "1.0.82" @@ -229,7 +241,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] @@ -307,6 +319,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byte-slice-cast" version = "1.2.2" @@ -326,10 +344,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] -name = "cc" -version = "1.0.94" +name = "cast" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" [[package]] name = "cfg-if" @@ -337,6 +361,33 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" version = "2.34.0" @@ -348,6 +399,31 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "clap" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + [[package]] name = "colored" version = "2.1.0" @@ -392,6 +468,42 @@ dependencies = [ "libc", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap 4.5.4", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -621,6 +733,20 @@ dependencies = [ "uint", ] +[[package]] +name = "evm-interpreter" +version = "1.0.0-dev" +source = "git+https://github.com/xermicus/evm.git?branch=separate-compilation#596447a3391f42fb4f99def5280c076a15e44d18" +dependencies = [ + "auto_impl", + "parity-scale-codec", + "primitive-types", + "rlp", + "scale-info", + "serde", + "sha3", +] + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -721,6 +847,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.13.2" @@ -751,6 +887,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "hex" version = "0.4.3" @@ -850,7 +992,18 @@ source = "git+https://github.com/TheDan64/inkwell.git#5d5a531c765a6ad37aa6591c02 dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", +] + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", ] [[package]] @@ -877,6 +1030,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "k256" version = "0.13.3" @@ -930,9 +1092,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[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", @@ -986,9 +1148,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", ] @@ -1084,6 +1246,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + [[package]] name = "pallet-contracts-pvm-llapi" version = "0.1.0" @@ -1111,7 +1279,7 @@ version = "3.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 2.0.2", "proc-macro2", "quote", "syn 1.0.109", @@ -1150,10 +1318,38 @@ dependencies = [ "spki", ] +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + [[package]] name = "polkavm" version = "0.9.3" -source = "git+https://github.com/koute/polkavm.git#e139f8c3261a29197886b33829bbb36d43403756" +source = "git+https://github.com/koute/polkavm.git#4ca06cc1b7cb435b2b92e81e30c2eb0e988f563e" dependencies = [ "libc", "log", @@ -1165,7 +1361,7 @@ dependencies = [ [[package]] name = "polkavm-assembler" version = "0.9.0" -source = "git+https://github.com/koute/polkavm.git#e139f8c3261a29197886b33829bbb36d43403756" +source = "git+https://github.com/koute/polkavm.git#4ca06cc1b7cb435b2b92e81e30c2eb0e988f563e" dependencies = [ "log", ] @@ -1173,7 +1369,7 @@ dependencies = [ [[package]] name = "polkavm-common" version = "0.9.0" -source = "git+https://github.com/koute/polkavm.git#e139f8c3261a29197886b33829bbb36d43403756" +source = "git+https://github.com/koute/polkavm.git#4ca06cc1b7cb435b2b92e81e30c2eb0e988f563e" dependencies = [ "log", ] @@ -1181,7 +1377,7 @@ dependencies = [ [[package]] name = "polkavm-linker" version = "0.9.2" -source = "git+https://github.com/koute/polkavm.git#e139f8c3261a29197886b33829bbb36d43403756" +source = "git+https://github.com/koute/polkavm.git#4ca06cc1b7cb435b2b92e81e30c2eb0e988f563e" dependencies = [ "gimli", "hashbrown 0.14.3", @@ -1195,7 +1391,7 @@ dependencies = [ [[package]] name = "polkavm-linux-raw" version = "0.9.0" -source = "git+https://github.com/koute/polkavm.git#e139f8c3261a29197886b33829bbb36d43403756" +source = "git+https://github.com/koute/polkavm.git#4ca06cc1b7cb435b2b92e81e30c2eb0e988f563e" [[package]] name = "ppv-lite86" @@ -1216,6 +1412,16 @@ dependencies = [ "uint", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + [[package]] name = "proc-macro-crate" version = "2.0.2" @@ -1223,7 +1429,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" dependencies = [ "toml_datetime", - "toml_edit", + "toml_edit 0.20.2", ] [[package]] @@ -1252,9 +1458,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -1416,6 +1622,18 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "revive-benchmarks" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "criterion", + "hex", + "polkavm", + "revive-differential", + "revive-integration", +] + [[package]] name = "revive-builtins" version = "0.1.0" @@ -1430,6 +1648,14 @@ dependencies = [ "serde_stacker", ] +[[package]] +name = "revive-differential" +version = "0.1.0" +dependencies = [ + "evm-interpreter", + "primitive-types", +] + [[package]] name = "revive-extensions" version = "0.1.0" @@ -1446,8 +1672,8 @@ dependencies = [ "env_logger", "era-compiler-llvm-context", "hex", - "parity-scale-codec", "polkavm", + "revive-differential", "revive-solidity", "sha1", ] @@ -1588,9 +1814,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", @@ -1617,6 +1843,40 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scale-info" +version = "2.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c453e59a955f81fb62ee5d596b450383d699f152d350e9d23a0db2adb78e4c0" +dependencies = [ + "bitvec", + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", +] + +[[package]] +name = "scale-info-derive" +version = "2.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18cf6c6447f813ef19eb450e985bcce6705f9ce7660db221b59093d15c79c4b7" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "sec1" version = "0.7.3" @@ -1660,29 +1920,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -1804,7 +2064,7 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" dependencies = [ - "clap", + "clap 2.34.0", "lazy_static", "structopt-derive", ] @@ -1841,9 +2101,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.59" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -1859,7 +2119,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] @@ -1891,22 +2151,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] @@ -1918,12 +2178,33 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "toml_datetime" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "toml_edit" version = "0.20.2" @@ -2004,12 +2285,86 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.60", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "which" version = "5.0.0" @@ -2039,6 +2394,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134306a13c5647ad6453e8deaec55d3a44d6021970129e6188735e74bf546697" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2219,7 +2583,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] @@ -2239,7 +2603,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index eaad105..902f9e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,14 +30,22 @@ rand = "0.8" polkavm-common = { git = "https://github.com/koute/polkavm.git" } polkavm-linker = { git = "https://github.com/koute/polkavm.git" } polkavm = { git = "https://github.com/koute/polkavm.git" } -parity-scale-codec = "3.6" alloy-primitives = "0.6" alloy-sol-types = "0.6" env_logger = { version = "0.10.0", default-features = false } serde_stacker = "0.1" +criterion = { version = "0.5", features = ["html_reports"] } + +# Benchmarking against EVM +primitive-types = { version = "0.12", features = ["codec"] } +evm-interpreter = { git = "https://github.com/xermicus/evm.git", branch = "separate-compilation" } [workspace.dependencies.inkwell] git = "https://github.com/TheDan64/inkwell.git" commit = "d916c66" default-features = false features = ["serde", "llvm18-0", "no-libffi-linking", "target-riscv"] + +[profile.benchmark] +inherits = "release" +lto = true diff --git a/Makefile b/Makefile index d4a97c5..5b29dc3 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,26 @@ test-solidity: install test-cli: install npm run test:cli +bench-prepare: install-bin + cargo criterion --bench prepare --features bench-evm,bench-pvm --message-format=json \ + | criterion-table > crates/benchmarks/PREPARE.md + +bench-execute: install-bin + cargo criterion --bench execute --features bench-evm,bench-pvm --message-format=json \ + | criterion-table > crates/benchmarks/EXECUTE.md + +bench-extensive: install-bin + cargo criterion --all --all-features --message-format=json \ + | criterion-table > crates/benchmarks/BENCHMARKS.md + +bench-quick: install-bin + cargo criterion --all --features bench-evm + +bench: install-bin + cargo criterion --all --features bench-evm,bench-pvm --message-format=json \ + | criterion-table > crates/benchmarks/BENCHMARKS.md + + clean: cargo clean ; \ rm -rf node_modules ; \ diff --git a/crates/benchmarks/Cargo.toml b/crates/benchmarks/Cargo.toml new file mode 100644 index 0000000..c04a6d2 --- /dev/null +++ b/crates/benchmarks/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "revive-benchmarks" +version = "0.1.0" +edition = "2021" +authors = [ + "Cyrill Leutwiler ", +] + +[features] +default = ["bench-pvm-interpreter"] +bench-pvm-interpreter = [] +bench-pvm = [] +bench-evm = ["revive-differential"] +bench-extensive = [] + +[dependencies] +hex = { workspace = true } +polkavm = { workspace = true } +revive-integration = { path = "../integration" } +revive-differential = { path = "../differential", optional = true } +alloy-primitives = { workspace = true } + +[dev-dependencies] +criterion = { workspace = true } + +[[bench]] +name = "execute" +harness = false + +[[bench]] +name = "prepare" +harness = false diff --git a/crates/benchmarks/benches/execute.rs b/crates/benchmarks/benches/execute.rs new file mode 100644 index 0000000..70de49a --- /dev/null +++ b/crates/benchmarks/benches/execute.rs @@ -0,0 +1,185 @@ +#[cfg(feature = "bench-extensive")] +use std::time::Duration; + +use criterion::{ + criterion_group, criterion_main, measurement::Measurement, BenchmarkGroup, BenchmarkId, + Criterion, +}; +#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))] +use polkavm::BackendKind; + +use revive_benchmarks::prepare_pvm; +use revive_integration::cases::Contract; + +fn bench(mut group: BenchmarkGroup<'_, M>, parameters: &[P], labels: &[L], contract: I) +where + P: Clone, + L: std::fmt::Display, + I: Fn(P) -> Contract, + M: Measurement, +{ + assert_eq!(parameters.len(), labels.len()); + + for (p, l) in parameters.iter().zip(labels.iter()) { + #[cfg(feature = "bench-evm")] + { + let contract = contract(p.clone()); + let vm = revive_differential::prepare(contract.evm_runtime, contract.calldata); + group.bench_with_input(BenchmarkId::new("EVM", l), p, move |b, _| { + b.iter(|| { + revive_differential::execute(vm.clone()); + }); + }); + } + + #[cfg(feature = "bench-pvm-interpreter")] + { + let contract = contract(p.clone()); + let (state, mut instance, export) = prepare_pvm( + &contract.pvm_runtime, + &contract.calldata, + BackendKind::Interpreter, + ); + group.bench_with_input(BenchmarkId::new("PVMInterpreter", l), p, |b, _| { + b.iter(|| { + revive_integration::mock_runtime::call(state.clone(), &mut instance, export); + }); + }); + } + + #[cfg(feature = "bench-pvm")] + { + let contract = contract(p.clone()); + let (state, mut instance, export) = prepare_pvm( + &contract.pvm_runtime, + &contract.calldata, + BackendKind::Compiler, + ); + group.bench_with_input(BenchmarkId::new("PVM", l), p, |b, _| { + b.iter(|| { + revive_integration::mock_runtime::call(state.clone(), &mut instance, export); + }); + }); + } + } + + group.finish(); +} + +#[cfg(feature = "bench-extensive")] +fn group_extensive<'error, M>( + c: &'error mut Criterion, + group_name: &str, +) -> BenchmarkGroup<'error, M> +where + M: Measurement, +{ + let mut group = c.benchmark_group(group_name); + group + .sample_size(10) + .measurement_time(Duration::from_secs(60)); + group +} + +fn bench_baseline(c: &mut Criterion) { + let parameters = &[0u8]; + + bench( + c.benchmark_group("Baseline"), + parameters, + parameters, + |_| Contract::baseline(), + ); +} + +fn bench_odd_product(c: &mut Criterion) { + #[cfg(feature = "bench-extensive")] + let group = group_extensive(c, "OddProduct"); + #[cfg(not(feature = "bench-extensive"))] + let group = c.benchmark_group("OddProduct"); + + #[cfg(feature = "bench-extensive")] + let parameters = &[2_000_000i32, 4_000_000, 8_000_000, 120_000_000]; + #[cfg(not(feature = "bench-extensive"))] + let parameters = &[10_000, 100_000]; + + bench(group, parameters, parameters, Contract::odd_product); +} + +fn bench_triangle_number(c: &mut Criterion) { + #[cfg(feature = "bench-extensive")] + let group = group_extensive(c, "TriangleNumber"); + #[cfg(not(feature = "bench-extensive"))] + let group = c.benchmark_group("TriangleNumber"); + + #[cfg(feature = "bench-extensive")] + let parameters = &[3_000_000i64, 6_000_000, 12_000_000, 180_000_000]; + #[cfg(not(feature = "bench-extensive"))] + let parameters = &[10_000, 100_000]; + + bench(group, parameters, parameters, Contract::triangle_number); +} + +fn bench_fibonacci_recurisve(c: &mut Criterion) { + #[cfg(not(feature = "bench-extensive"))] + let group = c.benchmark_group("FibonacciRecursive"); + #[cfg(feature = "bench-extensive")] + let group = group_extensive(c, "FibonacciRecursive"); + + #[cfg(feature = "bench-extensive")] + let parameters = &[26, 30, 34, 38]; + #[cfg(not(feature = "bench-extensive"))] + let parameters = &[12, 16, 20]; + + bench(group, parameters, parameters, Contract::fib_recursive); +} + +fn bench_fibonacci_iterative(c: &mut Criterion) { + #[cfg(not(feature = "bench-extensive"))] + let group = c.benchmark_group("FibonacciIterative"); + #[cfg(feature = "bench-extensive")] + let group = group_extensive(c, "FibonacciIterative"); + + #[cfg(feature = "bench-extensive")] + let parameters = &[256, 100000, 1000000, 100000000]; + #[cfg(not(feature = "bench-extensive"))] + let parameters = &[64, 128, 256]; + + bench(group, parameters, parameters, Contract::fib_iterative); +} + +fn bench_fibonacci_binet(c: &mut Criterion) { + let parameters = &[64, 128, 256]; + + bench( + c.benchmark_group("FibonacciBinet"), + parameters, + parameters, + Contract::fib_binet, + ); +} + +fn bench_sha1(c: &mut Criterion) { + let parameters = &[vec![0xff], vec![0xff; 64], vec![0xff; 512]]; + let labels = parameters.iter().map(|p| p.len()).collect::>(); + + bench( + c.benchmark_group("SHA1"), + parameters, + &labels, + Contract::sha1, + ); +} + +criterion_group!( + name = execute; + config = Criterion::default(); + targets = bench_baseline, + bench_odd_product, + bench_triangle_number, + bench_fibonacci_recurisve, + bench_fibonacci_iterative, + bench_fibonacci_binet, + bench_sha1 +); +criterion_main!(execute); diff --git a/crates/benchmarks/benches/prepare.rs b/crates/benchmarks/benches/prepare.rs new file mode 100644 index 0000000..98822e1 --- /dev/null +++ b/crates/benchmarks/benches/prepare.rs @@ -0,0 +1,172 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use polkavm::BackendKind; + +use revive_benchmarks::instantiate_engine; +use revive_integration::cases::Contract; + +fn bench( + c: &mut Criterion, + group_name: &str, + #[cfg(feature = "bench-evm")] evm_runtime: Vec, + #[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))] pvm_runtime: Vec, +) { + let mut group = c.benchmark_group(group_name); + let code_size = 0; + + #[cfg(feature = "bench-evm")] + group.bench_with_input( + BenchmarkId::new("Evm", code_size), + &evm_runtime, + |b, code| b.iter(|| revive_differential::prepare(code.clone(), Vec::new())), + ); + + #[cfg(feature = "bench-pvm-interpreter")] + { + let engine = instantiate_engine(BackendKind::Interpreter); + group.bench_with_input( + BenchmarkId::new("PVMInterpreterCompile", code_size), + &(&pvm_runtime, engine), + |b, (code, engine)| { + b.iter(|| { + revive_integration::mock_runtime::recompile_code(code, engine); + }); + }, + ); + } + + #[cfg(feature = "bench-pvm-interpreter")] + { + let engine = instantiate_engine(BackendKind::Interpreter); + let module = revive_integration::mock_runtime::recompile_code(&pvm_runtime, &engine); + group.bench_with_input( + BenchmarkId::new("PVMInterpreterInstantiate", code_size), + &(module, engine), + |b, (module, engine)| { + b.iter(|| { + revive_integration::mock_runtime::instantiate_module(module, engine); + }); + }, + ); + } + + #[cfg(feature = "bench-pvm")] + { + let engine = instantiate_engine(BackendKind::Compiler); + group.bench_with_input( + BenchmarkId::new("PVMCompile", code_size), + &(&pvm_runtime, engine), + |b, (code, engine)| { + b.iter(|| { + revive_integration::mock_runtime::recompile_code(code, engine); + }); + }, + ); + } + + #[cfg(feature = "bench-pvm")] + { + let engine = instantiate_engine(BackendKind::Compiler); + let module = revive_integration::mock_runtime::recompile_code(&pvm_runtime, &engine); + group.bench_with_input( + BenchmarkId::new("PVMInstantiate", code_size), + &(module, engine), + |b, (module, engine)| { + b.iter(|| { + revive_integration::mock_runtime::instantiate_module(module, engine); + }); + }, + ); + } + + group.finish(); +} + +fn bench_baseline(c: &mut Criterion) { + bench( + c, + "PrepareBaseline", + #[cfg(feature = "bench-evm")] + Contract::baseline().evm_runtime, + #[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))] + Contract::baseline().pvm_runtime, + ); +} + +fn bench_odd_product(c: &mut Criterion) { + bench( + c, + "PrepareOddProduct", + #[cfg(feature = "bench-evm")] + Contract::odd_product(0).evm_runtime, + #[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))] + Contract::baseline().pvm_runtime, + ); +} + +fn bench_triangle_number(c: &mut Criterion) { + bench( + c, + "PrepareTriangleNumber", + #[cfg(feature = "bench-evm")] + Contract::triangle_number(0).evm_runtime, + #[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))] + Contract::triangle_number(0).pvm_runtime, + ); +} + +fn bench_fibonacci_recursive(c: &mut Criterion) { + bench( + c, + "PrepareFibonacciRecursive", + #[cfg(feature = "bench-evm")] + Contract::fib_recursive(0).evm_runtime, + #[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))] + Contract::fib_recursive(0).pvm_runtime, + ); +} + +fn bench_fibonacci_iterative(c: &mut Criterion) { + bench( + c, + "PrepareFibonacciIterative", + #[cfg(feature = "bench-evm")] + Contract::fib_iterative(0).evm_runtime, + #[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))] + Contract::fib_iterative(0).pvm_runtime, + ); +} + +fn bench_fibonacci_binet(c: &mut Criterion) { + bench( + c, + "PrepareFibonacciBinet", + #[cfg(feature = "bench-evm")] + Contract::fib_binet(0).evm_runtime, + #[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))] + Contract::fib_binet(0).pvm_runtime, + ); +} + +fn bench_sha1(c: &mut Criterion) { + bench( + c, + "PrepareSHA1", + #[cfg(feature = "bench-evm")] + Contract::sha1(Default::default()).evm_runtime, + #[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))] + Contract::sha1(Default::default()).pvm_runtime, + ); +} + +criterion_group!( + name = prepare; + config = Criterion::default(); + targets = bench_baseline, + bench_odd_product, + bench_triangle_number, + bench_fibonacci_recursive, + bench_fibonacci_iterative, + bench_fibonacci_binet, + bench_sha1 +); +criterion_main!(prepare); diff --git a/crates/benchmarks/src/lib.rs b/crates/benchmarks/src/lib.rs new file mode 100644 index 0000000..d3d742d --- /dev/null +++ b/crates/benchmarks/src/lib.rs @@ -0,0 +1,24 @@ +use polkavm::{BackendKind, Config, Engine, ExportIndex, Instance, SandboxKind}; +use revive_integration::mock_runtime; +use revive_integration::mock_runtime::State; + +pub fn prepare_pvm( + code: &[u8], + input: &[u8], + backend: BackendKind, +) -> (State, Instance, ExportIndex) { + let mut config = Config::new(); + config.set_backend(Some(backend)); + config.set_sandbox(Some(SandboxKind::Linux)); + + let (instance, export_index) = mock_runtime::prepare(code, Some(config)); + + (State::new(input.to_vec()), instance, export_index) +} + +pub fn instantiate_engine(backend: BackendKind) -> Engine { + let mut config = Config::new(); + config.set_backend(Some(backend)); + config.set_sandbox(Some(SandboxKind::Linux)); + mock_runtime::setup(Some(config)) +} diff --git a/crates/differential/Cargo.toml b/crates/differential/Cargo.toml new file mode 100644 index 0000000..7d94a62 --- /dev/null +++ b/crates/differential/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "revive-differential" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +evm-interpreter = { workspace = true } +primitive-types = { workspace = true } diff --git a/crates/differential/src/lib.rs b/crates/differential/src/lib.rs new file mode 100644 index 0000000..99fbea4 --- /dev/null +++ b/crates/differential/src/lib.rs @@ -0,0 +1,154 @@ +use evm_interpreter::{ + interpreter::{EtableInterpreter, RunInterpreter}, + trap::CallCreateTrap, + Context, Etable, ExitError, Log, Machine, RuntimeBackend, RuntimeBaseBackend, + RuntimeEnvironment, RuntimeState, TransactionContext, Valids, +}; +use primitive_types::{H160, H256, U256}; + +static RUNTIME_ETABLE: Etable = + Etable::runtime(); + +pub struct UnimplementedHandler; + +impl RuntimeEnvironment for UnimplementedHandler { + fn block_hash(&self, _number: U256) -> H256 { + unimplemented!() + } + fn block_number(&self) -> U256 { + unimplemented!() + } + fn block_coinbase(&self) -> H160 { + unimplemented!() + } + fn block_timestamp(&self) -> U256 { + unimplemented!() + } + fn block_difficulty(&self) -> U256 { + unimplemented!() + } + fn block_randomness(&self) -> Option { + unimplemented!() + } + fn block_gas_limit(&self) -> U256 { + unimplemented!() + } + fn block_base_fee_per_gas(&self) -> U256 { + unimplemented!() + } + fn chain_id(&self) -> U256 { + unimplemented!() + } +} + +impl RuntimeBaseBackend for UnimplementedHandler { + fn balance(&self, _address: H160) -> U256 { + unimplemented!() + } + fn code_size(&self, _address: H160) -> U256 { + unimplemented!() + } + fn code_hash(&self, _address: H160) -> H256 { + unimplemented!() + } + fn code(&self, _address: H160) -> Vec { + unimplemented!() + } + fn storage(&self, _address: H160, _index: H256) -> H256 { + unimplemented!() + } + + fn exists(&self, _address: H160) -> bool { + unimplemented!() + } + + fn nonce(&self, _address: H160) -> U256 { + unimplemented!() + } +} + +impl RuntimeBackend for UnimplementedHandler { + fn original_storage(&self, _address: H160, _index: H256) -> H256 { + unimplemented!() + } + + fn deleted(&self, _address: H160) -> bool { + unimplemented!() + } + fn is_cold(&self, _address: H160, _index: Option) -> bool { + unimplemented!() + } + + fn mark_hot(&mut self, _address: H160, _index: Option) { + unimplemented!() + } + + fn set_storage(&mut self, _address: H160, _index: H256, _value: H256) -> Result<(), ExitError> { + unimplemented!() + } + fn log(&mut self, _log: Log) -> Result<(), ExitError> { + unimplemented!() + } + fn mark_delete(&mut self, _address: H160) { + unimplemented!() + } + + fn reset_storage(&mut self, _address: H160) { + unimplemented!() + } + + fn set_code(&mut self, _address: H160, _code: Vec) -> Result<(), ExitError> { + unimplemented!() + } + fn reset_balance(&mut self, _address: H160) { + unimplemented!() + } + + fn deposit(&mut self, _address: H160, _value: U256) { + unimplemented!() + } + fn withdrawal(&mut self, _address: H160, _value: U256) -> Result<(), ExitError> { + unimplemented!() + } + + fn inc_nonce(&mut self, _address: H160) -> Result<(), ExitError> { + unimplemented!() + } +} + +#[derive(Clone)] +pub struct PreparedEvm { + pub valids: Valids, + pub vm: Machine, +} + +pub fn prepare(code: Vec, data: Vec) -> PreparedEvm { + let state = RuntimeState { + context: Context { + address: H160::default(), + caller: H160::default(), + apparent_value: U256::default(), + }, + transaction_context: TransactionContext { + gas_price: U256::default(), + origin: H160::default(), + } + .into(), + retbuf: Vec::new(), + }; + + PreparedEvm { + valids: Valids::new(&code[..]), + vm: evm_interpreter::Machine::new(code.into(), data.to_vec().into(), 1024, 0xFFFF, state), + } +} + +pub fn execute(pre: PreparedEvm) -> Vec { + let mut vm = EtableInterpreter::new_valid(pre.vm, &RUNTIME_ETABLE, pre.valids); + vm.run(&mut UnimplementedHandler {}) + .exit() + .unwrap() + .unwrap(); + + vm.retval.clone() +} diff --git a/crates/integration/Cargo.toml b/crates/integration/Cargo.toml index eb53734..50d5f55 100644 --- a/crates/integration/Cargo.toml +++ b/crates/integration/Cargo.toml @@ -8,12 +8,13 @@ edition = "2021" [dependencies] polkavm = { workspace = true } alloy-primitives = { workspace = true } -parity-scale-codec = { workspace = true } -revive-solidity = { path = "../solidity" } -era-compiler-llvm-context = { path = "../llvm-context" } +alloy-sol-types = { workspace = true } hex = { workspace = true } env_logger = { workspace = true } +revive-solidity = { path = "../solidity" } +revive-differential = { path = "../differential" } +era-compiler-llvm-context = { path = "../llvm-context" } + [dev-dependencies] -alloy-sol-types = { workspace = true } sha1 = { workspace = true } diff --git a/crates/integration/contracts/Baseline.sol b/crates/integration/contracts/Baseline.sol new file mode 100644 index 0000000..34bfbda --- /dev/null +++ b/crates/integration/contracts/Baseline.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +contract Baseline { + function baseline() public payable {} +} diff --git a/crates/integration/contracts/Computation.sol b/crates/integration/contracts/Computation.sol index 39cc006..bfb2fc6 100644 --- a/crates/integration/contracts/Computation.sol +++ b/crates/integration/contracts/Computation.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT + pragma solidity ^0.8.24; contract Computation { diff --git a/crates/integration/contracts/Fibonacci.sol b/crates/integration/contracts/Fibonacci.sol index 719ca8a..e9f5869 100644 --- a/crates/integration/contracts/Fibonacci.sol +++ b/crates/integration/contracts/Fibonacci.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + // https://medium.com/coinmonks/fibonacci-in-solidity-8477d907e22a contract FibonacciRecursive { @@ -24,9 +26,11 @@ contract FibonacciIterative { uint a = 1; b = 1; for (uint i = 2; i < n; i++) { - uint c = a + b; - a = b; - b = c; + unchecked { + uint c = a + b; + a = b; + b = c; + } } return b; } diff --git a/crates/integration/contracts/MSize.sol b/crates/integration/contracts/MSize.sol index 2fef855..0f1b0e8 100644 --- a/crates/integration/contracts/MSize.sol +++ b/crates/integration/contracts/MSize.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + contract MSize { uint[] public data; diff --git a/crates/integration/contracts/sha1.sol b/crates/integration/contracts/SHA1.sol similarity index 100% rename from crates/integration/contracts/sha1.sol rename to crates/integration/contracts/SHA1.sol diff --git a/crates/integration/contracts/Value.sol b/crates/integration/contracts/Value.sol index 0ee97de..4841799 100644 --- a/crates/integration/contracts/Value.sol +++ b/crates/integration/contracts/Value.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + contract Value { function value() public payable returns (uint ret) { ret = msg.value; diff --git a/crates/integration/contracts/mStore8.sol b/crates/integration/contracts/mStore8.sol index 33c05ae..7dca974 100644 --- a/crates/integration/contracts/mStore8.sol +++ b/crates/integration/contracts/mStore8.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + contract MStore8 { function mStore8(uint value) public pure returns (uint256 word) { assembly { diff --git a/crates/integration/src/cases.rs b/crates/integration/src/cases.rs new file mode 100644 index 0000000..ca95ed7 --- /dev/null +++ b/crates/integration/src/cases.rs @@ -0,0 +1,119 @@ +use alloy_primitives::U256; +use alloy_sol_types::{sol, SolCall}; + +#[derive(Clone)] +pub struct Contract { + pub evm_runtime: Vec, + pub pvm_runtime: Vec, + pub calldata: Vec, +} + +sol!(contract Baseline { function baseline() public payable; }); + +sol!(contract Computation { + function odd_product(int32 n) public pure returns (int64); + function triangle_number(int64 n) public pure returns (int64 sum); +}); + +sol!( + contract FibonacciRecursive { + function fib3(uint n) public pure returns (uint); + } +); + +sol!( + contract FibonacciIterative { + function fib3(uint n) external pure returns (uint b); + } +); + +sol!( + contract FibonacciBinet { + function fib3(uint n) external pure returns (uint a); + } +); + +sol!( + contract SHA1 { + function sha1(bytes memory data) public pure returns (bytes20 ret); + } +); + +impl Contract { + pub fn baseline() -> Self { + let code = include_str!("../contracts/Baseline.sol"); + let name = "Baseline"; + + Self { + evm_runtime: crate::compile_evm_bin_runtime(name, code), + pvm_runtime: crate::compile_blob(name, code), + calldata: Baseline::baselineCall::new(()).abi_encode(), + } + } + + pub fn odd_product(n: i32) -> Self { + let code = include_str!("../contracts/Computation.sol"); + let name = "Computation"; + + Self { + evm_runtime: crate::compile_evm_bin_runtime(name, code), + pvm_runtime: crate::compile_blob(name, code), + calldata: Computation::odd_productCall::new((n,)).abi_encode(), + } + } + + pub fn triangle_number(n: i64) -> Self { + let code = include_str!("../contracts/Computation.sol"); + let name = "Computation"; + + Self { + evm_runtime: crate::compile_evm_bin_runtime(name, code), + pvm_runtime: crate::compile_blob(name, code), + calldata: Computation::triangle_numberCall::new((n,)).abi_encode(), + } + } + + pub fn fib_recursive(n: u32) -> Self { + let code = include_str!("../contracts/Fibonacci.sol"); + let name = "FibonacciRecursive"; + + Self { + evm_runtime: crate::compile_evm_bin_runtime(name, code), + pvm_runtime: crate::compile_blob(name, code), + calldata: FibonacciRecursive::fib3Call::new((U256::from(n),)).abi_encode(), + } + } + + pub fn fib_iterative(n: u32) -> Self { + let code = include_str!("../contracts/Fibonacci.sol"); + let name = "FibonacciIterative"; + + Self { + evm_runtime: crate::compile_evm_bin_runtime(name, code), + pvm_runtime: crate::compile_blob(name, code), + calldata: FibonacciIterative::fib3Call::new((U256::from(n),)).abi_encode(), + } + } + + pub fn fib_binet(n: u32) -> Self { + let code = include_str!("../contracts/Fibonacci.sol"); + let name = "FibonacciBinet"; + + Self { + evm_runtime: crate::compile_evm_bin_runtime(name, code), + pvm_runtime: crate::compile_blob(name, code), + calldata: FibonacciBinet::fib3Call::new((U256::from(n),)).abi_encode(), + } + } + + pub fn sha1(pre: Vec) -> Self { + let code = include_str!("../contracts/SHA1.sol"); + let name = "SHA1"; + + Self { + evm_runtime: crate::compile_evm_bin_runtime(name, code), + pvm_runtime: crate::compile_blob(name, code), + calldata: SHA1::sha1Call::new((pre,)).abi_encode(), + } + } +} diff --git a/crates/integration/src/lib.rs b/crates/integration/src/lib.rs index 8aa59d5..f2f2687 100644 --- a/crates/integration/src/lib.rs +++ b/crates/integration/src/lib.rs @@ -1,9 +1,42 @@ +use cases::Contract; +use mock_runtime::State; + +pub mod cases; pub mod mock_runtime; +#[cfg(test)] +mod tests; + /// Compile the blob of `contract_name` found in given `source_code`. /// The `solc` optimizer will be enabled pub fn compile_blob(contract_name: &str, source_code: &str) -> Vec { - compile_blob_with_options(contract_name, source_code, true) + compile_blob_with_options( + contract_name, + source_code, + true, + revive_solidity::SolcPipeline::Yul, + ) +} + +/// Compile the EVM bin-runtime of `contract_name` found in given `source_code`. +/// The `solc` optimizer will be enabled +pub fn compile_evm_bin_runtime(contract_name: &str, source_code: &str) -> Vec { + let file_name = "contract.sol"; + + let contracts = revive_solidity::test_utils::build_solidity_with_options_evm( + [(file_name.into(), source_code.into())].into(), + Default::default(), + None, + revive_solidity::SolcPipeline::Yul, + true, + ) + .expect("source should compile"); + let bin_runtime = &contracts + .get(contract_name) + .unwrap_or_else(|| panic!("contract '{}' didn't produce bin-runtime", contract_name)) + .object; + + hex::decode(bin_runtime).expect("bin-runtime shold be hex encoded") } /// Compile the blob of `contract_name` found in given `source_code`. @@ -11,6 +44,7 @@ pub fn compile_blob_with_options( contract_name: &str, source_code: &str, solc_optimizer_enabled: bool, + pipeline: revive_solidity::SolcPipeline, ) -> Vec { let file_name = "contract.sol"; @@ -18,7 +52,7 @@ pub fn compile_blob_with_options( [(file_name.into(), source_code.into())].into(), Default::default(), None, - revive_solidity::SolcPipeline::Yul, + pipeline, era_compiler_llvm_context::OptimizerSettings::cycles(), solc_optimizer_enabled, ) @@ -37,323 +71,15 @@ pub fn compile_blob_with_options( hex::decode(bytecode).expect("hex encoding should always be valid") } -#[cfg(test)] -mod tests { - use alloy_primitives::{FixedBytes, Keccak256, I256, U256}; - use alloy_sol_types::{sol, SolCall}; - use sha1::Digest; +pub fn assert_success(contract: Contract, differential: bool) -> State { + let (mut instance, export) = mock_runtime::prepare(&contract.pvm_runtime, None); + let state = mock_runtime::call(State::new(contract.calldata.clone()), &mut instance, export); + assert_eq!(state.output.flags, 0); - use crate::mock_runtime::{self, State}; - - #[test] - fn fibonacci() { - sol!( - #[derive(Debug, PartialEq, Eq)] - contract Fibonacci { - function fib3(uint n) public pure returns (uint); - } - ); - - for contract in ["FibonacciIterative", "FibonacciRecursive", "FibonacciBinet"] { - let code = crate::compile_blob(contract, include_str!("../contracts/Fibonacci.sol")); - - let parameter = U256::from(6); - let input = Fibonacci::fib3Call::new((parameter,)).abi_encode(); - - let state = State::new(input); - let (mut instance, export) = mock_runtime::prepare(&code, None); - let state = crate::mock_runtime::call(state, &mut instance, export); - - assert_eq!(state.output.flags, 0); - - let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); - let expected = U256::from(8); - assert_eq!(received, expected); - } + if differential { + let evm = revive_differential::prepare(contract.evm_runtime, contract.calldata); + assert_eq!(state.output.data.clone(), revive_differential::execute(evm)); } - #[test] - fn flipper() { - let code = crate::compile_blob("Flipper", include_str!("../contracts/flipper.sol")); - let state = State::new(0xcde4efa9u32.to_be_bytes().to_vec()); - let (mut instance, export) = mock_runtime::prepare(&code, None); - - let state = crate::mock_runtime::call(state, &mut instance, export); - assert_eq!(state.output.flags, 0); - assert_eq!(state.storage[&U256::ZERO], U256::try_from(1).unwrap()); - - let state = crate::mock_runtime::call(state, &mut instance, export); - assert_eq!(state.output.flags, 0); - assert_eq!(state.storage[&U256::ZERO], U256::ZERO); - } - - #[test] - fn hash_keccak_256() { - sol!( - #[derive(Debug, PartialEq, Eq)] - contract TestSha3 { - function test(string memory _pre) external payable returns (bytes32); - } - ); - let source = r#"contract TestSha3 { - function test(string memory _pre) external payable returns (bytes32 hash) { - hash = keccak256(bytes(_pre)); - } - }"#; - let code = crate::compile_blob("TestSha3", source); - - let param = "hello"; - let input = TestSha3::testCall::new((param.to_string(),)).abi_encode(); - - let state = State::new(input); - let (mut instance, export) = mock_runtime::prepare(&code, None); - let state = crate::mock_runtime::call(state, &mut instance, export); - - assert_eq!(state.output.flags, 0); - - let mut hasher = Keccak256::new(); - hasher.update(param); - let expected = hasher.finalize(); - let received = FixedBytes::<32>::from_slice(&state.output.data); - assert_eq!(received, expected); - } - - #[test] - fn erc20() { - let _ = crate::compile_blob("ERC20", include_str!("../contracts/ERC20.sol")); - } - - #[test] - fn triangle_number() { - let code = crate::compile_blob("Computation", include_str!("../contracts/Computation.sol")); - let param = U256::try_from(13).unwrap(); - let expected = U256::try_from(91).unwrap(); - - // function triangle_number(int64) - let mut input = 0x0f760610u32.to_be_bytes().to_vec(); - input.extend_from_slice(¶m.to_be_bytes::<32>()); - - let state = State::new(input); - let (mut instance, export) = mock_runtime::prepare(&code, None); - let state = crate::mock_runtime::call(state, &mut instance, export); - - assert_eq!(state.output.flags, 0); - - let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); - assert_eq!(received, expected); - } - - #[test] - fn odd_product() { - let code = crate::compile_blob("Computation", include_str!("../contracts/Computation.sol")); - let param = I256::try_from(5i32).unwrap(); - let expected = I256::try_from(945i64).unwrap(); - - // function odd_product(int32) - let mut input = 0x00261b66u32.to_be_bytes().to_vec(); - input.extend_from_slice(¶m.to_be_bytes::<32>()); - - let state = State::new(input); - let (mut instance, export) = mock_runtime::prepare(&code, None); - let state = crate::mock_runtime::call(state, &mut instance, export); - - assert_eq!(state.output.flags, 0); - - let received = I256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); - assert_eq!(received, expected); - } - - #[test] - fn msize_plain() { - sol!( - #[derive(Debug, PartialEq, Eq)] - contract MSize { - function mSize() public pure returns (uint); - } - ); - let code = crate::compile_blob_with_options( - "MSize", - include_str!("../contracts/MSize.sol"), - false, - ); - let (mut instance, export) = mock_runtime::prepare(&code, None); - - let input = MSize::mSizeCall::new(()).abi_encode(); - let state = crate::mock_runtime::call(State::new(input), &mut instance, export); - - assert_eq!(state.output.flags, 0); - - // Solidity always stores the "free memory pointer" (32 byte int) at offset 64. - let expected = U256::try_from(64 + 32).unwrap(); - let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); - assert_eq!(received, expected); - } - - #[test] - fn transferred_value() { - sol!( - contract Value { - function value() public payable returns (uint); - } - ); - let code = crate::compile_blob("Value", include_str!("../contracts/Value.sol")); - let mut state = State::new(Value::valueCall::SELECTOR.to_vec()); - state.value = 0x1; - - let (mut instance, export) = mock_runtime::prepare(&code, None); - let state = crate::mock_runtime::call(state, &mut instance, export); - - assert_eq!(state.output.flags, 0); - - let expected = I256::try_from(state.value).unwrap(); - let received = I256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); - assert_eq!(received, expected); - } - - #[test] - fn msize_non_word_sized_access() { - sol!( - #[derive(Debug, PartialEq, Eq)] - contract MSize { - function mStore100() public pure returns (uint); - } - ); - let code = crate::compile_blob_with_options( - "MSize", - include_str!("../contracts/MSize.sol"), - false, - ); - let (mut instance, export) = mock_runtime::prepare(&code, None); - - let input = MSize::mStore100Call::new(()).abi_encode(); - let state = crate::mock_runtime::call(State::new(input), &mut instance, export); - - assert_eq!(state.output.flags, 0); - - // https://docs.zksync.io/build/developer-reference/differences-with-ethereum.html#mstore-mload - // "Unlike EVM, where the memory growth is in words, on zkEVM the memory growth is counted in bytes." - // "For example, if you write mstore(100, 0) the msize on zkEVM will be 132, but on the EVM it will be 160." - let expected = U256::try_from(132).unwrap(); - let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); - assert_eq!(received, expected); - } - - #[test] - fn mstore8() { - sol!( - #[derive(Debug, PartialEq, Eq)] - contract MStore8 { - function mStore8(uint value) public pure returns (uint256 word); - } - ); - let code = crate::compile_blob_with_options( - "MStore8", - include_str!("../contracts/mStore8.sol"), - false, - ); - let (mut instance, export) = mock_runtime::prepare(&code, None); - - let mut assert = |parameter, expected| { - let input = MStore8::mStore8Call::new((parameter,)).abi_encode(); - let state = crate::mock_runtime::call(State::new(input), &mut instance, export); - - assert_eq!(state.output.flags, 0); - - let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); - assert_eq!(received, expected); - }; - - for (parameter, expected) in [ - (U256::MIN, U256::MIN), - ( - U256::from(1), - U256::from_str_radix( - "452312848583266388373324160190187140051835877600158453279131187530910662656", - 10, - ) - .unwrap(), - ), - ( - U256::from(2), - U256::from_str_radix( - "904625697166532776746648320380374280103671755200316906558262375061821325312", - 10, - ) - .unwrap(), - ), - ( - U256::from(255), - U256::from_str_radix( - "115339776388732929035197660848497720713218148788040405586178452820382218977280", - 10, - ) - .unwrap(), - ), - ( - U256::from(256), - U256::from(0), - ), - ( - U256::from(257), - U256::from_str_radix( - "452312848583266388373324160190187140051835877600158453279131187530910662656", - 10, - ) - .unwrap(), - ), - ( - U256::from(258), - U256::from_str_radix( - "904625697166532776746648320380374280103671755200316906558262375061821325312", - 10, - ) - .unwrap(), - ), - ( - U256::from(123456789), - U256::from_str_radix( - "9498569820248594155839807363993929941088553429603327518861754938149123915776", - 10, - ) - .unwrap(), - ), - ( - U256::MAX, - U256::from_str_radix( - "115339776388732929035197660848497720713218148788040405586178452820382218977280", - 10, - ) - .unwrap(), - ), - ] { - assert(parameter, expected); - } - } - - #[test] - fn sha1() { - sol!( - contract SHA1 { - function sha1(bytes memory data) public pure returns (bytes20); - } - ); - - let code = - crate::compile_blob_with_options("SHA1", include_str!("../contracts/sha1.sol"), false); - let (mut instance, export) = mock_runtime::prepare(&code, None); - - let pre = vec![0xffu8; 512]; - let mut hasher = sha1::Sha1::new(); - hasher.update(&pre); - let hash = hasher.finalize(); - - let input = SHA1::sha1Call::new((pre,)).abi_encode(); - let state = crate::mock_runtime::call(State::new(input), &mut instance, export); - - assert_eq!(state.output.flags, 0); - - let expected = FixedBytes::<20>::from_slice(&hash[..]); - let received = FixedBytes::<20>::from_slice(&state.output.data[..20]); - assert_eq!(received, expected); - } + state } diff --git a/crates/integration/src/mock_runtime.rs b/crates/integration/src/mock_runtime.rs index dacb388..55434d9 100644 --- a/crates/integration/src/mock_runtime.rs +++ b/crates/integration/src/mock_runtime.rs @@ -3,7 +3,6 @@ use std::collections::HashMap; use alloy_primitives::{Keccak256, U256}; -use parity_scale_codec::Encode; use polkavm::{ Caller, Config, Engine, ExportIndex, GasMeteringKind, Instance, Linker, Module, ModuleConfig, ProgramBlob, Trap, @@ -61,7 +60,7 @@ fn link_host_functions(engine: &Engine) -> Linker { assert!(state.input.len() <= caller.read_u32(out_len_ptr).unwrap() as usize); caller.write_memory(out_ptr, &state.input)?; - caller.write_memory(out_len_ptr, &(state.input.len() as u32).encode())?; + caller.write_memory(out_len_ptr, &(state.input.len() as u32).to_le_bytes())?; Ok(()) }, @@ -91,7 +90,7 @@ fn link_host_functions(engine: &Engine) -> Linker { let value = state.value.to_le_bytes(); caller.write_memory(out_ptr, &value)?; - caller.write_memory(out_len_ptr, &(value.len() as u32).encode())?; + caller.write_memory(out_len_ptr, &(value.len() as u32).to_le_bytes())?; Ok(()) }, @@ -200,14 +199,12 @@ pub fn recompile_code(code: &[u8], engine: &Engine) -> Module { let mut module_config = ModuleConfig::new(); module_config.set_gas_metering(Some(GasMeteringKind::Sync)); - Module::new(&engine, &module_config, code).unwrap() + Module::new(engine, &module_config, code).unwrap() } pub fn instantiate_module(module: &Module, engine: &Engine) -> (Instance, ExportIndex) { let export = module.lookup_export("call").unwrap(); - let func = link_host_functions(&engine) - .instantiate_pre(module) - .unwrap(); + let func = link_host_functions(engine).instantiate_pre(module).unwrap(); let instance = func.instantiate().unwrap(); (instance, export) diff --git a/crates/integration/src/tests.rs b/crates/integration/src/tests.rs new file mode 100644 index 0000000..7dfcad5 --- /dev/null +++ b/crates/integration/src/tests.rs @@ -0,0 +1,267 @@ +use alloy_primitives::{FixedBytes, Keccak256, I256, U256}; +use alloy_sol_types::{sol, SolCall}; +use sha1::Digest; + +use crate::{ + assert_success, + cases::Contract, + mock_runtime::{self, State}, +}; + +#[test] +fn fibonacci() { + let parameter = 6; + + for contract in [ + Contract::fib_recursive(parameter), + Contract::fib_iterative(parameter), + Contract::fib_binet(parameter), + ] { + let state = assert_success(contract, true); + let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); + let expected = U256::from(8); + assert_eq!(received, expected); + } +} + +#[test] +fn flipper() { + let code = crate::compile_blob("Flipper", include_str!("../contracts/flipper.sol")); + let state = State::new(0xcde4efa9u32.to_be_bytes().to_vec()); + let (mut instance, export) = mock_runtime::prepare(&code, None); + + let state = crate::mock_runtime::call(state, &mut instance, export); + assert_eq!(state.output.flags, 0); + assert_eq!(state.storage[&U256::ZERO], U256::try_from(1).unwrap()); + + let state = crate::mock_runtime::call(state, &mut instance, export); + assert_eq!(state.output.flags, 0); + assert_eq!(state.storage[&U256::ZERO], U256::ZERO); +} + +#[test] +fn hash_keccak_256() { + sol!( + #[derive(Debug, PartialEq, Eq)] + contract TestSha3 { + function test(string memory _pre) external payable returns (bytes32); + } + ); + let source = r#"contract TestSha3 { + function test(string memory _pre) external payable returns (bytes32 hash) { + hash = keccak256(bytes(_pre)); + } + }"#; + let code = crate::compile_blob("TestSha3", source); + + let param = "hello"; + let input = TestSha3::testCall::new((param.to_string(),)).abi_encode(); + + let state = State::new(input); + let (mut instance, export) = mock_runtime::prepare(&code, None); + let state = crate::mock_runtime::call(state, &mut instance, export); + + assert_eq!(state.output.flags, 0); + + let mut hasher = Keccak256::new(); + hasher.update(param); + let expected = hasher.finalize(); + let received = FixedBytes::<32>::from_slice(&state.output.data); + assert_eq!(received, expected); +} + +#[test] +fn erc20() { + let _ = crate::compile_blob("ERC20", include_str!("../contracts/ERC20.sol")); +} + +#[test] +fn triangle_number() { + let state = assert_success(Contract::triangle_number(13), true); + let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); + let expected = U256::try_from(91).unwrap(); + assert_eq!(received, expected); +} + +#[test] +fn odd_product() { + let state = assert_success(Contract::odd_product(5), true); + let received = I256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); + let expected = I256::try_from(945i64).unwrap(); + assert_eq!(received, expected); +} + +#[test] +fn msize_plain() { + sol!( + #[derive(Debug, PartialEq, Eq)] + contract MSize { + function mSize() public pure returns (uint); + } + ); + let code = crate::compile_blob_with_options( + "MSize", + include_str!("../contracts/MSize.sol"), + false, + revive_solidity::SolcPipeline::EVMLA, + ); + let (mut instance, export) = mock_runtime::prepare(&code, None); + + let input = MSize::mSizeCall::new(()).abi_encode(); + let state = crate::mock_runtime::call(State::new(input), &mut instance, export); + + assert_eq!(state.output.flags, 0); + + // Solidity always stores the "free memory pointer" (32 byte int) at offset 64. + let expected = U256::try_from(64 + 32).unwrap(); + let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); + assert_eq!(received, expected); +} + +#[test] +fn transferred_value() { + sol!( + contract Value { + function value() public payable returns (uint); + } + ); + let code = crate::compile_blob("Value", include_str!("../contracts/Value.sol")); + let mut state = State::new(Value::valueCall::SELECTOR.to_vec()); + state.value = 0x1; + + let (mut instance, export) = mock_runtime::prepare(&code, None); + let state = crate::mock_runtime::call(state, &mut instance, export); + + assert_eq!(state.output.flags, 0); + + let expected = I256::try_from(state.value).unwrap(); + let received = I256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); + assert_eq!(received, expected); +} + +#[test] +fn msize_non_word_sized_access() { + sol!( + #[derive(Debug, PartialEq, Eq)] + contract MSize { + function mStore100() public pure returns (uint); + } + ); + let code = crate::compile_blob_with_options( + "MSize", + include_str!("../contracts/MSize.sol"), + false, + revive_solidity::SolcPipeline::Yul, + ); + let (mut instance, export) = mock_runtime::prepare(&code, None); + + let input = MSize::mStore100Call::new(()).abi_encode(); + let state = crate::mock_runtime::call(State::new(input), &mut instance, export); + + assert_eq!(state.output.flags, 0); + + // https://docs.zksync.io/build/developer-reference/differences-with-ethereum.html#mstore-mload + // "Unlike EVM, where the memory growth is in words, on zkEVM the memory growth is counted in bytes." + // "For example, if you write mstore(100, 0) the msize on zkEVM will be 132, but on the EVM it will be 160." + let expected = U256::try_from(132).unwrap(); + let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); + assert_eq!(received, expected); +} + +#[test] +fn mstore8() { + sol!( + #[derive(Debug, PartialEq, Eq)] + contract MStore8 { + function mStore8(uint value) public pure returns (uint256 word); + } + ); + let code = crate::compile_blob("MStore8", include_str!("../contracts/mStore8.sol")); + let (mut instance, export) = mock_runtime::prepare(&code, None); + + let mut assert = |parameter, expected| { + let input = MStore8::mStore8Call::new((parameter,)).abi_encode(); + let state = crate::mock_runtime::call(State::new(input), &mut instance, export); + + assert_eq!(state.output.flags, 0); + + let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap()); + assert_eq!(received, expected); + }; + + for (parameter, expected) in [ + (U256::MIN, U256::MIN), + ( + U256::from(1), + U256::from_str_radix( + "452312848583266388373324160190187140051835877600158453279131187530910662656", + 10, + ) + .unwrap(), + ), + ( + U256::from(2), + U256::from_str_radix( + "904625697166532776746648320380374280103671755200316906558262375061821325312", + 10, + ) + .unwrap(), + ), + ( + U256::from(255), + U256::from_str_radix( + "115339776388732929035197660848497720713218148788040405586178452820382218977280", + 10, + ) + .unwrap(), + ), + (U256::from(256), U256::from(0)), + ( + U256::from(257), + U256::from_str_radix( + "452312848583266388373324160190187140051835877600158453279131187530910662656", + 10, + ) + .unwrap(), + ), + ( + U256::from(258), + U256::from_str_radix( + "904625697166532776746648320380374280103671755200316906558262375061821325312", + 10, + ) + .unwrap(), + ), + ( + U256::from(123456789), + U256::from_str_radix( + "9498569820248594155839807363993929941088553429603327518861754938149123915776", + 10, + ) + .unwrap(), + ), + ( + U256::MAX, + U256::from_str_radix( + "115339776388732929035197660848497720713218148788040405586178452820382218977280", + 10, + ) + .unwrap(), + ), + ] { + assert(parameter, expected); + } +} + +#[test] +fn sha1() { + let pre = vec![0xffu8; 512]; + let mut hasher = sha1::Sha1::new(); + hasher.update(&pre); + let hash = hasher.finalize(); + + let state = assert_success(Contract::sha1(pre), true); + let expected = FixedBytes::<20>::from_slice(&hash[..]); + let received = FixedBytes::<20>::from_slice(&state.output.data[..20]); + assert_eq!(received, expected); +} diff --git a/crates/lld-sys/build.rs b/crates/lld-sys/build.rs index 1ba2b63..1b33262 100644 --- a/crates/lld-sys/build.rs +++ b/crates/lld-sys/build.rs @@ -8,16 +8,7 @@ fn llvm_config(arg: &str) -> String { .unwrap_or_else(|_| panic!("output of `llvm-config {arg}` should be utf8")) } -fn main() { - let mut builder = cc::Build::new(); - llvm_config("--cxxflags") - .split_whitespace() - .fold(&mut builder, |builder, flag| builder.flag(flag)) - .flag("-Wno-unused-parameter") - .cpp(true) - .file("src/linker.cpp") - .compile("liblinker.a"); - +fn set_rustc_link_flags() { println!("cargo:rustc-link-search=native={}", llvm_config("--libdir")); for lib in [ @@ -30,9 +21,25 @@ fn main() { "LLVMLTO", "LLVMTargetParser", "LLVMBinaryFormat", + "LLVMDemangle", ] { println!("cargo:rustc-link-lib=static={lib}"); } + #[cfg(target_os = "linux")] + println!("cargo:rustc-link-lib=dylib=stdc++"); +} + +fn main() { + llvm_config("--cxxflags") + .split_whitespace() + .fold(&mut cc::Build::new(), |builder, flag| builder.flag(flag)) + .flag("-Wno-unused-parameter") + .cpp(true) + .file("src/linker.cpp") + .compile("liblinker.a"); + + set_rustc_link_flags(); + println!("cargo:rerun-if-changed=build.rs"); } diff --git a/crates/solidity/src/solc/standard_json/input/settings/selection/file/flag.rs b/crates/solidity/src/solc/standard_json/input/settings/selection/file/flag.rs index 2f57e50..89d0389 100644 --- a/crates/solidity/src/solc/standard_json/input/settings/selection/file/flag.rs +++ b/crates/solidity/src/solc/standard_json/input/settings/selection/file/flag.rs @@ -41,6 +41,8 @@ pub enum Flag { /// The EVM legacy assembly JSON. #[serde(rename = "evm.legacyAssembly")] EVMLA, + #[serde(rename = "evm.deployedBytecode")] + EVMDBC, } impl From for Flag { @@ -64,6 +66,7 @@ impl std::fmt::Display for Flag { Self::AST => write!(f, "ast"), Self::Yul => write!(f, "irOptimized"), Self::EVMLA => write!(f, "evm.legacyAssembly"), + Self::EVMDBC => write!(f, "evm.deployedBytecode"), } } } diff --git a/crates/solidity/src/solc/standard_json/input/settings/selection/file/mod.rs b/crates/solidity/src/solc/standard_json/input/settings/selection/file/mod.rs index da4f35a..29bd4ff 100644 --- a/crates/solidity/src/solc/standard_json/input/settings/selection/file/mod.rs +++ b/crates/solidity/src/solc/standard_json/input/settings/selection/file/mod.rs @@ -34,6 +34,7 @@ impl File { Self { per_file: Some(HashSet::from_iter([SelectionFlag::AST])), per_contract: Some(HashSet::from_iter([ + SelectionFlag::EVMDBC, SelectionFlag::MethodIdentifiers, SelectionFlag::Metadata, SelectionFlag::from(pipeline), diff --git a/crates/solidity/src/solc/standard_json/output/contract/evm/bytecode.rs b/crates/solidity/src/solc/standard_json/output/contract/evm/bytecode.rs index eababa4..215538d 100644 --- a/crates/solidity/src/solc/standard_json/output/contract/evm/bytecode.rs +++ b/crates/solidity/src/solc/standard_json/output/contract/evm/bytecode.rs @@ -23,3 +23,22 @@ impl Bytecode { Self { object } } } + +/// +/// The `solc --standard-json` output contract EVM deployed bytecode. +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct DeployedBytecode { + /// The bytecode object. + pub object: String, +} + +impl DeployedBytecode { + /// + /// A shortcut constructor. + /// + pub fn new(object: String) -> Self { + Self { object } + } +} diff --git a/crates/solidity/src/solc/standard_json/output/contract/evm/mod.rs b/crates/solidity/src/solc/standard_json/output/contract/evm/mod.rs index 062be28..82e0927 100644 --- a/crates/solidity/src/solc/standard_json/output/contract/evm/mod.rs +++ b/crates/solidity/src/solc/standard_json/output/contract/evm/mod.rs @@ -13,6 +13,7 @@ use serde::Serialize; use crate::evmla::assembly::Assembly; use self::bytecode::Bytecode; +use self::bytecode::DeployedBytecode; use self::extra_metadata::ExtraMetadata; /// @@ -32,6 +33,8 @@ pub struct EVM { /// The contract bytecode. /// Is reset by that of EraVM before yielding the compiled project artifacts. pub bytecode: Option, + /// The contract deployed bytecode. + pub deployed_bytecode: Option, /// The contract function signatures. #[serde(default, skip_serializing_if = "Option::is_none")] pub method_identifiers: Option>, diff --git a/crates/solidity/src/test_utils.rs b/crates/solidity/src/test_utils.rs index da82478..16ff1be 100644 --- a/crates/solidity/src/test_utils.rs +++ b/crates/solidity/src/test_utils.rs @@ -9,6 +9,7 @@ use crate::solc::pipeline::Pipeline as SolcPipeline; use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer; use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection; use crate::solc::standard_json::input::Input as SolcStandardJsonInput; +use crate::solc::standard_json::output::contract::evm::bytecode::DeployedBytecode; use crate::solc::standard_json::output::Output as SolcStandardJsonOutput; use crate::solc::Compiler as SolcCompiler; use crate::warning::Warning; @@ -100,6 +101,59 @@ pub fn build_solidity_with_options( Ok(output) } +/// Build a Solidity contract and get the EVM bin-runtime. +pub fn build_solidity_with_options_evm( + sources: BTreeMap, + libraries: BTreeMap>, + remappings: Option>, + pipeline: SolcPipeline, + solc_optimizer_enabled: bool, +) -> anyhow::Result> { + check_dependencies(); + + inkwell::support::enable_llvm_pretty_stack_trace(); + era_compiler_llvm_context::initialize_target(era_compiler_llvm_context::Target::PVM); + let _ = crate::process::EXECUTABLE.set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME)); + + let mut solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?; + let solc_version = solc.version()?; + + let input = SolcStandardJsonInput::try_from_sources( + None, + sources.clone(), + libraries.clone(), + remappings, + SolcStandardJsonInputSettingsSelection::new_required(pipeline), + SolcStandardJsonInputSettingsOptimizer::new( + solc_optimizer_enabled, + None, + &solc_version.default, + false, + false, + ), + None, + pipeline == SolcPipeline::Yul, + None, + )?; + + let mut output = solc.standard_json(input, pipeline, None, vec![], None)?; + + let mut contracts = BTreeMap::new(); + if let Some(files) = output.contracts.as_mut() { + for (_, file) in files.iter_mut() { + for (name, contract) in file.iter_mut() { + if let Some(evm) = contract.evm.as_mut() { + if let Some(deployed_bytecode) = evm.deployed_bytecode.as_ref() { + contracts.insert(name.clone(), deployed_bytecode.clone()); + } + } + } + } + } + + Ok(contracts) +} + /// /// Builds the Solidity project and returns the standard JSON output. ///