diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3cb25136a..962db74114 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,9 +6,9 @@ concurrency: on: push: - branches: [main, "release/**"] + branches: [main, "release/**", "scroll-evm-executor/**"] pull_request: - branches: [main, "release/**"] + branches: [main, "release/**", "scroll-evm-executor/**"] env: CARGO_TERM_COLOR: always @@ -22,7 +22,16 @@ jobs: fail-fast: false matrix: rust: ["stable", "beta", "nightly"] - flags: ["--no-default-features", "", "--all-features"] + flags: [ + "--no-default-features", + "", + "--features=\"scroll\"", + "--features=\"scroll, scroll-poseidon-codehash\"", + "--features=\"all, scroll\"", + "--features=\"all, scroll, scroll-poseidon-codehash\"", + "--features=\"optimism\"", + "--features=\"all, optimism\"" + ] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master @@ -66,7 +75,10 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - run: cargo clippy --workspace --all-targets --all-features + - run: cargo clippy --workspace --all-targets --features all,scroll + env: + RUSTFLAGS: -Dwarnings + - run: cargo clippy --workspace --all-targets --features all,optimism env: RUSTFLAGS: -Dwarnings @@ -79,7 +91,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: components: rust-docs - - run: cargo doc --workspace --all-features --no-deps --document-private-items + - run: cargo doc --workspace --features all,scroll --no-deps --document-private-items env: RUSTDOCFLAGS: "--cfg docsrs -D warnings" diff --git a/.github/workflows/ethereum-tests.yml b/.github/workflows/ethereum-tests.yml index 7c2ca02751..193ec45178 100644 --- a/.github/workflows/ethereum-tests.yml +++ b/.github/workflows/ethereum-tests.yml @@ -6,9 +6,9 @@ concurrency: on: push: - branches: [main, "release/**"] + branches: [main, "release/**", "scroll-evm-executor/**"] pull_request: - branches: [main, "release/**"] + branches: [main, "release/**", "scroll-evm-executor/**"] jobs: tests-stable: diff --git a/Cargo.lock b/Cargo.lock index 90989ca968..64131f105e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,7 +75,7 @@ dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "alloy-trie", "auto_impl", "c-kzg", @@ -93,15 +93,14 @@ dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde", ] [[package]] name = "alloy-eip2930" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" +source = "git+https://github.com/scroll-tech/alloy-eips?branch=v0.4.2#92b2892d7b5b325c1f2f3e90a5befadbb5e610d4" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -113,8 +112,7 @@ dependencies = [ [[package]] name = "alloy-eip7702" version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" +source = "git+https://github.com/scroll-tech/alloy-eips?branch=v0.4.2#92b2892d7b5b325c1f2f3e90a5befadbb5e610d4" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -140,14 +138,13 @@ dependencies = [ [[package]] name = "alloy-eips" version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6755b093afef5925f25079dd5a7c8d096398b804ba60cb5275397b06b31689" +source = "git+https://github.com/scroll-tech/alloy.git?branch=v0.7.3#b2ffb9fa816b41f1409d2b87485e5c62953ae8f5" dependencies = [ "alloy-eip2930", "alloy-eip7702 0.4.2", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 0.7.3 (git+https://github.com/scroll-tech/alloy.git?branch=v0.7.3)", "c-kzg", "derive_more 1.0.0", "once_cell", @@ -157,9 +154,8 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded610181f3dad5810f6ff12d1a99994cf9b42d2fcb7709029352398a5da5ae6" +version = "0.8.18" +source = "git+https://github.com/scroll-tech/alloy-core?branch=v0.8.18#aab106b9b7ca2e76a7b073064f86a04adb3122fe" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -195,7 +191,7 @@ dependencies = [ "alloy-primitives", "alloy-rpc-types-any", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "alloy-signer", "alloy-sol-types", "async-trait", @@ -215,15 +211,14 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", - "alloy-serde", + "alloy-serde 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde", ] [[package]] name = "alloy-primitives" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6259a506ab13e1d658796c31e6e39d2e2ee89243bcc505ddc613b35732e0a430" +version = "0.8.18" +source = "git+https://github.com/scroll-tech/alloy-core?branch=v0.8.18#aab106b9b7ca2e76a7b073064f86a04adb3122fe" dependencies = [ "alloy-rlp", "arbitrary", @@ -235,7 +230,6 @@ dependencies = [ "foldhash", "getrandom", "hashbrown 0.15.0", - "hex-literal", "indexmap", "itoa", "k256", @@ -341,7 +335,7 @@ checksum = "200661999b6e235d9840be5d60a6e8ae2f0af9eb2a256dd378786744660e36ec" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -356,7 +350,7 @@ dependencies = [ "alloy-network-primitives", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "alloy-sol-types", "derive_more 1.0.0", "itertools 0.13.0", @@ -375,6 +369,16 @@ dependencies = [ "serde_json", ] +[[package]] +name = "alloy-serde" +version = "0.7.3" +source = "git+https://github.com/scroll-tech/alloy.git?branch=v0.7.3#b2ffb9fa816b41f1409d2b87485e5c62953ae8f5" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + [[package]] name = "alloy-signer" version = "0.7.3" @@ -395,8 +399,21 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a1b42ac8f45e2f49f4bcdd72cbfde0bb148f5481d403774ffa546e48b83efc1" dependencies = [ - "alloy-sol-macro-expander", - "alloy-sol-macro-input", + "alloy-sol-macro-expander 0.8.11", + "alloy-sol-macro-input 0.8.11", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.8.18" +source = "git+https://github.com/scroll-tech/alloy-core?branch=v0.8.18#aab106b9b7ca2e76a7b073064f86a04adb3122fe" +dependencies = [ + "alloy-sol-macro-expander 0.8.18", + "alloy-sol-macro-input 0.8.18", "proc-macro-error2", "proc-macro2", "quote", @@ -409,7 +426,24 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06318f1778e57f36333e850aa71bd1bb5e560c10279e236622faae0470c50412" dependencies = [ - "alloy-sol-macro-input", + "alloy-sol-macro-input 0.8.11", + "const-hex", + "heck 0.5.0", + "indexmap", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.90", + "syn-solidity 0.8.11", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.8.18" +source = "git+https://github.com/scroll-tech/alloy-core?branch=v0.8.18#aab106b9b7ca2e76a7b073064f86a04adb3122fe" +dependencies = [ + "alloy-sol-macro-input 0.8.18", "const-hex", "heck 0.5.0", "indexmap", @@ -417,7 +451,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.90", - "syn-solidity", + "syn-solidity 0.8.18", "tiny-keccak", ] @@ -433,14 +467,27 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.90", - "syn-solidity", + "syn-solidity 0.8.11", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "0.8.18" +source = "git+https://github.com/scroll-tech/alloy-core?branch=v0.8.18#aab106b9b7ca2e76a7b073064f86a04adb3122fe" +dependencies = [ + "const-hex", + "dunce", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.90", + "syn-solidity 0.8.18", ] [[package]] name = "alloy-sol-type-parser" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12c71028bfbfec210e24106a542aad3def7caf1a70e2c05710e92a98481980d3" +version = "0.8.18" +source = "git+https://github.com/scroll-tech/alloy-core?branch=v0.8.18#aab106b9b7ca2e76a7b073064f86a04adb3122fe" dependencies = [ "serde", "winnow 0.6.18", @@ -448,13 +495,12 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d7fb042d68ddfe79ccb23359de3007f6d4d53c13f703b64fb0db422132111" +version = "0.8.18" +source = "git+https://github.com/scroll-tech/alloy-core?branch=v0.8.18#aab106b9b7ca2e76a7b073064f86a04adb3122fe" dependencies = [ "alloy-json-abi", "alloy-primitives", - "alloy-sol-macro", + "alloy-sol-macro 0.8.18", "const-hex", "serde", ] @@ -670,6 +716,12 @@ dependencies = [ "rand", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.4" @@ -865,6 +917,17 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2b_simd" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -886,6 +949,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bn254" +version = "0.1.0" +source = "git+https://github.com/scroll-tech/bn254.git?branch=master#81e1dcc92ee9a2798b13b84b24de182e9c42256e" +dependencies = [ + "ff", + "getrandom", + "rand", + "rand_core", + "sp1-intrinsics", + "subtle", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -898,6 +974,12 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" +[[package]] +name = "bytemuck" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" + [[package]] name = "byteorder" version = "1.5.0" @@ -1054,6 +1136,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation" version = "0.9.4" @@ -1552,9 +1640,9 @@ dependencies = [ [[package]] name = "ff" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +source = "git+https://github.com/scroll-tech/ff?branch=feat%2Fsp1#244b1098f6be1d19c5fd3f0ec60117ac2940e6ca" dependencies = [ + "bitvec", "byteorder", "ff_derive", "rand_core", @@ -1564,8 +1652,7 @@ dependencies = [ [[package]] name = "ff_derive" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f54704be45ed286151c5e11531316eaef5b8f5af7d597b806fdb8af108d84a" +source = "git+https://github.com/scroll-tech/ff?branch=feat%2Fsp1#244b1098f6be1d19c5fd3f0ec60117ac2940e6ca" dependencies = [ "addchain", "cfg-if", @@ -1597,9 +1684,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] name = "foreign-types" @@ -1850,6 +1937,33 @@ dependencies = [ "crunchy", ] +[[package]] +name = "halo2curves-axiom" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75bb262279138550a603b35a73da944fcde987a321eb550c05276ce7b0a4e692" +dependencies = [ + "blake2b_simd", + "digest 0.10.7", + "ff", + "group", + "hex", + "lazy_static", + "maybe-rayon", + "num-bigint 0.4.6", + "num-traits", + "pairing", + "pasta_curves", + "paste", + "rand", + "rand_core", + "serde", + "serde_arrays", + "sha2", + "static_assertions", + "subtle", +] + [[package]] name = "hash-db" version = "0.15.2" @@ -2374,6 +2488,16 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "memchr" version = "2.7.4" @@ -2659,6 +2783,175 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "openvm" +version = "1.0.0-rc.1" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.0.0-rc.1#30576cc6ce838f213bf05b2e4ad035d95498c8b3" +dependencies = [ + "bytemuck", + "hex-literal", + "num-bigint 0.4.6", + "num-traits", + "openvm-platform", + "openvm-rv32im-guest", + "serde", +] + +[[package]] +name = "openvm-algebra-complex-macros" +version = "0.1.0" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.0.0-rc.1#30576cc6ce838f213bf05b2e4ad035d95498c8b3" +dependencies = [ + "openvm-macros-common", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "openvm-algebra-guest" +version = "1.0.0-rc.1" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.0.0-rc.1#30576cc6ce838f213bf05b2e4ad035d95498c8b3" +dependencies = [ + "halo2curves-axiom", + "num-bigint 0.4.6", + "openvm", + "openvm-algebra-complex-macros", + "openvm-algebra-moduli-macros", + "openvm-platform", + "serde", + "serde-big-array", + "strum_macros", +] + +[[package]] +name = "openvm-algebra-moduli-macros" +version = "1.0.0-rc.1" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.0.0-rc.1#30576cc6ce838f213bf05b2e4ad035d95498c8b3" +dependencies = [ + "openvm-macros-common", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "openvm-custom-insn" +version = "0.1.0" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.0.0-rc.1#30576cc6ce838f213bf05b2e4ad035d95498c8b3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "openvm-ecc-guest" +version = "1.0.0-rc.1" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.0.0-rc.1#30576cc6ce838f213bf05b2e4ad035d95498c8b3" +dependencies = [ + "ecdsa", + "elliptic-curve", + "group", + "halo2curves-axiom", + "hex-literal", + "itertools 0.13.0", + "k256", + "lazy_static", + "num-bigint 0.4.6", + "num-traits", + "openvm", + "openvm-algebra-guest", + "openvm-algebra-moduli-macros", + "openvm-ecc-sw-macros", + "openvm-platform", + "openvm-rv32im-guest", + "rand", + "serde", + "strum_macros", +] + +[[package]] +name = "openvm-ecc-sw-macros" +version = "1.0.0-rc.1" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.0.0-rc.1#30576cc6ce838f213bf05b2e4ad035d95498c8b3" +dependencies = [ + "openvm-macros-common", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "openvm-keccak256-guest" +version = "1.0.0-rc.1" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.0.0-rc.1#30576cc6ce838f213bf05b2e4ad035d95498c8b3" +dependencies = [ + "openvm-platform", + "serde", + "tiny-keccak", +] + +[[package]] +name = "openvm-macros-common" +version = "1.0.0-rc.1" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.0.0-rc.1#30576cc6ce838f213bf05b2e4ad035d95498c8b3" +dependencies = [ + "syn 2.0.90", +] + +[[package]] +name = "openvm-pairing-guest" +version = "1.0.0-rc.1" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.0.0-rc.1#30576cc6ce838f213bf05b2e4ad035d95498c8b3" +dependencies = [ + "group", + "hex-literal", + "itertools 0.13.0", + "lazy_static", + "num-bigint 0.4.6", + "num-traits", + "openvm", + "openvm-algebra-complex-macros", + "openvm-algebra-guest", + "openvm-algebra-moduli-macros", + "openvm-ecc-guest", + "openvm-ecc-sw-macros", + "openvm-platform", + "openvm-rv32im-guest", + "rand", + "serde", + "strum_macros", +] + +[[package]] +name = "openvm-platform" +version = "1.0.0-rc.1" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.0.0-rc.1#30576cc6ce838f213bf05b2e4ad035d95498c8b3" +dependencies = [ + "getrandom", + "libm", + "openvm-custom-insn", + "stability", + "strum_macros", +] + +[[package]] +name = "openvm-rv32im-guest" +version = "1.0.0-rc.1" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.0.0-rc.1#30576cc6ce838f213bf05b2e4ad035d95498c8b3" +dependencies = [ + "openvm-platform", + "strum_macros", +] + +[[package]] +name = "openvm-sha256-guest" +version = "1.0.0-rc.1" +source = "git+https://github.com/openvm-org/openvm.git?tag=v1.0.0-rc.1#30576cc6ce838f213bf05b2e4ad035d95498c8b3" +dependencies = [ + "openvm", + "openvm-platform", + "sha2", +] + [[package]] name = "p256" version = "0.13.2" @@ -2729,6 +3022,21 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "pasta_curves" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "lazy_static", + "rand", + "static_assertions", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" @@ -2904,6 +3212,16 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +[[package]] +name = "poseidon-bn254" +version = "0.1.0" +source = "git+https://github.com/scroll-tech/poseidon-bn254?branch=master#254baa0e3e85c0975c16fe6f33042b1fa9ae0a44" +dependencies = [ + "bn254", + "itertools 0.13.0", + "sp1-intrinsics", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3296,6 +3614,10 @@ dependencies = [ "k256", "kzg-rs", "once_cell", + "openvm-ecc-guest", + "openvm-keccak256-guest", + "openvm-pairing-guest", + "openvm-sha256-guest", "p256", "rand", "revm-primitives", @@ -3325,6 +3647,8 @@ dependencies = [ "enumn", "hex", "kzg-rs", + "openvm-keccak256-guest", + "poseidon-bn254", "serde", ] @@ -3332,7 +3656,7 @@ dependencies = [ name = "revm-test" version = "1.0.0" dependencies = [ - "alloy-sol-macro", + "alloy-sol-macro 0.8.11", "alloy-sol-types", "bytes", "eyre", @@ -3502,9 +3826,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" dependencies = [ "rand", ] @@ -3811,6 +4135,24 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_arrays" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38636132857f68ec3d5f3eb121166d2af33cb55174c4d5ff645db6165cbef0fd" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.209" @@ -3955,6 +4297,14 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "sp1-intrinsics" +version = "0.0.0" +source = "git+https://github.com/scroll-tech/sp1-intrinsics.git?branch=master#7e038e60db0b2e847f6d8f49e148ccac8c6fc394" +dependencies = [ + "cfg-if", +] + [[package]] name = "sp1-lib" version = "1.2.0-rc1" @@ -4006,6 +4356,16 @@ dependencies = [ "der", ] +[[package]] +name = "stability" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn 2.0.90", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -4117,6 +4477,17 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "syn-solidity" +version = "0.8.18" +source = "git+https://github.com/scroll-tech/alloy-core?branch=v0.8.18#aab106b9b7ca2e76a7b073064f86a04adb3122fe" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "sync_wrapper" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index 426d6e403f..8f9b445629 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,14 @@ default-members = ["crates/revm"] all-features = true rustdoc-args = ["--cfg", "docsrs"] +[workspace.dependencies] +# openvm +openvm = { git = "https://github.com/openvm-org/openvm.git", tag = "v1.0.0-rc.1" } +openvm-ecc-guest = { git = "https://github.com/openvm-org/openvm.git", tag = "v1.0.0-rc.1" } +openvm-pairing-guest = { git = "https://github.com/openvm-org/openvm.git", tag = "v1.0.0-rc.1" } +openvm-keccak256-guest = { git = "https://github.com/openvm-org/openvm.git", tag = "v1.0.0-rc.1" } +openvm-sha256-guest = { git = "https://github.com/openvm-org/openvm.git", tag = "v1.0.0-rc.1" } + [profile.release] lto = true codegen-units = 1 @@ -22,3 +30,14 @@ debug = true [profile.ethtests] inherits = "test" opt-level = 3 + +[patch.crates-io] +ff = { git = "https://github.com/scroll-tech/ff", branch = "feat/sp1" } + +alloy-eips = { git = "https://github.com/scroll-tech/alloy.git", branch = "v0.7.3" } + +alloy-eip2930 = { git = "https://github.com/scroll-tech/alloy-eips", branch = "v0.4.2" } +alloy-eip7702 = { git = "https://github.com/scroll-tech/alloy-eips", branch = "v0.4.2" } + +alloy-primitives = { git = "https://github.com/scroll-tech/alloy-core", branch = "v0.8.18" } +alloy-sol-types = { git = "https://github.com/scroll-tech/alloy-core", branch = "v0.8.18" } diff --git a/bins/revm-test/Cargo.toml b/bins/revm-test/Cargo.toml index bca92098a3..35c06f9cd9 100644 --- a/bins/revm-test/Cargo.toml +++ b/bins/revm-test/Cargo.toml @@ -26,3 +26,7 @@ name = "transfer" [[bin]] name = "burntpix" + +[features] +scroll = ["revm/scroll"] +scroll-poseidon-codehash = ["scroll", "revm/scroll-poseidon-codehash"] diff --git a/bins/revme/Cargo.toml b/bins/revme/Cargo.toml index c29347be01..b80898a994 100644 --- a/bins/revme/Cargo.toml +++ b/bins/revme/Cargo.toml @@ -32,3 +32,7 @@ thiserror = "1.0" triehash = "0.8" walkdir = "2.5" k256 = { version = "0.13.3", features = ["ecdsa"] } + +[features] +scroll = ["revm/scroll"] +scroll-poseidon-codehash = ["scroll", "revm/scroll-poseidon-codehash"] diff --git a/bins/revme/src/cmd.rs b/bins/revme/src/cmd.rs index 97ea618aee..ab7d360e24 100644 --- a/bins/revme/src/cmd.rs +++ b/bins/revme/src/cmd.rs @@ -40,7 +40,7 @@ impl MainCmd { pub fn run(&self) -> Result<(), Error> { match self { Self::Statetest(cmd) => cmd.run().map_err(Into::into), - Self::EofValidation(cmd) => cmd.run().map_err(Into::into), + Self::EofValidation(cmd) => cmd.run(), Self::Evm(cmd) => cmd.run().map_err(Into::into), Self::Bytecode(cmd) => { cmd.run(); diff --git a/bins/revme/src/cmd/statetest/runner.rs b/bins/revme/src/cmd/statetest/runner.rs index a22b7d08d9..0d3856d3da 100644 --- a/bins/revme/src/cmd/statetest/runner.rs +++ b/bins/revme/src/cmd/statetest/runner.rs @@ -257,11 +257,19 @@ pub fn execute_test_suite( // Create database and insert cache let mut cache_state = revm::CacheState::new(false); for (address, info) in unit.pre { - let code_hash = keccak256(&info.code); + #[cfg(feature = "scroll")] + let code_size = info.code.len(); + let keccak_code_hash = keccak256(&info.code); + #[cfg(feature = "scroll-poseidon-codehash")] + let poseidon_code_hash = revm::primitives::poseidon(&info.code); let bytecode = to_analysed(Bytecode::new_raw(info.code)); let acc_info = revm::primitives::AccountInfo { balance: info.balance, - code_hash, + #[cfg(feature = "scroll")] + code_size, + code_hash: keccak_code_hash, + #[cfg(feature = "scroll-poseidon-codehash")] + poseidon_code_hash, code: Some(bytecode), nonce: info.nonce, }; diff --git a/crates/interpreter/Cargo.toml b/crates/interpreter/Cargo.toml index e7b0cc9cd9..b51885f6d2 100644 --- a/crates/interpreter/Cargo.toml +++ b/crates/interpreter/Cargo.toml @@ -60,6 +60,20 @@ negate-optimism-default-handler = [ "revm-primitives/negate-optimism-default-handler", ] +scroll = ["revm-primitives/scroll"] +# Scroll default handler enabled Scroll handler register by default in EvmBuilder. +scroll-default-handler = [ + "scroll", + "revm-primitives/scroll-default-handler", +] +negate-scroll-default-handler = [ + "revm-primitives/negate-scroll-default-handler", +] +scroll-poseidon-codehash = [ + "scroll", + "revm-primitives/scroll-poseidon-codehash", +] + dev = [ "memory_limit", "optional_balance_check", @@ -78,3 +92,14 @@ optional_no_base_fee = ["revm-primitives/optional_no_base_fee"] optional_beneficiary_reward = ["revm-primitives/optional_beneficiary_reward"] kzg-rs = ["revm-primitives/kzg-rs"] + +all = [ + "std", + "serde", + "arbitrary", + "asm-keccak", + "portable", + "parse", + "dev", + "kzg-rs", +] diff --git a/crates/interpreter/src/host.rs b/crates/interpreter/src/host.rs index 9b6274e64b..5b8926b962 100644 --- a/crates/interpreter/src/host.rs +++ b/crates/interpreter/src/host.rs @@ -12,6 +12,27 @@ pub trait Host { /// Returns a mutable reference to the environment. fn env_mut(&mut self) -> &mut Env; + #[cfg(feature = "scroll")] + /// Check an address is in the access list. + fn is_address_in_access_list(&self, address: Address) -> bool { + self.env() + .tx + .access_list + .iter() + .any(|item| item.address == address) + } + + #[cfg(feature = "scroll")] + /// Check a storage key is in the access list. + fn is_storage_key_in_access_list(&self, address: Address, index: U256) -> bool { + self.env() + .tx + .access_list + .iter() + .filter(|item| item.address == address) + .any(|item| item.storage_keys.contains(&B256::from(index))) + } + /// Load an account code. fn load_account_delegated(&mut self, address: Address) -> Option; @@ -27,6 +48,10 @@ pub trait Host { /// Get code hash of `address` and if the account is cold. fn code_hash(&mut self, address: Address) -> Option>; + #[cfg(feature = "scroll")] + /// Get code size of `address` and if the account is cold. + fn code_size(&mut self, address: Address) -> Option>; + /// Get storage value of `address` at `index` and if the account is cold. fn sload(&mut self, address: Address, index: U256) -> Option>; diff --git a/crates/interpreter/src/host/dummy.rs b/crates/interpreter/src/host/dummy.rs index 955d250f47..6a5f7bb1c7 100644 --- a/crates/interpreter/src/host/dummy.rs +++ b/crates/interpreter/src/host/dummy.rs @@ -69,6 +69,12 @@ impl Host for DummyHost { Some(StateLoad::new(KECCAK_EMPTY, false)) } + #[inline] + #[cfg(feature = "scroll")] + fn code_size(&mut self, _address: Address) -> Option> { + Some(StateLoad::new(0, false)) + } + #[inline] fn sload(&mut self, _address: Address, index: U256) -> Option> { match self.storage.entry(index) { diff --git a/crates/interpreter/src/instructions/contract.rs b/crates/interpreter/src/instructions/contract.rs index 281e147582..d5d8f49ccd 100644 --- a/crates/interpreter/src/instructions/contract.rs +++ b/crates/interpreter/src/instructions/contract.rs @@ -416,6 +416,10 @@ pub fn call(interpreter: &mut Interpreter, host: & interpreter.instruction_result = InstructionResult::FatalExternalError; return; }; + #[cfg(feature = "scroll")] + if account_load.is_cold && host.is_address_in_access_list(to) { + panic!("access list account should be either loaded or never accessed"); + } let Some(mut gas_limit) = calc_call_gas::(interpreter, account_load, has_transfer, local_gas_limit) else { @@ -464,6 +468,10 @@ pub fn call_code(interpreter: &mut Interpreter, ho }; // set is_empty to false as we are not creating this account. load.is_empty = false; + #[cfg(feature = "scroll")] + if load.is_cold && host.is_address_in_access_list(to) { + panic!("access list account should be either loaded or never accessed"); + } let Some(mut gas_limit) = calc_call_gas::(interpreter, load, !value.is_zero(), local_gas_limit) else { @@ -512,6 +520,10 @@ pub fn delegate_call(interpreter: &mut Interpreter }; // set is_empty to false as we are not creating this account. load.is_empty = false; + #[cfg(feature = "scroll")] + if load.is_cold && host.is_address_in_access_list(to) { + panic!("access list account should be either loaded or never accessed"); + } let Some(gas_limit) = calc_call_gas::(interpreter, load, false, local_gas_limit) else { return; }; @@ -553,6 +565,10 @@ pub fn static_call(interpreter: &mut Interpreter, }; // set is_empty to false as we are not creating this account. load.is_empty = false; + #[cfg(feature = "scroll")] + if load.is_cold && host.is_address_in_access_list(to) { + panic!("access list account should be either loaded or never accessed"); + } let Some(gas_limit) = calc_call_gas::(interpreter, load, false, local_gas_limit) else { return; }; diff --git a/crates/interpreter/src/instructions/host.rs b/crates/interpreter/src/instructions/host.rs index e708120647..e14a24057c 100644 --- a/crates/interpreter/src/instructions/host.rs +++ b/crates/interpreter/src/instructions/host.rs @@ -13,6 +13,10 @@ pub fn balance(interpreter: &mut Interpreter, host interpreter.instruction_result = InstructionResult::FatalExternalError; return; }; + #[cfg(feature = "scroll")] + if balance.is_cold && host.is_address_in_access_list(interpreter.contract.target_address) { + panic!("access list account should be either loaded or never accessed"); + } gas!( interpreter, if SPEC::enabled(BERLIN) { @@ -40,6 +44,7 @@ pub fn selfbalance(interpreter: &mut Interpreter, push!(interpreter, balance.data); } +#[cfg(not(feature = "scroll"))] pub fn extcodesize(interpreter: &mut Interpreter, host: &mut H) { pop_address!(interpreter, address); let Some(code) = host.code(address) else { @@ -57,6 +62,21 @@ pub fn extcodesize(interpreter: &mut Interpreter, push!(interpreter, U256::from(code.len())); } +#[cfg(feature = "scroll")] +pub fn extcodesize(interpreter: &mut Interpreter, host: &mut H) { + pop_address!(interpreter, address); + let Some(code_size) = host.code_size(address) else { + interpreter.instruction_result = InstructionResult::FatalExternalError; + return; + }; + if code_size.is_cold && host.is_address_in_access_list(interpreter.contract.target_address) { + panic!("access list account should be either loaded or never accessed"); + } + gas!(interpreter, warm_cold_cost(code_size.is_cold)); + + push!(interpreter, U256::from(*code_size)); +} + /// EIP-1052: EXTCODEHASH opcode pub fn extcodehash(interpreter: &mut Interpreter, host: &mut H) { check!(interpreter, CONSTANTINOPLE); @@ -65,6 +85,10 @@ pub fn extcodehash(interpreter: &mut Interpreter, interpreter.instruction_result = InstructionResult::FatalExternalError; return; }; + #[cfg(feature = "scroll")] + if code_hash.is_cold && host.is_address_in_access_list(interpreter.contract.target_address) { + panic!("access list account should be either loaded or never accessed"); + } if SPEC::enabled(BERLIN) { gas!(interpreter, warm_cold_cost(code_hash.is_cold)) } else if SPEC::enabled(ISTANBUL) { @@ -83,6 +107,10 @@ pub fn extcodecopy(interpreter: &mut Interpreter, interpreter.instruction_result = InstructionResult::FatalExternalError; return; }; + #[cfg(feature = "scroll")] + if code.is_cold && host.is_address_in_access_list(interpreter.contract.target_address) { + panic!("access list account should be either loaded or never accessed"); + } let len = as_usize_or_fail!(interpreter, len_u256); gas_or_fail!( @@ -102,6 +130,7 @@ pub fn extcodecopy(interpreter: &mut Interpreter, .set_data(memory_offset, code_offset, len, &code); } +#[cfg(not(feature = "scroll"))] pub fn blockhash(interpreter: &mut Interpreter, host: &mut H) { gas!(interpreter, gas::BLOCKHASH); pop_top!(interpreter, number); @@ -114,12 +143,49 @@ pub fn blockhash(interpreter: &mut Interpreter, ho *number = U256::from_be_bytes(hash.0); } +#[cfg(feature = "scroll")] +pub fn blockhash(interpreter: &mut Interpreter, host: &mut H) { + use revm_primitives::BLOCK_HASH_HISTORY; + + gas!(interpreter, gas::BLOCKHASH); + pop_top!(interpreter, number); + + let block_number = host.env().block.number; + + match block_number.checked_sub(*number) { + Some(diff) if !diff.is_zero() => { + let diff = as_u64_saturated!(diff); + let block_number = as_u64_or_fail!(interpreter, number); + + if SPEC::enabled(PRE_BERNOULLI) && diff <= BLOCK_HASH_HISTORY { + let mut hasher = crate::primitives::Keccak256::new(); + hasher.update(host.env().cfg.chain_id.to_be_bytes()); + hasher.update(block_number.to_be_bytes()); + *number = U256::from_be_bytes(*hasher.finalize()); + return; + } + } + _ => { + // If blockhash is requested for the current block, the hash should be 0, so we fall + // through. + } + } + + *number = U256::ZERO; +} + pub fn sload(interpreter: &mut Interpreter, host: &mut H) { pop_top!(interpreter, index); let Some(value) = host.sload(interpreter.contract.target_address, *index) else { interpreter.instruction_result = InstructionResult::FatalExternalError; return; }; + #[cfg(feature = "scroll")] + if value.is_cold + && host.is_storage_key_in_access_list(interpreter.contract.target_address, *index) + { + panic!("access list account should be either loaded or never accessed"); + } gas!(interpreter, gas::sload_cost(SPEC::SPEC_ID, value.is_cold)); *index = value.data; } @@ -132,6 +198,13 @@ pub fn sstore(interpreter: &mut Interpreter, host: interpreter.instruction_result = InstructionResult::FatalExternalError; return; }; + #[cfg(feature = "scroll")] + if state_load.is_cold + && host.is_storage_key_in_access_list(interpreter.contract.target_address, index) + { + interpreter.instruction_result = InstructionResult::NotActivated; + return; + } gas_or_fail!(interpreter, { let remaining_gas = interpreter.gas.remaining(); gas::sstore_cost( @@ -150,6 +223,9 @@ pub fn sstore(interpreter: &mut Interpreter, host: /// EIP-1153: Transient storage opcodes /// Store value to transient storage pub fn tstore(interpreter: &mut Interpreter, host: &mut H) { + #[cfg(feature = "scroll")] + check!(interpreter, CURIE); + #[cfg(not(feature = "scroll"))] check!(interpreter, CANCUN); require_non_staticcall!(interpreter); gas!(interpreter, gas::WARM_STORAGE_READ_COST); @@ -162,6 +238,9 @@ pub fn tstore(interpreter: &mut Interpreter, host: /// EIP-1153: Transient storage opcodes /// Load value from transient storage pub fn tload(interpreter: &mut Interpreter, host: &mut H) { + #[cfg(feature = "scroll")] + check!(interpreter, CURIE); + #[cfg(not(feature = "scroll"))] check!(interpreter, CANCUN); gas!(interpreter, gas::WARM_STORAGE_READ_COST); @@ -207,6 +286,12 @@ pub fn selfdestruct(interpreter: &mut Interpreter, require_non_staticcall!(interpreter); pop_address!(interpreter, target); + #[cfg(feature = "scroll")] + if SPEC::enabled(PRE_BERNOULLI) { + interpreter.instruction_result = InstructionResult::NotActivated; + return; + } + let Some(res) = host.selfdestruct(interpreter.contract.target_address, target) else { interpreter.instruction_result = InstructionResult::FatalExternalError; return; diff --git a/crates/interpreter/src/instructions/host_env.rs b/crates/interpreter/src/instructions/host_env.rs index ce934d492f..2c632ec76b 100644 --- a/crates/interpreter/src/instructions/host_env.rs +++ b/crates/interpreter/src/instructions/host_env.rs @@ -47,6 +47,12 @@ pub fn gasprice(interpreter: &mut Interpreter, host: &mut H) { /// EIP-3198: BASEFEE opcode pub fn basefee(interpreter: &mut Interpreter, host: &mut H) { + #[cfg(feature = "scroll")] + if !SPEC::enabled(CURIE) { + interpreter.instruction_result = crate::InstructionResult::NotActivated; + return; + } + check!(interpreter, LONDON); gas!(interpreter, gas::BASE); push!(interpreter, host.env().block.basefee); diff --git a/crates/interpreter/src/instructions/macros.rs b/crates/interpreter/src/instructions/macros.rs index 4df6f62d1a..dec79cd971 100644 --- a/crates/interpreter/src/instructions/macros.rs +++ b/crates/interpreter/src/instructions/macros.rs @@ -345,3 +345,40 @@ macro_rules! as_usize_or_fail_ret { } }; } + +/// Converts a `U256` value to a `u64`, failing the instruction if the value is too large. +#[macro_export] +macro_rules! as_u64_or_fail { + ($interp:expr, $v:expr) => { + $crate::as_u64_or_fail_ret!($interp, $v, ()) + }; + ($interp:expr, $v:expr, $reason:expr) => { + $crate::as_u64_or_fail_ret!($interp, $v, $reason, ()) + }; +} + +/// Converts a `U256` value to a `usize` and returns `ret`, +/// failing the instruction if the value is too large. +#[macro_export] +macro_rules! as_u64_or_fail_ret { + ($interp:expr, $v:expr, $ret:expr) => { + $crate::as_u64_or_fail_ret!( + $interp, + $v, + $crate::InstructionResult::InvalidOperandOOG, + $ret + ) + }; + + ($interp:expr, $v:expr, $reason:expr, $ret:expr) => { + match $v.as_limbs() { + x => { + if (x[0] > u64::MAX) | (x[1] != 0) | (x[2] != 0) | (x[3] != 0) { + $interp.instruction_result = $reason; + return $ret; + } + x[0] as usize + } + } + }; +} diff --git a/crates/interpreter/src/instructions/memory.rs b/crates/interpreter/src/instructions/memory.rs index e5bdde911c..ece9b44910 100644 --- a/crates/interpreter/src/instructions/memory.rs +++ b/crates/interpreter/src/instructions/memory.rs @@ -36,6 +36,9 @@ pub fn msize(interpreter: &mut Interpreter, _host: &mut H) { // EIP-5656: MCOPY - Memory copying instruction pub fn mcopy(interpreter: &mut Interpreter, _host: &mut H) { + #[cfg(feature = "scroll")] + check!(interpreter, CURIE); + #[cfg(not(feature = "scroll"))] check!(interpreter, CANCUN); pop!(interpreter, dst, src, len); diff --git a/crates/precompile/Cargo.toml b/crates/precompile/Cargo.toml index 396d7bfaca..a756591bb4 100644 --- a/crates/precompile/Cargo.toml +++ b/crates/precompile/Cargo.toml @@ -17,6 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"] unreachable_pub = "warn" unused_must_use = "deny" rust_2018_idioms = "deny" +unexpected_cfgs = "allow" [lints.rustdoc] all = "warn" @@ -63,6 +64,12 @@ p256 = { version = "0.13.2", optional = true, default-features = false, features # utils cfg-if = { version = "1.0", default-features = false } +# Optionally use openvm intrinsics +openvm-ecc-guest = { workspace = true, optional = true } +openvm-keccak256-guest = { workspace = true, optional = true } +openvm-sha256-guest = { workspace = true, optional = true } +openvm-pairing-guest = { workspace = true, optional = true } + [dev-dependencies] criterion = "0.5" rand = { version = "0.8", features = ["std"] } @@ -73,7 +80,7 @@ serde_json = "1.0" serde_derive = "1.0" [features] -default = ["std", "c-kzg", "secp256k1", "portable", "blst"] +default = ["std", "c-kzg", "portable", "blst"] std = [ "revm-primitives/std", "k256/std", @@ -96,6 +103,25 @@ negate-optimism-default-handler = [ "revm-primitives/negate-optimism-default-handler", ] +scroll = ["revm-primitives/scroll", "secp256r1"] +# Scroll default handler enabled Scroll handler register by default in EvmBuilder. +scroll-default-handler = [ + "scroll", + "revm-primitives/scroll-default-handler", +] +negate-scroll-default-handler = [ + "revm-primitives/negate-scroll-default-handler", +] + +openvm = [ + "dep:openvm-ecc-guest", + "dep:openvm-keccak256-guest", + "dep:openvm-sha256-guest", + "dep:openvm-pairing-guest", + "openvm-ecc-guest/k256", + "openvm-pairing-guest/bn254", +] + # Enables the p256verify precompile. secp256r1 = ["dep:p256"] @@ -116,6 +142,16 @@ secp256k1 = ["dep:secp256k1"] # Enables the BLS12-381 precompiles. blst = ["dep:blst"] +all = [ + "std", + "hashbrown", + "asm-keccak", + "secp256r1", + "c-kzg", + "portable", + "secp256k1", +] + [[bench]] name = "bench" path = "benches/bench.rs" diff --git a/crates/precompile/src/blake2.rs b/crates/precompile/src/blake2.rs index 6ecf55bebe..32c51ec9b6 100644 --- a/crates/precompile/src/blake2.rs +++ b/crates/precompile/src/blake2.rs @@ -1,12 +1,23 @@ use crate::{Error, Precompile, PrecompileResult, PrecompileWithAddress}; use revm_primitives::{Bytes, PrecompileOutput}; +#[cfg(feature = "scroll")] +use revm_primitives::PrecompileError; + const F_ROUND: u64 = 1; const INPUT_LENGTH: usize = 213; pub const FUN: PrecompileWithAddress = PrecompileWithAddress(crate::u64_to_address(9), Precompile::Standard(run)); +#[cfg(feature = "scroll")] +pub const BERNOULLI: PrecompileWithAddress = PrecompileWithAddress( + crate::u64_to_address(9), + Precompile::Standard(|_input: &Bytes, _gas_limit: u64| { + Err(PrecompileError::NotImplemented.into()) + }), +); + /// reference: /// input format: /// [4 bytes for rounds][64 bytes for h][128 bytes for m][8 bytes for t_0][8 bytes for t_1][1 byte for f] diff --git a/crates/precompile/src/bn128.rs b/crates/precompile/src/bn128.rs index 416a41672f..ba0a6e13b4 100644 --- a/crates/precompile/src/bn128.rs +++ b/crates/precompile/src/bn128.rs @@ -2,10 +2,25 @@ use crate::{ utilities::{bool_to_bytes32, right_pad}, Address, Error, Precompile, PrecompileResult, PrecompileWithAddress, }; +#[cfg(not(feature = "openvm"))] use bn::{AffineG1, AffineG2, Fq, Fq2, Group, Gt, G1, G2}; use revm_primitives::PrecompileOutput; use std::vec::Vec; +#[cfg(feature = "openvm")] +use { + bn as _, + openvm_ecc_guest::{ + weierstrass::{IntrinsicCurve, WeierstrassPoint}, + AffinePoint, + }, + openvm_pairing_guest::{ + algebra::IntMod, + bn254::{Bn254, Fp, Fp2, G1Affine, Scalar}, + pairing::PairingCheck, + }, +}; + pub mod add { use super::*; @@ -74,6 +89,35 @@ pub mod pair { ) }), ); + + #[cfg(feature = "scroll")] + mod scroll { + use crate::{Precompile, PrecompileWithAddress}; + + /// The number of pairing inputs per pairing operation. If the inputs provided to the precompile + /// call are < 4, we append (G1::infinity, G2::generator) until we have the required no. of inputs. + const N_PAIRING_PER_OP: usize = 4; + + /// The number of bytes taken to represent a pair (G1, G2). + const N_BYTES_PER_PAIR: usize = 192; + + pub const BERNOULLI: PrecompileWithAddress = PrecompileWithAddress( + super::ADDRESS, + Precompile::Standard(|input, gas_limit| { + if input.len() > N_PAIRING_PER_OP * N_BYTES_PER_PAIR { + return Err(crate::PrecompileError::NotImplemented.into()); + } + super::run_pair( + input, + super::ISTANBUL_PAIR_PER_POINT, + super::ISTANBUL_PAIR_BASE, + gas_limit, + ) + }), + ); + } + #[cfg(feature = "scroll")] + pub use scroll::*; } /// Input length for the add operation. @@ -94,16 +138,28 @@ pub const PAIR_ELEMENT_LEN: usize = 64 + 128; /// # Panics /// /// Panics if the input is not at least 32 bytes long. +#[cfg(not(feature = "openvm"))] #[inline] pub fn read_fq(input: &[u8]) -> Result { Fq::from_slice(&input[..32]).map_err(|_| Error::Bn128FieldPointNotAMember) } +#[cfg(feature = "openvm")] +#[inline] +pub fn read_fq(input: &[u8]) -> Result { + if input.len() < 32 { + Err(Error::Bn128FieldPointNotAMember) + } else { + Ok(Fp::from_be_bytes(&input[..32])) + } +} + /// Reads the `x` and `y` points from the input slice. /// /// # Panics /// /// Panics if the input is not at least 64 bytes long. +#[cfg(not(feature = "openvm"))] #[inline] pub fn read_point(input: &[u8]) -> Result { let px = read_fq(&input[0..32])?; @@ -111,7 +167,16 @@ pub fn read_point(input: &[u8]) -> Result { new_g1_point(px, py) } +#[cfg(feature = "openvm")] +#[inline] +pub fn read_point(input: &[u8]) -> Result { + let px = read_fq(&input[0..32])?; + let py = read_fq(&input[32..64])?; + new_g1_point(px, py) +} + /// Creates a new `G1` point from the given `x` and `y` coordinates. +#[cfg(not(feature = "openvm"))] pub fn new_g1_point(px: Fq, py: Fq) -> Result { if px == Fq::zero() && py == Fq::zero() { Ok(G1::zero()) @@ -122,6 +187,11 @@ pub fn new_g1_point(px: Fq, py: Fq) -> Result { } } +#[cfg(feature = "openvm")] +pub fn new_g1_point(px: Fp, py: Fp) -> Result { + G1Affine::from_xy(px, py).ok_or(Error::Bn128AffineGFailedToCreate) +} + pub fn run_add(input: &[u8], gas_cost: u64, gas_limit: u64) -> PrecompileResult { if gas_cost > gas_limit { return Err(Error::OutOfGas.into()); @@ -133,11 +203,27 @@ pub fn run_add(input: &[u8], gas_cost: u64, gas_limit: u64) -> PrecompileResult let p2 = read_point(&input[64..])?; let mut output = [0u8; 64]; - if let Some(sum) = AffineG1::from_jacobian(p1 + p2) { - sum.x().to_big_endian(&mut output[..32]).unwrap(); - sum.y().to_big_endian(&mut output[32..]).unwrap(); + #[cfg(not(feature = "openvm"))] + { + if let Some(sum) = AffineG1::from_jacobian(p1 + p2) { + sum.x().to_big_endian(&mut output[..32]).unwrap(); + sum.y().to_big_endian(&mut output[32..]).unwrap(); + } + Ok(PrecompileOutput::new(gas_cost, output.into())) + } + #[cfg(feature = "openvm")] + { + let sum = p1 + p2; + // TODO: we should add as_be_bytes to SW point. + // manually reverse to avoid allocation + let x_bytes: &[u8] = sum.x().as_le_bytes(); + let y_bytes: &[u8] = sum.y().as_le_bytes(); + for i in 0..32 { + output[i] = x_bytes[31 - i]; + output[i + 32] = y_bytes[31 - i]; + } + Ok(PrecompileOutput::new(gas_cost, output.into())) } - Ok(PrecompileOutput::new(gas_cost, output.into())) } pub fn run_mul(input: &[u8], gas_cost: u64, gas_limit: u64) -> PrecompileResult { @@ -149,17 +235,36 @@ pub fn run_mul(input: &[u8], gas_cost: u64, gas_limit: u64) -> PrecompileResult let p = read_point(&input[..64])?; - // `Fr::from_slice` can only fail when the length is not 32. - let fr = bn::Fr::from_slice(&input[64..96]).unwrap(); - let mut output = [0u8; 64]; - if let Some(mul) = AffineG1::from_jacobian(p * fr) { - mul.x().to_big_endian(&mut output[..32]).unwrap(); - mul.y().to_big_endian(&mut output[32..]).unwrap(); + + #[cfg(not(feature = "openvm"))] + { + // `Fr::from_slice` can only fail when the length is not 32. + let fr = bn::Fr::from_slice(&input[64..96]).unwrap(); + + if let Some(mul) = AffineG1::from_jacobian(p * fr) { + mul.x().to_big_endian(&mut output[..32]).unwrap(); + mul.y().to_big_endian(&mut output[32..]).unwrap(); + } + Ok(PrecompileOutput::new(gas_cost, output.into())) + } + #[cfg(feature = "openvm")] + { + let scalar = Scalar::from_be_bytes(&input[64..96]); + + let res = Bn254::msm(&[scalar], &[p]); + // manually reverse to avoid allocation + let x_bytes: &[u8] = res.x().as_le_bytes(); + let y_bytes: &[u8] = res.y().as_le_bytes(); + for i in 0..32 { + output[i] = x_bytes[31 - i]; + output[i + 32] = y_bytes[31 - i]; + } + Ok(PrecompileOutput::new(gas_cost, output.into())) } - Ok(PrecompileOutput::new(gas_cost, output.into())) } +#[allow(non_snake_case)] pub fn run_pair( input: &[u8], pair_per_point_cost: u64, @@ -180,43 +285,71 @@ pub fn run_pair( } else { let elements = input.len() / PAIR_ELEMENT_LEN; + #[cfg(not(feature = "openvm"))] let mut points = Vec::with_capacity(elements); + #[cfg(feature = "openvm")] + let mut P = Vec::with_capacity(elements); + #[cfg(feature = "openvm")] + let mut Q = Vec::with_capacity(elements); + // read points for idx in 0..elements { + // At each idx, there is (G1, G2) which is 6 Fp points let read_fq_at = |n: usize| { debug_assert!(n < PAIR_ELEMENT_LEN / 32); let start = idx * PAIR_ELEMENT_LEN + n * 32; // SAFETY: We're reading `6 * 32 == PAIR_ELEMENT_LEN` bytes from `input[idx..]` // per iteration. This is guaranteed to be in-bounds. let slice = unsafe { input.get_unchecked(start..start + 32) }; - Fq::from_slice(slice).map_err(|_| Error::Bn128FieldPointNotAMember) + read_fq(slice) }; - let ax = read_fq_at(0)?; - let ay = read_fq_at(1)?; - let bay = read_fq_at(2)?; - let bax = read_fq_at(3)?; - let bby = read_fq_at(4)?; - let bbx = read_fq_at(5)?; - - let a = new_g1_point(ax, ay)?; - let b = { - let ba = Fq2::new(bax, bay); - let bb = Fq2::new(bbx, bby); - // TODO: check whether or not we need these zero checks - if ba.is_zero() && bb.is_zero() { - G2::zero() - } else { - G2::from(AffineG2::new(ba, bb).map_err(|_| Error::Bn128AffineGFailedToCreate)?) - } - }; - - points.push((a, b)); + // https://eips.ethereum.org/EIPS/eip-197, Fp2 is encoded as (a, b) where a * i + b + let g1_x = read_fq_at(0)?; + let g1_y = read_fq_at(1)?; + let g2_x_c1 = read_fq_at(2)?; + let g2_x_c0 = read_fq_at(3)?; + let g2_y_c1 = read_fq_at(4)?; + let g2_y_c0 = read_fq_at(5)?; + + #[cfg(not(feature = "openvm"))] + { + let g1 = new_g1_point(g1_x, g1_y)?; + let g2 = { + let g2_x = Fq2::new(g2_x_c0, g2_x_c1); + let g2_y = Fq2::new(g2_y_c0, g2_y_c1); + // TODO: check whether or not we need these zero checks + if g2_x.is_zero() && g2_y.is_zero() { + G2::zero() + } else { + G2::from( + AffineG2::new(g2_x, g2_y) + .map_err(|_| Error::Bn128AffineGFailedToCreate)?, + ) + } + }; + points.push((g1, g2)); + } + + #[cfg(feature = "openvm")] + { + let g1 = AffinePoint::new(g1_x, g1_y); + let g2_x = Fp2::new(g2_x_c0, g2_x_c1); + let g2_y = Fp2::new(g2_y_c0, g2_y_c1); + let g2 = AffinePoint::new(g2_x, g2_y); + + P.push(g1); + Q.push(g2); + } } - let mul = bn::pairing_batch(&points); + #[cfg(not(feature = "openvm"))] + let success = bn::pairing_batch(&points) == Gt::one(); + + #[cfg(feature = "openvm")] + let success = Bn254::pairing_check(&P, &Q).is_ok(); - mul == Gt::one() + success }; Ok(PrecompileOutput::new(gas_used, bool_to_bytes32(success))) } diff --git a/crates/precompile/src/hash.rs b/crates/precompile/src/hash.rs index 1ea5e73dfc..ae2d4a7205 100644 --- a/crates/precompile/src/hash.rs +++ b/crates/precompile/src/hash.rs @@ -3,14 +3,33 @@ use crate::{Error, Precompile, PrecompileResult, PrecompileWithAddress}; use revm_primitives::{Bytes, PrecompileOutput}; use sha2::Digest; +#[cfg(feature = "scroll")] +use revm_primitives::PrecompileError; + pub const SHA256: PrecompileWithAddress = PrecompileWithAddress(crate::u64_to_address(2), Precompile::Standard(sha256_run)); +#[cfg(feature = "scroll")] +pub const SHA256_PRE_BERNOULLI: PrecompileWithAddress = PrecompileWithAddress( + crate::u64_to_address(2), + Precompile::Standard(|_input: &Bytes, _gas_limit: u64| { + Err(PrecompileError::NotImplemented.into()) + }), +); + pub const RIPEMD160: PrecompileWithAddress = PrecompileWithAddress( crate::u64_to_address(3), Precompile::Standard(ripemd160_run), ); +#[cfg(feature = "scroll")] +pub const RIPEMD160_PRE_BERNOULLI: PrecompileWithAddress = PrecompileWithAddress( + crate::u64_to_address(3), + Precompile::Standard(|_input: &Bytes, _gas_limit: u64| { + Err(PrecompileError::NotImplemented.into()) + }), +); + /// Computes the SHA-256 hash of the input data. /// /// This function follows specifications defined in the following references: @@ -22,7 +41,10 @@ pub fn sha256_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { if cost > gas_limit { Err(Error::OutOfGas.into()) } else { + #[cfg(not(feature = "openvm"))] let output = sha2::Sha256::digest(input); + #[cfg(feature = "openvm")] + let output = openvm_sha256_guest::sha256(input); Ok(PrecompileOutput::new(cost, output.to_vec().into())) } } diff --git a/crates/precompile/src/lib.rs b/crates/precompile/src/lib.rs index a19cb5b053..08d2df3c2d 100644 --- a/crates/precompile/src/lib.rs +++ b/crates/precompile/src/lib.rs @@ -60,6 +60,10 @@ impl Precompiles { PrecompileSpecId::BYZANTIUM => Self::byzantium(), PrecompileSpecId::ISTANBUL => Self::istanbul(), PrecompileSpecId::BERLIN => Self::berlin(), + #[cfg(feature = "scroll")] + PrecompileSpecId::PRE_BERNOULLI => Self::pre_bernoulli(), + #[cfg(feature = "scroll")] + PrecompileSpecId::BERNOULLI => Self::bernoulli(), PrecompileSpecId::CANCUN => Self::cancun(), PrecompileSpecId::PRAGUE => Self::prague(), PrecompileSpecId::LATEST => Self::latest(), @@ -179,6 +183,41 @@ impl Precompiles { }) } + /// Returns precompiles for Scroll + #[cfg(feature = "scroll")] + pub fn pre_bernoulli() -> &'static Self { + static INSTANCE: OnceBox = OnceBox::new(); + INSTANCE.get_or_init(|| { + let mut precompiles = Precompiles::default(); + precompiles.extend([ + secp256k1::ECRECOVER, // 0x01 + hash::SHA256_PRE_BERNOULLI, // 0x02 + hash::RIPEMD160_PRE_BERNOULLI, // 0x03 + identity::FUN, // 0x04 + modexp::BERNOULLI, // 0x05 + bn128::add::ISTANBUL, // 0x06 + bn128::mul::ISTANBUL, // 0x07 + bn128::pair::BERNOULLI, // 0x08 + blake2::BERNOULLI, // 0x09 + ]); + Box::new(precompiles) + }) + } + + /// Returns precompiles for Scroll + #[cfg(feature = "scroll")] + pub fn bernoulli() -> &'static Self { + static INSTANCE: OnceBox = OnceBox::new(); + INSTANCE.get_or_init(|| { + let mut precompiles = Self::pre_bernoulli().clone(); + precompiles.extend([ + hash::SHA256, // 0x02 + ]); + + Box::new(precompiles) + }) + } + /// Returns the precompiles for the latest spec. pub fn latest() -> &'static Self { Self::prague() @@ -269,12 +308,17 @@ impl PrecompileWithAddress { } } +#[allow(non_camel_case_types)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] pub enum PrecompileSpecId { HOMESTEAD, BYZANTIUM, ISTANBUL, BERLIN, + #[cfg(feature = "scroll")] + PRE_BERNOULLI, + #[cfg(feature = "scroll")] + BERNOULLI, CANCUN, PRAGUE, LATEST, @@ -294,6 +338,10 @@ impl PrecompileSpecId { CANCUN => Self::CANCUN, PRAGUE | OSAKA => Self::PRAGUE, LATEST => Self::LATEST, + #[cfg(feature = "scroll")] + PRE_BERNOULLI => Self::PRE_BERNOULLI, + #[cfg(feature = "scroll")] + BERNOULLI | CURIE | EUCLID => Self::BERNOULLI, #[cfg(feature = "optimism")] BEDROCK | REGOLITH | CANYON => Self::BERLIN, #[cfg(feature = "optimism")] diff --git a/crates/precompile/src/modexp.rs b/crates/precompile/src/modexp.rs index 4839bff751..f2f0f4ea38 100644 --- a/crates/precompile/src/modexp.rs +++ b/crates/precompile/src/modexp.rs @@ -7,6 +7,9 @@ use aurora_engine_modexp::modexp; use core::cmp::{max, min}; use revm_primitives::{Bytes, PrecompileOutput}; +#[cfg(feature = "scroll")] +const SCROLL_LEN_LIMIT: U256 = U256::from_limbs([32, 0, 0, 0]); + pub const BYZANTIUM: PrecompileWithAddress = PrecompileWithAddress( crate::u64_to_address(5), Precompile::Standard(byzantium_run), @@ -15,6 +18,12 @@ pub const BYZANTIUM: PrecompileWithAddress = PrecompileWithAddress( pub const BERLIN: PrecompileWithAddress = PrecompileWithAddress(crate::u64_to_address(5), Precompile::Standard(berlin_run)); +#[cfg(feature = "scroll")] +pub const BERNOULLI: PrecompileWithAddress = PrecompileWithAddress( + crate::u64_to_address(5), + Precompile::Standard(bernoilli_run), +); + /// See: /// See: pub fn byzantium_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { @@ -29,6 +38,28 @@ pub fn berlin_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { }) } +#[cfg(feature = "scroll")] +pub fn bernoilli_run(input: &Bytes, gas_limit: u64) -> PrecompileResult { + let base_len = U256::from_be_bytes(right_pad_with_offset::<32>(input, 0).into_owned()); + let exp_len = U256::from_be_bytes(right_pad_with_offset::<32>(input, 32).into_owned()); + let mod_len = U256::from_be_bytes(right_pad_with_offset::<32>(input, 64).into_owned()); + + // modexp temporarily only accepts inputs of 32 bytes (256 bits) or less + if base_len > SCROLL_LEN_LIMIT { + return Err(Error::ModexpBaseOverflow.into()); + } + if exp_len > SCROLL_LEN_LIMIT { + return Err(Error::ModexpExpOverflow.into()); + } + if mod_len > SCROLL_LEN_LIMIT { + return Err(Error::ModexpModOverflow.into()); + } + + run_inner(input, gas_limit, 200, |a, b, c, d| { + berlin_gas_calc(a, b, c, d) + }) +} + pub fn calculate_iteration_count(exp_length: u64, exp_highp: &U256) -> u64 { let mut iteration_count: u64 = 0; diff --git a/crates/precompile/src/secp256k1.rs b/crates/precompile/src/secp256k1.rs index 70da5c3a71..2740f0cf3a 100644 --- a/crates/precompile/src/secp256k1.rs +++ b/crates/precompile/src/secp256k1.rs @@ -8,7 +8,47 @@ pub const ECRECOVER: PrecompileWithAddress = PrecompileWithAddress( pub use self::secp256k1::ecrecover; -#[cfg(not(feature = "secp256k1"))] +#[cfg(feature = "openvm")] +#[allow(clippy::module_inception)] +mod secp256k1 { + use k256::{ + ecdsa::{Error, RecoveryId, Signature}, + Secp256k1, + }; + use openvm_ecc_guest::{algebra::IntMod, ecdsa::VerifyingKey, weierstrass::WeierstrassPoint}; + use openvm_keccak256_guest::keccak256; + use revm_primitives::{alloy_primitives::B512, B256}; + #[cfg(feature = "secp256k1")] + use secp256k1 as _; + + pub fn ecrecover(sig: &B512, mut recid: u8, msg: &B256) -> Result { + // parse signature + let mut sig = Signature::from_slice(sig.as_slice())?; + if let Some(sig_normalized) = sig.normalize_s() { + sig = sig_normalized; + recid ^= 1; + } + let recid = RecoveryId::from_byte(recid).expect("recovery ID is valid"); + + // annoying: Signature::to_bytes copies from slice + let recovered_key = VerifyingKey::::recover_from_prehash_noverify( + &msg[..], + &sig.to_bytes(), + recid, + ); + let public_key = recovered_key.as_affine(); + let mut encoded = [0u8; 64]; + encoded[..32].copy_from_slice(&public_key.x().to_be_bytes()); + encoded[32..].copy_from_slice(&public_key.y().to_be_bytes()); + // hash it + let mut hash = keccak256(&encoded); + // truncate to 20 bytes + hash[..12].fill(0); + Ok(B256::from(hash)) + } +} + +#[cfg(not(any(feature = "secp256k1", feature = "openvm")))] #[allow(clippy::module_inception)] mod secp256k1 { use k256::ecdsa::{Error, RecoveryId, Signature, VerifyingKey}; @@ -40,7 +80,7 @@ mod secp256k1 { } } -#[cfg(feature = "secp256k1")] +#[cfg(all(feature = "secp256k1", not(feature = "openvm")))] #[allow(clippy::module_inception)] mod secp256k1 { use revm_primitives::{alloy_primitives::B512, keccak256, B256}; diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index c562883310..a8f5b2560b 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -56,6 +56,12 @@ serde = { version = "1.0", default-features = false, features = [ "rc", ], optional = true } +# scroll +poseidon-bn254 = { git = "https://github.com/scroll-tech/poseidon-bn254", branch = "master", features = ["bn254"], optional = true } + +# Optionally use openvm intrinsics +openvm-keccak256-guest = { workspace = true, optional = true } + [build-dependencies] hex = { version = "0.4", default-features = false } @@ -96,6 +102,15 @@ optimism = [] optimism-default-handler = ["optimism"] negate-optimism-default-handler = [] +scroll = [] +# Scroll default handler enabled Scroll handler register by default in EvmBuilder. +scroll-default-handler = ["scroll"] +negate-scroll-default-handler = [] +scroll-poseidon-codehash = ["scroll", "poseidon-bn254"] + +openvm = ["dep:openvm-keccak256-guest"] + + dev = [ "memory_limit", "optional_balance_check", @@ -119,3 +134,12 @@ c-kzg = ["dep:c-kzg"] # `kzg-rs` is not audited but useful for `no_std` environment. # use it with causing and default to `c-kzg` if possible! kzg-rs = ["dep:kzg-rs"] + +all = [ + "std", + "serde", + "arbitrary", + "asm-keccak", + "portable", + "dev", +] diff --git a/crates/primitives/src/bytecode.rs b/crates/primitives/src/bytecode.rs index bae4161806..7c0792e02d 100644 --- a/crates/primitives/src/bytecode.rs +++ b/crates/primitives/src/bytecode.rs @@ -60,6 +60,18 @@ impl Bytecode { } } + /// Calculate poseidon hash of the bytecode. + #[cfg(feature = "scroll-poseidon-codehash")] + pub fn poseidon_hash_slow(&self) -> B256 { + use crate::{poseidon, POSEIDON_EMPTY}; + + if self.is_empty() { + POSEIDON_EMPTY + } else { + poseidon(self.original_byte_slice()) + } + } + /// Return reference to the EOF if bytecode is EOF. #[inline] pub const fn eof(&self) -> Option<&Arc> { diff --git a/crates/primitives/src/env.rs b/crates/primitives/src/env.rs index 8da1daccf5..378bf2de5f 100644 --- a/crates/primitives/src/env.rs +++ b/crates/primitives/src/env.rs @@ -94,8 +94,16 @@ impl Env { pub fn validate_tx(&self) -> Result<(), InvalidTransaction> { // Check if the transaction's chain id is correct if let Some(tx_chain_id) = self.tx.chain_id { - if tx_chain_id != self.cfg.chain_id { - return Err(InvalidTransaction::InvalidChainId); + cfg_if::cfg_if! { + if #[cfg(not(feature = "scroll"))] { + if tx_chain_id != self.cfg.chain_id { + return Err(InvalidTransaction::InvalidChainId); + } + } else { + if !self.tx.scroll.is_l1_msg && tx_chain_id != self.cfg.chain_id { + return Err(InvalidTransaction::InvalidChainId); + } + } } } @@ -623,6 +631,11 @@ pub struct TxEnv { #[cfg(feature = "optimism")] /// Optimism fields. pub optimism: OptimismFields, + + #[cfg_attr(feature = "serde", serde(flatten))] + #[cfg(feature = "scroll")] + /// Scroll fields + pub scroll: ScrollFields, } pub enum TxType { @@ -666,6 +679,8 @@ impl Default for TxEnv { authorization_list: None, #[cfg(feature = "optimism")] optimism: OptimismFields::default(), + #[cfg(feature = "scroll")] + scroll: ScrollFields::default(), } } } @@ -749,6 +764,17 @@ pub struct OptimismFields { pub enveloped_tx: Option, } +/// Additional [TxEnv] fields for scroll. +#[cfg(feature = "scroll")] +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ScrollFields { + pub is_l1_msg: bool, + /// The RLP-encoded bytes of the transaction. This is used + /// to compute the L1 tx cost using the L1 block info. + pub rlp_bytes: Option, +} + /// Transaction destination pub type TransactTo = TxKind; diff --git a/crates/primitives/src/env/handler_cfg.rs b/crates/primitives/src/env/handler_cfg.rs index 56560ae675..4897095d9f 100644 --- a/crates/primitives/src/env/handler_cfg.rs +++ b/crates/primitives/src/env/handler_cfg.rs @@ -12,6 +12,9 @@ pub struct HandlerCfg { /// Optimism related field, it will append the Optimism handle register to the EVM. #[cfg(feature = "optimism")] pub is_optimism: bool, + /// Scroll related field, it will append the Scroll handle register to the EVM. + #[cfg(feature = "scroll")] + pub is_scroll: bool, } impl Default for HandlerCfg { @@ -31,10 +34,20 @@ impl HandlerCfg { let is_optimism = false; } } + cfg_if::cfg_if! { + if #[cfg(all(feature = "scroll-default-handler", + not(feature = "negate-scroll-default-handler")))] { + let is_scroll = true; + } else if #[cfg(feature = "scroll")] { + let is_scroll = false; + } + } Self { spec_id, #[cfg(feature = "optimism")] is_optimism, + #[cfg(feature = "scroll")] + is_scroll, } } @@ -57,6 +70,23 @@ impl HandlerCfg { } } } + + /// Creates new `HandlerCfg` instance with the scroll feature. + #[cfg(feature = "scroll")] + pub fn new_with_scroll(spec_id: SpecId, is_scroll: bool) -> Self { + Self { spec_id, is_scroll } + } + + /// Returns true if the scroll feature is enabled and flag is set to true. + pub fn is_scroll(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(feature = "scroll")] { + self.is_scroll + } else { + false + } + } + } } /// Configuration environment with the chain spec id. @@ -80,6 +110,8 @@ impl CfgEnvWithHandlerCfg { /// Returns new `CfgEnvWithHandlerCfg` instance with the chain spec id. /// /// is_optimism will be set to default value depending on `optimism-default-handler` feature. + /// + /// is_scroll will be set to default value depending on `scroll-default-handler` feature. pub fn new_with_spec_id(cfg_env: CfgEnv, spec_id: SpecId) -> Self { Self::new(cfg_env, HandlerCfg::new(spec_id)) } @@ -122,6 +154,8 @@ impl EnvWithHandlerCfg { /// Returns new `EnvWithHandlerCfg` instance with the chain spec id. /// + /// is_scroll will be set to default value depending on `scroll-default-handler` feature. + /// /// is_optimism will be set to default value depending on `optimism-default-handler` feature. pub fn new_with_spec_id(env: Box, spec_id: SpecId) -> Self { Self::new(env, HandlerCfg::new(spec_id)) diff --git a/crates/primitives/src/precompile.rs b/crates/primitives/src/precompile.rs index 22a7839021..d16633c250 100644 --- a/crates/primitives/src/precompile.rs +++ b/crates/primitives/src/precompile.rs @@ -174,6 +174,7 @@ pub enum PrecompileError { BlobMismatchedVersion, /// The proof verification failed. BlobVerifyKzgProofFailed, + NotImplemented, /// Catch-all variant for other errors. Other(String), } @@ -214,6 +215,7 @@ impl fmt::Display for PrecompileError { Self::BlobInvalidInputLength => "invalid blob input length", Self::BlobMismatchedVersion => "mismatched blob version", Self::BlobVerifyKzgProofFailed => "verifying blob kzg proof failed", + Self::NotImplemented => "not implemented", Self::Other(s) => s, }; f.write_str(s) diff --git a/crates/primitives/src/specification.rs b/crates/primitives/src/specification.rs index f2abf01bc5..41ded4d902 100644 --- a/crates/primitives/src/specification.rs +++ b/crates/primitives/src/specification.rs @@ -5,7 +5,7 @@ pub use SpecId::*; /// Specification IDs and their activation block. /// /// Information was obtained from the [Ethereum Execution Specifications](https://github.com/ethereum/execution-specs) -#[cfg(not(feature = "optimism"))] +#[cfg(not(any(feature = "optimism", feature = "scroll")))] #[repr(u8)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, enumn::N)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -74,6 +74,52 @@ pub enum SpecId { LATEST = u8::MAX, } +/// Specification IDs and their activation block. +/// +/// Information was obtained from the [Ethereum Execution Specifications](https://github.com/ethereum/execution-specs) +#[cfg(feature = "scroll")] +#[repr(u8)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, enumn::N)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum SpecId { + FRONTIER = 0, + FRONTIER_THAWING = 1, + HOMESTEAD = 2, + DAO_FORK = 3, + TANGERINE = 4, + SPURIOUS_DRAGON = 5, + BYZANTIUM = 6, + CONSTANTINOPLE = 7, + PETERSBURG = 8, + ISTANBUL = 9, + MUIR_GLACIER = 10, + BERLIN = 11, + LONDON = 12, + ARROW_GLACIER = 13, + GRAY_GLACIER = 14, + MERGE = 15, + SHANGHAI = 16, + /// The scroll network initially started with Shanghai with some features disabled. + PRE_BERNOULLI = 17, + /// Bernoulli update introduces: + /// - Enable `SHA-256` precompile. + /// - Use `EIP-4844` blobs for Data Availability (not part of layer2). + BERNOULLI = 18, + /// Curie update introduces: + /// - Support `EIP-1559` transactions. + /// - Support the `BASEFEE`, `MCOPY`, `TLOAD`, `TSTORE` opcodes. + /// + /// Although the Curie update include new opcodes in Cancun, the most important change + /// `EIP-4844` is not included. So we sort it before Cancun. + CURIE = 19, + EUCLID = 20, + CANCUN = 21, + PRAGUE = 22, + OSAKA = 23, + #[default] + LATEST = u8::MAX, +} + impl SpecId { /// Returns the `SpecId` for the given `u8`. #[inline] @@ -129,6 +175,14 @@ impl From<&str> for SpecId { "Holocene" => SpecId::HOLOCENE, #[cfg(feature = "optimism")] "Isthmus" => SpecId::ISTHMUS, + #[cfg(feature = "scroll")] + "PreBernoulli" => SpecId::PRE_BERNOULLI, + #[cfg(feature = "scroll")] + "Bernoulli" => SpecId::BERNOULLI, + #[cfg(feature = "scroll")] + "Curie" => SpecId::CURIE, + #[cfg(feature = "scroll")] + "Euclid" => SpecId::EUCLID, _ => Self::LATEST, } } @@ -173,6 +227,14 @@ impl From for &'static str { SpecId::HOLOCENE => "Holocene", #[cfg(feature = "optimism")] SpecId::ISTHMUS => "Isthmus", + #[cfg(feature = "scroll")] + SpecId::PRE_BERNOULLI => "PreBernoulli", + #[cfg(feature = "scroll")] + SpecId::BERNOULLI => "Bernoulli", + #[cfg(feature = "scroll")] + SpecId::CURIE => "Curie", + #[cfg(feature = "scroll")] + SpecId::EUCLID => "Euclid", SpecId::LATEST => "Latest", } } @@ -241,7 +303,17 @@ spec!(HOLOCENE, HoloceneSpec); #[cfg(feature = "optimism")] spec!(ISTHMUS, IsthmusSpec); -#[cfg(not(feature = "optimism"))] +// Scroll Hardforks +#[cfg(feature = "scroll")] +spec!(PRE_BERNOULLI, PreBernoulliSpec); +#[cfg(feature = "scroll")] +spec!(BERNOULLI, BernoulliSpec); +#[cfg(feature = "scroll")] +spec!(CURIE, CurieSpec); +#[cfg(feature = "scroll")] +spec!(EUCLID, EuclidSpec); + +#[cfg(not(any(feature = "optimism", feature = "scroll")))] #[macro_export] macro_rules! spec_to_generic { ($spec_id:expr, $e:expr) => {{ @@ -415,6 +487,95 @@ macro_rules! spec_to_generic { }}; } +#[cfg(feature = "scroll")] +#[macro_export] +macro_rules! spec_to_generic { + ($spec_id:expr, $e:expr) => {{ + // We are transitioning from var to generic spec. + match $spec_id { + $crate::SpecId::FRONTIER | SpecId::FRONTIER_THAWING => { + use $crate::FrontierSpec as SPEC; + $e + } + $crate::SpecId::HOMESTEAD | SpecId::DAO_FORK => { + use $crate::HomesteadSpec as SPEC; + $e + } + $crate::SpecId::TANGERINE => { + use $crate::TangerineSpec as SPEC; + $e + } + $crate::SpecId::SPURIOUS_DRAGON => { + use $crate::SpuriousDragonSpec as SPEC; + $e + } + $crate::SpecId::BYZANTIUM => { + use $crate::ByzantiumSpec as SPEC; + $e + } + $crate::SpecId::PETERSBURG | $crate::SpecId::CONSTANTINOPLE => { + use $crate::PetersburgSpec as SPEC; + $e + } + $crate::SpecId::ISTANBUL | $crate::SpecId::MUIR_GLACIER => { + use $crate::IstanbulSpec as SPEC; + $e + } + $crate::SpecId::BERLIN => { + use $crate::BerlinSpec as SPEC; + $e + } + $crate::SpecId::LONDON + | $crate::SpecId::ARROW_GLACIER + | $crate::SpecId::GRAY_GLACIER => { + use $crate::LondonSpec as SPEC; + $e + } + $crate::SpecId::MERGE => { + use $crate::MergeSpec as SPEC; + $e + } + $crate::SpecId::SHANGHAI => { + use $crate::ShanghaiSpec as SPEC; + $e + } + $crate::SpecId::CANCUN => { + use $crate::CancunSpec as SPEC; + $e + } + $crate::SpecId::LATEST => { + use $crate::LatestSpec as SPEC; + $e + } + $crate::SpecId::PRAGUE => { + use $crate::PragueSpec as SPEC; + $e + } + $crate::SpecId::OSAKA => { + use $crate::OsakaSpec as SPEC; + $e + } + $crate::SpecId::PRE_BERNOULLI => { + use $crate::PreBernoulliSpec as SPEC; + $e + } + $crate::SpecId::BERNOULLI => { + use $crate::BernoulliSpec as SPEC; + $e + } + $crate::SpecId::CURIE => { + use $crate::CurieSpec as SPEC; + $e + } + #[cfg(feature = "scroll")] + $crate::SpecId::EUCLID => { + use $crate::EuclidSpec as SPEC; + $e + } + } + }}; +} + #[cfg(test)] mod tests { use super::*; @@ -446,6 +607,14 @@ mod tests { spec_to_generic!(SHANGHAI, assert_eq!(SPEC::SPEC_ID, SHANGHAI)); #[cfg(feature = "optimism")] spec_to_generic!(CANYON, assert_eq!(SPEC::SPEC_ID, CANYON)); + #[cfg(feature = "scroll")] + spec_to_generic!(PRE_BERNOULLI, assert_eq!(SPEC::SPEC_ID, PRE_BERNOULLI)); + #[cfg(feature = "scroll")] + spec_to_generic!(BERNOULLI, assert_eq!(SPEC::SPEC_ID, BERNOULLI)); + #[cfg(feature = "scroll")] + spec_to_generic!(CURIE, assert_eq!(SPEC::SPEC_ID, CURIE)); + #[cfg(feature = "scroll")] + spec_to_generic!(EUCLID, assert_eq!(SPEC::SPEC_ID, EUCLID)); spec_to_generic!(CANCUN, assert_eq!(SPEC::SPEC_ID, CANCUN)); #[cfg(feature = "optimism")] spec_to_generic!(ECOTONE, assert_eq!(SPEC::SPEC_ID, ECOTONE)); @@ -630,3 +799,50 @@ mod optimism_tests { assert!(!SpecId::enabled(SpecId::HOLOCENE, SpecId::LATEST)); } } + +#[cfg(feature = "scroll")] +#[cfg(test)] +mod scroll_tests { + use super::*; + + #[test] + fn test_pre_bernoulli_post_merge_hardforks() { + assert!(PreBernoulliSpec::enabled(SpecId::MERGE)); + assert!(PreBernoulliSpec::enabled(SpecId::SHANGHAI)); + assert!(!PreBernoulliSpec::enabled(SpecId::BERNOULLI)); + assert!(!PreBernoulliSpec::enabled(SpecId::CURIE)); + assert!(!PreBernoulliSpec::enabled(SpecId::CANCUN)); + assert!(!PreBernoulliSpec::enabled(SpecId::LATEST)); + } + + #[test] + fn test_bernoulli_post_merge_hardforks() { + assert!(BernoulliSpec::enabled(SpecId::MERGE)); + assert!(BernoulliSpec::enabled(SpecId::SHANGHAI)); + assert!(BernoulliSpec::enabled(SpecId::PRE_BERNOULLI)); + assert!(!BernoulliSpec::enabled(SpecId::CURIE)); + assert!(!BernoulliSpec::enabled(SpecId::CANCUN)); + assert!(!BernoulliSpec::enabled(SpecId::LATEST)); + } + + #[test] + fn test_curie_post_merge_hardforks() { + assert!(CurieSpec::enabled(SpecId::MERGE)); + assert!(CurieSpec::enabled(SpecId::SHANGHAI)); + assert!(CurieSpec::enabled(SpecId::PRE_BERNOULLI)); + assert!(CurieSpec::enabled(SpecId::BERNOULLI)); + assert!(!CurieSpec::enabled(SpecId::CANCUN)); + assert!(!CurieSpec::enabled(SpecId::LATEST)); + } + + #[test] + fn test_euclid_post_merge_hardforks() { + assert!(EuclidSpec::enabled(SpecId::MERGE)); + assert!(EuclidSpec::enabled(SpecId::SHANGHAI)); + assert!(EuclidSpec::enabled(SpecId::PRE_BERNOULLI)); + assert!(EuclidSpec::enabled(SpecId::BERNOULLI)); + assert!(EuclidSpec::enabled(SpecId::CURIE)); + assert!(!EuclidSpec::enabled(SpecId::CANCUN)); + assert!(!EuclidSpec::enabled(SpecId::LATEST)); + } +} diff --git a/crates/primitives/src/state.rs b/crates/primitives/src/state.rs index c42f5588aa..0f0c13bfa0 100644 --- a/crates/primitives/src/state.rs +++ b/crates/primitives/src/state.rs @@ -228,8 +228,14 @@ pub struct AccountInfo { pub balance: U256, /// Account nonce. pub nonce: u64, + #[cfg(feature = "scroll")] + /// code size, + pub code_size: usize, /// code hash, pub code_hash: B256, + #[cfg(feature = "scroll-poseidon-codehash")] + /// poseidon code hash, won't be calculated if code is not changed. + pub poseidon_code_hash: B256, /// code: if None, `code_by_hash` will be used to fetch it if code needs to be loaded from /// inside `revm`. pub code: Option, @@ -239,7 +245,11 @@ impl Default for AccountInfo { fn default() -> Self { Self { balance: U256::ZERO, + #[cfg(feature = "scroll")] + code_size: 0, code_hash: KECCAK_EMPTY, + #[cfg(feature = "scroll-poseidon-codehash")] + poseidon_code_hash: crate::POSEIDON_EMPTY, code: Some(Bytecode::default()), nonce: 0, } @@ -248,9 +258,19 @@ impl Default for AccountInfo { impl PartialEq for AccountInfo { fn eq(&self, other: &Self) -> bool { - self.balance == other.balance + let eq = self.balance == other.balance && self.nonce == other.nonce - && self.code_hash == other.code_hash + && self.code_hash == other.code_hash; + + #[cfg(all(debug_assertions, feature = "scroll"))] + if eq { + assert_eq!(self.code_size, other.code_size); + #[cfg(feature = "scroll-poseidon-codehash")] + if self.poseidon_code_hash != B256::ZERO && other.poseidon_code_hash != B256::ZERO { + assert_eq!(self.poseidon_code_hash, other.poseidon_code_hash); + } + } + eq } } @@ -267,8 +287,12 @@ impl AccountInfo { Self { balance, nonce, + #[cfg(feature = "scroll")] + code_size: code.len(), code: Some(code), code_hash, + #[cfg(feature = "scroll-poseidon-codehash")] + poseidon_code_hash: B256::ZERO, } } @@ -285,8 +309,12 @@ impl AccountInfo { Self { balance: self.balance, nonce: self.nonce, + #[cfg(feature = "scroll")] + code_size: self.code_size, code_hash: self.code_hash, code: None, + #[cfg(feature = "scroll-poseidon-codehash")] + poseidon_code_hash: self.poseidon_code_hash, } } @@ -312,6 +340,15 @@ impl AccountInfo { /// - nonce is zero pub fn is_empty(&self) -> bool { let code_empty = self.is_empty_code_hash() || self.code_hash.is_zero(); + + #[cfg(all(feature = "scroll", debug_assertions))] + if code_empty { + assert_eq!( + self.code_size, 0, + "code size should be zero if code hash is empty" + ); + } + code_empty && self.balance.is_zero() && self.nonce == 0 } @@ -334,6 +371,15 @@ impl AccountInfo { /// Returns true if the code hash is the Keccak256 hash of the empty string `""`. #[inline] pub fn is_empty_code_hash(&self) -> bool { + #[cfg(all(debug_assertions, feature = "scroll-poseidon-codehash"))] + if self.code_hash == KECCAK_EMPTY { + assert_eq!(self.code_size, 0); + assert!( + self.poseidon_code_hash == crate::POSEIDON_EMPTY + || self.poseidon_code_hash == B256::ZERO + ); + } + self.code_hash == KECCAK_EMPTY } @@ -350,13 +396,30 @@ impl AccountInfo { } pub fn from_bytecode(bytecode: Bytecode) -> Self { - let hash = bytecode.hash_slow(); - - AccountInfo { - balance: U256::ZERO, - nonce: 1, - code: Some(bytecode), - code_hash: hash, + let code_hash = bytecode.hash_slow(); + cfg_if::cfg_if! { + if #[cfg(not(feature = "scroll"))] { + AccountInfo { + balance: U256::ZERO, + nonce: 1, + code: Some(bytecode), + code_hash, + } + } else { + let code_size = bytecode.len(); + #[cfg(feature = "scroll-poseidon-codehash")] + let poseidon_code_hash = bytecode.poseidon_hash_slow(); + + AccountInfo { + balance: U256::ZERO, + nonce: 1, + code_size, + code: Some(bytecode), + code_hash, + #[cfg(feature = "scroll-poseidon-codehash")] + poseidon_code_hash, + } + } } } } diff --git a/crates/primitives/src/utilities.rs b/crates/primitives/src/utilities.rs index 53b4d73d55..86544984e6 100644 --- a/crates/primitives/src/utilities.rs +++ b/crates/primitives/src/utilities.rs @@ -2,12 +2,118 @@ use crate::{ b256, B256, BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, BLOB_BASE_FEE_UPDATE_FRACTION_ELECTRA, MIN_BLOB_GASPRICE, }; -pub use alloy_primitives::keccak256; +#[cfg(not(feature = "openvm"))] +pub use alloy_primitives::{keccak256, Keccak256}; +#[cfg(feature = "openvm")] +pub use openvm_keccak::*; /// The Keccak-256 hash of the empty string `""`. pub const KECCAK_EMPTY: B256 = b256!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); +#[cfg(feature = "scroll-poseidon-codehash")] +pub const POSEIDON_EMPTY: B256 = + b256!("2098f5fb9e239eab3ceac3f27b81e481dc3124d55ffed523a839ee8446b64864"); + +#[cfg(feature = "openvm")] +mod openvm_keccak { + use alloy_primitives::B256; + use core::fmt; + use core::mem::MaybeUninit; + + /// Simple interface to the [`Keccak-256`] hash function. + /// + /// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3 + pub fn keccak256>(bytes: T) -> B256 { + openvm_keccak256_guest::keccak256(bytes.as_ref()).into() + } + + /// Simple [`Keccak-256`] hasher. + /// + /// Note that the "native-keccak" feature is not supported for this struct, and will default to the + /// [`tiny_keccak`] implementation. + /// + /// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3 + #[derive(Clone)] + pub struct Keccak256 { + buffer: Vec, + } + + impl Default for Keccak256 { + #[inline] + fn default() -> Self { + Self::new() + } + } + + impl fmt::Debug for Keccak256 { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Keccak256").finish_non_exhaustive() + } + } + + impl Keccak256 { + /// Creates a new [`Keccak256`] hasher. + #[inline] + pub fn new() -> Self { + Self { + buffer: Vec::with_capacity(64), + } + } + + /// Absorbs additional input. Can be called multiple times. + #[inline] + pub fn update(&mut self, bytes: impl AsRef<[u8]>) { + self.buffer.extend_from_slice(bytes.as_ref()); + } + + /// Pad and squeeze the state. + #[inline] + pub fn finalize(self) -> B256 { + let mut output = MaybeUninit::::uninit(); + // SAFETY: The output is 32-bytes. + unsafe { self.finalize_into_raw(output.as_mut_ptr().cast()) }; + // SAFETY: Initialized above. + unsafe { output.assume_init() } + } + + /// Pad and squeeze the state into `output`. + /// + /// # Panics + /// + /// Panics if `output` is not 32 bytes long. + #[inline] + #[track_caller] + pub fn finalize_into(self, output: &mut [u8]) { + self.finalize_into_array(output.try_into().unwrap()) + } + + /// Pad and squeeze the state into `output`. + #[inline] + #[allow(clippy::useless_conversion)] + pub fn finalize_into_array(self, output: &mut [u8; 32]) { + openvm_keccak256_guest::set_keccak256(&self.buffer, output); + } + + /// Pad and squeeze the state into `output`. + /// + /// # Safety + /// + /// `output` must point to a buffer that is at least 32-bytes long. + #[inline] + pub unsafe fn finalize_into_raw(self, output: *mut u8) { + self.finalize_into_array(&mut *output.cast::<[u8; 32]>()) + } + } +} + +/// Poseidon code hash +#[cfg(feature = "scroll-poseidon-codehash")] +pub fn poseidon(code: &[u8]) -> B256 { + poseidon_bn254::hash_code(code).into() +} + /// Calculates the `excess_blob_gas` from the parent header's `blob_gas_used` and `excess_blob_gas`. /// /// See also [the EIP-4844 helpers] diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index 45fa00107e..62ed5cd31c 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -69,7 +69,7 @@ rstest = "0.22.0" alloy-provider = "0.7" [features] -default = ["std", "c-kzg", "secp256k1", "portable", "blst"] +default = ["std", "c-kzg", "portable", "blst"] std = [ "serde?/std", "serde_json?/std", @@ -102,6 +102,24 @@ negate-optimism-default-handler = [ "revm-interpreter/negate-optimism-default-handler", ] +scroll = ["revm-interpreter/scroll", "revm-precompile/scroll"] +# Scroll default handler enabled Scroll handler register by default in EvmBuilder. +scroll-default-handler = [ + "scroll", + "revm-precompile/scroll-default-handler", + "revm-interpreter/scroll-default-handler", +] +negate-scroll-default-handler = [ + "revm-precompile/negate-scroll-default-handler", + "revm-interpreter/negate-scroll-default-handler", +] +scroll-poseidon-codehash = [ + "scroll", + "revm-interpreter/scroll-poseidon-codehash", +] + +openvm = ["revm-precompile/openvm"] + ethersdb = ["std", "dep:tokio", "dep:ethers-providers", "dep:ethers-core"] alloydb = [ @@ -136,6 +154,21 @@ c-kzg = ["revm-precompile/c-kzg"] kzg-rs = ["revm-precompile/kzg-rs"] blst = ["revm-precompile/blst"] +all = [ + "std", + "hashbrown", + "serde", + "serde-json", + "arbitrary", + "asm-keccak", + "portable", + "ethersdb", + "alloydb", + "dev", + "revm-interpreter/all", + "revm-precompile/all", +] + [[example]] name = "fork_ref_transact" path = "../../examples/fork_ref_transact.rs" diff --git a/crates/revm/src/builder.rs b/crates/revm/src/builder.rs index 1ce88486ba..302cbdd1d6 100644 --- a/crates/revm/src/builder.rs +++ b/crates/revm/src/builder.rs @@ -36,7 +36,11 @@ impl<'a> Default for EvmBuilder<'a, SetGenericStage, (), EmptyDB> { let mut handler_cfg = HandlerCfg::new(SpecId::LATEST); // set is_optimism to true by default. handler_cfg.is_optimism = true; - + } else if #[cfg(all(feature = "scroll-default-handler", + not(feature = "negate-scroll-default-handler")))] { + let mut handler_cfg = HandlerCfg::new(SpecId::EUCLID); + // set is_scroll to true by default. + handler_cfg.is_scroll = true; } else { let handler_cfg = HandlerCfg::new(SpecId::LATEST); } @@ -168,10 +172,26 @@ impl<'a, EXT, DB: Database> EvmBuilder<'a, SetGenericStage, EXT, DB> { } } + /// Sets the Scroll handler with latest spec. + /// + /// If `scroll-default-handler` feature is enabled this is not needed. + #[cfg(feature = "scroll")] + pub fn scroll(mut self) -> EvmBuilder<'a, HandlerStage, EXT, DB> { + self.handler = Handler::scroll_with_spec(self.handler.cfg.spec_id); + EvmBuilder { + context: self.context, + handler: self.handler, + phantom: PhantomData, + } + } + /// Sets the mainnet handler with latest spec. /// - /// Enabled only with `optimism-default-handler` feature. - #[cfg(feature = "optimism-default-handler")] + /// Enabled only with `optimism-default-handler` or `scroll-default-handler` feature. + #[cfg(any( + feature = "optimism-default-handler", + feature = "scroll-default-handler" + ))] pub fn mainnet(mut self) -> EvmBuilder<'a, HandlerStage, EXT, DB> { self.handler = Handler::mainnet_with_spec(self.handler.cfg.spec_id); EvmBuilder { @@ -209,8 +229,11 @@ impl<'a, EXT, DB: Database> EvmBuilder<'a, HandlerStage, EXT, DB> { /// Resets the [`Handler`] and sets base mainnet handler. /// - /// Enabled only with `optimism-default-handler` feature. - #[cfg(feature = "optimism-default-handler")] + /// Enabled only with `optimism-default-handler` or `scroll-default-handler` feature. + #[cfg(any( + feature = "optimism-default-handler", + feature = "scroll-default-handler" + ))] pub fn reset_handler_with_mainnet(mut self) -> EvmBuilder<'a, HandlerStage, EXT, DB> { self.handler = Handler::mainnet_with_spec(self.handler.cfg.spec_id); EvmBuilder { diff --git a/crates/revm/src/context.rs b/crates/revm/src/context.rs index b592f3d14f..861bcc9e9a 100644 --- a/crates/revm/src/context.rs +++ b/crates/revm/src/context.rs @@ -152,6 +152,14 @@ impl Host for Context { .ok() } + #[cfg(feature = "scroll")] + fn code_size(&mut self, address: Address) -> Option> { + self.evm + .code_size(address) + .map_err(|e| self.evm.error = Err(e)) + .ok() + } + fn code_hash(&mut self, address: Address) -> Option> { self.evm .code_hash(address) diff --git a/crates/revm/src/context/context_precompiles.rs b/crates/revm/src/context/context_precompiles.rs index f3fa881a14..41b3dcb7d3 100644 --- a/crates/revm/src/context/context_precompiles.rs +++ b/crates/revm/src/context/context_precompiles.rs @@ -166,7 +166,7 @@ impl ContextPrecompiles { impl Extend<(Address, ContextPrecompile)> for ContextPrecompiles { fn extend)>>(&mut self, iter: T) { - self.to_mut().extend(iter.into_iter().map(Into::into)) + self.to_mut().extend(iter) } } diff --git a/crates/revm/src/context/evm_context.rs b/crates/revm/src/context/evm_context.rs index 80f0257a39..c5fcce3b27 100644 --- a/crates/revm/src/context/evm_context.rs +++ b/crates/revm/src/context/evm_context.rs @@ -480,7 +480,7 @@ pub(crate) mod test_utils { use crate::{ db::{CacheDB, EmptyDB}, journaled_state::JournaledState, - primitives::{address, HashSet, SpecId, B256}, + primitives::{address, HashSet, SpecId}, }; /// Mock caller address. @@ -513,10 +513,8 @@ pub(crate) mod test_utils { db.insert_account_info( test_utils::MOCK_CALLER, crate::primitives::AccountInfo { - nonce: 0, balance, - code_hash: B256::default(), - code: None, + ..Default::default() }, ); create_cache_db_evm_context(env, db) @@ -533,7 +531,7 @@ pub(crate) mod test_utils { journaled_state: JournaledState::new(SpecId::CANCUN, HashSet::default()), db, error: Ok(()), - #[cfg(feature = "optimism")] + #[cfg(any(feature = "optimism", feature = "scroll"))] l1_block_info: None, }, precompiles: ContextPrecompiles::default(), @@ -548,7 +546,7 @@ pub(crate) mod test_utils { journaled_state: JournaledState::new(SpecId::CANCUN, HashSet::default()), db, error: Ok(()), - #[cfg(feature = "optimism")] + #[cfg(any(feature = "optimism", feature = "scroll"))] l1_block_info: None, }, precompiles: ContextPrecompiles::default(), @@ -639,7 +637,11 @@ mod tests { crate::primitives::AccountInfo { nonce: 0, balance: bal, + #[cfg(feature = "scroll")] + code_size: by.len(), code_hash: by.clone().hash_slow(), + #[cfg(feature = "scroll-poseidon-codehash")] + poseidon_code_hash: by.clone().poseidon_hash_slow(), code: Some(by), }, ); diff --git a/crates/revm/src/context/inner_evm_context.rs b/crates/revm/src/context/inner_evm_context.rs index d61e758f69..15c840ef36 100644 --- a/crates/revm/src/context/inner_evm_context.rs +++ b/crates/revm/src/context/inner_evm_context.rs @@ -30,6 +30,9 @@ pub struct InnerEvmContext { /// Used as temporary value holder to store L1 block info. #[cfg(feature = "optimism")] pub l1_block_info: Option, + /// Used as temporary value holder to store L1 block info. + #[cfg(feature = "scroll")] + pub l1_block_info: Option, } impl Clone for InnerEvmContext @@ -42,7 +45,7 @@ where journaled_state: self.journaled_state.clone(), db: self.db.clone(), error: self.error.clone(), - #[cfg(feature = "optimism")] + #[cfg(any(feature = "optimism", feature = "scroll"))] l1_block_info: self.l1_block_info.clone(), } } @@ -55,7 +58,7 @@ impl InnerEvmContext { journaled_state: JournaledState::new(SpecId::LATEST, HashSet::default()), db, error: Ok(()), - #[cfg(feature = "optimism")] + #[cfg(any(feature = "optimism", feature = "scroll"))] l1_block_info: None, } } @@ -68,7 +71,7 @@ impl InnerEvmContext { journaled_state: JournaledState::new(SpecId::LATEST, HashSet::default()), db, error: Ok(()), - #[cfg(feature = "optimism")] + #[cfg(any(feature = "optimism", feature = "scroll"))] l1_block_info: None, } } @@ -83,7 +86,7 @@ impl InnerEvmContext { journaled_state: self.journaled_state, db, error: Ok(()), - #[cfg(feature = "optimism")] + #[cfg(any(feature = "optimism", feature = "scroll"))] l1_block_info: self.l1_block_info, } } @@ -104,11 +107,33 @@ impl InnerEvmContext { storage_keys, } in self.env.tx.access_list.iter() { - self.journaled_state.initial_account_load( + let result = self.journaled_state.initial_account_load( *address, storage_keys.iter().map(|i| U256::from_be_bytes(i.0)), &mut self.db, - )?; + ); + cfg_if::cfg_if! { + if #[cfg(feature = "scroll")] { + // In scroll, we don't include the access list accounts/storages in the partial + // merkle trie proofs if it was not actually accessed in the transaction. + // The load will fail in that case, we just ignore the error. + // This is not a problem as the accounts/storages was never accessed. + match result { + // the concrete error in scroll is + // https://github.com/scroll-tech/stateless-block-verifier/blob/851f5141ded76ddba7594814b9761df1dc469a12/crates/core/src/error.rs#L4-L13 + // We cannot check it since `Database::Error` is an opaque type + // without any trait bounds (like `Debug` or `Display`). + // only thing we can do is to check the type name. + Err(EVMError::Database(e)) + if core::any::type_name_of_val(&e) == "sbv_core::error::DatabaseError" => {} + _ => { + result?; + } + } + } else { + result?; + } + } } Ok(()) } @@ -189,16 +214,33 @@ impl InnerEvmContext { Ok(StateLoad::new(code, a.is_cold)) } + #[inline] + #[cfg(feature = "scroll")] + pub fn code_size(&mut self, address: Address) -> Result, EVMError> { + self.journaled_state + .load_account(address, &mut self.db) + .map(|acc| StateLoad::new(acc.info.code_size, acc.is_cold)) + } + /// Get code hash of address. /// /// In case of EOF account it will return `EOF_MAGIC_HASH` /// (the hash of `0xEF00`). #[inline] + #[cfg_attr(feature = "scroll", allow(unreachable_code))] pub fn code_hash(&mut self, address: Address) -> Result, EVMError> { + #[cfg(not(feature = "scroll"))] let acc = self.journaled_state.load_code(address, &mut self.db)?; + // Scroll does not support EOF yet, code won't be loaded if only EXTCODEHASH is called. + #[cfg(feature = "scroll")] + let acc = self.journaled_state.load_account(address, &mut self.db)?; if acc.is_empty() { return Ok(StateLoad::new(B256::ZERO, acc.is_cold)); } + + #[cfg(feature = "scroll")] + return Ok(StateLoad::new(acc.info.code_hash, acc.is_cold)); + // SAFETY: safe to unwrap as load_code will insert code if it is empty. let code = acc.info.code.as_ref().unwrap(); diff --git a/crates/revm/src/db/in_memory_db.rs b/crates/revm/src/db/in_memory_db.rs index c5b6593a41..fc77de3b8d 100644 --- a/crates/revm/src/db/in_memory_db.rs +++ b/crates/revm/src/db/in_memory_db.rs @@ -66,6 +66,18 @@ impl CacheDB { if account.code_hash == KECCAK_EMPTY { account.code_hash = code.hash_slow(); } + #[cfg(feature = "scroll")] + { + account.code_size = code.len(); + #[cfg(feature = "scroll-poseidon-codehash")] + { + if account.poseidon_code_hash == crate::primitives::POSEIDON_EMPTY + || account.poseidon_code_hash == B256::ZERO + { + account.poseidon_code_hash = code.poseidon_hash_slow(); + } + } + } self.contracts .entry(account.code_hash) .or_insert_with(|| code.clone()); @@ -363,6 +375,8 @@ impl AccountState { pub struct BenchmarkDB { pub bytecode: Bytecode, pub hash: B256, + #[cfg(feature = "scroll-poseidon-codehash")] + pub poseidon_hash: B256, pub target: Address, pub caller: Address, } @@ -371,9 +385,13 @@ impl BenchmarkDB { /// Create a new benchmark database with the given bytecode. pub fn new_bytecode(bytecode: Bytecode) -> Self { let hash = bytecode.hash_slow(); + #[cfg(feature = "scroll-poseidon-codehash")] + let poseidon_hash = bytecode.poseidon_hash_slow(); Self { bytecode, hash, + #[cfg(feature = "scroll-poseidon-codehash")] + poseidon_hash, target: Address::ZERO, caller: Address::with_last_byte(1), } @@ -397,17 +415,19 @@ impl Database for BenchmarkDB { if address == self.target { return Ok(Some(AccountInfo { nonce: 1, + #[cfg(feature = "scroll")] + code_size: self.bytecode.len(), balance: U256::from(10000000), code: Some(self.bytecode.clone()), code_hash: self.hash, + #[cfg(feature = "scroll-poseidon-codehash")] + poseidon_code_hash: self.poseidon_hash, })); } if address == self.caller { return Ok(Some(AccountInfo { - nonce: 0, balance: U256::from(10000000), - code: None, - code_hash: KECCAK_EMPTY, + ..Default::default() })); } Ok(None) diff --git a/crates/revm/src/db/states/bundle_state.rs b/crates/revm/src/db/states/bundle_state.rs index f478bfefa7..3412f8df91 100644 --- a/crates/revm/src/db/states/bundle_state.rs +++ b/crates/revm/src/db/states/bundle_state.rs @@ -864,8 +864,7 @@ mod tests { let acc1 = AccountInfo { balance: U256::from(10), nonce: 1, - code_hash: KECCAK_EMPTY, - code: None, + ..Default::default() }; let mut bundle_state = BundleState::default(); @@ -915,8 +914,7 @@ mod tests { Some(AccountInfo { nonce: 1, balance: U256::from(10), - code_hash: KECCAK_EMPTY, - code: None, + ..Default::default() }), HashMap::from_iter([ (slot1(), (U256::from(0), U256::from(10))), @@ -929,8 +927,7 @@ mod tests { Some(AccountInfo { nonce: 1, balance: U256::from(10), - code_hash: KECCAK_EMPTY, - code: None, + ..Default::default() }), HashMap::default(), ), @@ -957,8 +954,7 @@ mod tests { Some(AccountInfo { nonce: 3, balance: U256::from(20), - code_hash: KECCAK_EMPTY, - code: None, + ..Default::default() }), HashMap::from_iter([(slot1(), (U256::from(0), U256::from(15)))]), )], @@ -967,8 +963,7 @@ mod tests { Some(Some(AccountInfo { nonce: 1, balance: U256::from(10), - code_hash: KECCAK_EMPTY, - code: None, + ..Default::default() })), vec![(slot1(), U256::from(10))], )]], @@ -984,8 +979,7 @@ mod tests { AccountInfo { nonce: 1, balance: U256::from(10), - code_hash: KECCAK_EMPTY, - code: None, + ..Default::default() }, ) .state_storage( @@ -998,8 +992,7 @@ mod tests { AccountInfo { nonce: 1, balance: U256::from(10), - code_hash: KECCAK_EMPTY, - code: None, + ..Default::default() }, ) .revert_address(0, account1()) @@ -1017,8 +1010,7 @@ mod tests { AccountInfo { nonce: 3, balance: U256::from(20), - code_hash: KECCAK_EMPTY, - code: None, + ..Default::default() }, ) .state_storage( @@ -1032,8 +1024,7 @@ mod tests { Some(Some(AccountInfo { nonce: 1, balance: U256::from(10), - code_hash: KECCAK_EMPTY, - code: None, + ..Default::default() })), ) .revert_storage(0, account1(), vec![(slot1(), U256::from(10))]) diff --git a/crates/revm/src/handler.rs b/crates/revm/src/handler.rs index ed31917bbe..a033823150 100644 --- a/crates/revm/src/handler.rs +++ b/crates/revm/src/handler.rs @@ -51,6 +51,12 @@ impl<'a, EXT, DB: Database> EvmHandler<'a, EXT, DB> { } else { Handler::mainnet_with_spec(cfg.spec_id) } + } else if #[cfg(feature = "scroll")] { + if cfg.is_scroll { + Handler::scroll_with_spec(cfg.spec_id) + } else { + Handler::mainnet_with_spec(cfg.spec_id) + } } else { Handler::mainnet_with_spec(cfg.spec_id) } @@ -86,12 +92,29 @@ impl<'a, EXT, DB: Database> EvmHandler<'a, EXT, DB> { handler } + /// Handler for scroll + #[cfg(feature = "scroll")] + pub fn scroll() -> Self { + let mut handler = Self::mainnet::(); + handler.cfg.is_scroll = true; + handler.append_handler_register(HandleRegisters::Plain( + crate::scroll::scroll_handle_register::, + )); + handler + } + /// Optimism with spec. Similar to [`Self::mainnet_with_spec`]. #[cfg(feature = "optimism")] pub fn optimism_with_spec(spec_id: SpecId) -> Self { spec_to_generic!(spec_id, Self::optimism::()) } + /// Scroll with spec. Similar to [`Self::mainnet_with_spec`] + #[cfg(feature = "scroll")] + pub fn scroll_with_spec(spec_id: SpecId) -> Self { + spec_to_generic!(spec_id, Self::scroll::()) + } + /// Creates handler with variable spec id, inside it will call `mainnet::` for /// appropriate spec. pub fn mainnet_with_spec(spec_id: SpecId) -> Self { diff --git a/crates/revm/src/inspector/customprinter.rs b/crates/revm/src/inspector/customprinter.rs index ed42c4f7b4..bad09b7586 100644 --- a/crates/revm/src/inspector/customprinter.rs +++ b/crates/revm/src/inspector/customprinter.rs @@ -132,7 +132,11 @@ mod test { let code = bytes!("5b597fb075978b6c412c64d169d56d839a8fe01b3f4607ed603b2c78917ce8be1430fe6101e8527ffe64706ecad72a2f5c97a95e006e279dc57081902029ce96af7edae5de116fec610208527f9fc1ef09d4dd80683858ae3ea18869fe789ddc365d8d9d800e26c9872bac5e5b6102285260276102485360d461024953601661024a53600e61024b53607d61024c53600961024d53600b61024e5360b761024f5360596102505360796102515360a061025253607261025353603a6102545360fb61025553601261025653602861025753600761025853606f61025953601761025a53606161025b53606061025c5360a661025d53602b61025e53608961025f53607a61026053606461026153608c6102625360806102635360d56102645360826102655360ae61026653607f6101e8610146610220677a814b184591c555735fdcca53617f4d2b9134b29090c87d01058e27e962047654f259595947443b1b816b65cdb6277f4b59c10a36f4e7b8658f5a5e6f5561"); let info = crate::primitives::AccountInfo { balance: "0x100c5d668240db8e00".parse().unwrap(), + #[cfg(feature = "scroll")] + code_size: code.len(), code_hash: crate::primitives::keccak256(&code), + #[cfg(feature = "scroll-poseidon-codehash")] + poseidon_code_hash: crate::primitives::poseidon(&code), code: Some(crate::primitives::Bytecode::new_raw(code.clone())), nonce: 1, }; diff --git a/crates/revm/src/journaled_state.rs b/crates/revm/src/journaled_state.rs index 7dce85a2f0..78d3e7dc28 100644 --- a/crates/revm/src/journaled_state.rs +++ b/crates/revm/src/journaled_state.rs @@ -166,6 +166,14 @@ impl JournaledState { .push(JournalEntry::CodeChange { address }); account.info.code_hash = hash; + #[cfg(feature = "scroll")] + { + account.info.code_size = code.len(); + #[cfg(feature = "scroll-poseidon-codehash")] + { + account.info.poseidon_code_hash = code.poseidon_hash_slow(); + } + } account.info.code = Some(code); } @@ -274,7 +282,7 @@ impl JournaledState { // Bytecode is not empty. // Nonce is not zero // Account is not precompile. - if account.info.code_hash != KECCAK_EMPTY || account.info.nonce != 0 { + if !account.info.is_empty_code_hash() || account.info.nonce != 0 { self.checkpoint_revert(checkpoint); return Err(InstructionResult::CreateCollision); } @@ -420,6 +428,14 @@ impl JournaledState { let acc = state.get_mut(&address).unwrap(); acc.info.code_hash = KECCAK_EMPTY; acc.info.code = None; + #[cfg(feature = "scroll")] + { + acc.info.code_size = 0; + #[cfg(feature = "scroll-poseidon-codehash")] + { + acc.info.poseidon_code_hash = crate::primitives::POSEIDON_EMPTY; + } + } } } } diff --git a/crates/revm/src/lib.rs b/crates/revm/src/lib.rs index 5097388526..18dbe72d3b 100644 --- a/crates/revm/src/lib.rs +++ b/crates/revm/src/lib.rs @@ -22,6 +22,8 @@ mod inspector; mod journaled_state; #[cfg(feature = "optimism")] pub mod optimism; +#[cfg(feature = "scroll")] +pub mod scroll; // Export items. diff --git a/crates/revm/src/scroll.rs b/crates/revm/src/scroll.rs new file mode 100644 index 0000000000..d8b4959184 --- /dev/null +++ b/crates/revm/src/scroll.rs @@ -0,0 +1,7 @@ +mod handler_register; +mod l1block; + +pub use crate::scroll::handler_register::{ + deduct_caller, load_accounts, reward_beneficiary, scroll_handle_register, +}; +pub use crate::scroll::l1block::{L1BlockInfo, L1_GAS_PRICE_ORACLE_ADDRESS}; diff --git a/crates/revm/src/scroll/handler_register.rs b/crates/revm/src/scroll/handler_register.rs new file mode 100644 index 0000000000..d0555dc67e --- /dev/null +++ b/crates/revm/src/scroll/handler_register.rs @@ -0,0 +1,136 @@ +//! Handler related to Scroll chain +use crate::handler::mainnet; +use crate::handler::mainnet::deduct_caller_inner; +use crate::{ + handler::register::EvmHandler, + interpreter::Gas, + primitives::{ + db::Database, spec_to_generic, EVMError, InvalidTransaction, Spec, SpecId, TransactTo, U256, + }, + Context, +}; +#[cfg(not(feature = "std"))] +use std::string::ToString; +use std::sync::Arc; + +pub fn scroll_handle_register(handler: &mut EvmHandler<'_, EXT, DB>) { + spec_to_generic!(handler.cfg.spec_id, { + // load l1 data + handler.pre_execution.load_accounts = Arc::new(load_accounts::); + // l1_fee is added to the gas cost. + handler.pre_execution.deduct_caller = Arc::new(deduct_caller::); + // basefee is sent to coinbase + handler.post_execution.reward_beneficiary = Arc::new(reward_beneficiary::); + }); +} + +/// Load account (make them warm) and l1 data from database. +#[inline] +pub fn load_accounts( + context: &mut Context, +) -> Result<(), EVMError> { + let l1_block_info = + crate::scroll::L1BlockInfo::try_fetch(&mut context.evm.inner.db, SPEC::SPEC_ID) + .map_err(EVMError::Database)?; + context.evm.inner.l1_block_info = Some(l1_block_info); + + mainnet::load_accounts::(context) +} + +/// Deducts the caller balance to the transaction limit. +#[inline] +pub fn deduct_caller( + context: &mut Context, +) -> Result<(), EVMError> { + // load caller's account. + let caller_account = context + .evm + .inner + .journaled_state + .load_account(context.evm.inner.env.tx.caller, &mut context.evm.inner.db)?; + + if !context.evm.inner.env.tx.scroll.is_l1_msg { + // We deduct caller max balance after minting and before deducing the + // l1 cost, max values is already checked in pre_validate but l1 cost wasn't. + deduct_caller_inner::(caller_account.data, &context.evm.inner.env); + + let Some(rlp_bytes) = &context.evm.inner.env.tx.scroll.rlp_bytes else { + return Err(EVMError::Custom( + "[SCROLL] Failed to load transaction rlp_bytes.".to_string(), + )); + }; + // Deduct l1 fee from caller. + let tx_l1_cost = context + .evm + .inner + .l1_block_info + .as_ref() + .expect("L1BlockInfo should be loaded") + .calculate_tx_l1_cost(rlp_bytes, SPEC::SPEC_ID); + if tx_l1_cost.gt(&caller_account.info.balance) { + return Err(EVMError::Transaction( + InvalidTransaction::LackOfFundForMaxFee { + fee: tx_l1_cost.into(), + balance: caller_account.info.balance.into(), + }, + )); + } + caller_account.data.info.balance = + caller_account.data.info.balance.saturating_sub(tx_l1_cost); + } else { + // bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`. + if matches!(context.evm.inner.env.tx.transact_to, TransactTo::Call(_)) { + // Nonce is already checked + caller_account.data.info.nonce = caller_account.data.info.nonce.saturating_add(1); + } + + // touch account so we know it is changed. + caller_account.data.mark_touch(); + } + Ok(()) +} + +/// Reward beneficiary with gas fee. +#[inline] +pub fn reward_beneficiary( + context: &mut Context, + gas: &Gas, +) -> Result<(), EVMError> { + let beneficiary = context.evm.env.block.coinbase; + let effective_gas_price = context.evm.env.effective_gas_price(); + + // transfer fee to coinbase/beneficiary. + let coinbase_gas_price = effective_gas_price; + + let coinbase_account = context + .evm + .inner + .journaled_state + .load_account(beneficiary, &mut context.evm.inner.db)?; + + if !context.evm.inner.env.tx.scroll.is_l1_msg { + let Some(l1_block_info) = &context.evm.inner.l1_block_info else { + return Err(EVMError::Custom( + "[SCROLL] Failed to load L1 block information.".to_string(), + )); + }; + + let Some(rlp_bytes) = &context.evm.inner.env.tx.scroll.rlp_bytes else { + return Err(EVMError::Custom( + "[SCROLL] Failed to load transaction rlp_bytes.".to_string(), + )); + }; + + let l1_cost = l1_block_info.calculate_tx_l1_cost(rlp_bytes, SPEC::SPEC_ID); + + coinbase_account.data.mark_touch(); + coinbase_account.data.info.balance = coinbase_account + .data + .info + .balance + .saturating_add(coinbase_gas_price * U256::from(gas.spent() - gas.refunded() as u64)) + .saturating_add(l1_cost); + } + + Ok(()) +} diff --git a/crates/revm/src/scroll/l1block.rs b/crates/revm/src/scroll/l1block.rs new file mode 100644 index 0000000000..03c905aa6d --- /dev/null +++ b/crates/revm/src/scroll/l1block.rs @@ -0,0 +1,122 @@ +use crate::primitives::{address, Address, SpecId, U256}; +use crate::Database; + +const ZERO_BYTE_COST: u64 = 4; +const NON_ZERO_BYTE_COST: u64 = 16; + +const TX_L1_COMMIT_EXTRA_COST: U256 = U256::from_limbs([64u64, 0, 0, 0]); +const TX_L1_FEE_PRECISION: U256 = U256::from_limbs([1_000_000_000u64, 0, 0, 0]); + +pub const L1_GAS_PRICE_ORACLE_ADDRESS: Address = + address!("5300000000000000000000000000000000000002"); + +const L1_BASE_FEE_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); +const L1_OVERHEAD_SLOT: U256 = U256::from_limbs([2u64, 0, 0, 0]); +const L1_SCALAR_SLOT: U256 = U256::from_limbs([3u64, 0, 0, 0]); +const L1_BLOB_BASE_FEE_SLOT: U256 = U256::from_limbs([5u64, 0, 0, 0]); +const L1_COMMIT_SCALAR_SLOT: U256 = U256::from_limbs([6u64, 0, 0, 0]); +const L1_BLOB_SCALAR_SLOT: U256 = U256::from_limbs([7u64, 0, 0, 0]); +// const L1_IS_CURIE_SLOT: U256 = U256::from_limbs([8u64, 0, 0, 0]); + +/// L1 block info +#[derive(Clone, Debug, Default)] +pub struct L1BlockInfo { + /// The base fee of the L1 origin block. + pub l1_base_fee: U256, + /// The current L1 fee overhead. + pub l1_fee_overhead: U256, + /// The current L1 fee scalar. + pub l1_base_fee_scalar: U256, + /// The current L1 blob base fee, None if before Curie. + pub l1_blob_base_fee: Option, + /// The current L1 commit scalar, None if before Curie. + pub l1_commit_scalar: Option, + /// The current L1 blob scalar, None if before Curie. + pub l1_blob_scalar: Option, + /// The current call data gas (l1_blob_scalar * l1_base_fee), None if before Curie. + pub calldata_gas: Option, +} + +impl L1BlockInfo { + /// Try to fetch the L1 block info from the database. + pub fn try_fetch(db: &mut DB, spec_id: SpecId) -> Result { + let l1_base_fee = db.storage(L1_GAS_PRICE_ORACLE_ADDRESS, L1_BASE_FEE_SLOT)?; + let l1_fee_overhead = db.storage(L1_GAS_PRICE_ORACLE_ADDRESS, L1_OVERHEAD_SLOT)?; + let l1_base_fee_scalar = db.storage(L1_GAS_PRICE_ORACLE_ADDRESS, L1_SCALAR_SLOT)?; + + if !spec_id.is_enabled_in(SpecId::CURIE) { + Ok(L1BlockInfo { + l1_base_fee, + l1_fee_overhead, + l1_base_fee_scalar, + ..Default::default() + }) + } else { + let l1_blob_base_fee = + db.storage(L1_GAS_PRICE_ORACLE_ADDRESS, L1_BLOB_BASE_FEE_SLOT)?; + let l1_commit_scalar = + db.storage(L1_GAS_PRICE_ORACLE_ADDRESS, L1_COMMIT_SCALAR_SLOT)?; + let l1_blob_scalar = db.storage(L1_GAS_PRICE_ORACLE_ADDRESS, L1_BLOB_SCALAR_SLOT)?; + + // calldata component of commit fees (calldata gas + execution) + let calldata_gas = l1_commit_scalar.saturating_mul(l1_base_fee); + + Ok(L1BlockInfo { + l1_base_fee, + l1_fee_overhead, + l1_base_fee_scalar, + l1_blob_base_fee: Some(l1_blob_base_fee), + l1_commit_scalar: Some(l1_commit_scalar), + l1_blob_scalar: Some(l1_blob_scalar), + calldata_gas: Some(calldata_gas), + }) + } + } + + /// Calculate the data gas for posting the transaction on L1. Calldata costs 16 gas per non-zero + /// byte and 4 gas per zero byte. + pub fn data_gas(&self, input: &[u8], spec_id: SpecId) -> U256 { + if !spec_id.is_enabled_in(SpecId::CURIE) { + U256::from(input.iter().fold(0, |acc, byte| { + acc + if *byte == 0x00 { + ZERO_BYTE_COST + } else { + NON_ZERO_BYTE_COST + } + })) + .saturating_add(self.l1_fee_overhead) + .saturating_add(TX_L1_COMMIT_EXTRA_COST) + } else { + U256::from(input.len()) + .saturating_mul(self.l1_blob_base_fee.unwrap()) + .saturating_mul(self.l1_blob_scalar.unwrap()) + } + } + + fn calculate_tx_l1_cost_pre_bernoulli(&self, input: &[u8], spec_id: SpecId) -> U256 { + let tx_l1_gas = self.data_gas(input, spec_id); + tx_l1_gas + .saturating_mul(self.l1_base_fee) + .saturating_mul(self.l1_base_fee_scalar) + .wrapping_div(TX_L1_FEE_PRECISION) + } + + fn calculate_tx_l1_cost_curie(&self, input: &[u8], spec_id: SpecId) -> U256 { + // "commitScalar * l1BaseFee + blobScalar * _data.length * l1BlobBaseFee" + let blob_gas = self.data_gas(input, spec_id); + + self.calldata_gas + .unwrap() + .saturating_add(blob_gas) + .wrapping_div(TX_L1_FEE_PRECISION) + } + + /// Calculate the gas cost of a transaction based on L1 block data posted on L2. + pub fn calculate_tx_l1_cost(&self, input: &[u8], spec_id: SpecId) -> U256 { + if !spec_id.is_enabled_in(SpecId::CURIE) { + self.calculate_tx_l1_cost_pre_bernoulli(input, spec_id) + } else { + self.calculate_tx_l1_cost_curie(input, spec_id) + } + } +}