diff --git a/Cargo.lock b/Cargo.lock index 3c7646fa66..f544dfa464 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1364,8 +1364,9 @@ dependencies = [ [[package]] name = "ethereum" -version = "0.9.0" -source = "git+https://github.com/purestake/ethereum?branch=joshy-scale-info#2b98173bd88c05bfeefad421d260101b9008497b" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fb916554a4dba293ea69c69ad5653e21d770a9d0c2496b5fa0a1f5a3946d87" dependencies = [ "bytes 1.0.1", "ethereum-types", @@ -1404,8 +1405,9 @@ checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" [[package]] name = "evm" -version = "0.30.1" -source = "git+https://github.com/purestake/evm?branch=moonbeam-polkadot-v0.9.11#443d0f66509dc4830b821ac45fa7e655de6df394" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2068bbfe82315b76637b601c73810ec7e92d542bad02f0155182915e832c6357" dependencies = [ "environmental", "ethereum", @@ -1423,8 +1425,9 @@ dependencies = [ [[package]] name = "evm-core" -version = "0.30.0" -source = "git+https://github.com/purestake/evm?branch=moonbeam-polkadot-v0.9.11#443d0f66509dc4830b821ac45fa7e655de6df394" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dfe4f2a56c4c05a8107b8596380e2332fc2019ffcf56b8f2d01971393a30c4d" dependencies = [ "funty", "parity-scale-codec", @@ -1435,8 +1438,9 @@ dependencies = [ [[package]] name = "evm-gasometer" -version = "0.30.0" -source = "git+https://github.com/purestake/evm?branch=moonbeam-polkadot-v0.9.11#443d0f66509dc4830b821ac45fa7e655de6df394" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c446679607eacac4e8c8738e20c97ea9b3c86eddd8b43666744b05f416037bd9" dependencies = [ "environmental", "evm-core", @@ -1446,8 +1450,9 @@ dependencies = [ [[package]] name = "evm-runtime" -version = "0.30.0" -source = "git+https://github.com/purestake/evm?branch=moonbeam-polkadot-v0.9.11#443d0f66509dc4830b821ac45fa7e655de6df394" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e8434ac6e850a8a4bc09a19406264582d1940913b2920be2af948f4ffc49b" dependencies = [ "environmental", "evm-core", @@ -1461,7 +1466,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e43f2f1833d64e33f15592464d6fdd70f349dda7b1a53088eb83cd94014008c5" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", ] [[package]] @@ -1494,7 +1499,7 @@ dependencies = [ "fc-db", "fp-consensus", "fp-rpc", - "futures 0.3.16", + "futures 0.3.17", "log", "parity-scale-codec", "sc-client-api", @@ -1533,7 +1538,7 @@ dependencies = [ "fc-db", "fp-consensus", "fp-rpc", - "futures 0.3.16", + "futures 0.3.17", "futures-timer 3.0.2", "log", "sc-client-api", @@ -1556,7 +1561,7 @@ dependencies = [ "fp-evm", "fp-rpc", "fp-storage", - "futures 0.3.16", + "futures 0.3.17", "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", @@ -1590,14 +1595,17 @@ dependencies = [ name = "fc-rpc-core" version = "1.1.0-dev" dependencies = [ + "ethereum", "ethereum-types", "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", "jsonrpc-pubsub", + "rlp", "rustc-hex", "serde", "serde_json", + "sha3 0.8.2", ] [[package]] @@ -1626,7 +1634,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8ac3ff5224ef91f3c97e03eb1de2db82743427e91aaa5ac635f454f0b164f5a" dependencies = [ "either", - "futures 0.3.16", + "futures 0.3.17", "futures-timer 3.0.2", "log", "num-traits", @@ -1714,7 +1722,6 @@ name = "fp-evm" version = "3.0.0-dev" dependencies = [ "evm", - "impl-trait-for-tuples 0.1.3", "parity-scale-codec", "serde", "sp-core", @@ -1839,7 +1846,7 @@ dependencies = [ "bitflags", "frame-metadata", "frame-support-procedural", - "impl-trait-for-tuples 0.2.1", + "impl-trait-for-tuples", "log", "once_cell", "parity-scale-codec", @@ -1950,10 +1957,11 @@ dependencies = [ "frame-benchmarking", "frame-benchmarking-cli", "frontier-template-runtime", - "futures 0.3.16", + "futures 0.3.17", "jsonrpc-core", "jsonrpc-pubsub", "log", + "pallet-base-fee", "pallet-dynamic-fee", "pallet-ethereum", "pallet-evm", @@ -2003,6 +2011,7 @@ dependencies = [ "frame-system-rpc-runtime-api", "pallet-aura", "pallet-balances", + "pallet-base-fee", "pallet-dynamic-fee", "pallet-ethereum", "pallet-evm", @@ -2085,9 +2094,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adc00f486adfc9ce99f77d717836f0c5aa84965eb0b4f051f4e83f7cab53f8b" +checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" dependencies = [ "futures-channel", "futures-core", @@ -2116,9 +2125,9 @@ checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" [[package]] name = "futures-executor" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d0d535a57b87e1ae31437b892713aee90cd2d7b0ee48727cd11fc72ef54761c" +checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c" dependencies = [ "futures-core", "futures-task", @@ -2583,7 +2592,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae8ab7f67bad3240049cb24fb9cb0b4c2c6af4c245840917fbbdededeee91179" dependencies = [ "async-io", - "futures 0.3.16", + "futures 0.3.17", "futures-lite", "if-addrs", "ipnet", @@ -2619,17 +2628,6 @@ dependencies = [ "serde", ] -[[package]] -name = "impl-trait-for-tuples" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef5550a42e3740a0e71f909d4c861056a284060af885ae7aa6242820f920d9d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "impl-trait-for-tuples" version = "0.2.1" @@ -2676,7 +2674,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64fa110ec7b8f493f416eed552740d10e7030ad5f63b2308f82c9608ec2df275" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "futures-timer 2.0.2", ] @@ -2763,7 +2761,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2b99d4207e2a04fb4581746903c2bb7eb376f88de9c699d0f3e10feeac0cd3a" dependencies = [ "derive_more", - "futures 0.3.16", + "futures 0.3.17", "jsonrpc-core", "jsonrpc-pubsub", "log", @@ -2778,7 +2776,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "futures-executor", "futures-util", "log", @@ -2793,7 +2791,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b51da17abecbdab3e3d4f26b01c5ec075e88d3abe3ab3b05dc9aa69392764ec0" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "jsonrpc-client-transports", ] @@ -2815,7 +2813,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1dea6e07251d9ce6a552abfb5d7ad6bc290a4596c8dcc3d795fae2bbdc1f3ff" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "hyper", "jsonrpc-core", "jsonrpc-server-utils", @@ -2831,7 +2829,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "382bb0206323ca7cda3dcd7e245cea86d37d02457a02a975e3378fb149a48845" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "jsonrpc-core", "jsonrpc-server-utils", "log", @@ -2846,7 +2844,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240f87695e6c6f62fb37f05c02c04953cf68d6408b8c1c89de85c7a0125b1011" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "jsonrpc-core", "lazy_static", "log", @@ -2862,7 +2860,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4fdea130485b572c39a460d50888beb00afb3e35de23ccd7fad8ff19f0e0d4" dependencies = [ "bytes 1.0.1", - "futures 0.3.16", + "futures 0.3.17", "globset", "jsonrpc-core", "lazy_static", @@ -2879,7 +2877,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f892c7d766369475ab7b0669f417906302d7c0fb521285c0a0c92e52e7c8e946" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "jsonrpc-core", "jsonrpc-server-utils", "log", @@ -3007,7 +3005,7 @@ checksum = "9004c06878ef8f3b4b4067e69a140d87ed20bf777287f82223e49713b36ee433" dependencies = [ "atomic", "bytes 1.0.1", - "futures 0.3.16", + "futures 0.3.17", "lazy_static", "libp2p-core", "libp2p-deflate", @@ -3049,7 +3047,7 @@ dependencies = [ "ed25519-dalek", "either", "fnv", - "futures 0.3.16", + "futures 0.3.17", "futures-timer 3.0.2", "lazy_static", "libsecp256k1 0.5.0", @@ -3079,7 +3077,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66097fccc0b7f8579f90a03ea76ba6196332ea049fd07fd969490a06819dcdc8" dependencies = [ "flate2", - "futures 0.3.16", + "futures 0.3.17", "libp2p-core", ] @@ -3090,7 +3088,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58ff08b3196b85a17f202d80589e93b1660a574af67275706657fdc762e42c32" dependencies = [ "async-std-resolver", - "futures 0.3.16", + "futures 0.3.17", "libp2p-core", "log", "smallvec", @@ -3105,7 +3103,7 @@ checksum = "404eca8720967179dac7a5b4275eb91f904a53859c69ca8d018560ad6beb214f" dependencies = [ "cuckoofilter", "fnv", - "futures 0.3.16", + "futures 0.3.17", "libp2p-core", "libp2p-swarm", "log", @@ -3126,7 +3124,7 @@ dependencies = [ "byteorder", "bytes 1.0.1", "fnv", - "futures 0.3.16", + "futures 0.3.17", "hex_fmt", "libp2p-core", "libp2p-swarm", @@ -3147,7 +3145,7 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7b61f6cf07664fb97016c318c4d4512b3dd4cc07238607f3f0163245f99008e" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "libp2p-core", "libp2p-swarm", "log", @@ -3168,7 +3166,7 @@ dependencies = [ "bytes 1.0.1", "either", "fnv", - "futures 0.3.16", + "futures 0.3.17", "libp2p-core", "libp2p-swarm", "log", @@ -3192,7 +3190,7 @@ dependencies = [ "async-io", "data-encoding", "dns-parser", - "futures 0.3.16", + "futures 0.3.17", "if-watch", "lazy_static", "libp2p-core", @@ -3212,7 +3210,7 @@ checksum = "313d9ea526c68df4425f580024e67a9d3ffd49f2c33de5154b1f5019816f7a99" dependencies = [ "asynchronous-codec 0.6.0", "bytes 1.0.1", - "futures 0.3.16", + "futures 0.3.17", "libp2p-core", "log", "nohash-hasher", @@ -3230,7 +3228,7 @@ checksum = "3f1db7212f342b6ba7c981cc40e31f76e9e56cb48e65fa4c142ecaca5839523e" dependencies = [ "bytes 1.0.1", "curve25519-dalek 3.2.0", - "futures 0.3.16", + "futures 0.3.17", "lazy_static", "libp2p-core", "log", @@ -3250,7 +3248,7 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2482cfd9eb0b7a0baaf3e7b329dc4f2785181a161b1a47b7192f8d758f54a439" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "libp2p-core", "libp2p-swarm", "log", @@ -3267,7 +3265,7 @@ checksum = "13b4783e5423870b9a5c199f65a7a3bc66d86ab56b2b9beebf3c338d889cf8e4" dependencies = [ "asynchronous-codec 0.6.0", "bytes 1.0.1", - "futures 0.3.16", + "futures 0.3.17", "libp2p-core", "log", "prost 0.8.0", @@ -3282,7 +3280,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07cb4dd4b917e5b40ddefe49b96b07adcd8d342e0317011d175b7b2bb1dcc974" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "log", "pin-project 1.0.8", "rand 0.7.3", @@ -3298,7 +3296,7 @@ checksum = "0133f6cfd81cdc16e716de2982e012c62e6b9d4f12e41967b3ee361051c622aa" dependencies = [ "asynchronous-codec 0.6.0", "bytes 1.0.1", - "futures 0.3.16", + "futures 0.3.17", "futures-timer 3.0.2", "libp2p-core", "libp2p-swarm", @@ -3321,7 +3319,7 @@ checksum = "06cdae44b6821466123af93cbcdec7c9e6ba9534a8af9cdc296446d39416d241" dependencies = [ "async-trait", "bytes 1.0.1", - "futures 0.3.16", + "futures 0.3.17", "libp2p-core", "libp2p-swarm", "log", @@ -3340,7 +3338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7083861341e1555467863b4cd802bea1e8c4787c0f7b5110097d0f1f3248f9a9" dependencies = [ "either", - "futures 0.3.16", + "futures 0.3.17", "libp2p-core", "log", "rand 0.7.3", @@ -3366,7 +3364,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79edd26b6b4bb5feee210dcda562dca186940dfecb0024b979c3f50824b3bf28" dependencies = [ "async-io", - "futures 0.3.16", + "futures 0.3.17", "futures-timer 3.0.2", "if-watch", "ipnet", @@ -3383,7 +3381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "280e793440dd4e9f273d714f4497325c72cddb0fe85a49f9a03c88f41dd20182" dependencies = [ "async-std", - "futures 0.3.16", + "futures 0.3.17", "libp2p-core", "log", ] @@ -3394,7 +3392,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f553b7140fad3d7a76f50497b0ea591e26737d9607428a75509fc191e4d1b1f6" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "js-sys", "libp2p-core", "parity-send-wrapper", @@ -3409,7 +3407,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddf99dcbf5063e9d59087f61b1e85c686ceab2f5abedb472d32288065c0e5e27" dependencies = [ "either", - "futures 0.3.16", + "futures 0.3.17", "futures-rustls", "libp2p-core", "log", @@ -3426,7 +3424,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "214cc0dd9c37cbed27f0bb1eba0c41bbafdb93a8be5e9d6ae1e6b4b42cd044bf" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "libp2p-core", "parking_lot", "thiserror", @@ -3930,7 +3928,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d91ec0a2440aaff5f78ec35631a7027d50386c6163aa975f7caa0d5da4b6ff8" dependencies = [ "bytes 1.0.1", - "futures 0.3.16", + "futures 0.3.17", "log", "pin-project 1.0.8", "smallvec", @@ -4227,7 +4225,7 @@ source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0 dependencies = [ "frame-support", "frame-system", - "impl-trait-for-tuples 0.2.1", + "impl-trait-for-tuples", "parity-scale-codec", "scale-info", "sp-authorship", @@ -4250,6 +4248,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-base-fee" +version = "1.0.0" +dependencies = [ + "frame-support", + "frame-system", + "pallet-evm", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", +] + [[package]] name = "pallet-dynamic-fee" version = "4.0.0-dev" @@ -4470,7 +4483,7 @@ source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0 dependencies = [ "frame-support", "frame-system", - "impl-trait-for-tuples 0.2.1", + "impl-trait-for-tuples", "log", "pallet-timestamp", "parity-scale-codec", @@ -4588,7 +4601,7 @@ dependencies = [ "arrayvec 0.7.1", "bitvec 0.20.4", "byte-slice-cast", - "impl-trait-for-tuples 0.2.1", + "impl-trait-for-tuples", "parity-scale-codec-derive", "serde", ] @@ -4617,7 +4630,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "libc", "log", "rand 0.7.3", @@ -4634,7 +4647,7 @@ dependencies = [ "cfg-if 1.0.0", "ethereum-types", "hashbrown", - "impl-trait-for-tuples 0.2.1", + "impl-trait-for-tuples", "lru 0.6.6", "parity-util-mem-derive", "parking_lot", @@ -5600,7 +5613,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "pin-project 0.4.28", "static_assertions", ] @@ -5654,7 +5667,7 @@ name = "sc-basic-authorship" version = "0.10.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "futures-timer 3.0.2", "log", "parity-scale-codec", @@ -5693,7 +5706,7 @@ name = "sc-chain-spec" version = "4.0.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ - "impl-trait-for-tuples 0.2.1", + "impl-trait-for-tuples", "memmap2 0.5.0", "parity-scale-codec", "sc-chain-spec-derive", @@ -5723,7 +5736,7 @@ source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0 dependencies = [ "chrono", "fdlimit", - "futures 0.3.16", + "futures 0.3.17", "hex", "libp2p", "log", @@ -5760,7 +5773,7 @@ version = "4.0.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ "fnv", - "futures 0.3.16", + "futures 0.3.17", "hash-db", "log", "parity-scale-codec", @@ -5813,7 +5826,7 @@ version = "0.10.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ "async-trait", - "futures 0.3.16", + "futures 0.3.17", "futures-timer 3.0.2", "libp2p", "log", @@ -5838,7 +5851,7 @@ source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0 dependencies = [ "async-trait", "derive_more", - "futures 0.3.16", + "futures 0.3.17", "log", "parity-scale-codec", "sc-block-builder", @@ -5868,7 +5881,7 @@ dependencies = [ "async-trait", "derive_more", "fork-tree", - "futures 0.3.16", + "futures 0.3.17", "log", "merlin", "num-bigint 0.2.6", @@ -5924,7 +5937,7 @@ dependencies = [ "assert_matches", "async-trait", "derive_more", - "futures 0.3.16", + "futures 0.3.17", "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", @@ -5956,7 +5969,7 @@ version = "0.10.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ "async-trait", - "futures 0.3.16", + "futures 0.3.17", "futures-timer 3.0.2", "log", "parity-scale-codec", @@ -6065,7 +6078,7 @@ dependencies = [ "dyn-clone", "finality-grandpa", "fork-tree", - "futures 0.3.16", + "futures 0.3.17", "futures-timer 3.0.2", "log", "parity-scale-codec", @@ -6098,7 +6111,7 @@ version = "0.10.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ "ansi_term 0.12.1", - "futures 0.3.16", + "futures 0.3.17", "futures-timer 3.0.2", "log", "parity-util-mem", @@ -6139,7 +6152,7 @@ dependencies = [ "either", "fnv", "fork-tree", - "futures 0.3.16", + "futures 0.3.17", "futures-timer 3.0.2", "hex", "ip_network", @@ -6180,7 +6193,7 @@ name = "sc-network-gossip" version = "0.10.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "futures-timer 3.0.2", "libp2p", "log", @@ -6198,7 +6211,7 @@ source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0 dependencies = [ "bytes 1.0.1", "fnv", - "futures 0.3.16", + "futures 0.3.17", "futures-timer 3.0.2", "hex", "hyper", @@ -6224,7 +6237,7 @@ name = "sc-peerset" version = "4.0.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "libp2p", "log", "sc-utils", @@ -6246,7 +6259,7 @@ name = "sc-rpc" version = "4.0.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "hash-db", "jsonrpc-core", "jsonrpc-pubsub", @@ -6277,7 +6290,7 @@ name = "sc-rpc-api" version = "0.10.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", @@ -6302,7 +6315,7 @@ name = "sc-rpc-server" version = "4.0.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "jsonrpc-core", "jsonrpc-http-server", "jsonrpc-ipc-server", @@ -6322,7 +6335,7 @@ dependencies = [ "async-trait", "directories", "exit-future", - "futures 0.3.16", + "futures 0.3.17", "futures-timer 3.0.2", "hash-db", "jsonrpc-core", @@ -6398,7 +6411,7 @@ version = "4.0.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ "chrono", - "futures 0.3.16", + "futures 0.3.17", "libp2p", "log", "parking_lot", @@ -6457,7 +6470,7 @@ name = "sc-transaction-pool" version = "4.0.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "intervalier", "linked-hash-map", "log", @@ -6485,7 +6498,7 @@ version = "4.0.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ "derive_more", - "futures 0.3.16", + "futures 0.3.17", "log", "serde", "sp-blockchain", @@ -6498,7 +6511,7 @@ name = "sc-utils" version = "4.0.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "futures-timer 3.0.2", "lazy_static", "prometheus", @@ -6887,7 +6900,7 @@ dependencies = [ "base64 0.12.3", "bytes 0.5.6", "flate2", - "futures 0.3.16", + "futures 0.3.17", "httparse", "log", "rand 0.7.3", @@ -6980,7 +6993,7 @@ name = "sp-blockchain" version = "4.0.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "log", "lru 0.7.0", "parity-scale-codec", @@ -6999,7 +7012,7 @@ version = "0.10.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ "async-trait", - "futures 0.3.16", + "futures 0.3.17", "futures-timer 3.0.2", "log", "parity-scale-codec", @@ -7088,7 +7101,7 @@ dependencies = [ "byteorder", "dyn-clonable", "ed25519-dalek", - "futures 0.3.16", + "futures 0.3.17", "hash-db", "hash256-std-hasher", "hex", @@ -7203,7 +7216,7 @@ version = "4.0.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ "async-trait", - "impl-trait-for-tuples 0.2.1", + "impl-trait-for-tuples", "parity-scale-codec", "sp-core", "sp-runtime", @@ -7216,7 +7229,7 @@ name = "sp-io" version = "4.0.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "hash-db", "libsecp256k1 0.6.0", "log", @@ -7253,7 +7266,7 @@ source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0 dependencies = [ "async-trait", "derive_more", - "futures 0.3.16", + "futures 0.3.17", "merlin", "parity-scale-codec", "parking_lot", @@ -7308,7 +7321,7 @@ source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0 dependencies = [ "either", "hash256-std-hasher", - "impl-trait-for-tuples 0.2.1", + "impl-trait-for-tuples", "log", "parity-scale-codec", "parity-util-mem", @@ -7328,7 +7341,7 @@ name = "sp-runtime-interface" version = "4.0.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ - "impl-trait-for-tuples 0.2.1", + "impl-trait-for-tuples", "parity-scale-codec", "primitive-types", "sp-externalities", @@ -7540,7 +7553,7 @@ name = "sp-wasm-interface" version = "4.0.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ - "impl-trait-for-tuples 0.2.1", + "impl-trait-for-tuples", "parity-scale-codec", "sp-std", "wasmi", @@ -7682,7 +7695,7 @@ version = "4.0.0-dev" source = "git+https://github.com/purestake/substrate?branch=moonbeam-polkadot-v0.9.13#b17ba4cf804460c310581ce873a0bf59ff35694b" dependencies = [ "frame-system-rpc-runtime-api", - "futures 0.3.16", + "futures 0.3.17", "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", @@ -8442,7 +8455,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "js-sys", "parking_lot", "pin-utils", @@ -8778,7 +8791,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d9028f208dd5e63c614be69f115c1b53cacc1111437d4c765185856666c107" dependencies = [ - "futures 0.3.16", + "futures 0.3.17", "log", "nohash-hasher", "parking_lot", diff --git a/Cargo.toml b/Cargo.toml index cbfe4f8563..e6fcdf9a58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,9 +23,3 @@ members = [ "template/node", "template/runtime", ] - -[patch.crates-io] -evm = { git = "https://github.com/purestake/evm", branch = "moonbeam-polkadot-v0.9.11"} -evm-runtime = { git = "https://github.com/purestake/evm", branch = "moonbeam-polkadot-v0.9.11" } -evm-gasometer = { git = "https://github.com/purestake/evm", branch = "moonbeam-polkadot-v0.9.11" } -ethereum = { git = "https://github.com/purestake/ethereum", branch = "joshy-scale-info" } \ No newline at end of file diff --git a/client/rpc-core/Cargo.toml b/client/rpc-core/Cargo.toml index 20ccd865d8..0e97eec64e 100644 --- a/client/rpc-core/Cargo.toml +++ b/client/rpc-core/Cargo.toml @@ -12,6 +12,9 @@ jsonrpc-core-client = "18.0" jsonrpc-derive = "18.0" jsonrpc-pubsub = "18.0" rustc-hex = "2.1.0" -ethereum-types = "0.12.0" +ethereum = { version = "0.10.0", features = ["with-codec"] } +sha3 = "0.8" +ethereum-types = "0.12" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +rlp = "0.5" diff --git a/client/rpc-core/src/types/block.rs b/client/rpc-core/src/types/block.rs index 611f4a0b56..488f39e048 100644 --- a/client/rpc-core/src/types/block.rs +++ b/client/rpc-core/src/types/block.rs @@ -58,6 +58,9 @@ pub struct Block { pub transactions: BlockTransactions, /// Size in bytes pub size: Option, + /// Base Fee for post-EIP1559 blocks. + #[serde(skip_serializing_if = "Option::is_none")] + pub base_fee_per_gas: Option, } /// Block header representation. diff --git a/client/rpc-core/src/types/call_request.rs b/client/rpc-core/src/types/call_request.rs index a97ab513d3..4e82157e94 100644 --- a/client/rpc-core/src/types/call_request.rs +++ b/client/rpc-core/src/types/call_request.rs @@ -31,6 +31,10 @@ pub struct CallRequest { pub to: Option, /// Gas Price pub gas_price: Option, + /// EIP-1559 Max base fee the caller is willing to pay + pub max_fee_per_gas: Option, + /// EIP-1559 Priority fee the caller is paying to the block author + pub max_priority_fee_per_gas: Option, /// Gas pub gas: Option, /// Value diff --git a/client/rpc-core/src/types/mod.rs b/client/rpc-core/src/types/mod.rs index b47f52e01b..4d56a0ccc2 100644 --- a/client/rpc-core/src/types/mod.rs +++ b/client/rpc-core/src/types/mod.rs @@ -52,6 +52,6 @@ pub use self::{ Peers, PipProtocolInfo, SyncInfo, SyncStatus, TransactionStats, }, transaction::{LocalTransactionStatus, RichRawTransaction, Transaction}, - transaction_request::TransactionRequest, + transaction_request::{TransactionMessage, TransactionRequest}, work::Work, }; diff --git a/client/rpc-core/src/types/receipt.rs b/client/rpc-core/src/types/receipt.rs index d916be0dc6..78f59bfd28 100644 --- a/client/rpc-core/src/types/receipt.rs +++ b/client/rpc-core/src/types/receipt.rs @@ -54,4 +54,6 @@ pub struct Receipt { // NOTE(niklasad1): Unknown after EIP98 rules, if it's missing then skip serializing it #[serde(skip_serializing_if = "Option::is_none", rename = "status")] pub status_code: Option, + /// Effective gas price. Pre-eip1559 this is just the gasprice. Post-eip1559 this is base fee + priority fee. + pub effective_gas_price: U256, } diff --git a/client/rpc-core/src/types/transaction.rs b/client/rpc-core/src/types/transaction.rs index 955064b218..e5ae07133a 100644 --- a/client/rpc-core/src/types/transaction.rs +++ b/client/rpc-core/src/types/transaction.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use crate::types::Bytes; +use ethereum::{AccessListItem, TransactionV2}; use ethereum_types::{H160, H256, H512, U256, U64}; use serde::{ser::SerializeStruct, Serialize, Serializer}; @@ -41,7 +42,14 @@ pub struct Transaction { /// Transfered value pub value: U256, /// Gas Price - pub gas_price: U256, + #[serde(skip_serializing_if = "Option::is_none")] + pub gas_price: Option, + /// Max BaseFeePerGas the user is willing to pay. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_fee_per_gas: Option, + /// The miner's tip. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_priority_fee_per_gas: Option, /// Gas pub gas: U256, /// Data @@ -62,6 +70,91 @@ pub struct Transaction { pub r: U256, /// The S field of the signature. pub s: U256, + /// Pre-pay to warm storage access. + #[cfg_attr(feature = "std", serde(skip_serializing_if = "Option::is_none"))] + pub access_list: Option>, +} + +impl From for Transaction { + fn from(transaction: TransactionV2) -> Self { + let serialized = rlp::encode(&transaction); + let hash = transaction.hash(); + let raw = Bytes(serialized.to_vec()); + match transaction { + TransactionV2::Legacy(t) => Transaction { + hash, + nonce: t.nonce, + block_hash: None, + block_number: None, + transaction_index: None, + from: H160::default(), + to: None, + value: t.value, + gas_price: Some(t.gas_price), + max_fee_per_gas: Some(t.gas_price), + max_priority_fee_per_gas: Some(t.gas_price), + gas: t.gas_limit, + input: Bytes(t.clone().input), + creates: None, + raw, + public_key: None, + chain_id: t.signature.chain_id().map(U64::from), + standard_v: U256::from(t.signature.standard_v()), + v: U256::from(t.signature.v()), + r: U256::from(t.signature.r().as_bytes()), + s: U256::from(t.signature.s().as_bytes()), + access_list: None, + }, + TransactionV2::EIP2930(t) => Transaction { + hash, + nonce: t.nonce, + block_hash: None, + block_number: None, + transaction_index: None, + from: H160::default(), + to: None, + value: t.value, + gas_price: Some(t.gas_price), + max_fee_per_gas: Some(t.gas_price), + max_priority_fee_per_gas: Some(t.gas_price), + gas: t.gas_limit, + input: Bytes(t.clone().input), + creates: None, + raw, + public_key: None, + chain_id: Some(U64::from(t.chain_id)), + standard_v: U256::from(t.odd_y_parity as u8), + v: U256::from(t.odd_y_parity as u8), + r: U256::from(t.r.as_bytes()), + s: U256::from(t.s.as_bytes()), + access_list: Some(t.access_list), + }, + TransactionV2::EIP1559(t) => Transaction { + hash, + nonce: t.nonce, + block_hash: None, + block_number: None, + transaction_index: None, + from: H160::default(), + to: None, + value: t.value, + gas_price: None, + max_fee_per_gas: Some(t.max_fee_per_gas), + max_priority_fee_per_gas: Some(t.max_priority_fee_per_gas), + gas: t.gas_limit, + input: Bytes(t.clone().input), + creates: None, + raw, + public_key: None, + chain_id: Some(U64::from(t.chain_id)), + standard_v: U256::from(t.odd_y_parity as u8), + v: U256::from(t.odd_y_parity as u8), + r: U256::from(t.r.as_bytes()), + s: U256::from(t.s.as_bytes()), + access_list: Some(t.access_list), + }, + } + } } /// Local Transaction Status diff --git a/client/rpc-core/src/types/transaction_request.rs b/client/rpc-core/src/types/transaction_request.rs index 8f04e45194..ad7cc95779 100644 --- a/client/rpc-core/src/types/transaction_request.rs +++ b/client/rpc-core/src/types/transaction_request.rs @@ -19,9 +19,18 @@ //! `TransactionRequest` type use crate::types::Bytes; -use ethereum_types::{H160, U256}; +use ethereum::{ + AccessListItem, EIP1559TransactionMessage, EIP2930TransactionMessage, LegacyTransactionMessage, +}; +use ethereum_types::{H160, H256, U256}; use serde::{Deserialize, Serialize}; +pub enum TransactionMessage { + Legacy(LegacyTransactionMessage), + EIP2930(EIP2930TransactionMessage), + EIP1559(EIP1559TransactionMessage), +} + /// Transaction request coming from RPC #[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(deny_unknown_fields)] @@ -31,8 +40,15 @@ pub struct TransactionRequest { pub from: Option, /// Recipient pub to: Option, - /// Gas Price + /// Gas Price, legacy. + #[serde(default)] pub gas_price: Option, + /// Max BaseFeePerGas the user is willing to pay. + #[serde(default)] + pub max_fee_per_gas: Option, + /// The miner's tip. + #[serde(default)] + pub max_priority_fee_per_gas: Option, /// Gas pub gas: Option, /// Value of transaction in wei @@ -41,4 +57,76 @@ pub struct TransactionRequest { pub data: Option, /// Transaction's nonce pub nonce: Option, + /// TODO! Pre-pay to warm storage access. + #[serde(default)] + pub access_list: Option)>>, +} + +impl Into> for TransactionRequest { + fn into(self) -> Option { + match ( + self.gas_price, + self.max_fee_per_gas, + self.access_list.clone(), + ) { + // Legacy + (Some(_), None, None) => Some(TransactionMessage::Legacy(LegacyTransactionMessage { + nonce: U256::zero(), + gas_price: self.gas_price.unwrap_or_default(), + gas_limit: self.gas.unwrap_or_default(), + value: self.value.unwrap_or(U256::zero()), + input: self.data.map(|s| s.into_vec()).unwrap_or_default(), + action: match self.to { + Some(to) => ethereum::TransactionAction::Call(to), + None => ethereum::TransactionAction::Create, + }, + chain_id: None, + })), + // EIP2930 + (_, None, Some(_)) => Some(TransactionMessage::EIP2930(EIP2930TransactionMessage { + nonce: U256::zero(), + gas_price: self.gas_price.unwrap_or_default(), + gas_limit: self.gas.unwrap_or_default(), + value: self.value.unwrap_or(U256::zero()), + input: self.data.map(|s| s.into_vec()).unwrap_or_default(), + action: match self.to { + Some(to) => ethereum::TransactionAction::Call(to), + None => ethereum::TransactionAction::Create, + }, + chain_id: 0, + access_list: self + .access_list + .unwrap() + .into_iter() + .map(|(address, slots)| AccessListItem { address, slots }) + .collect(), + })), + // EIP1559 + (None, Some(_), _) | (None, None, None) => { + // Empty fields fall back to the canonical transaction schema. + Some(TransactionMessage::EIP1559(EIP1559TransactionMessage { + nonce: U256::zero(), + max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(), + max_priority_fee_per_gas: self + .max_priority_fee_per_gas + .unwrap_or(U256::from(0)), + gas_limit: self.gas.unwrap_or_default(), + value: self.value.unwrap_or(U256::zero()), + input: self.data.map(|s| s.into_vec()).unwrap_or_default(), + action: match self.to { + Some(to) => ethereum::TransactionAction::Call(to), + None => ethereum::TransactionAction::Create, + }, + chain_id: 0, + access_list: self + .access_list + .unwrap_or(Vec::new()) + .into_iter() + .map(|(address, slots)| AccessListItem { address, slots }) + .collect(), + })) + } + _ => None, + } + } } diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index 1b99516583..fa1baa8745 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -12,8 +12,8 @@ jsonrpc-derive = "18.0" jsonrpc-core-client = "18.0" jsonrpc-pubsub = "18.0" log = "0.4.8" -ethereum-types = "0.12.1" -evm = "0.30.1" +ethereum-types = "0.12" +evm = "0.33.0" fc-consensus = { version = "2.0.0-dev", path = "../consensus" } fc-db = { version = "2.0.0-dev", path = "../db" } fc-rpc-core = { version = "1.1.0-dev", path = "../rpc-core" } @@ -35,7 +35,7 @@ sc-network = { version = "0.10.0-dev", git = "https://github.com/purestake/subst pallet-evm = { version = "6.0.0-dev", path = "../../frame/evm" } fp-evm = { version = "3.0.0-dev", path = "../../primitives/evm" } pallet-ethereum = { version = "4.0.0-dev", path = "../../frame/ethereum" } -ethereum = { version = "0.9.0", features = ["with-codec"] } +ethereum = { version = "0.10.0", features = ["with-codec"] } codec = { package = "parity-scale-codec", version = "2.0.0" } rlp = "0.5" futures = { version = "0.3.1", features = ["compat"] } diff --git a/client/rpc/src/eth.rs b/client/rpc/src/eth.rs index c5d77c836c..89a4fae767 100644 --- a/client/rpc/src/eth.rs +++ b/client/rpc/src/eth.rs @@ -19,14 +19,15 @@ use crate::{ error_on_execution_failure, format::Formatter, frontier_backend_client, internal_err, public_key, EthSigner, StorageOverride, }; -use ethereum::{BlockV0 as EthereumBlock, TransactionV0 as EthereumTransaction}; +use ethereum::{BlockV2 as EthereumBlock, TransactionV2 as EthereumTransaction}; use ethereum_types::{H160, H256, H512, H64, U256, U64}; use evm::{ExitError, ExitReason}; use fc_rpc_core::{ types::{ Block, BlockNumber, BlockTransactions, Bytes, CallRequest, Filter, FilterChanges, FilterPool, FilterPoolItem, FilterType, FilteredParams, Header, Index, Log, PeerCount, - Receipt, Rich, RichBlock, SyncInfo, SyncStatus, Transaction, TransactionRequest, Work, + Receipt, Rich, RichBlock, SyncInfo, SyncStatus, Transaction, TransactionMessage, + TransactionRequest, Work, }, EthApi as EthApiT, EthFilterApi as EthFilterApiT, NetApi as NetApiT, Web3Api as Web3ApiT, }; @@ -42,7 +43,7 @@ use sc_network::{ExHashT, NetworkService}; use sc_transaction_pool::{ChainApi, Pool}; use sc_transaction_pool_api::{InPoolTransaction, TransactionPool}; use sha3::{Digest, Keccak256}; -use sp_api::{BlockId, Core, HeaderT, ProvideRuntimeApi}; +use sp_api::{ApiExt, BlockId, Core, HeaderT, ProvideRuntimeApi}; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; use sp_runtime::{ traits::{BlakeTwo256, Block as BlockT, NumberFor, One, Saturating, UniqueSaturatedInto, Zero}, @@ -116,10 +117,12 @@ where } fn rich_block_build( - block: ethereum::BlockV0, + block: ethereum::Block, statuses: Vec>, hash: Option, full_transactions: bool, + base_fee: Option, + is_eip1559: bool, ) -> RichBlock { Rich { inner: Block { @@ -161,6 +164,8 @@ fn rich_block_build( transaction.clone(), Some(block.clone()), Some(statuses[index].clone().unwrap_or_default()), + is_eip1559, + base_fee, ) }) .collect(), @@ -170,79 +175,103 @@ fn rich_block_build( block .transactions .iter() - .map(|transaction| { - H256::from_slice( - Keccak256::digest(&rlp::encode(&transaction.clone())) - .as_slice(), - ) - }) + .map(|transaction| transaction.hash()) .collect(), ) } }, size: Some(U256::from(rlp::encode(&block).len() as u32)), + base_fee_per_gas: base_fee, }, extra_info: BTreeMap::new(), } } fn transaction_build( - transaction: EthereumTransaction, - block: Option, + ethereum_transaction: EthereumTransaction, + block: Option>, status: Option, + is_eip1559: bool, + base_fee: Option, ) -> Transaction { - let pubkey = match public_key(&transaction) { + let mut transaction: Transaction = ethereum_transaction.clone().into(); + + if let EthereumTransaction::EIP1559(_) = ethereum_transaction { + if block.is_none() && status.is_none() { + // If transaction is not mined yet, gas price is considered just max fee per gas. + transaction.gas_price = transaction.max_fee_per_gas; + } else { + // If transaction is already mined, gas price is considered base fee + priority fee. + // A.k.a. effective gas price. + let base_fee = base_fee.unwrap_or(U256::zero()); + let max_priority_fee_per_gas = + transaction.max_priority_fee_per_gas.unwrap_or(U256::zero()); + transaction.gas_price = Some( + base_fee + .checked_add(max_priority_fee_per_gas) + .unwrap_or(U256::max_value()), + ); + } + } else if !is_eip1559 { + // This is a pre-eip1559 support transaction a.k.a. txns on frontier before we introduced EIP1559 support in + // pallet-ethereum schema V2. + // They do not include `maxFeePerGas` or `maxPriorityFeePerGas` fields. + transaction.max_fee_per_gas = None; + transaction.max_priority_fee_per_gas = None; + } + + let pubkey = match public_key(ðereum_transaction) { Ok(p) => Some(p), Err(_e) => None, }; - Transaction { - hash: H256::from_slice(Keccak256::digest(&rlp::encode(&transaction)).as_slice()), - nonce: transaction.nonce, - block_hash: block.as_ref().map_or(None, |block| { - Some(H256::from_slice( - Keccak256::digest(&rlp::encode(&block.header)).as_slice(), - )) - }), - block_number: block.as_ref().map(|block| block.header.number), - transaction_index: status.as_ref().map(|status| { - U256::from(UniqueSaturatedInto::::unique_saturated_into( - status.transaction_index, - )) - }), - from: status.as_ref().map_or( - { - match pubkey { - Some(pk) => H160::from(H256::from_slice(Keccak256::digest(&pk).as_slice())), - _ => H160::default(), - } - }, - |status| status.from, - ), - to: status.as_ref().map_or( - { - match transaction.action { - ethereum::TransactionAction::Call(to) => Some(to), - _ => None, - } - }, - |status| status.to, - ), - value: transaction.value, - gas_price: transaction.gas_price, - gas: transaction.gas_limit, - input: Bytes(transaction.clone().input), - creates: status - .as_ref() - .map_or(None, |status| status.contract_address), - raw: Bytes(rlp::encode(&transaction).to_vec()), - public_key: pubkey.as_ref().map(|pk| H512::from(pk)), - chain_id: transaction.signature.chain_id().map(U64::from), - standard_v: U256::from(transaction.signature.standard_v()), - v: U256::from(transaction.signature.v()), - r: U256::from(transaction.signature.r().as_bytes()), - s: U256::from(transaction.signature.s().as_bytes()), - } + // Block hash. + transaction.block_hash = block.as_ref().map_or(None, |block| { + Some(H256::from_slice( + Keccak256::digest(&rlp::encode(&block.header)).as_slice(), + )) + }); + // Block number. + transaction.block_number = block.as_ref().map(|block| block.header.number); + // Transaction index. + transaction.transaction_index = status.as_ref().map(|status| { + U256::from(UniqueSaturatedInto::::unique_saturated_into( + status.transaction_index, + )) + }); + // From. + transaction.from = status.as_ref().map_or( + { + match pubkey { + Some(pk) => H160::from(H256::from_slice(Keccak256::digest(&pk).as_slice())), + _ => H160::default(), + } + }, + |status| status.from, + ); + // To. + transaction.to = status.as_ref().map_or( + { + let action = match ethereum_transaction { + EthereumTransaction::Legacy(t) => t.action, + EthereumTransaction::EIP2930(t) => t.action, + EthereumTransaction::EIP1559(t) => t.action, + }; + match action { + ethereum::TransactionAction::Call(to) => Some(to), + _ => None, + } + }, + |status| status.to, + ); + // Creates. + transaction.creates = status + .as_ref() + .map_or(None, |status| status.contract_address); + // Public key. + transaction.public_key = pubkey.as_ref().map(|pk| H512::from(pk)); + + transaction } fn filter_range_logs( @@ -423,6 +452,46 @@ fn filter_block_logs<'a>( ret } +struct FeeDetails { + gas_price: Option, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, +} + +fn fee_details( + request_gas_price: Option, + request_max_fee: Option, + request_priority: Option, +) -> Result { + match (request_gas_price, request_max_fee, request_priority) { + (gas_price, None, None) => { + // Legacy request, all default to gas price. + Ok(FeeDetails { + gas_price, + max_fee_per_gas: gas_price, + max_priority_fee_per_gas: gas_price, + }) + } + (_, max_fee, max_priority) => { + // eip-1559 + // Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`. + if let Some(max_priority) = max_priority { + let max_fee = max_fee.unwrap_or_default(); + if max_priority > max_fee { + return Err(internal_err(format!( + "Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`" + ))); + } + } + Ok(FeeDetails { + gas_price: max_fee, + max_fee_per_gas: max_fee, + max_priority_fee_per_gas: max_priority, + }) + } + } +} + impl EthApiT for EthApi where C: ProvideRuntimeApi + StorageProvider, @@ -588,12 +657,17 @@ where .block_data_cache .current_transaction_statuses(handler, substrate_hash); + let base_fee = handler.base_fee(&id); + let is_eip1559 = handler.is_eip1559(&id); + match (block, statuses) { (Some(block), Some(statuses)) => Ok(Some(rich_block_build( block, statuses.into_iter().map(|s| Some(s)).collect(), Some(hash), full, + base_fee, + is_eip1559, ))), _ => Ok(None), } @@ -626,6 +700,9 @@ where .block_data_cache .current_transaction_statuses(handler, substrate_hash); + let base_fee = handler.base_fee(&id); + let is_eip1559 = handler.is_eip1559(&id); + match (block, statuses) { (Some(block), Some(statuses)) => { let hash = @@ -636,6 +713,8 @@ where statuses.into_iter().map(|s| Some(s)).collect(), Some(hash), full, + base_fee, + is_eip1559, ))) } _ => Ok(None), @@ -791,21 +870,63 @@ where }; let chain_id = match self.chain_id() { - Ok(chain_id) => chain_id, + Ok(Some(chain_id)) => chain_id.as_u64(), + Ok(None) => return Box::pin(future::err(internal_err("chain id not available"))), Err(e) => return Box::pin(future::err(e)), }; - let message = ethereum::LegacyTransactionMessage { - nonce, - gas_price: request.gas_price.unwrap_or(U256::from(1)), - gas_limit: request.gas.unwrap_or(U256::max_value()), - value: request.value.unwrap_or(U256::zero()), - input: request.data.map(|s| s.into_vec()).unwrap_or_default(), - action: match request.to { - Some(to) => ethereum::TransactionAction::Call(to), - None => ethereum::TransactionAction::Create, - }, - chain_id: chain_id.map(|s| s.as_u64()), + let hash = self.client.info().best_hash; + + let gas_price = request.gas_price; + let gas_limit = match request.gas { + Some(gas_limit) => gas_limit, + None => { + let block = self + .client + .runtime_api() + .current_block(&BlockId::Hash(hash)); + if let Ok(Some(block)) = block { + block.header.gas_limit + } else { + return Box::pin(future::err(internal_err(format!( + "block unavailable, cannot query gas limit" + )))); + } + } + }; + let max_fee_per_gas = request.max_fee_per_gas; + let message: Option = request.into(); + let message = match message { + Some(TransactionMessage::Legacy(mut m)) => { + m.nonce = nonce; + m.chain_id = Some(chain_id); + m.gas_limit = gas_limit; + if gas_price.is_none() { + m.gas_price = self.gas_price().unwrap_or(U256::default()); + } + TransactionMessage::Legacy(m) + } + Some(TransactionMessage::EIP2930(mut m)) => { + m.nonce = nonce; + m.chain_id = chain_id; + m.gas_limit = gas_limit; + if gas_price.is_none() { + m.gas_price = self.gas_price().unwrap_or(U256::default()); + } + TransactionMessage::EIP2930(m) + } + Some(TransactionMessage::EIP1559(mut m)) => { + m.nonce = nonce; + m.chain_id = chain_id; + m.gas_limit = gas_limit; + if max_fee_per_gas.is_none() { + m.max_fee_per_gas = self.gas_price().unwrap_or(U256::default()); + } + TransactionMessage::EIP1559(m) + } + _ => { + return Box::pin(future::err(internal_err("invalid transaction parameters"))); + } }; let mut transaction = None; @@ -824,9 +945,7 @@ where Some(transaction) => transaction, None => return Box::pin(future::err(internal_err("no signer available"))), }; - let transaction_hash = - H256::from_slice(Keccak256::digest(&rlp::encode(&transaction)).as_slice()); - let hash = self.client.info().best_hash; + let transaction_hash = transaction.hash(); Box::pin( self.pool .submit_one( @@ -841,12 +960,31 @@ where } fn send_raw_transaction(&self, bytes: Bytes) -> BoxFuture> { - let transaction = match rlp::decode::(&bytes.0[..]) { - Ok(transaction) => transaction, - Err(_) => return Box::pin(future::err(internal_err("decode transaction failed"))), + let slice = &bytes.0[..]; + if slice.len() == 0 { + return Box::pin(future::err(internal_err("transaction data is empty"))); + } + let first = slice.get(0).unwrap(); + let transaction = if first > &0x7f { + // Legacy transaction. Decode and wrap in envelope. + match rlp::decode::(slice) { + Ok(transaction) => ethereum::TransactionV2::Legacy(transaction), + Err(_) => return Box::pin(future::err(internal_err("decode transaction failed"))), + } + } else { + // Typed Transaction. + // `ethereum` crate decode implementation for `TransactionV2` expects a valid rlp input, + // and EIP-1559 breaks that assumption by prepending a version byte. + // We re-encode the payload input to get a valid rlp, and the decode implementation will strip + // them to check the transaction version byte. + let extend = rlp::encode(&slice); + match rlp::decode::(&extend[..]) { + Ok(transaction) => transaction, + Err(_) => return Box::pin(future::err(internal_err("decode transaction failed"))), + } }; - let transaction_hash = - H256::from_slice(Keccak256::digest(&rlp::encode(&transaction)).as_slice()); + + let transaction_hash = transaction.hash(); let hash = self.client.info().best_hash; Box::pin( self.pool @@ -862,25 +1000,38 @@ where } fn call(&self, request: CallRequest, _: Option) -> Result { + // TODO required support for requests with block parameter instead of defaulting to latest. + // That's the main reason for creating a new runtime api version for the `call` and `create` methods. let hash = self.client.info().best_hash; let CallRequest { from, to, gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, gas, value, data, nonce, } = request; + let (gas_price, max_fee_per_gas, max_priority_fee_per_gas) = { + let details = fee_details(gas_price, max_fee_per_gas, max_priority_fee_per_gas)?; + ( + details.gas_price, + details.max_fee_per_gas, + details.max_priority_fee_per_gas, + ) + }; + + let api = self.client.runtime_api(); + // use given gas limit or query current block's limit let gas_limit = match gas { Some(amount) => amount, None => { - let block = self - .client - .runtime_api() + let block = api .current_block(&BlockId::Hash(hash)) .map_err(|err| internal_err(format!("runtime error: {:?}", err)))?; if let Some(block) = block { @@ -894,12 +1045,20 @@ where }; let data = data.map(|d| d.0).unwrap_or_default(); + let api_version = if let Ok(Some(api_version)) = + api.api_version::>(&BlockId::Hash(hash)) + { + api_version + } else { + return Err(internal_err(format!( + "failed to retrieve Runtime Api version" + ))); + }; match to { Some(to) => { - let info = self - .client - .runtime_api() - .call( + if api_version == 1 { + #[allow(deprecated)] + let info = api.call_before_version_2( &BlockId::Hash(hash), from.unwrap_or_default(), to, @@ -913,15 +1072,37 @@ where .map_err(|err| internal_err(format!("runtime error: {:?}", err)))? .map_err(|err| internal_err(format!("execution fatal: {:?}", err)))?; - error_on_execution_failure(&info.exit_reason, &info.value)?; + error_on_execution_failure(&info.exit_reason, &info.value)?; + Ok(Bytes(info.value)) + } else if api_version == 2 { + let info = api + .call( + &BlockId::Hash(hash), + from.unwrap_or_default(), + to, + data, + value.unwrap_or_default(), + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + false, + ) + .map_err(|err| internal_err(format!("runtime error: {:?}", err)))? + .map_err(|err| internal_err(format!("execution fatal: {:?}", err)))?; - Ok(Bytes(info.value)) + error_on_execution_failure(&info.exit_reason, &info.value)?; + Ok(Bytes(info.value)) + } else { + return Err(internal_err(format!( + "failed to retrieve Runtime Api version" + ))); + } } None => { - let info = self - .client - .runtime_api() - .create( + if api_version == 1 { + #[allow(deprecated)] + let info = api.create_before_version_2( &BlockId::Hash(hash), from.unwrap_or_default(), data, @@ -934,19 +1115,51 @@ where .map_err(|err| internal_err(format!("runtime error: {:?}", err)))? .map_err(|err| internal_err(format!("execution fatal: {:?}", err)))?; - error_on_execution_failure(&info.exit_reason, &[])?; + error_on_execution_failure(&info.exit_reason, &[])?; + Ok(Bytes(info.value[..].to_vec())) + } else if api_version == 2 { + let info = api + .create( + &BlockId::Hash(hash), + from.unwrap_or_default(), + data, + value.unwrap_or_default(), + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + false, + ) + .map_err(|err| internal_err(format!("runtime error: {:?}", err)))? + .map_err(|err| internal_err(format!("execution fatal: {:?}", err)))?; - Ok(Bytes(info.value[..].to_vec())) + error_on_execution_failure(&info.exit_reason, &[])?; + Ok(Bytes(info.value[..].to_vec())) + } else { + return Err(internal_err(format!( + "failed to retrieve Runtime Api version" + ))); + } } } } fn estimate_gas(&self, request: CallRequest, _: Option) -> Result { - // Get best hash + // Get best hash (TODO missing support for estimating gas historically) let best_hash = self.client.info().best_hash; - // Get gas price - let gas_price = request.gas_price.unwrap_or_default(); + let (gas_price, max_fee_per_gas, max_priority_fee_per_gas) = { + let details = fee_details( + request.gas_price, + request.max_fee_per_gas, + request.max_priority_fee_per_gas, + )?; + ( + details.gas_price, + details.max_fee_per_gas, + details.max_priority_fee_per_gas, + ) + }; let get_current_block_gas_limit = || -> Result { let substrate_hash = self.client.info().best_hash; @@ -975,12 +1188,13 @@ where } }; + let api = self.client.runtime_api(); + // Recap the highest gas allowance with account's balance. if let Some(from) = request.from { + let gas_price = gas_price.unwrap_or_default(); if gas_price > U256::zero() { - let balance = self - .client - .runtime_api() + let balance = api .account_basic(&BlockId::Hash(best_hash), from) .map_err(|err| internal_err(format!("runtime error: {:?}", err)))? .balance; @@ -1013,69 +1227,109 @@ where } // Create a helper to check if a gas allowance results in an executable transaction - let executable = move |request: CallRequest, gas_limit| -> Result { - let CallRequest { - from, - to, - gas_price, - gas, - value, - data, - nonce, - } = request; - - // Use request gas limit only if it less than gas_limit parameter - let gas_limit = core::cmp::min(gas.unwrap_or(gas_limit), gas_limit); - - let data = data.map(|d| d.0).unwrap_or_default(); - - let (exit_reason, data, used_gas) = match to { - Some(to) => { - let info = self - .client - .runtime_api() - .call( - &BlockId::Hash(best_hash), - from.unwrap_or_default(), - to, - data, - value.unwrap_or_default(), - gas_limit, - gas_price, - nonce, - true, - ) - .map_err(|err| internal_err(format!("runtime error: {:?}", err)))? - .map_err(|err| internal_err(format!("execution fatal: {:?}", err)))?; - - (info.exit_reason, info.value, info.used_gas) - } - None => { - let info = self - .client - .runtime_api() - .create( - &BlockId::Hash(best_hash), - from.unwrap_or_default(), - data, - value.unwrap_or_default(), - gas_limit, - gas_price, - nonce, - true, - ) - .map_err(|err| internal_err(format!("runtime error: {:?}", err)))? - .map_err(|err| internal_err(format!("execution fatal: {:?}", err)))?; - - (info.exit_reason, Vec::new(), info.used_gas) - } + let executable = + move |request: CallRequest, gas_limit, api_version| -> Result { + let CallRequest { + from, + to, + gas, + value, + data, + nonce, + .. + } = request; + + // Use request gas limit only if it less than gas_limit parameter + let gas_limit = core::cmp::min(gas.unwrap_or(gas_limit), gas_limit); + + let data = data.map(|d| d.0).unwrap_or_default(); + + let (exit_reason, data, used_gas) = match to { + Some(to) => { + let info = if api_version == 1 { + #[allow(deprecated)] + api.call_before_version_2( + &BlockId::Hash(best_hash), + from.unwrap_or_default(), + to, + data, + value.unwrap_or_default(), + gas_limit, + gas_price, + nonce, + true, + ) + .map_err(|err| internal_err(format!("runtime error: {:?}", err)))? + .map_err(|err| internal_err(format!("execution fatal: {:?}", err)))? + } else { + api.call( + &BlockId::Hash(best_hash), + from.unwrap_or_default(), + to, + data, + value.unwrap_or_default(), + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + true, + ) + .map_err(|err| internal_err(format!("runtime error: {:?}", err)))? + .map_err(|err| internal_err(format!("execution fatal: {:?}", err)))? + }; + + (info.exit_reason, info.value, info.used_gas) + } + None => { + let info = if api_version == 1 { + #[allow(deprecated)] + api.create_before_version_2( + &BlockId::Hash(best_hash), + from.unwrap_or_default(), + data, + value.unwrap_or_default(), + gas_limit, + gas_price, + nonce, + true, + ) + .map_err(|err| internal_err(format!("runtime error: {:?}", err)))? + .map_err(|err| internal_err(format!("execution fatal: {:?}", err)))? + } else { + api.create( + &BlockId::Hash(best_hash), + from.unwrap_or_default(), + data, + value.unwrap_or_default(), + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + true, + ) + .map_err(|err| internal_err(format!("runtime error: {:?}", err)))? + .map_err(|err| internal_err(format!("execution fatal: {:?}", err)))? + }; + + (info.exit_reason, Vec::new(), info.used_gas) + } + }; + Ok(ExecutableResult { + exit_reason, + data, + used_gas, + }) }; - - Ok(ExecutableResult { - exit_reason, - data, - used_gas, - }) + let api_version = if let Ok(Some(api_version)) = + self.client + .runtime_api() + .api_version::>(&BlockId::Hash(best_hash)) + { + api_version + } else { + return Err(internal_err(format!( + "failed to retrieve Runtime Api version" + ))); }; // Verify that the transaction succeed with highest capacity @@ -1084,7 +1338,7 @@ where data, exit_reason, used_gas, - } = executable(request.clone(), highest)?; + } = executable(request.clone(), highest, api_version)?; match exit_reason { ExitReason::Succeed(_) => (), ExitReason::Error(ExitError::OutOfGas) => { @@ -1105,7 +1359,7 @@ where data, exit_reason, used_gas: _, - } = executable(request.clone(), get_current_block_gas_limit()?)?; + } = executable(request.clone(), get_current_block_gas_limit()?, api_version)?; match exit_reason { ExitReason::Succeed(_) => { return Err(internal_err(format!( @@ -1144,7 +1398,7 @@ where data, exit_reason, used_gas: _, - } = executable(request.clone(), highest)?; + } = executable(request.clone(), highest, api_version)?; match exit_reason { ExitReason::Succeed(_) => { highest = mid; @@ -1201,7 +1455,7 @@ where ); let best_block: BlockId = BlockId::Hash(self.client.info().best_hash); - let ethereum_transactions: Vec = self + let ethereum_transactions: Vec = self .client .runtime_api() .extrinsic_filter(&best_block, xts) @@ -1210,10 +1464,9 @@ where })?; for txn in ethereum_transactions { - let inner_hash = - H256::from_slice(Keccak256::digest(&rlp::encode(&txn)).as_slice()); + let inner_hash = txn.hash(); if hash == inner_hash { - return Ok(Some(transaction_build(txn, None, None))); + return Ok(Some(transaction_build(txn, None, None, true, None))); } } // Unknown transaction. @@ -1245,11 +1498,16 @@ where .block_data_cache .current_transaction_statuses(handler, substrate_hash); + let base_fee = handler.base_fee(&id); + let is_eip1559 = handler.is_eip1559(&id); + match (block, statuses) { (Some(block), Some(statuses)) => Ok(Some(transaction_build( block.transactions[index].clone(), Some(block), Some(statuses[index].clone()), + is_eip1559, + base_fee, ))), _ => Ok(None), } @@ -1286,11 +1544,16 @@ where .block_data_cache .current_transaction_statuses(handler, substrate_hash); + let base_fee = handler.base_fee(&id); + let is_eip1559 = handler.is_eip1559(&id); + match (block, statuses) { (Some(block), Some(statuses)) => Ok(Some(transaction_build( block.transactions[index].clone(), Some(block), Some(statuses[index].clone()), + is_eip1559, + base_fee, ))), _ => Ok(None), } @@ -1328,11 +1591,16 @@ where .block_data_cache .current_transaction_statuses(handler, substrate_hash); + let base_fee = handler.base_fee(&id); + let is_eip1559 = handler.is_eip1559(&id); + match (block, statuses) { (Some(block), Some(statuses)) => Ok(Some(transaction_build( block.transactions[index].clone(), Some(block), Some(statuses[index].clone()), + is_eip1559, + base_fee, ))), _ => Ok(None), } @@ -1376,8 +1644,10 @@ where .current_transaction_statuses(handler, substrate_hash); let receipts = handler.current_receipts(&id); - match (block, statuses, receipts) { - (Some(block), Some(statuses), Some(receipts)) => { + let base_fee = handler.base_fee(&id); + + match (block, statuses, receipts, base_fee) { + (Some(block), Some(statuses), Some(receipts), Some(base_fee)) => { let block_hash = H256::from_slice(Keccak256::digest(&rlp::encode(&block.header)).as_slice()); let receipt = receipts[index].clone(); @@ -1385,6 +1655,15 @@ where let mut cumulative_receipts = receipts.clone(); cumulative_receipts.truncate((status.transaction_index + 1) as usize); + let transaction = block.transactions[index].clone(); + let effective_gas_price = match transaction { + EthereumTransaction::Legacy(t) => t.gas_price, + EthereumTransaction::EIP2930(t) => t.gas_price, + EthereumTransaction::EIP1559(t) => base_fee + .checked_add(t.max_priority_fee_per_gas) + .unwrap_or(U256::max_value()), + }; + return Ok(Some(Receipt { transaction_hash: Some(status.transaction_hash), transaction_index: Some(status.transaction_index.into()), @@ -1435,6 +1714,7 @@ where status_code: Some(U64::from(receipt.state_root.to_low_u64_be())), logs_bloom: receipt.logs_bloom, state_root: None, + effective_gas_price, })); } _ => Ok(None), @@ -1946,7 +2226,11 @@ where B: BlockT, { /// Task that caches at which best hash a new EthereumStorageSchema was inserted in the Runtime Storage. - pub async fn ethereum_schema_cache_task(client: Arc, backend: Arc>) { + pub async fn ethereum_schema_cache_task( + client: Arc, + backend: Arc>, + genesis_schema_version: EthereumStorageSchema, + ) { use fp_storage::PALLET_ETHEREUM_SCHEMA; use log::warn; use sp_storage::{StorageData, StorageKey}; @@ -1954,7 +2238,7 @@ where if let Ok(None) = frontier_backend_client::load_cached_schema::(backend.as_ref()) { let mut cache: Vec<(EthereumStorageSchema, H256)> = Vec::new(); if let Ok(Some(header)) = client.header(BlockId::Number(Zero::zero())) { - cache.push((EthereumStorageSchema::V1, header.hash())); + cache.push((genesis_schema_version, header.hash())); let _ = frontier_backend_client::write_cached_schema::(backend.as_ref(), cache) .map_err(|err| { warn!("Error schema cache insert for genesis: {:?}", err); diff --git a/client/rpc/src/eth_pubsub.rs b/client/rpc/src/eth_pubsub.rs index c51ea0b0b8..f0e713e427 100644 --- a/client/rpc/src/eth_pubsub.rs +++ b/client/rpc/src/eth_pubsub.rs @@ -30,6 +30,7 @@ use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; use sp_runtime::traits::{BlakeTwo256, Block as BlockT, UniqueSaturatedInto}; use std::{collections::BTreeMap, iter, marker::PhantomData, sync::Arc}; +use ethereum::BlockV2 as EthereumBlock; use ethereum_types::{H256, U256}; use fc_rpc_core::{ types::{ @@ -118,7 +119,7 @@ impl SubscriptionResult { pub fn new() -> Self { SubscriptionResult {} } - pub fn new_heads(&self, block: ethereum::BlockV0) -> PubSubResult { + pub fn new_heads(&self, block: EthereumBlock) -> PubSubResult { PubSubResult::Header(Box::new(Rich { inner: Header { hash: Some(H256::from_slice( @@ -149,7 +150,7 @@ impl SubscriptionResult { } pub fn logs( &self, - block: ethereum::BlockV0, + block: EthereumBlock, receipts: Vec, params: &FilteredParams, ) -> Vec { @@ -193,7 +194,7 @@ impl SubscriptionResult { &self, block_hash: H256, ethereum_log: ðereum::Log, - block: ðereum::BlockV0, + block: &EthereumBlock, params: &FilteredParams, ) -> bool { let log = Log { diff --git a/client/rpc/src/lib.rs b/client/rpc/src/lib.rs index 370dca30f1..f45c0fe062 100644 --- a/client/rpc/src/lib.rs +++ b/client/rpc/src/lib.rs @@ -27,13 +27,14 @@ pub use eth::{ NetApiServer, Web3Api, Web3ApiServer, }; pub use eth_pubsub::{EthPubSubApi, EthPubSubApiServer, HexEncodedIdProvider}; -pub use overrides::{OverrideHandle, RuntimeApiStorageOverride, SchemaV1Override, StorageOverride}; - -use ethereum::{ - LegacyTransactionMessage as EthereumTransactionMessage, TransactionV0 as EthereumTransaction, +pub use ethereum::TransactionV2 as EthereumTransaction; +pub use overrides::{ + OverrideHandle, RuntimeApiStorageOverride, SchemaV1Override, SchemaV2Override, StorageOverride, }; + use ethereum_types::{H160, H256}; use evm::ExitError; +pub use fc_rpc_core::types::TransactionMessage; use jsonrpc_core::{Error, ErrorCode, Value}; use pallet_evm::ExitReason; use rustc_hex::ToHex; @@ -250,11 +251,26 @@ pub fn error_on_execution_failure(reason: &ExitReason, data: &[u8]) -> Result<() pub fn public_key(transaction: &EthereumTransaction) -> Result<[u8; 64], sp_io::EcdsaVerifyError> { let mut sig = [0u8; 65]; let mut msg = [0u8; 32]; - sig[0..32].copy_from_slice(&transaction.signature.r()[..]); - sig[32..64].copy_from_slice(&transaction.signature.s()[..]); - sig[64] = transaction.signature.standard_v(); - msg.copy_from_slice(&EthereumTransactionMessage::from(transaction.clone()).hash()[..]); - + match transaction { + EthereumTransaction::Legacy(t) => { + sig[0..32].copy_from_slice(&t.signature.r()[..]); + sig[32..64].copy_from_slice(&t.signature.s()[..]); + sig[64] = t.signature.standard_v(); + msg.copy_from_slice(ðereum::LegacyTransactionMessage::from(t.clone()).hash()[..]); + } + EthereumTransaction::EIP2930(t) => { + sig[0..32].copy_from_slice(&t.r[..]); + sig[32..64].copy_from_slice(&t.s[..]); + sig[64] = t.odd_y_parity as u8; + msg.copy_from_slice(ðereum::EIP2930TransactionMessage::from(t.clone()).hash()[..]); + } + EthereumTransaction::EIP1559(t) => { + sig[0..32].copy_from_slice(&t.r[..]); + sig[32..64].copy_from_slice(&t.s[..]); + sig[64] = t.odd_y_parity as u8; + msg.copy_from_slice(ðereum::EIP1559TransactionMessage::from(t.clone()).hash()[..]); + } + } sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg) } @@ -265,9 +281,9 @@ pub trait EthSigner: Send + Sync { /// Sign a transaction message using the given account in message. fn sign( &self, - message: ethereum::LegacyTransactionMessage, + message: TransactionMessage, address: &H160, - ) -> Result; + ) -> Result; } pub struct EthDevSigner { @@ -303,9 +319,9 @@ impl EthSigner for EthDevSigner { fn sign( &self, - message: ethereum::LegacyTransactionMessage, + message: TransactionMessage, address: &H160, - ) -> Result { + ) -> Result { let mut transaction = None; for secret in &self.keys { @@ -317,29 +333,76 @@ impl EthSigner for EthDevSigner { }; if &key_address == address { - let signing_message = secp256k1::Message::parse_slice(&message.hash()[..]) - .map_err(|_| internal_err("invalid signing message"))?; - let (signature, recid) = secp256k1::sign(&signing_message, secret); - - let v = match message.chain_id { - None => 27 + recid.serialize() as u64, - Some(chain_id) => 2 * chain_id + 35 + recid.serialize() as u64, - }; - let rs = signature.serialize(); - let r = H256::from_slice(&rs[0..32]); - let s = H256::from_slice(&rs[32..64]); - - transaction = Some(ethereum::TransactionV0 { - nonce: message.nonce, - gas_price: message.gas_price, - gas_limit: message.gas_limit, - action: message.action, - value: message.value, - input: message.input.clone(), - signature: ethereum::TransactionSignature::new(v, r, s) - .ok_or(internal_err("signer generated invalid signature"))?, - }); - + match message { + TransactionMessage::Legacy(m) => { + let signing_message = secp256k1::Message::parse_slice(&m.hash()[..]) + .map_err(|_| internal_err("invalid signing message"))?; + let (signature, recid) = secp256k1::sign(&signing_message, secret); + let v = match m.chain_id { + None => 27 + recid.serialize() as u64, + Some(chain_id) => 2 * chain_id + 35 + recid.serialize() as u64, + }; + let rs = signature.serialize(); + let r = H256::from_slice(&rs[0..32]); + let s = H256::from_slice(&rs[32..64]); + transaction = + Some(EthereumTransaction::Legacy(ethereum::LegacyTransaction { + nonce: m.nonce, + gas_price: m.gas_price, + gas_limit: m.gas_limit, + action: m.action, + value: m.value, + input: m.input.clone(), + signature: ethereum::TransactionSignature::new(v, r, s) + .ok_or(internal_err("signer generated invalid signature"))?, + })); + } + TransactionMessage::EIP2930(m) => { + let signing_message = secp256k1::Message::parse_slice(&m.hash()[..]) + .map_err(|_| internal_err("invalid signing message"))?; + let (signature, recid) = secp256k1::sign(&signing_message, secret); + let rs = signature.serialize(); + let r = H256::from_slice(&rs[0..32]); + let s = H256::from_slice(&rs[32..64]); + transaction = + Some(EthereumTransaction::EIP2930(ethereum::EIP2930Transaction { + chain_id: m.chain_id, + nonce: m.nonce, + gas_price: m.gas_price, + gas_limit: m.gas_limit, + action: m.action, + value: m.value, + input: m.input.clone(), + access_list: m.access_list, + odd_y_parity: recid.serialize() != 0, + r, + s, + })); + } + TransactionMessage::EIP1559(m) => { + let signing_message = secp256k1::Message::parse_slice(&m.hash()[..]) + .map_err(|_| internal_err("invalid signing message"))?; + let (signature, recid) = secp256k1::sign(&signing_message, secret); + let rs = signature.serialize(); + let r = H256::from_slice(&rs[0..32]); + let s = H256::from_slice(&rs[32..64]); + transaction = + Some(EthereumTransaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: m.chain_id, + nonce: m.nonce, + max_priority_fee_per_gas: m.max_priority_fee_per_gas, + max_fee_per_gas: m.max_fee_per_gas, + gas_limit: m.gas_limit, + action: m.action, + value: m.value, + input: m.input.clone(), + access_list: m.access_list, + odd_y_parity: recid.serialize() != 0, + r, + s, + })); + } + } break; } } diff --git a/client/rpc/src/overrides/mod.rs b/client/rpc/src/overrides/mod.rs index d7d27f79fb..676db4bf47 100644 --- a/client/rpc/src/overrides/mod.rs +++ b/client/rpc/src/overrides/mod.rs @@ -15,19 +15,21 @@ // along with Substrate. If not, see . use std::collections::BTreeMap; -use ethereum::BlockV0 as EthereumBlock; +use ethereum::BlockV2 as EthereumBlock; use ethereum_types::{H160, H256, U256}; use fp_rpc::{EthereumRuntimeRPCApi, TransactionStatus}; -use sp_api::{BlockId, ProvideRuntimeApi}; +use sp_api::{ApiExt, BlockId, ProvideRuntimeApi}; use sp_io::hashing::{blake2_128, twox_128}; use sp_runtime::traits::Block as BlockT; use std::{marker::PhantomData, sync::Arc}; mod schema_v1_override; +mod schema_v2_override; pub use fc_rpc_core::{EthApiServer, NetApiServer}; use pallet_ethereum::EthereumStorageSchema; pub use schema_v1_override::SchemaV1Override; +pub use schema_v2_override::SchemaV2Override; pub struct OverrideHandle { pub schemas: BTreeMap + Send + Sync>>, @@ -53,6 +55,10 @@ pub trait StorageOverride { &self, block: &BlockId, ) -> Option>; + /// Return the base fee at the given height. + fn base_fee(&self, block: &BlockId) -> Option; + /// Return `true` if the request BlockId is post-eip1559. + fn is_eip1559(&self, block: &BlockId) -> bool; } fn storage_prefix_build(module: &[u8], storage: &[u8]) -> Vec { @@ -111,8 +117,27 @@ where } /// Return the current block. - fn current_block(&self, block: &BlockId) -> Option { - self.client.runtime_api().current_block(&block).ok()? + fn current_block(&self, block: &BlockId) -> Option { + let api = self.client.runtime_api(); + + let api_version = if let Ok(Some(api_version)) = + api.api_version::>(&block) + { + api_version + } else { + return None; + }; + if api_version == 1 { + #[allow(deprecated)] + let old_block = api.current_block_before_version_2(&block).ok()?; + if let Some(block) = old_block { + Some(block.into()) + } else { + None + } + } else { + api.current_block(&block).ok()? + } } /// Return the current receipt. @@ -130,4 +155,24 @@ where .current_transaction_statuses(&block) .ok()? } + + /// Return the base fee at the given post-eip1559 height. + fn base_fee(&self, block: &BlockId) -> Option { + if self.is_eip1559(block) { + self.client.runtime_api().gas_price(&block).ok() + } else { + None + } + } + + fn is_eip1559(&self, block: &BlockId) -> bool { + if let Ok(Some(api_version)) = self + .client + .runtime_api() + .api_version::>(&block) + { + return api_version >= 2; + } + return false; + } } diff --git a/client/rpc/src/overrides/schema_v1_override.rs b/client/rpc/src/overrides/schema_v1_override.rs index e054f078e4..00905a3ddb 100644 --- a/client/rpc/src/overrides/schema_v1_override.rs +++ b/client/rpc/src/overrides/schema_v1_override.rs @@ -15,7 +15,6 @@ // along with Substrate. If not, see . use codec::Decode; -use ethereum::BlockV0 as EthereumBlock; use ethereum_types::{H160, H256, U256}; use fp_rpc::TransactionStatus; use sc_client_api::backend::{AuxStore, Backend, StateBackend, StorageProvider}; @@ -100,11 +99,12 @@ where } /// Return the current block. - fn current_block(&self, block: &BlockId) -> Option { + fn current_block(&self, block: &BlockId) -> Option { self.query_storage::( block, &StorageKey(storage_prefix_build(b"Ethereum", b"CurrentBlock")), ) + .map(Into::into) } /// Return the current receipt. @@ -128,4 +128,13 @@ where )), ) } + + /// Prior to eip-1559 there is no base fee. + fn base_fee(&self, _block: &BlockId) -> Option { + None + } + + fn is_eip1559(&self, _block: &BlockId) -> bool { + false + } } diff --git a/client/rpc/src/overrides/schema_v2_override.rs b/client/rpc/src/overrides/schema_v2_override.rs new file mode 100644 index 0000000000..45d61be573 --- /dev/null +++ b/client/rpc/src/overrides/schema_v2_override.rs @@ -0,0 +1,142 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// This file is part of Frontier. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use codec::Decode; +use ethereum_types::{H160, H256, U256}; +use fp_rpc::TransactionStatus; +use sc_client_api::backend::{AuxStore, Backend, StateBackend, StorageProvider}; +use sp_api::BlockId; +use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; +use sp_runtime::traits::{BlakeTwo256, Block as BlockT}; +use sp_storage::StorageKey; +use std::{marker::PhantomData, sync::Arc}; + +use super::{blake2_128_extend, storage_prefix_build, StorageOverride}; + +/// An override for runtimes that use Schema V1 +pub struct SchemaV2Override { + client: Arc, + _marker: PhantomData<(B, BE)>, +} + +impl SchemaV2Override { + pub fn new(client: Arc) -> Self { + Self { + client, + _marker: PhantomData, + } + } +} + +impl SchemaV2Override +where + C: StorageProvider + AuxStore, + C: HeaderBackend + HeaderMetadata + 'static, + BE: Backend + 'static, + BE::State: StateBackend, + B: BlockT + Send + Sync + 'static, + C: Send + Sync + 'static, +{ + // My attempt using result + // fn query_storage(&self, id: &BlockId, key: &StorageKey) -> Result { + // let raw_data = self.client.storage(id, key)? + // .ok_or("Storage provider returned Ok(None)")?; + // + // Decode::decode(&mut &raw_data.0[..]).map_err(|_| "Could not decode data".into()) + // } + + fn query_storage(&self, id: &BlockId, key: &StorageKey) -> Option { + if let Ok(Some(data)) = self.client.storage(id, key) { + if let Ok(result) = Decode::decode(&mut &data.0[..]) { + return Some(result); + } + } + None + } +} + +impl StorageOverride for SchemaV2Override +where + C: StorageProvider, + C: AuxStore, + C: HeaderBackend, + C: HeaderMetadata + 'static, + BE: Backend + 'static, + BE::State: StateBackend, + Block: BlockT + Send + Sync + 'static, + C: Send + Sync + 'static, +{ + /// For a given account address, returns pallet_evm::AccountCodes. + fn account_code_at(&self, block: &BlockId, address: H160) -> Option> { + let mut key: Vec = storage_prefix_build(b"EVM", b"AccountCodes"); + key.extend(blake2_128_extend(address.as_bytes())); + self.query_storage::>(block, &StorageKey(key)) + } + + /// For a given account address and index, returns pallet_evm::AccountStorages. + fn storage_at(&self, block: &BlockId, address: H160, index: U256) -> Option { + let tmp: &mut [u8; 32] = &mut [0; 32]; + index.to_big_endian(tmp); + + let mut key: Vec = storage_prefix_build(b"EVM", b"AccountStorages"); + key.extend(blake2_128_extend(address.as_bytes())); + key.extend(blake2_128_extend(tmp)); + + self.query_storage::(block, &StorageKey(key)) + } + + /// Return the current block. + fn current_block(&self, block: &BlockId) -> Option { + self.query_storage::( + block, + &StorageKey(storage_prefix_build(b"Ethereum", b"CurrentBlock")), + ) + } + + /// Return the current receipt. + fn current_receipts(&self, block: &BlockId) -> Option> { + self.query_storage::>( + block, + &StorageKey(storage_prefix_build(b"Ethereum", b"CurrentReceipts")), + ) + } + + /// Return the current transaction status. + fn current_transaction_statuses( + &self, + block: &BlockId, + ) -> Option> { + self.query_storage::>( + block, + &StorageKey(storage_prefix_build( + b"Ethereum", + b"CurrentTransactionStatuses", + )), + ) + } + + /// Return the base fee at the given height. + fn base_fee(&self, block: &BlockId) -> Option { + self.query_storage::( + block, + &StorageKey(storage_prefix_build(b"BaseFee", b"BaseFeePerGas")), + ) + } + + fn is_eip1559(&self, _block: &BlockId) -> bool { + true + } +} diff --git a/frame/base-fee/Cargo.toml b/frame/base-fee/Cargo.toml new file mode 100644 index 0000000000..859cbb67a2 --- /dev/null +++ b/frame/base-fee/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "pallet-base-fee" +version = "1.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/frontier/" +description = "EIP-1559 fee utils" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { version = "1.0.101", optional = true } +codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } +frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" } +frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" } +sp-core = { version = "4.0.0-dev", default-features = false, git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" } +sp-runtime = { version = "4.0.0-dev", default-features = false, git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" } +pallet-evm = { path = "../evm", default-features = false } +scale-info = { version = "1.0.0", default-features = false, features = ["derive"] } + +[dev-dependencies] +sp-io = { version = "4.0.0-dev", default-features = false, git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" } + +[features] +default = ["std"] +std = [ + "serde", + "codec/std", + "sp-core/std", + "sp-runtime/std", + "frame-support/std", + "frame-system/std", + "pallet-evm/std", + "scale-info/std", +] diff --git a/frame/base-fee/src/lib.rs b/frame/base-fee/src/lib.rs new file mode 100644 index 0000000000..e1ea9fb7c9 --- /dev/null +++ b/frame/base-fee/src/lib.rs @@ -0,0 +1,446 @@ +// SPDX-License-Identifier: Apache-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2021 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; + use frame_system::pallet_prelude::*; + use sp_core::U256; + use sp_runtime::Permill; + + pub trait BaseFeeThreshold { + fn lower() -> Permill; + fn upper() -> Permill; + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type Event: From + IsType<::Event>; + /// Lower and upper bounds for increasing / decreasing `BaseFeePerGas`. + type Threshold: BaseFeeThreshold; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub base_fee_per_gas: U256, + pub is_active: bool, + pub elasticity: Permill, + _marker: PhantomData, + } + + #[cfg(feature = "std")] + impl GenesisConfig { + pub fn new(base_fee_per_gas: U256, is_active: bool, elasticity: Permill) -> Self { + Self { + base_fee_per_gas, + is_active, + elasticity, + _marker: PhantomData, + } + } + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self { + // 1 GWEI + base_fee_per_gas: U256::from(1_000_000_000), + is_active: true, + elasticity: Permill::from_parts(125_000), + _marker: PhantomData, + } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + >::put(self.base_fee_per_gas); + >::put(self.is_active); + } + } + + #[pallet::type_value] + pub fn DefaultBaseFeePerGas() -> U256 { + U256::from(1_000_000_000) + } + + #[pallet::storage] + #[pallet::getter(fn base_fee_per_gas)] + pub type BaseFeePerGas = StorageValue<_, U256, ValueQuery, DefaultBaseFeePerGas>; + + #[pallet::type_value] + pub fn DefaultIsActive() -> bool { + true + } + + #[pallet::storage] + #[pallet::getter(fn is_active)] + pub type IsActive = StorageValue<_, bool, ValueQuery, DefaultIsActive>; + + #[pallet::type_value] + pub fn DefaultElasticity() -> Permill { + Permill::from_parts(125_000) + } + + #[pallet::storage] + #[pallet::getter(fn elasticity)] + pub type Elasticity = StorageValue<_, Permill, ValueQuery, DefaultElasticity>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + NewBaseFeePerGas(U256), + BaseFeeOverflow, + IsActive(bool), + NewElasticity(Permill), + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_finalize(_n: ::BlockNumber) { + if >::get() { + let lower = T::Threshold::lower(); + let upper = T::Threshold::upper(); + // `target` is the ideal congestion of the network where the base fee should remain unchanged. + // Under normal circumstances the `target` should be 50%. + // If we go below the `target`, the base fee is linearly decreased by the Elasticity delta of lower~target. + // If we go above the `target`, the base fee is linearly increased by the Elasticity delta of upper~target. + // The base fee is fully increased (default 12.5%) if the block is upper full (default 100%). + // The base fee is fully decreased (default 12.5%) if the block is lower empty (default 0%). + let weight = >::block_weight(); + let max_weight = + <::BlockWeights as frame_support::traits::Get<_>>::get() + .max_block; + + // We convert `weight` into block fullness and ensure we are within the lower and upper bound. + let weight_used = + Permill::from_rational(weight.total(), max_weight).clamp(lower, upper); + // After clamp `weighted_used` is always between `lower` and `upper`. + // We scale the block fullness range to the lower/upper range, and the usage represents the + // actual percentage within this new scale. + let usage = (weight_used - lower) / (upper - lower); + + // 50% block fullness is our threshold. + let target = Permill::from_parts(500_000); + if usage > target { + // Above target, increase. + let coef = + Permill::from_parts((usage.deconstruct() - target.deconstruct()) * 2u32); + // How much of the Elasticity is used to mutate base fee. + let coef = >::get() * coef; + >::mutate(|bf| { + if let Some(scaled_basefee) = bf.checked_mul(U256::from(coef.deconstruct())) + { + // Normalize to GWEI. + let increase = scaled_basefee + .checked_div(U256::from(1_000_000)) + .unwrap_or(U256::zero()); + *bf = bf.saturating_add(U256::from(increase)); + Self::deposit_event(Event::NewBaseFeePerGas(*bf)); + } else { + Self::deposit_event(Event::BaseFeeOverflow); + } + }); + } else if usage < target { + // Below target, decrease. + let coef = + Permill::from_parts((target.deconstruct() - usage.deconstruct()) * 2u32); + // How much of the Elasticity is used to mutate base fee. + let coef = >::get() * coef; + >::mutate(|bf| { + if let Some(scaled_basefee) = bf.checked_mul(U256::from(coef.deconstruct())) + { + // Normalize to GWEI. + let decrease = scaled_basefee + .checked_div(U256::from(1_000_000)) + .unwrap_or(U256::zero()); + *bf = bf.saturating_sub(U256::from(decrease)); + Self::deposit_event(Event::NewBaseFeePerGas(*bf)); + } else { + Self::deposit_event(Event::BaseFeeOverflow); + } + }); + } + } + } + } + + #[pallet::call] + impl Pallet { + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn set_base_fee_per_gas(origin: OriginFor, fee: U256) -> DispatchResult { + ensure_root(origin)?; + >::put(fee); + Self::deposit_event(Event::NewBaseFeePerGas(fee)); + Ok(()) + } + + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn set_is_active(origin: OriginFor, is_active: bool) -> DispatchResult { + ensure_root(origin)?; + >::put(is_active); + Self::deposit_event(Event::IsActive(is_active)); + Ok(()) + } + + #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] + pub fn set_elasticity(origin: OriginFor, elasticity: Permill) -> DispatchResult { + ensure_root(origin)?; + >::put(elasticity); + Self::deposit_event(Event::NewElasticity(elasticity)); + Ok(()) + } + } + + impl pallet_evm::FeeCalculator for Pallet { + fn min_gas_price() -> U256 { + >::get() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate as pallet_base_fee; + + use frame_support::{ + assert_ok, pallet_prelude::GenesisBuild, parameter_types, traits::OnFinalize, + weights::DispatchClass, + }; + use sp_core::{H256, U256}; + use sp_io::TestExternalities; + use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + Permill, + }; + + pub fn new_test_ext(base_fee: U256) -> TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + pallet_base_fee::GenesisConfig::::new(base_fee, true, Permill::from_parts(125_000)) + .assimilate_storage(&mut t) + .unwrap(); + TestExternalities::new(t) + } + + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + type Block = frame_system::mocking::MockBlock; + + parameter_types! { + pub const BlockHashCount: u64 = 250; + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(1024); + } + impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Call = Call; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + } + + frame_support::parameter_types! { + pub const Threshold: (u8, u8) = (0, 100); + } + + pub struct BaseFeeThreshold; + impl pallet_base_fee::BaseFeeThreshold for BaseFeeThreshold { + fn lower() -> Permill { + Permill::zero() + } + fn upper() -> Permill { + Permill::from_parts(1_000_000) + } + } + + impl Config for Test { + type Event = Event; + type Threshold = BaseFeeThreshold; + } + + frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + BaseFee: pallet_base_fee::{Pallet, Call, Storage, Event}, + } + ); + + #[test] + fn should_not_overflow_u256() { + let base_fee = U256::max_value(); + new_test_ext(base_fee).execute_with(|| { + let init = BaseFee::base_fee_per_gas(); + System::register_extra_weight_unchecked(1000000000000, DispatchClass::Normal); + BaseFee::on_finalize(System::block_number()); + assert_eq!(BaseFee::base_fee_per_gas(), init); + }); + } + + #[test] + fn should_handle_zero() { + let base_fee = U256::zero(); + new_test_ext(base_fee).execute_with(|| { + let init = BaseFee::base_fee_per_gas(); + BaseFee::on_finalize(System::block_number()); + assert_eq!(BaseFee::base_fee_per_gas(), init); + }); + } + + #[test] + fn should_handle_consecutive_empty_blocks() { + let base_fee = U256::from(1_000_000_000); + new_test_ext(base_fee).execute_with(|| { + for _ in 0..10000 { + BaseFee::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + } + assert_eq!( + BaseFee::base_fee_per_gas(), + // 8 is the lowest number which's 12.5% is >= 1. + U256::from(7) + ); + }); + } + + #[test] + fn should_handle_consecutive_full_blocks() { + let base_fee = U256::from(1_000_000_000); + new_test_ext(base_fee).execute_with(|| { + for _ in 0..10000 { + // Register max weight in block. + System::register_extra_weight_unchecked(1000000000000, DispatchClass::Normal); + BaseFee::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + } + assert_eq!( + BaseFee::base_fee_per_gas(), + // Max value allowed in the algorithm before overflowing U256. + U256::from_dec_str( + "930583037201699994746877284806656508753618758732556029383742480470471799" + ) + .unwrap() + ); + }); + } + + #[test] + fn should_increase_total_base_fee() { + let base_fee = U256::from(1_000_000_000); + new_test_ext(base_fee).execute_with(|| { + assert_eq!(BaseFee::base_fee_per_gas(), U256::from(1000000000)); + // Register max weight in block. + System::register_extra_weight_unchecked(1000000000000, DispatchClass::Normal); + BaseFee::on_finalize(System::block_number()); + // Expect the base fee to increase by 12.5%. + assert_eq!(BaseFee::base_fee_per_gas(), U256::from(1125000000)); + }); + } + + #[test] + fn should_increase_delta_of_base_fee() { + let base_fee = U256::from(1_000_000_000); + new_test_ext(base_fee).execute_with(|| { + assert_eq!(BaseFee::base_fee_per_gas(), U256::from(1000000000)); + // Register 75% capacity in block weight. + System::register_extra_weight_unchecked(750000000000, DispatchClass::Normal); + BaseFee::on_finalize(System::block_number()); + // Expect a 6.25% increase in base fee for a target capacity of 50% ((75/50)-1 = 0.5 * 0.125 = 0.0625). + assert_eq!(BaseFee::base_fee_per_gas(), U256::from(1062500000)); + }); + } + + #[test] + fn should_idle_base_fee() { + let base_fee = U256::from(1_000_000_000); + new_test_ext(base_fee).execute_with(|| { + assert_eq!(BaseFee::base_fee_per_gas(), U256::from(1000000000)); + // Register half capacity in block weight. + System::register_extra_weight_unchecked(500000000000, DispatchClass::Normal); + BaseFee::on_finalize(System::block_number()); + // Expect the base fee to remain unchanged + assert_eq!(BaseFee::base_fee_per_gas(), U256::from(1000000000)); + }); + } + + #[test] + fn set_base_fee_per_gas_dispatchable() { + let base_fee = U256::from(1_000_000_000); + new_test_ext(base_fee).execute_with(|| { + assert_eq!(BaseFee::base_fee_per_gas(), U256::from(1000000000)); + assert_ok!(BaseFee::set_base_fee_per_gas(Origin::root(), U256::from(1))); + assert_eq!(BaseFee::base_fee_per_gas(), U256::from(1)); + }); + } + + #[test] + fn set_is_active_dispatchable() { + let base_fee = U256::from(1_000_000_000); + new_test_ext(base_fee).execute_with(|| { + assert_eq!(BaseFee::is_active(), true); + assert_ok!(BaseFee::set_is_active(Origin::root(), false)); + assert_eq!(BaseFee::is_active(), false); + }); + } + + #[test] + fn set_elasticity_dispatchable() { + let base_fee = U256::from(1_000_000_000); + new_test_ext(base_fee).execute_with(|| { + assert_eq!(BaseFee::elasticity(), Permill::from_parts(125_000)); + assert_ok!(BaseFee::set_elasticity( + Origin::root(), + Permill::from_parts(1_000) + )); + assert_eq!(BaseFee::elasticity(), Permill::from_parts(1_000)); + }); + } +} diff --git a/frame/dynamic-fee/src/lib.rs b/frame/dynamic-fee/src/lib.rs index d7f822d76f..8a0d6c0e70 100644 --- a/frame/dynamic-fee/src/lib.rs +++ b/frame/dynamic-fee/src/lib.rs @@ -130,7 +130,7 @@ pub mod pallet { fn create_inherent(data: &InherentData) -> Option { let target = data.get_data::(&INHERENT_IDENTIFIER).ok()??; - Some(Call::note_min_gas_price_target {target}) + Some(Call::note_min_gas_price_target { target }) } fn check_inherent( diff --git a/frame/ethereum/Cargo.toml b/frame/ethereum/Cargo.toml index 5662685051..63f6f398e1 100644 --- a/frame/ethereum/Cargo.toml +++ b/frame/ethereum/Cargo.toml @@ -20,8 +20,8 @@ sp-runtime = { version = "4.0.0-dev", default-features = false, git = "https://g sp-std = { version = "4.0.0-dev", default-features = false, git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" } sp-io = { version = "4.0.0-dev", default-features = false, git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" } fp-evm = { version = "3.0.0-dev", default-features = false, path = "../../primitives/evm" } -evm = { version = "0.30.0", features = ["with-codec"], default-features = false } -ethereum = { version = "0.9.0", default-features = false, features = ["with-codec"] } +evm = { version = "0.33.0", features = ["with-codec"], default-features = false } +ethereum = { version = "0.10.0", default-features = false, features = ["with-codec"] } ethereum-types = { version = "0.12", default-features = false } rlp = { version = "0.5", default-features = false } sha3 = { version = "0.8", default-features = false } diff --git a/frame/ethereum/src/lib.rs b/frame/ethereum/src/lib.rs index 5efc055f06..f9954a298c 100644 --- a/frame/ethereum/src/lib.rs +++ b/frame/ethereum/src/lib.rs @@ -48,8 +48,8 @@ use sp_runtime::{ use sp_std::{marker::PhantomData, prelude::*}; pub use ethereum::{ - BlockV0 as Block, LegacyTransactionMessage, Log, Receipt, TransactionAction, - TransactionV0 as Transaction, + BlockV2 as Block, LegacyTransactionMessage, Log, Receipt, TransactionAction, + TransactionV2 as Transaction, }; pub use fp_rpc::TransactionStatus; @@ -73,6 +73,20 @@ where } } +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)] +struct TransactionData { + action: TransactionAction, + input: Vec, + nonce: U256, + gas_limit: U256, + gas_price: Option, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, + value: U256, + chain_id: Option, + access_list: Vec<(H160, Vec)>, +} + pub struct EnsureEthereumTransaction; impl> + From> EnsureOrigin for EnsureEthereumTransaction @@ -223,7 +237,9 @@ pub mod pallet { OriginFor: Into>>, { /// Transact an Ethereum transaction. - #[pallet::weight(::GasWeightMapping::gas_to_weight(transaction.gas_limit.unique_saturated_into()))] + #[pallet::weight(::GasWeightMapping::gas_to_weight( + Pallet::::transaction_data(transaction).gas_limit.unique_saturated_into() + ))] pub fn transact( origin: OriginFor, transaction: Transaction, @@ -261,7 +277,7 @@ pub mod pallet { /// The current Ethereum block. #[pallet::storage] - pub(super) type CurrentBlock = StorageValue<_, ethereum::BlockV0>; + pub(super) type CurrentBlock = StorageValue<_, ethereum::BlockV2>; /// The current Ethereum receipts. #[pallet::storage] @@ -285,21 +301,91 @@ pub mod pallet { >::store_block(false, U256::zero()); frame_support::storage::unhashed::put::( &PALLET_ETHEREUM_SCHEMA, - &EthereumStorageSchema::V1, + &EthereumStorageSchema::V2, ); } } } impl Pallet { + fn transaction_data(transaction: &Transaction) -> TransactionData { + match transaction { + Transaction::Legacy(t) => TransactionData { + action: t.action, + input: t.input.clone(), + nonce: t.nonce, + gas_limit: t.gas_limit, + gas_price: Some(t.gas_price), + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + value: t.value, + chain_id: t.signature.chain_id(), + access_list: Vec::new(), + }, + Transaction::EIP2930(t) => TransactionData { + action: t.action, + input: t.input.clone(), + nonce: t.nonce, + gas_limit: t.gas_limit, + gas_price: Some(t.gas_price), + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + value: t.value, + chain_id: Some(t.chain_id), + access_list: t + .access_list + .iter() + .map(|d| (d.address, d.slots.clone())) + .collect(), + }, + Transaction::EIP1559(t) => TransactionData { + action: t.action, + input: t.input.clone(), + nonce: t.nonce, + gas_limit: t.gas_limit, + gas_price: None, + max_fee_per_gas: Some(t.max_fee_per_gas), + max_priority_fee_per_gas: Some(t.max_priority_fee_per_gas), + value: t.value, + chain_id: Some(t.chain_id), + access_list: t + .access_list + .iter() + .map(|d| (d.address, d.slots.clone())) + .collect(), + }, + } + } + fn recover_signer(transaction: &Transaction) -> Option { let mut sig = [0u8; 65]; let mut msg = [0u8; 32]; - sig[0..32].copy_from_slice(&transaction.signature.r()[..]); - sig[32..64].copy_from_slice(&transaction.signature.s()[..]); - sig[64] = transaction.signature.standard_v(); - msg.copy_from_slice(&LegacyTransactionMessage::from(transaction.clone()).hash()[..]); - + match transaction { + Transaction::Legacy(t) => { + sig[0..32].copy_from_slice(&t.signature.r()[..]); + sig[32..64].copy_from_slice(&t.signature.s()[..]); + sig[64] = t.signature.standard_v(); + msg.copy_from_slice( + ðereum::LegacyTransactionMessage::from(t.clone()).hash()[..], + ); + } + Transaction::EIP2930(t) => { + sig[0..32].copy_from_slice(&t.r[..]); + sig[32..64].copy_from_slice(&t.s[..]); + sig[64] = t.odd_y_parity as u8; + msg.copy_from_slice( + ðereum::EIP2930TransactionMessage::from(t.clone()).hash()[..], + ); + } + Transaction::EIP1559(t) => { + sig[0..32].copy_from_slice(&t.r[..]); + sig[32..64].copy_from_slice(&t.s[..]); + sig[64] = t.odd_y_parity as u8; + msg.copy_from_slice( + ðereum::EIP1559TransactionMessage::from(t.clone()).hash()[..], + ); + } + } let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg).ok()?; Some(H160::from(H256::from_slice( Keccak256::digest(&pubkey).as_slice(), @@ -371,19 +457,25 @@ impl Pallet { // This is the case for all controls except those concerning the nonce. fn validate_transaction_common( origin: H160, - transaction: &Transaction, - ) -> Result { + transaction_data: &TransactionData, + ) -> Result<(U256, u64), TransactionValidityError> { + let gas_limit = transaction_data.gas_limit; + // We must ensure a transaction can pay the cost of its data bytes. // If it can't it should not be included in a block. let mut gasometer = evm::gasometer::Gasometer::new( - transaction.gas_limit.low_u64(), + gas_limit.low_u64(), ::config(), ); - let transaction_cost = match transaction.action { - TransactionAction::Call(_) => evm::gasometer::call_transaction_cost(&transaction.input), - TransactionAction::Create => { - evm::gasometer::create_transaction_cost(&transaction.input) - } + let transaction_cost = match transaction_data.action { + TransactionAction::Call(_) => evm::gasometer::call_transaction_cost( + &transaction_data.input, + &transaction_data.access_list, + ), + TransactionAction::Create => evm::gasometer::create_transaction_cost( + &transaction_data.input, + &transaction_data.access_list, + ), }; if gasometer.record_transaction(transaction_cost).is_err() { return Err(InvalidTransaction::Custom( @@ -392,7 +484,7 @@ impl Pallet { .into()); } - if let Some(chain_id) = transaction.signature.chain_id() { + if let Some(chain_id) = transaction_data.chain_id { if chain_id != T::ChainId::get() { return Err(InvalidTransaction::Custom( TransactionValidationError::InvalidChainId as u8, @@ -401,35 +493,56 @@ impl Pallet { } } - if transaction.gas_limit >= T::BlockGasLimit::get() { + if gas_limit >= T::BlockGasLimit::get() { return Err(InvalidTransaction::Custom( TransactionValidationError::GasLimitTooHigh as u8, ) .into()); } + let base_fee = T::FeeCalculator::min_gas_price(); + let mut priority = 0; + + let gas_price = if let Some(gas_price) = transaction_data.gas_price { + // Legacy and EIP-2930 transactions. + // Handle priority here. On legacy transaction everything in gas_price except + // the current base_fee is considered a tip to the miner and thus the priority. + priority = gas_price.saturating_sub(base_fee).unique_saturated_into(); + gas_price + } else if let Some(max_fee_per_gas) = transaction_data.max_fee_per_gas { + // EIP-1559 transactions. + max_fee_per_gas + } else { + return Err(InvalidTransaction::Payment.into()); + }; + + if gas_price < base_fee { + return Err(InvalidTransaction::Payment.into()); + } + + let mut fee = gas_price.saturating_mul(gas_limit); + if let Some(max_priority_fee_per_gas) = transaction_data.max_priority_fee_per_gas { + // EIP-1559 transaction priority is determined by `max_priority_fee_per_gas`. + // If the transaction do not include this optional parameter, priority is now considered zero. + priority = max_priority_fee_per_gas.unique_saturated_into(); + // Add the priority tip to the payable fee. + fee = fee.saturating_add(max_priority_fee_per_gas.saturating_mul(gas_limit)); + } + let account_data = pallet_evm::Pallet::::account_basic(&origin); - if account_data.balance < transaction.value { + if account_data.balance < transaction_data.value { return Err(InvalidTransaction::Custom( TransactionValidationError::InsufficientFundsForTransfer as u8, ) .into()); } - - let fee = transaction.gas_price.saturating_mul(transaction.gas_limit); - let total_payment = transaction.value.saturating_add(fee); + let total_payment = transaction_data.value.saturating_add(fee); if account_data.balance < total_payment { return Err(InvalidTransaction::Payment.into()); } - let min_gas_price = T::FeeCalculator::min_gas_price(); - - if transaction.gas_price < min_gas_price { - return Err(InvalidTransaction::Payment.into()); - } - - Ok(account_data.nonce) + Ok((account_data.nonce, priority)) } // Controls that must be performed by the pool. @@ -439,21 +552,25 @@ impl Pallet { origin: H160, transaction: &Transaction, ) -> TransactionValidity { - let account_nonce = Self::validate_transaction_common(origin, transaction)?; + let transaction_data = Pallet::::transaction_data(&transaction); + let transaction_nonce = transaction_data.nonce; - if transaction.nonce < account_nonce { + let (account_nonce, priority) = + Self::validate_transaction_common(origin, &transaction_data)?; + + if transaction_nonce < account_nonce { return Err(InvalidTransaction::Stale.into()); } // The tag provides and requires must be filled correctly according to the nonce. let mut builder = ValidTransactionBuilder::default() - .and_provides((origin, transaction.nonce)) - .priority(transaction.gas_price.unique_saturated_into()); + .and_provides((origin, transaction_nonce)) + .priority(priority); // In the context of the pool, a transaction with // too high a nonce is still considered valid - if transaction.nonce > account_nonce { - if let Some(prev_nonce) = transaction.nonce.checked_sub(1.into()) { + if transaction_nonce > account_nonce { + if let Some(prev_nonce) = transaction_nonce.checked_sub(1.into()) { builder = builder.and_requires((origin, prev_nonce)) } } @@ -466,17 +583,8 @@ impl Pallet { H256::from_slice(Keccak256::digest(&rlp::encode(&transaction)).as_slice()); let transaction_index = Pending::::get().len() as u32; - let (to, _, info) = Self::execute( - source, - transaction.input.clone(), - transaction.value, - transaction.gas_limit, - Some(transaction.gas_price), - Some(transaction.nonce), - transaction.action, - None, - ) - .expect("transaction is already validated; error indicates that the block is invalid"); + let (to, _, info) = Self::execute(source, &transaction, None) + .expect("transaction is already validated; error indicates that the block is invalid"); let (reason, status, used_gas, dest) = match info { CallOrCreateInfo::Call(info) => ( @@ -552,7 +660,7 @@ impl Pallet { } /// Get current block. - pub fn current_block() -> Option { + pub fn current_block() -> Option { CurrentBlock::::get() } @@ -569,14 +677,81 @@ impl Pallet { /// Execute an Ethereum transaction. pub fn execute( from: H160, - input: Vec, - value: U256, - gas_limit: U256, - gas_price: Option, - nonce: Option, - action: TransactionAction, + transaction: &Transaction, config: Option, ) -> Result<(Option, Option, CallOrCreateInfo), DispatchError> { + let ( + input, + value, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + action, + access_list, + ) = { + match transaction { + // max_fee_per_gas and max_priority_fee_per_gas in legacy and 2930 transactions is + // the provided gas_price. + Transaction::Legacy(t) => { + let base_fee = T::FeeCalculator::min_gas_price(); + let priority_fee = t + .gas_price + .checked_sub(base_fee) + .ok_or_else(|| DispatchError::Other("Gas price too low"))?; + ( + t.input.clone(), + t.value, + t.gas_limit, + Some(base_fee), + Some(priority_fee), + Some(t.nonce), + t.action, + Vec::new(), + ) + } + Transaction::EIP2930(t) => { + let base_fee = T::FeeCalculator::min_gas_price(); + let priority_fee = t + .gas_price + .checked_sub(base_fee) + .ok_or_else(|| DispatchError::Other("Gas price too low"))?; + let access_list: Vec<(H160, Vec)> = t + .access_list + .iter() + .map(|item| (item.address, item.slots.clone())) + .collect(); + ( + t.input.clone(), + t.value, + t.gas_limit, + Some(base_fee), + Some(priority_fee), + Some(t.nonce), + t.action, + access_list, + ) + } + Transaction::EIP1559(t) => { + let access_list: Vec<(H160, Vec)> = t + .access_list + .iter() + .map(|item| (item.address, item.slots.clone())) + .collect(); + ( + t.input.clone(), + t.value, + t.gas_limit, + Some(t.max_fee_per_gas), + Some(t.max_priority_fee_per_gas), + Some(t.nonce), + t.action, + access_list, + ) + } + } + }; + match action { ethereum::TransactionAction::Call(target) => { let res = T::Runner::call( @@ -585,8 +760,10 @@ impl Pallet { input.clone(), value, gas_limit.low_u64(), - gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, nonce, + access_list, config.as_ref().unwrap_or(T::config()), ) .map_err(Into::into)?; @@ -599,8 +776,10 @@ impl Pallet { input.clone(), value, gas_limit.low_u64(), - gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, nonce, + access_list, config.as_ref().unwrap_or(T::config()), ) .map_err(Into::into)?; @@ -616,17 +795,19 @@ impl Pallet { /// (just before applying the extrinsic). pub fn validate_transaction_in_block( origin: H160, - transaction: ðereum::TransactionV0, + transaction: &Transaction, ) -> Result<(), TransactionValidityError> { - let account_nonce = Self::validate_transaction_common(origin, transaction)?; + let transaction_data = Pallet::::transaction_data(&transaction); + let transaction_nonce = transaction_data.nonce; + let (account_nonce, _) = Self::validate_transaction_common(origin, &transaction_data)?; // In the context of the block, a transaction with a nonce that is // too high should be considered invalid and make the whole block invalid. - if transaction.nonce > account_nonce { + if transaction_nonce > account_nonce { Err(TransactionValidityError::Invalid( InvalidTransaction::Future, )) - } else if transaction.nonce < account_nonce { + } else if transaction_nonce < account_nonce { Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)) } else { Ok(()) @@ -645,6 +826,7 @@ pub enum ReturnValue { pub enum EthereumStorageSchema { Undefined, V1, + V2, } impl Default for EthereumStorageSchema { diff --git a/frame/ethereum/src/mock.rs b/frame/ethereum/src/mock.rs index 5a8a385722..3e69333b15 100644 --- a/frame/ethereum/src/mock.rs +++ b/frame/ethereum/src/mock.rs @@ -155,7 +155,8 @@ impl pallet_evm::Config for Test { type AddressMapping = HashedAddressMapping; type Currency = Balances; type Event = Event; - type Precompiles = (); + type PrecompilesType = (); + type PrecompilesValue = (); type Runner = pallet_evm::runner::stack::Runner; type ChainId = ChainId; type BlockGasLimit = BlockGasLimit; @@ -209,7 +210,7 @@ impl fp_self_contained::SelfContainedCall for Call { ) -> Option>> { use sp_runtime::traits::Dispatchable as _; match self { - call @ Call::Ethereum(crate::Call::transact(_)) => { + call @ Call::Ethereum(crate::Call::transact { .. }) => { Some(call.dispatch(Origin::from(crate::RawOrigin::EthereumTransaction(info)))) } _ => None, @@ -276,7 +277,7 @@ pub fn storage_address(sender: H160, slot: H256) -> H256 { )) } -pub struct UnsignedTransaction { +pub struct LegacyUnsignedTransaction { pub nonce: U256, pub gas_price: U256, pub gas_limit: U256, @@ -285,7 +286,7 @@ pub struct UnsignedTransaction { pub input: Vec, } -impl UnsignedTransaction { +impl LegacyUnsignedTransaction { fn signing_rlp_append(&self, s: &mut RlpStream) { s.begin_list(9); s.append(&self.nonce); @@ -325,7 +326,7 @@ impl UnsignedTransaction { ) .unwrap(); - Transaction { + Transaction::Legacy(ethereum::LegacyTransaction { nonce: self.nonce, gas_price: self.gas_price, gas_limit: self.gas_limit, @@ -333,6 +334,107 @@ impl UnsignedTransaction { value: self.value, input: self.input.clone(), signature: sig, - } + }) + } +} + +pub struct EIP2930UnsignedTransaction { + pub nonce: U256, + pub gas_price: U256, + pub gas_limit: U256, + pub action: TransactionAction, + pub value: U256, + pub input: Vec, +} + +impl EIP2930UnsignedTransaction { + pub fn sign(&self, secret: &H256, chain_id: Option) -> Transaction { + let secret = { + let mut sk: [u8; 32] = [0u8; 32]; + sk.copy_from_slice(&secret[0..]); + libsecp256k1::SecretKey::parse(&sk).unwrap() + }; + let chain_id = chain_id.unwrap_or(ChainId::get()); + let msg = ethereum::EIP2930TransactionMessage { + chain_id: chain_id, + nonce: self.nonce, + gas_price: self.gas_price, + gas_limit: self.gas_limit, + action: self.action, + value: self.value, + input: self.input.clone(), + access_list: vec![], + }; + let signing_message = libsecp256k1::Message::parse_slice(&msg.hash()[..]).unwrap(); + + let (signature, recid) = libsecp256k1::sign(&signing_message, &secret); + let rs = signature.serialize(); + let r = H256::from_slice(&rs[0..32]); + let s = H256::from_slice(&rs[32..64]); + Transaction::EIP2930(ethereum::EIP2930Transaction { + chain_id: msg.chain_id, + nonce: msg.nonce, + gas_price: msg.gas_price, + gas_limit: msg.gas_limit, + action: msg.action, + value: msg.value, + input: msg.input.clone(), + access_list: msg.access_list, + odd_y_parity: recid.serialize() != 0, + r, + s, + }) + } +} + +pub struct EIP1559UnsignedTransaction { + pub nonce: U256, + pub max_priority_fee_per_gas: U256, + pub max_fee_per_gas: U256, + pub gas_limit: U256, + pub action: TransactionAction, + pub value: U256, + pub input: Vec, +} + +impl EIP1559UnsignedTransaction { + pub fn sign(&self, secret: &H256, chain_id: Option) -> Transaction { + let secret = { + let mut sk: [u8; 32] = [0u8; 32]; + sk.copy_from_slice(&secret[0..]); + libsecp256k1::SecretKey::parse(&sk).unwrap() + }; + let chain_id = chain_id.unwrap_or(ChainId::get()); + let msg = ethereum::EIP1559TransactionMessage { + chain_id: chain_id, + nonce: self.nonce, + max_priority_fee_per_gas: self.max_priority_fee_per_gas, + max_fee_per_gas: self.max_fee_per_gas, + gas_limit: self.gas_limit, + action: self.action, + value: self.value, + input: self.input.clone(), + access_list: vec![], + }; + let signing_message = libsecp256k1::Message::parse_slice(&msg.hash()[..]).unwrap(); + + let (signature, recid) = libsecp256k1::sign(&signing_message, &secret); + let rs = signature.serialize(); + let r = H256::from_slice(&rs[0..32]); + let s = H256::from_slice(&rs[32..64]); + Transaction::EIP1559(ethereum::EIP1559Transaction { + chain_id: msg.chain_id, + nonce: msg.nonce, + max_priority_fee_per_gas: msg.max_priority_fee_per_gas, + max_fee_per_gas: msg.max_fee_per_gas, + gas_limit: msg.gas_limit, + action: msg.action, + value: msg.value, + input: msg.input.clone(), + access_list: msg.access_list, + odd_y_parity: recid.serialize() != 0, + r, + s, + }) } } diff --git a/frame/ethereum/src/tests/eip1559.rs b/frame/ethereum/src/tests/eip1559.rs new file mode 100644 index 0000000000..7d8f8675d9 --- /dev/null +++ b/frame/ethereum/src/tests/eip1559.rs @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: Apache-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2020 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Consensus extension module tests for BABE consensus. + +use super::*; + +fn eip1559_erc20_creation_unsigned_transaction() -> EIP1559UnsignedTransaction { + EIP1559UnsignedTransaction { + nonce: U256::zero(), + max_priority_fee_per_gas: U256::from(1), + max_fee_per_gas: U256::from(1), + gas_limit: U256::from(0x100000), + action: ethereum::TransactionAction::Create, + value: U256::zero(), + input: FromHex::from_hex(ERC20_CONTRACT_BYTECODE).unwrap(), + } +} + +fn eip1559_erc20_creation_transaction(account: &AccountInfo) -> Transaction { + eip1559_erc20_creation_unsigned_transaction().sign(&account.private_key, None) +} + +#[test] +fn transaction_should_increment_nonce() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + ext.execute_with(|| { + let t = eip1559_erc20_creation_transaction(alice); + assert_ok!(Ethereum::execute(alice.address, &t, None,)); + assert_eq!(EVM::account_basic(&alice.address).nonce, U256::from(1)); + }); +} + +#[test] +fn transaction_without_enough_gas_should_not_work() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + ext.execute_with(|| { + let mut transaction = eip1559_erc20_creation_transaction(alice); + match &mut transaction { + Transaction::EIP1559(t) => t.max_fee_per_gas = U256::from(11_000_000), + _ => {} + } + + let call = crate::Call::::transact { transaction }; + let source = call.check_self_contained().unwrap().unwrap(); + + assert_err!( + call.validate_self_contained(&source).unwrap(), + InvalidTransaction::Payment + ); + }); +} + +#[test] +fn transaction_with_to_low_nonce_should_not_work() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + ext.execute_with(|| { + // nonce is 0 + let mut transaction = eip1559_erc20_creation_unsigned_transaction(); + transaction.nonce = U256::from(1); + let signed = transaction.sign(&alice.private_key, None); + let call = crate::Call::::transact { + transaction: signed, + }; + let source = call.check_self_contained().unwrap().unwrap(); + + assert_eq!( + call.validate_self_contained(&source).unwrap(), + ValidTransactionBuilder::default() + .and_provides((alice.address, U256::from(1))) + .priority(1u64) + .and_requires((alice.address, U256::from(0))) + .build() + ); + + let t = eip1559_erc20_creation_transaction(alice); + + // nonce is 1 + assert_ok!(Ethereum::execute(alice.address, &t, None,)); + + transaction.nonce = U256::from(0); + + let signed2 = transaction.sign(&alice.private_key, None); + let call2 = crate::Call::::transact { + transaction: signed2, + }; + let source2 = call2.check_self_contained().unwrap().unwrap(); + + assert_err!( + call2.validate_self_contained(&source2).unwrap(), + InvalidTransaction::Stale + ); + }); +} + +#[test] +fn transaction_with_to_hight_nonce_should_fail_in_block() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + ext.execute_with(|| { + let mut transaction = eip1559_erc20_creation_unsigned_transaction(); + transaction.nonce = U256::one(); + + let signed = transaction.sign(&alice.private_key, None); + let call = crate::Call::::transact { + transaction: signed, + }; + let source = call.check_self_contained().unwrap().unwrap(); + let extrinsic = fp_self_contained::CheckedExtrinsic::<_, _, SignedExtra, _> { + signed: fp_self_contained::CheckedSignature::SelfContained(source), + function: Call::Ethereum(call), + }; + use frame_support::weights::GetDispatchInfo as _; + let dispatch_info = extrinsic.get_dispatch_info(); + assert_err!( + extrinsic.apply::(&dispatch_info, 0), + TransactionValidityError::Invalid(InvalidTransaction::Future) + ); + }); +} + +#[test] +fn transaction_with_invalid_chain_id_should_fail_in_block() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + ext.execute_with(|| { + let transaction = + eip1559_erc20_creation_unsigned_transaction().sign(&alice.private_key, Some(1)); + + let call = crate::Call::::transact { transaction }; + let source = call.check_self_contained().unwrap().unwrap(); + let extrinsic = fp_self_contained::CheckedExtrinsic::<_, _, SignedExtra, _> { + signed: fp_self_contained::CheckedSignature::SelfContained(source), + function: Call::Ethereum(call), + }; + use frame_support::weights::GetDispatchInfo as _; + let dispatch_info = extrinsic.get_dispatch_info(); + assert_err!( + extrinsic.apply::(&dispatch_info, 0), + TransactionValidityError::Invalid(InvalidTransaction::Custom( + crate::TransactionValidationError::InvalidChainId as u8, + )) + ); + }); +} + +#[test] +fn contract_constructor_should_get_executed() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + let erc20_address = contract_address(alice.address, 0); + let alice_storage_address = storage_address(alice.address, H256::zero()); + + ext.execute_with(|| { + let t = eip1559_erc20_creation_transaction(alice); + + assert_ok!(Ethereum::execute(alice.address, &t, None,)); + assert_eq!( + EVM::account_storages(erc20_address, alice_storage_address), + H256::from_str("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + .unwrap() + ) + }); +} + +#[test] +fn source_should_be_derived_from_signature() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + let erc20_address = contract_address(alice.address, 0); + let alice_storage_address = storage_address(alice.address, H256::zero()); + + ext.execute_with(|| { + Ethereum::transact( + RawOrigin::EthereumTransaction(alice.address).into(), + eip1559_erc20_creation_transaction(alice), + ) + .expect("Failed to execute transaction"); + + // We verify the transaction happened with alice account. + assert_eq!( + EVM::account_storages(erc20_address, alice_storage_address), + H256::from_str("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + .unwrap() + ) + }); +} + +#[test] +fn contract_should_be_created_at_given_address() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + let erc20_address = contract_address(alice.address, 0); + + ext.execute_with(|| { + let t = eip1559_erc20_creation_transaction(alice); + assert_ok!(Ethereum::execute(alice.address, &t, None,)); + assert_ne!(EVM::account_codes(erc20_address).len(), 0); + }); +} + +#[test] +fn transaction_should_generate_correct_gas_used() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + let expected_gas = U256::from(891328); + + ext.execute_with(|| { + let t = eip1559_erc20_creation_transaction(alice); + let (_, _, info) = Ethereum::execute(alice.address, &t, None).unwrap(); + + match info { + CallOrCreateInfo::Create(info) => { + assert_eq!(info.used_gas, expected_gas); + } + CallOrCreateInfo::Call(_) => panic!("expected create info"), + } + }); +} + +#[test] +fn call_should_handle_errors() { + // pragma solidity ^0.6.6; + // contract Test { + // function foo() external pure returns (bool) { + // return true; + // } + // function bar() external pure { + // require(false, "error_msg"); + // } + // } + let contract: &str = "608060405234801561001057600080fd5b50610113806100206000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c8063c2985578146037578063febb0f7e146057575b600080fd5b603d605f565b604051808215151515815260200191505060405180910390f35b605d6068565b005b60006001905090565b600060db576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260098152602001807f6572726f725f6d7367000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b56fea2646970667358221220fde68a3968e0e99b16fabf9b2997a78218b32214031f8e07e2c502daf603a69e64736f6c63430006060033"; + + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + ext.execute_with(|| { + let t = EIP1559UnsignedTransaction { + nonce: U256::zero(), + max_priority_fee_per_gas: U256::from(1), + max_fee_per_gas: U256::from(1), + gas_limit: U256::from(0x100000), + action: ethereum::TransactionAction::Create, + value: U256::zero(), + input: FromHex::from_hex(contract).unwrap(), + } + .sign(&alice.private_key, None); + assert_ok!(Ethereum::execute(alice.address, &t, None,)); + + let contract_address: Vec = + FromHex::from_hex("32dcab0ef3fb2de2fce1d2e0799d36239671f04a").unwrap(); + let foo: Vec = FromHex::from_hex("c2985578").unwrap(); + let bar: Vec = FromHex::from_hex("febb0f7e").unwrap(); + + let t2 = EIP1559UnsignedTransaction { + nonce: U256::from(1), + max_priority_fee_per_gas: U256::from(1), + max_fee_per_gas: U256::from(1), + gas_limit: U256::from(0x100000), + action: TransactionAction::Call(H160::from_slice(&contract_address)), + value: U256::zero(), + input: foo, + } + .sign(&alice.private_key, None); + + // calling foo will succeed + let (_, _, info) = Ethereum::execute(alice.address, &t2, None).unwrap(); + + match info { + CallOrCreateInfo::Call(info) => { + assert_eq!( + info.value.to_hex::(), + "0000000000000000000000000000000000000000000000000000000000000001".to_owned() + ); + } + CallOrCreateInfo::Create(_) => panic!("expected call info"), + } + + let t3 = EIP1559UnsignedTransaction { + nonce: U256::from(2), + max_priority_fee_per_gas: U256::from(1), + max_fee_per_gas: U256::from(1), + gas_limit: U256::from(0x100000), + action: TransactionAction::Call(H160::from_slice(&contract_address)), + value: U256::zero(), + input: bar, + } + .sign(&alice.private_key, None); + + // calling should always succeed even if the inner EVM execution fails. + Ethereum::execute(alice.address, &t3, None).ok().unwrap(); + }); +} diff --git a/frame/ethereum/src/tests/eip2930.rs b/frame/ethereum/src/tests/eip2930.rs new file mode 100644 index 0000000000..3d41fc16ff --- /dev/null +++ b/frame/ethereum/src/tests/eip2930.rs @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: Apache-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2020 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Consensus extension module tests for BABE consensus. + +use super::*; + +fn eip2930_erc20_creation_unsigned_transaction() -> EIP2930UnsignedTransaction { + EIP2930UnsignedTransaction { + nonce: U256::zero(), + gas_price: U256::from(1), + gas_limit: U256::from(0x100000), + action: ethereum::TransactionAction::Create, + value: U256::zero(), + input: FromHex::from_hex(ERC20_CONTRACT_BYTECODE).unwrap(), + } +} + +fn eip2930_erc20_creation_transaction(account: &AccountInfo) -> Transaction { + eip2930_erc20_creation_unsigned_transaction().sign(&account.private_key, None) +} + +#[test] +fn transaction_should_increment_nonce() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + ext.execute_with(|| { + let t = eip2930_erc20_creation_transaction(alice); + assert_ok!(Ethereum::execute(alice.address, &t, None,)); + assert_eq!(EVM::account_basic(&alice.address).nonce, U256::from(1)); + }); +} + +#[test] +fn transaction_without_enough_gas_should_not_work() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + ext.execute_with(|| { + let mut transaction = eip2930_erc20_creation_transaction(alice); + match &mut transaction { + Transaction::EIP2930(t) => t.gas_price = U256::from(11_000_000), + _ => {} + } + + let call = crate::Call::::transact { transaction }; + let source = call.check_self_contained().unwrap().unwrap(); + + assert_err!( + call.validate_self_contained(&source).unwrap(), + InvalidTransaction::Payment + ); + }); +} + +#[test] +fn transaction_with_to_low_nonce_should_not_work() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + ext.execute_with(|| { + // nonce is 0 + let mut transaction = eip2930_erc20_creation_unsigned_transaction(); + transaction.nonce = U256::from(1); + + let signed = transaction.sign(&alice.private_key, None); + let call = crate::Call::::transact { + transaction: signed, + }; + let source = call.check_self_contained().unwrap().unwrap(); + + assert_eq!( + call.validate_self_contained(&source).unwrap(), + ValidTransactionBuilder::default() + .and_provides((alice.address, U256::from(1))) + .priority(0u64) + .and_requires((alice.address, U256::from(0))) + .build() + ); + + let t = eip2930_erc20_creation_transaction(alice); + + // nonce is 1 + assert_ok!(Ethereum::execute(alice.address, &t, None,)); + + transaction.nonce = U256::from(0); + + let signed2 = transaction.sign(&alice.private_key, None); + let call2 = crate::Call::::transact { + transaction: signed2, + }; + let source2 = call2.check_self_contained().unwrap().unwrap(); + + assert_err!( + call2.validate_self_contained(&source2).unwrap(), + InvalidTransaction::Stale + ); + }); +} + +#[test] +fn transaction_with_to_hight_nonce_should_fail_in_block() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + ext.execute_with(|| { + let mut transaction = eip2930_erc20_creation_unsigned_transaction(); + transaction.nonce = U256::one(); + + let signed = transaction.sign(&alice.private_key, None); + let call = crate::Call::::transact { + transaction: signed, + }; + let source = call.check_self_contained().unwrap().unwrap(); + let extrinsic = fp_self_contained::CheckedExtrinsic::<_, _, SignedExtra, _> { + signed: fp_self_contained::CheckedSignature::SelfContained(source), + function: Call::Ethereum(call), + }; + use frame_support::weights::GetDispatchInfo as _; + let dispatch_info = extrinsic.get_dispatch_info(); + assert_err!( + extrinsic.apply::(&dispatch_info, 0), + TransactionValidityError::Invalid(InvalidTransaction::Future) + ); + }); +} + +#[test] +fn transaction_with_invalid_chain_id_should_fail_in_block() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + ext.execute_with(|| { + let transaction = + eip2930_erc20_creation_unsigned_transaction().sign(&alice.private_key, Some(1)); + + let call = crate::Call::::transact { transaction }; + let source = call.check_self_contained().unwrap().unwrap(); + let extrinsic = fp_self_contained::CheckedExtrinsic::<_, _, SignedExtra, _> { + signed: fp_self_contained::CheckedSignature::SelfContained(source), + function: Call::Ethereum(call), + }; + use frame_support::weights::GetDispatchInfo as _; + let dispatch_info = extrinsic.get_dispatch_info(); + assert_err!( + extrinsic.apply::(&dispatch_info, 0), + TransactionValidityError::Invalid(InvalidTransaction::Custom( + crate::TransactionValidationError::InvalidChainId as u8, + )) + ); + }); +} + +#[test] +fn contract_constructor_should_get_executed() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + let erc20_address = contract_address(alice.address, 0); + let alice_storage_address = storage_address(alice.address, H256::zero()); + + ext.execute_with(|| { + let t = eip2930_erc20_creation_transaction(alice); + + assert_ok!(Ethereum::execute(alice.address, &t, None,)); + assert_eq!( + EVM::account_storages(erc20_address, alice_storage_address), + H256::from_str("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + .unwrap() + ) + }); +} + +#[test] +fn source_should_be_derived_from_signature() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + let erc20_address = contract_address(alice.address, 0); + let alice_storage_address = storage_address(alice.address, H256::zero()); + + ext.execute_with(|| { + Ethereum::transact( + RawOrigin::EthereumTransaction(alice.address).into(), + eip2930_erc20_creation_transaction(alice), + ) + .expect("Failed to execute transaction"); + + // We verify the transaction happened with alice account. + assert_eq!( + EVM::account_storages(erc20_address, alice_storage_address), + H256::from_str("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + .unwrap() + ) + }); +} + +#[test] +fn contract_should_be_created_at_given_address() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + let erc20_address = contract_address(alice.address, 0); + + ext.execute_with(|| { + let t = eip2930_erc20_creation_transaction(alice); + assert_ok!(Ethereum::execute(alice.address, &t, None,)); + assert_ne!(EVM::account_codes(erc20_address).len(), 0); + }); +} + +#[test] +fn transaction_should_generate_correct_gas_used() { + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + let expected_gas = U256::from(891328); + + ext.execute_with(|| { + let t = eip2930_erc20_creation_transaction(alice); + let (_, _, info) = Ethereum::execute(alice.address, &t, None).unwrap(); + + match info { + CallOrCreateInfo::Create(info) => { + assert_eq!(info.used_gas, expected_gas); + } + CallOrCreateInfo::Call(_) => panic!("expected create info"), + } + }); +} + +#[test] +fn call_should_handle_errors() { + // pragma solidity ^0.6.6; + // contract Test { + // function foo() external pure returns (bool) { + // return true; + // } + // function bar() external pure { + // require(false, "error_msg"); + // } + // } + let contract: &str = "608060405234801561001057600080fd5b50610113806100206000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c8063c2985578146037578063febb0f7e146057575b600080fd5b603d605f565b604051808215151515815260200191505060405180910390f35b605d6068565b005b60006001905090565b600060db576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260098152602001807f6572726f725f6d7367000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b56fea2646970667358221220fde68a3968e0e99b16fabf9b2997a78218b32214031f8e07e2c502daf603a69e64736f6c63430006060033"; + + let (pairs, mut ext) = new_test_ext(1); + let alice = &pairs[0]; + + ext.execute_with(|| { + let t = EIP2930UnsignedTransaction { + nonce: U256::zero(), + gas_price: U256::from(1), + gas_limit: U256::from(0x100000), + action: ethereum::TransactionAction::Create, + value: U256::zero(), + input: FromHex::from_hex(contract).unwrap(), + } + .sign(&alice.private_key, None); + assert_ok!(Ethereum::execute(alice.address, &t, None,)); + + let contract_address: Vec = + FromHex::from_hex("32dcab0ef3fb2de2fce1d2e0799d36239671f04a").unwrap(); + let foo: Vec = FromHex::from_hex("c2985578").unwrap(); + let bar: Vec = FromHex::from_hex("febb0f7e").unwrap(); + + let t2 = EIP2930UnsignedTransaction { + nonce: U256::from(1), + gas_price: U256::from(1), + gas_limit: U256::from(0x100000), + action: TransactionAction::Call(H160::from_slice(&contract_address)), + value: U256::zero(), + input: foo, + } + .sign(&alice.private_key, None); + + // calling foo will succeed + let (_, _, info) = Ethereum::execute(alice.address, &t2, None).unwrap(); + + match info { + CallOrCreateInfo::Call(info) => { + assert_eq!( + info.value.to_hex::(), + "0000000000000000000000000000000000000000000000000000000000000001".to_owned() + ); + } + CallOrCreateInfo::Create(_) => panic!("expected call info"), + } + + let t3 = EIP2930UnsignedTransaction { + nonce: U256::from(2), + gas_price: U256::from(1), + gas_limit: U256::from(0x100000), + action: TransactionAction::Call(H160::from_slice(&contract_address)), + value: U256::zero(), + input: bar, + } + .sign(&alice.private_key, None); + + // calling should always succeed even if the inner EVM execution fails. + Ethereum::execute(alice.address, &t3, None).ok().unwrap(); + }); +} diff --git a/frame/ethereum/src/tests.rs b/frame/ethereum/src/tests/legacy.rs similarity index 67% rename from frame/ethereum/src/tests.rs rename to frame/ethereum/src/tests/legacy.rs index 342abdf001..310dc98f12 100644 --- a/frame/ethereum/src/tests.rs +++ b/frame/ethereum/src/tests/legacy.rs @@ -17,31 +17,10 @@ //! Consensus extension module tests for BABE consensus. -use crate::{ - mock::*, CallOrCreateInfo, Error, RawOrigin, Transaction, TransactionAction, H160, H256, U256, -}; -use ethereum::TransactionSignature; -use frame_support::{ - assert_err, assert_noop, assert_ok, - unsigned::{TransactionValidityError, ValidateUnsigned}, -}; -use rustc_hex::{FromHex, ToHex}; -use sp_runtime::{ - traits::Applyable, - transaction_validity::{InvalidTransaction, TransactionSource, ValidTransactionBuilder}, -}; -use std::str::FromStr; - -// This ERC-20 contract mints the maximum amount of tokens to the contract creator. -// pragma solidity ^0.5.0;` -// import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/token/ERC20/ERC20.sol"; -// contract MyToken is ERC20 { -// constructor() public { _mint(msg.sender, 2**256 - 1); } -// } -const ERC20_CONTRACT_BYTECODE: &str = include_str!("../res/erc20_contract_bytecode.txt"); - -fn default_erc20_creation_unsigned_transaction() -> UnsignedTransaction { - UnsignedTransaction { +use super::*; + +fn legacy_erc20_creation_unsigned_transaction() -> LegacyUnsignedTransaction { + LegacyUnsignedTransaction { nonce: U256::zero(), gas_price: U256::from(1), gas_limit: U256::from(0x100000), @@ -51,8 +30,8 @@ fn default_erc20_creation_unsigned_transaction() -> UnsignedTransaction { } } -fn default_erc20_creation_transaction(account: &AccountInfo) -> Transaction { - default_erc20_creation_unsigned_transaction().sign(&account.private_key) +fn legacy_erc20_creation_transaction(account: &AccountInfo) -> Transaction { + legacy_erc20_creation_unsigned_transaction().sign(&account.private_key) } #[test] @@ -61,17 +40,8 @@ fn transaction_should_increment_nonce() { let alice = &pairs[0]; ext.execute_with(|| { - let t = default_erc20_creation_transaction(alice); - assert_ok!(Ethereum::execute( - alice.address, - t.input, - t.value, - t.gas_limit, - Some(t.gas_price), - Some(t.nonce), - t.action, - None, - )); + let t = legacy_erc20_creation_transaction(alice); + assert_ok!(Ethereum::execute(alice.address, &t, None,)); assert_eq!(EVM::account_basic(&alice.address).nonce, U256::from(1)); }); } @@ -82,10 +52,13 @@ fn transaction_without_enough_gas_should_not_work() { let alice = &pairs[0]; ext.execute_with(|| { - let mut transaction = default_erc20_creation_transaction(alice); - transaction.gas_price = U256::from(11_000_000); + let mut transaction = legacy_erc20_creation_transaction(alice); + match &mut transaction { + Transaction::Legacy(t) => t.gas_price = U256::from(11_000_000), + _ => {} + } - let call = crate::Call::::transact(transaction); + let call = crate::Call::::transact { transaction }; let source = call.check_self_contained().unwrap().unwrap(); assert_err!( @@ -102,40 +75,35 @@ fn transaction_with_to_low_nonce_should_not_work() { ext.execute_with(|| { // nonce is 0 - let mut transaction = default_erc20_creation_unsigned_transaction(); + let mut transaction = legacy_erc20_creation_unsigned_transaction(); transaction.nonce = U256::from(1); let signed = transaction.sign(&alice.private_key); - let call = crate::Call::::transact(signed); + let call = crate::Call::::transact { + transaction: signed, + }; let source = call.check_self_contained().unwrap().unwrap(); assert_eq!( call.validate_self_contained(&source).unwrap(), ValidTransactionBuilder::default() .and_provides((alice.address, U256::from(1))) - .priority(1u64) + .priority(0u64) .and_requires((alice.address, U256::from(0))) .build() ); - let t = default_erc20_creation_transaction(alice); + let t = legacy_erc20_creation_transaction(alice); // nonce is 1 - assert_ok!(Ethereum::execute( - alice.address, - t.input, - t.value, - t.gas_limit, - Some(t.gas_price), - Some(t.nonce), - t.action, - None, - )); + assert_ok!(Ethereum::execute(alice.address, &t, None,)); transaction.nonce = U256::from(0); let signed2 = transaction.sign(&alice.private_key); - let call2 = crate::Call::::transact(signed2); + let call2 = crate::Call::::transact { + transaction: signed2, + }; let source2 = call2.check_self_contained().unwrap().unwrap(); assert_err!( @@ -151,11 +119,13 @@ fn transaction_with_to_hight_nonce_should_fail_in_block() { let alice = &pairs[0]; ext.execute_with(|| { - let mut transaction = default_erc20_creation_unsigned_transaction(); + let mut transaction = legacy_erc20_creation_unsigned_transaction(); transaction.nonce = U256::one(); let signed = transaction.sign(&alice.private_key); - let call = crate::Call::::transact(signed); + let call = crate::Call::::transact { + transaction: signed, + }; let source = call.check_self_contained().unwrap().unwrap(); let extrinsic = fp_self_contained::CheckedExtrinsic::<_, _, SignedExtra, _> { signed: fp_self_contained::CheckedSignature::SelfContained(source), @@ -177,9 +147,9 @@ fn transaction_with_invalid_chain_id_should_fail_in_block() { ext.execute_with(|| { let transaction = - default_erc20_creation_unsigned_transaction().sign_with_chain_id(&alice.private_key, 1); + legacy_erc20_creation_unsigned_transaction().sign_with_chain_id(&alice.private_key, 1); - let call = crate::Call::::transact(transaction); + let call = crate::Call::::transact { transaction }; let source = call.check_self_contained().unwrap().unwrap(); let extrinsic = fp_self_contained::CheckedExtrinsic::<_, _, SignedExtra, _> { signed: fp_self_contained::CheckedSignature::SelfContained(source), @@ -204,18 +174,9 @@ fn contract_constructor_should_get_executed() { let alice_storage_address = storage_address(alice.address, H256::zero()); ext.execute_with(|| { - let t = default_erc20_creation_transaction(alice); - - assert_ok!(Ethereum::execute( - alice.address, - t.input, - t.value, - t.gas_limit, - Some(t.gas_price), - Some(t.nonce), - t.action, - None, - )); + let t = legacy_erc20_creation_transaction(alice); + + assert_ok!(Ethereum::execute(alice.address, &t, None,)); assert_eq!( EVM::account_storages(erc20_address, alice_storage_address), H256::from_str("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") @@ -235,7 +196,7 @@ fn source_should_be_derived_from_signature() { ext.execute_with(|| { Ethereum::transact( RawOrigin::EthereumTransaction(alice.address).into(), - default_erc20_creation_transaction(alice), + legacy_erc20_creation_transaction(alice), ) .expect("Failed to execute transaction"); @@ -256,17 +217,8 @@ fn contract_should_be_created_at_given_address() { let erc20_address = contract_address(alice.address, 0); ext.execute_with(|| { - let t = default_erc20_creation_transaction(alice); - assert_ok!(Ethereum::execute( - alice.address, - t.input, - t.value, - t.gas_limit, - Some(t.gas_price), - Some(t.nonce), - t.action, - None, - )); + let t = legacy_erc20_creation_transaction(alice); + assert_ok!(Ethereum::execute(alice.address, &t, None,)); assert_ne!(EVM::account_codes(erc20_address).len(), 0); }); } @@ -279,18 +231,8 @@ fn transaction_should_generate_correct_gas_used() { let expected_gas = U256::from(891328); ext.execute_with(|| { - let t = default_erc20_creation_transaction(alice); - let (_, _, info) = Ethereum::execute( - alice.address, - t.input, - t.value, - t.gas_limit, - Some(t.gas_price), - Some(t.nonce), - t.action, - None, - ) - .unwrap(); + let t = legacy_erc20_creation_transaction(alice); + let (_, _, info) = Ethereum::execute(alice.address, &t, None).unwrap(); match info { CallOrCreateInfo::Create(info) => { @@ -318,7 +260,7 @@ fn call_should_handle_errors() { let alice = &pairs[0]; ext.execute_with(|| { - let t = UnsignedTransaction { + let t = LegacyUnsignedTransaction { nonce: U256::zero(), gas_price: U256::from(1), gas_limit: U256::from(0x100000), @@ -327,34 +269,25 @@ fn call_should_handle_errors() { input: FromHex::from_hex(contract).unwrap(), } .sign(&alice.private_key); - assert_ok!(Ethereum::execute( - alice.address, - t.input, - t.value, - t.gas_limit, - Some(t.gas_price), - Some(t.nonce), - t.action, - None, - )); + assert_ok!(Ethereum::execute(alice.address, &t, None,)); let contract_address: Vec = FromHex::from_hex("32dcab0ef3fb2de2fce1d2e0799d36239671f04a").unwrap(); let foo: Vec = FromHex::from_hex("c2985578").unwrap(); let bar: Vec = FromHex::from_hex("febb0f7e").unwrap(); + let t2 = LegacyUnsignedTransaction { + nonce: U256::from(1), + gas_price: U256::from(1), + gas_limit: U256::from(0x100000), + action: TransactionAction::Call(H160::from_slice(&contract_address)), + value: U256::zero(), + input: foo, + } + .sign(&alice.private_key); + // calling foo will succeed - let (_, _, info) = Ethereum::execute( - alice.address, - foo, - U256::zero(), - U256::from(1048576), - Some(U256::from(1)), - Some(U256::from(1)), - TransactionAction::Call(H160::from_slice(&contract_address)), - None, - ) - .unwrap(); + let (_, _, info) = Ethereum::execute(alice.address, &t2, None).unwrap(); match info { CallOrCreateInfo::Call(info) => { @@ -366,18 +299,17 @@ fn call_should_handle_errors() { CallOrCreateInfo::Create(_) => panic!("expected call info"), } + let t3 = LegacyUnsignedTransaction { + nonce: U256::from(2), + gas_price: U256::from(1), + gas_limit: U256::from(0x100000), + action: TransactionAction::Call(H160::from_slice(&contract_address)), + value: U256::zero(), + input: bar, + } + .sign(&alice.private_key); + // calling should always succeed even if the inner EVM execution fails. - Ethereum::execute( - alice.address, - bar, - U256::zero(), - U256::from(1048576), - Some(U256::from(1)), - Some(U256::from(2)), - TransactionAction::Call(H160::from_slice(&contract_address)), - None, - ) - .ok() - .unwrap(); + Ethereum::execute(alice.address, &t3, None).ok().unwrap(); }); } diff --git a/frame/ethereum/src/tests/mod.rs b/frame/ethereum/src/tests/mod.rs new file mode 100644 index 0000000000..b47c94bc5b --- /dev/null +++ b/frame/ethereum/src/tests/mod.rs @@ -0,0 +1,26 @@ +use crate::{ + mock::*, CallOrCreateInfo, Error, RawOrigin, Transaction, TransactionAction, H160, H256, U256, +}; +use ethereum::TransactionSignature; +use frame_support::{ + assert_err, assert_noop, assert_ok, + unsigned::{TransactionValidityError, ValidateUnsigned}, +}; +use rustc_hex::{FromHex, ToHex}; +use sp_runtime::{ + traits::Applyable, + transaction_validity::{InvalidTransaction, TransactionSource, ValidTransactionBuilder}, +}; +use std::str::FromStr; + +mod eip1559; +mod eip2930; +mod legacy; + +// This ERC-20 contract mints the maximum amount of tokens to the contract creator. +// pragma solidity ^0.5.0;` +// import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/token/ERC20/ERC20.sol"; +// contract MyToken is ERC20 { +// constructor() public { _mint(msg.sender, 2**256 - 1); } +// } +pub const ERC20_CONTRACT_BYTECODE: &str = include_str!("./res/erc20_contract_bytecode.txt"); diff --git a/frame/ethereum/res/erc20_contract_bytecode.txt b/frame/ethereum/src/tests/res/erc20_contract_bytecode.txt similarity index 100% rename from frame/ethereum/res/erc20_contract_bytecode.txt rename to frame/ethereum/src/tests/res/erc20_contract_bytecode.txt diff --git a/frame/evm/Cargo.toml b/frame/evm/Cargo.toml index 4caa2b1165..c5d7f37a97 100644 --- a/frame/evm/Cargo.toml +++ b/frame/evm/Cargo.toml @@ -27,9 +27,9 @@ frame-benchmarking = { version = "4.0.0-dev", git = "https://github.com/purestak fp-evm = { version = "3.0.0-dev", default-features = false, path = "../../primitives/evm" } primitive-types = { version = "0.10.0", default-features = false, features = ["rlp", "byteorder"] } rlp = { version = "0.5", default-features = false } -evm = { version = "0.30.0", default-features = false, features = ["with-codec"] } -evm-runtime = { version = "0.30.0", default-features = false } -evm-gasometer = { version = "0.30.0", default-features = false } +evm = { version = "0.33.0", default-features = false, features = ["with-codec"] } +evm-runtime = { version = "0.33.0", default-features = false } +evm-gasometer = { version = "0.33.0", default-features = false } sha3 = { version = "0.8", default-features = false } log = { version = "0.4", default-features = false } hex = { version = "0.4", default-features = false } diff --git a/frame/evm/precompile/blake2/src/lib.rs b/frame/evm/precompile/blake2/src/lib.rs index 51ba4dad99..42f47a917e 100644 --- a/frame/evm/precompile/blake2/src/lib.rs +++ b/frame/evm/precompile/blake2/src/lib.rs @@ -22,7 +22,10 @@ extern crate alloc; mod eip_152; use core::mem::size_of; -use fp_evm::{Context, ExitError, ExitSucceed, Precompile, PrecompileOutput}; +use fp_evm::{ + Context, ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileOutput, + PrecompileResult, +}; pub struct Blake2F; @@ -37,13 +40,16 @@ impl Precompile for Blake2F { input: &[u8], target_gas: Option, _context: &Context, - ) -> core::result::Result { + _is_static: bool, + ) -> PrecompileResult { const BLAKE2_F_ARG_LEN: usize = 213; if input.len() != BLAKE2_F_ARG_LEN { - return Err(ExitError::Other( - "input length for Blake2 F precompile should be exactly 213 bytes".into(), - )); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other( + "input length for Blake2 F precompile should be exactly 213 bytes".into(), + ), + }); } let mut rounds_buf: [u8; 4] = [0; 4]; @@ -53,7 +59,9 @@ impl Precompile for Blake2F { let gas_cost: u64 = (rounds as u64) * Blake2F::GAS_COST_PER_ROUND; if let Some(gas_left) = target_gas { if gas_left < gas_cost { - return Err(ExitError::OutOfGas); + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); } } @@ -94,9 +102,9 @@ impl Precompile for Blake2F { } else if input[212] == 0 { false } else { - return Err(ExitError::Other( - "incorrect final block indicator flag".into(), - )); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("incorrect final block indicator flag".into()), + }); }; crate::eip_152::compress(&mut h, m, [t_0.into(), t_1.into()], f, rounds as usize); diff --git a/frame/evm/precompile/bn128/src/lib.rs b/frame/evm/precompile/bn128/src/lib.rs index f8e114a7f7..867e43a1ee 100644 --- a/frame/evm/precompile/bn128/src/lib.rs +++ b/frame/evm/precompile/bn128/src/lib.rs @@ -20,34 +20,50 @@ extern crate alloc; use alloc::vec::Vec; -use fp_evm::{Context, ExitError, ExitSucceed, Precompile, PrecompileOutput}; +use fp_evm::{ + Context, ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileOutput, + PrecompileResult, +}; use sp_core::U256; -fn read_fr(input: &[u8], start_inx: usize) -> Result { +fn read_fr(input: &[u8], start_inx: usize) -> Result { if input.len() < start_inx + 32 { - return Err(ExitError::Other("Input not long enough".into())); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Input not long enough".into()), + }); } - bn::Fr::from_slice(&input[start_inx..(start_inx + 32)]) - .map_err(|_| ExitError::Other("Invalid field element".into())) + bn::Fr::from_slice(&input[start_inx..(start_inx + 32)]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid field element".into()), + }) } -fn read_point(input: &[u8], start_inx: usize) -> Result { +fn read_point(input: &[u8], start_inx: usize) -> Result { use bn::{AffineG1, Fq, Group, G1}; if input.len() < start_inx + 64 { - return Err(ExitError::Other("Input not long enough".into())); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Input not long enough".into()), + }); } - let px = Fq::from_slice(&input[start_inx..(start_inx + 32)]) - .map_err(|_| ExitError::Other("Invalid point x coordinate".into()))?; - let py = Fq::from_slice(&input[(start_inx + 32)..(start_inx + 64)]) - .map_err(|_| ExitError::Other("Invalid point y coordinate".into()))?; + let px = Fq::from_slice(&input[start_inx..(start_inx + 32)]).map_err(|_| { + PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid point x coordinate".into()), + } + })?; + let py = Fq::from_slice(&input[(start_inx + 32)..(start_inx + 64)]).map_err(|_| { + PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid point y coordinate".into()), + } + })?; Ok(if px == Fq::zero() && py == Fq::zero() { G1::zero() } else { AffineG1::new(px, py) - .map_err(|_| ExitError::Other("Invalid curve point".into()))? + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid curve point".into()), + })? .into() }) } @@ -64,7 +80,8 @@ impl Precompile for Bn128Add { input: &[u8], _target_gas: Option, _context: &Context, - ) -> core::result::Result { + _is_static: bool, + ) -> PrecompileResult { use bn::AffineG1; let p1 = read_point(input, 0)?; @@ -73,12 +90,20 @@ impl Precompile for Bn128Add { let mut buf = [0u8; 64]; if let Some(sum) = AffineG1::from_jacobian(p1 + p2) { // point not at infinity - sum.x().to_big_endian(&mut buf[0..32]).map_err(|_| { - ExitError::Other("Cannot fail since 0..32 is 32-byte length".into()) - })?; - sum.y().to_big_endian(&mut buf[32..64]).map_err(|_| { - ExitError::Other("Cannot fail since 32..64 is 32-byte length".into()) - })?; + sum.x() + .to_big_endian(&mut buf[0..32]) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other( + "Cannot fail since 0..32 is 32-byte length".into(), + ), + })?; + sum.y() + .to_big_endian(&mut buf[32..64]) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other( + "Cannot fail since 32..64 is 32-byte length".into(), + ), + })?; } Ok(PrecompileOutput { @@ -102,7 +127,8 @@ impl Precompile for Bn128Mul { input: &[u8], _target_gas: Option, _context: &Context, - ) -> core::result::Result { + _is_static: bool, + ) -> PrecompileResult { use bn::AffineG1; let p = read_point(input, 0)?; @@ -111,12 +137,20 @@ impl Precompile for Bn128Mul { let mut buf = [0u8; 64]; if let Some(sum) = AffineG1::from_jacobian(p * fr) { // point not at infinity - sum.x().to_big_endian(&mut buf[0..32]).map_err(|_| { - ExitError::Other("Cannot fail since 0..32 is 32-byte length".into()) - })?; - sum.y().to_big_endian(&mut buf[32..64]).map_err(|_| { - ExitError::Other("Cannot fail since 32..64 is 32-byte length".into()) - })?; + sum.x() + .to_big_endian(&mut buf[0..32]) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other( + "Cannot fail since 0..32 is 32-byte length".into(), + ), + })?; + sum.y() + .to_big_endian(&mut buf[32..64]) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other( + "Cannot fail since 32..64 is 32-byte length".into(), + ), + })?; } Ok(PrecompileOutput { @@ -142,7 +176,8 @@ impl Precompile for Bn128Pairing { input: &[u8], target_gas: Option, _context: &Context, - ) -> core::result::Result { + _is_static: bool, + ) -> PrecompileResult { use bn::{pairing_batch, AffineG1, AffineG2, Fq, Fq2, Group, Gt, G1, G2}; let (ret_val, gas_cost) = if input.is_empty() { @@ -155,36 +190,60 @@ impl Precompile for Bn128Pairing { + (elements as u64 * Bn128Pairing::GAS_COST_PER_PAIRING); if let Some(gas_left) = target_gas { if gas_left < gas_cost { - return Err(ExitError::OutOfGas); + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); } } let mut vals = Vec::new(); for idx in 0..elements { - let a_x = Fq::from_slice(&input[idx * 192..idx * 192 + 32]) - .map_err(|_| ExitError::Other("Invalid a argument x coordinate".into()))?; - - let a_y = Fq::from_slice(&input[idx * 192 + 32..idx * 192 + 64]) - .map_err(|_| ExitError::Other("Invalid a argument y coordinate".into()))?; + let a_x = Fq::from_slice(&input[idx * 192..idx * 192 + 32]).map_err(|_| { + PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid a argument x coordinate".into()), + } + })?; + + let a_y = Fq::from_slice(&input[idx * 192 + 32..idx * 192 + 64]).map_err(|_| { + PrecompileFailure::Error { + exit_status: ExitError::Other("Invalid a argument y coordinate".into()), + } + })?; let b_a_y = Fq::from_slice(&input[idx * 192 + 64..idx * 192 + 96]).map_err(|_| { - ExitError::Other("Invalid b argument imaginary coeff x coordinate".into()) + PrecompileFailure::Error { + exit_status: ExitError::Other( + "Invalid b argument imaginary coeff x coordinate".into(), + ), + } })?; let b_a_x = Fq::from_slice(&input[idx * 192 + 96..idx * 192 + 128]).map_err(|_| { - ExitError::Other("Invalid b argument imaginary coeff y coordinate".into()) + PrecompileFailure::Error { + exit_status: ExitError::Other( + "Invalid b argument imaginary coeff y coordinate".into(), + ), + } })?; let b_b_y = Fq::from_slice(&input[idx * 192 + 128..idx * 192 + 160]).map_err(|_| { - ExitError::Other("Invalid b argument real coeff x coordinate".into()) + PrecompileFailure::Error { + exit_status: ExitError::Other( + "Invalid b argument real coeff x coordinate".into(), + ), + } })?; let b_b_x = Fq::from_slice(&input[idx * 192 + 160..idx * 192 + 192]).map_err(|_| { - ExitError::Other("Invalid b argument real coeff y coordinate".into()) + PrecompileFailure::Error { + exit_status: ExitError::Other( + "Invalid b argument real coeff y coordinate".into(), + ), + } })?; let b_a = Fq2::new(b_a_x, b_a_y); @@ -192,16 +251,24 @@ impl Precompile for Bn128Pairing { let b = if b_a.is_zero() && b_b.is_zero() { G2::zero() } else { - G2::from(AffineG2::new(b_a, b_b).map_err(|_| { - ExitError::Other("Invalid b argument - not on curve".into()) - })?) + G2::from( + AffineG2::new(b_a, b_b).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other( + "Invalid b argument - not on curve".into(), + ), + })?, + ) }; let a = if a_x.is_zero() && a_y.is_zero() { G1::zero() } else { - G1::from(AffineG1::new(a_x, a_y).map_err(|_| { - ExitError::Other("Invalid a argument - not on curve".into()) - })?) + G1::from( + AffineG1::new(a_x, a_y).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other( + "Invalid a argument - not on curve".into(), + ), + })?, + ) }; vals.push((a, b)); } diff --git a/frame/evm/precompile/curve25519/src/lib.rs b/frame/evm/precompile/curve25519/src/lib.rs index 00cd824162..91966e7d3d 100644 --- a/frame/evm/precompile/curve25519/src/lib.rs +++ b/frame/evm/precompile/curve25519/src/lib.rs @@ -24,7 +24,7 @@ use curve25519_dalek::{ scalar::Scalar, traits::Identity, }; -use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile}; +use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile, PrecompileFailure}; // Adds at most 10 curve25519 points and returns the CompressedRistretto bytes representation pub struct Curve25519Add; @@ -33,17 +33,22 @@ impl LinearCostPrecompile for Curve25519Add { const BASE: u64 = 60; const WORD: u64 = 12; - fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + fn execute( + input: &[u8], + _: u64, + ) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { if input.len() % 32 != 0 { - return Err(ExitError::Other( - "input must contain multiple of 32 bytes".into(), - )); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("input must contain multiple of 32 bytes".into()), + }); }; if input.len() > 320 { - return Err(ExitError::Other( - "input cannot be greater than 320 bytes (10 compressed points)".into(), - )); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other( + "input cannot be greater than 320 bytes (10 compressed points)".into(), + ), + }); }; let mut points = Vec::new(); @@ -76,11 +81,16 @@ impl LinearCostPrecompile for Curve25519ScalarMul { const BASE: u64 = 60; const WORD: u64 = 12; - fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + fn execute( + input: &[u8], + _: u64, + ) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { if input.len() != 64 { - return Err(ExitError::Other( - "input must contain 64 bytes (scalar - 32 bytes, point - 32 bytes)".into(), - )); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other( + "input must contain 64 bytes (scalar - 32 bytes, point - 32 bytes)".into(), + ), + }); }; // first 32 bytes is for the scalar value @@ -109,7 +119,7 @@ mod tests { use curve25519_dalek::constants; #[test] - fn test_sum() -> std::result::Result<(), ExitError> { + fn test_sum() -> std::result::Result<(), PrecompileFailure> { let s1 = Scalar::from(999u64); let p1 = &constants::RISTRETTO_BASEPOINT_POINT * &s1; @@ -136,7 +146,7 @@ mod tests { } #[test] - fn test_empty() -> std::result::Result<(), ExitError> { + fn test_empty() -> std::result::Result<(), PrecompileFailure> { // Test that sum works for the empty iterator let input = vec![]; @@ -154,7 +164,7 @@ mod tests { } #[test] - fn test_scalar_mul() -> std::result::Result<(), ExitError> { + fn test_scalar_mul() -> std::result::Result<(), PrecompileFailure> { let s1 = Scalar::from(999u64); let s2 = Scalar::from(333u64); let p1 = &constants::RISTRETTO_BASEPOINT_POINT * &s1; @@ -179,7 +189,7 @@ mod tests { } #[test] - fn test_scalar_mul_empty_error() -> std::result::Result<(), ExitError> { + fn test_scalar_mul_empty_error() -> std::result::Result<(), PrecompileFailure> { let input = vec![]; let cost: u64 = 1; @@ -191,9 +201,12 @@ mod tests { Err(e) => { assert_eq!( e, - ExitError::Other( - "input must contain 64 bytes (scalar - 32 bytes, point - 32 bytes)".into() - ) + PrecompileFailure::Error { + exit_status: ExitError::Other( + "input must contain 64 bytes (scalar - 32 bytes, point - 32 bytes)" + .into() + ) + } ); Ok(()) } @@ -201,7 +214,7 @@ mod tests { } #[test] - fn test_point_addition_bad_length() -> std::result::Result<(), ExitError> { + fn test_point_addition_bad_length() -> std::result::Result<(), PrecompileFailure> { let input: Vec = [0u8; 33].to_vec(); let cost: u64 = 1; @@ -213,7 +226,11 @@ mod tests { Err(e) => { assert_eq!( e, - ExitError::Other("input must contain multiple of 32 bytes".into()) + PrecompileFailure::Error { + exit_status: ExitError::Other( + "input must contain multiple of 32 bytes".into() + ) + } ); Ok(()) } @@ -221,7 +238,7 @@ mod tests { } #[test] - fn test_point_addition_too_many_points() -> std::result::Result<(), ExitError> { + fn test_point_addition_too_many_points() -> std::result::Result<(), PrecompileFailure> { let mut input = vec![]; input.extend_from_slice(&constants::RISTRETTO_BASEPOINT_POINT.compress().to_bytes()); // 1 input.extend_from_slice(&constants::RISTRETTO_BASEPOINT_POINT.compress().to_bytes()); // 2 @@ -244,9 +261,11 @@ mod tests { Err(e) => { assert_eq!( e, - ExitError::Other( - "input cannot be greater than 320 bytes (10 compressed points)".into() - ) + PrecompileFailure::Error { + exit_status: ExitError::Other( + "input cannot be greater than 320 bytes (10 compressed points)".into() + ) + } ); Ok(()) } diff --git a/frame/evm/precompile/dispatch/src/lib.rs b/frame/evm/precompile/dispatch/src/lib.rs index ffd71af4e7..08fcc7e02c 100644 --- a/frame/evm/precompile/dispatch/src/lib.rs +++ b/frame/evm/precompile/dispatch/src/lib.rs @@ -21,7 +21,10 @@ extern crate alloc; use codec::Decode; use core::marker::PhantomData; -use fp_evm::{Context, ExitError, ExitSucceed, Precompile, PrecompileOutput}; +use fp_evm::{ + Context, ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileOutput, + PrecompileResult, +}; use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, weights::{DispatchClass, Pays}, @@ -42,20 +45,26 @@ where input: &[u8], target_gas: Option, context: &Context, - ) -> core::result::Result { - let call = T::Call::decode(&mut &input[..]) - .map_err(|_| ExitError::Other("decode failed".into()))?; + _is_static: bool, + ) -> PrecompileResult { + let call = T::Call::decode(&mut &input[..]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("decode failed".into()), + })?; let info = call.get_dispatch_info(); let valid_call = info.pays_fee == Pays::Yes && info.class == DispatchClass::Normal; if !valid_call { - return Err(ExitError::Other("invalid call".into())); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("invalid call".into()), + }); } if let Some(gas) = target_gas { let valid_weight = info.weight <= T::GasWeightMapping::gas_to_weight(gas); if !valid_weight { - return Err(ExitError::OutOfGas); + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); } } @@ -73,7 +82,9 @@ where logs: Default::default(), }) } - Err(_) => Err(ExitError::Other("dispatch execution failed".into())), + Err(_) => Err(PrecompileFailure::Error { + exit_status: ExitError::Other("dispatch execution failed".into()), + }), } } } diff --git a/frame/evm/precompile/ed25519/src/lib.rs b/frame/evm/precompile/ed25519/src/lib.rs index 87d45e5730..ccdd900068 100644 --- a/frame/evm/precompile/ed25519/src/lib.rs +++ b/frame/evm/precompile/ed25519/src/lib.rs @@ -22,7 +22,7 @@ extern crate alloc; use alloc::vec::Vec; use core::convert::TryFrom; use ed25519_dalek::{PublicKey, Signature, Verifier}; -use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile}; +use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile, PrecompileFailure}; pub struct Ed25519Verify; @@ -30,9 +30,14 @@ impl LinearCostPrecompile for Ed25519Verify { const BASE: u64 = 15; const WORD: u64 = 3; - fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + fn execute( + input: &[u8], + _: u64, + ) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { if input.len() < 128 { - return Err(ExitError::Other("input must contain 128 bytes".into())); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("input must contain 128 bytes".into()), + }); }; let mut i = [0u8; 128]; @@ -41,10 +46,12 @@ impl LinearCostPrecompile for Ed25519Verify { let mut buf = [0u8; 4]; let msg = &i[0..32]; - let pk = PublicKey::from_bytes(&i[32..64]) - .map_err(|_| ExitError::Other("Public key recover failed".into()))?; - let sig = Signature::try_from(&i[64..128]) - .map_err(|_| ExitError::Other("Signature recover failed".into()))?; + let pk = PublicKey::from_bytes(&i[32..64]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Public key recover failed".into()), + })?; + let sig = Signature::try_from(&i[64..128]).map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::Other("Signature recover failed".into()), + })?; // https://docs.rs/rust-crypto/0.2.36/crypto/ed25519/fn.verify.html if pk.verify(msg, &sig).is_ok() { @@ -63,7 +70,7 @@ mod tests { use ed25519_dalek::{Keypair, SecretKey, Signer}; #[test] - fn test_empty_input() -> std::result::Result<(), ExitError> { + fn test_empty_input() -> std::result::Result<(), PrecompileFailure> { let input: [u8; 0] = []; let cost: u64 = 1; @@ -72,14 +79,19 @@ mod tests { panic!("Test not expected to pass"); } Err(e) => { - assert_eq!(e, ExitError::Other("input must contain 128 bytes".into())); + assert_eq!( + e, + PrecompileFailure::Error { + exit_status: ExitError::Other("input must contain 128 bytes".into()) + } + ); Ok(()) } } } #[test] - fn test_verify() -> std::result::Result<(), ExitError> { + fn test_verify() -> std::result::Result<(), PrecompileFailure> { let secret_key_bytes: [u8; ed25519_dalek::SECRET_KEY_LENGTH] = [ 157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068, 073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096, diff --git a/frame/evm/precompile/modexp/src/lib.rs b/frame/evm/precompile/modexp/src/lib.rs index ef34bfb540..a7ae46e544 100644 --- a/frame/evm/precompile/modexp/src/lib.rs +++ b/frame/evm/precompile/modexp/src/lib.rs @@ -20,7 +20,10 @@ extern crate alloc; use alloc::vec::Vec; -use fp_evm::{Context, ExitError, ExitSucceed, Precompile, PrecompileOutput}; +use fp_evm::{ + Context, ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileOutput, + PrecompileResult, +}; use num::{BigUint, FromPrimitive, One, ToPrimitive, Zero}; use core::{cmp::max, ops::BitAnd}; @@ -98,11 +101,12 @@ impl Precompile for Modexp { input: &[u8], target_gas: Option, _context: &Context, - ) -> core::result::Result { + _is_static: bool, + ) -> PrecompileResult { if input.len() < 96 { - return Err(ExitError::Other( - "input must contain at least 96 bytes".into(), - )); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("input must contain at least 96 bytes".into()), + }); }; // reasonable assumption: this must fit within the Ethereum EVM's max stack size @@ -112,23 +116,25 @@ impl Precompile for Modexp { buf.copy_from_slice(&input[0..32]); let base_len_big = BigUint::from_bytes_be(&buf); if base_len_big > max_size_big { - return Err(ExitError::Other("unreasonably large base length".into())); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("unreasonably large base length".into()), + }); } buf.copy_from_slice(&input[32..64]); let exp_len_big = BigUint::from_bytes_be(&buf); if exp_len_big > max_size_big { - return Err(ExitError::Other( - "unreasonably large exponent length".into(), - )); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("unreasonably large exponent length".into()), + }); } buf.copy_from_slice(&input[64..96]); let mod_len_big = BigUint::from_bytes_be(&buf); if mod_len_big > max_size_big { - return Err(ExitError::Other( - "unreasonably large exponent length".into(), - )); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("unreasonably large exponent length".into()), + }); } // bounds check handled above @@ -139,7 +145,9 @@ impl Precompile for Modexp { // input length should be at least 96 + user-specified length of base + exp + mod let total_len = base_len + exp_len + mod_len + 96; if input.len() < total_len { - return Err(ExitError::Other("insufficient input size".into())); + return Err(PrecompileFailure::Error { + exit_status: ExitError::Other("insufficient input size".into()), + }); } // Gas formula allows arbitrary large exp_len when base and modulus are empty, so we need to handle empty base first. @@ -159,7 +167,9 @@ impl Precompile for Modexp { calculate_gas_cost(base_len as u64, exp_len as u64, mod_len as u64, &exponent); if let Some(gas_left) = target_gas { if gas_left < gas_cost { - return Err(ExitError::OutOfGas); + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); } }; @@ -196,7 +206,9 @@ impl Precompile for Modexp { logs: Default::default(), }) } else { - Err(ExitError::Other("failed".into())) + Err(PrecompileFailure::Error { + exit_status: ExitError::Other("failed".into()), + }) } } } @@ -214,7 +226,7 @@ mod tests { } #[test] - fn test_empty_input() -> std::result::Result<(), ExitError> { + fn test_empty_input() -> std::result::Result<(), PrecompileFailure> { let input: [u8; 0] = []; let cost: u64 = 1; @@ -225,14 +237,18 @@ mod tests { apparent_value: From::from(0), }; - match Modexp::execute(&input, Some(cost), &context) { + match Modexp::execute(&input, Some(cost), &context, false) { Ok(_) => { panic!("Test not expected to pass"); } Err(e) => { assert_eq!( e, - ExitError::Other("input must contain at least 96 bytes".into()) + PrecompileFailure::Error { + exit_status: ExitError::Other( + "input must contain at least 96 bytes".into() + ) + } ); Ok(()) } @@ -240,7 +256,7 @@ mod tests { } #[test] - fn test_insufficient_input() -> std::result::Result<(), ExitError> { + fn test_insufficient_input() -> std::result::Result<(), PrecompileFailure> { let input = hex::decode( "0000000000000000000000000000000000000000000000000000000000000001\ 0000000000000000000000000000000000000000000000000000000000000001\ @@ -256,19 +272,24 @@ mod tests { apparent_value: From::from(0), }; - match Modexp::execute(&input, Some(cost), &context) { + match Modexp::execute(&input, Some(cost), &context, false) { Ok(_) => { panic!("Test not expected to pass"); } Err(e) => { - assert_eq!(e, ExitError::Other("insufficient input size".into())); + assert_eq!( + e, + PrecompileFailure::Error { + exit_status: ExitError::Other("insufficient input size".into()) + } + ); Ok(()) } } } #[test] - fn test_excessive_input() -> std::result::Result<(), ExitError> { + fn test_excessive_input() -> std::result::Result<(), PrecompileFailure> { let input = hex::decode( "1000000000000000000000000000000000000000000000000000000000000001\ 0000000000000000000000000000000000000000000000000000000000000001\ @@ -284,12 +305,17 @@ mod tests { apparent_value: From::from(0), }; - match Modexp::execute(&input, Some(cost), &context) { + match Modexp::execute(&input, Some(cost), &context, false) { Ok(_) => { panic!("Test not expected to pass"); } Err(e) => { - assert_eq!(e, ExitError::Other("unreasonably large base length".into())); + assert_eq!( + e, + PrecompileFailure::Error { + exit_status: ExitError::Other("unreasonably large base length".into()) + } + ); Ok(()) } } @@ -317,7 +343,7 @@ mod tests { apparent_value: From::from(0), }; - match Modexp::execute(&input, Some(cost), &context) { + match Modexp::execute(&input, Some(cost), &context, false) { Ok(precompile_result) => { assert_eq!(precompile_result.output.len(), 1); // should be same length as mod let result = BigUint::from_bytes_be(&precompile_result.output[..]); @@ -352,7 +378,7 @@ mod tests { apparent_value: From::from(0), }; - match Modexp::execute(&input, Some(cost), &context) { + match Modexp::execute(&input, Some(cost), &context, false) { Ok(precompile_result) => { assert_eq!(precompile_result.output.len(), 32); // should be same length as mod let result = BigUint::from_bytes_be(&precompile_result.output[..]); @@ -385,7 +411,7 @@ mod tests { apparent_value: From::from(0), }; - match Modexp::execute(&input, Some(cost), &context) { + match Modexp::execute(&input, Some(cost), &context, false) { Ok(precompile_result) => { assert_eq!(precompile_result.output.len(), 32); // should be same length as mod let result = BigUint::from_bytes_be(&precompile_result.output[..]); diff --git a/frame/evm/precompile/sha3fips/src/lib.rs b/frame/evm/precompile/sha3fips/src/lib.rs index 114742883f..604e947074 100644 --- a/frame/evm/precompile/sha3fips/src/lib.rs +++ b/frame/evm/precompile/sha3fips/src/lib.rs @@ -22,7 +22,7 @@ extern crate alloc; use alloc::vec::Vec; use tiny_keccak::Hasher; -use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile}; +use fp_evm::{ExitSucceed, LinearCostPrecompile, PrecompileFailure}; pub struct Sha3FIPS256; @@ -30,7 +30,10 @@ impl LinearCostPrecompile for Sha3FIPS256 { const BASE: u64 = 60; const WORD: u64 = 12; - fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + fn execute( + input: &[u8], + _: u64, + ) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { let mut output = [0; 32]; let mut sha3 = tiny_keccak::Sha3::v256(); sha3.update(input); @@ -45,7 +48,10 @@ impl LinearCostPrecompile for Sha3FIPS512 { const BASE: u64 = 60; const WORD: u64 = 12; - fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + fn execute( + input: &[u8], + _: u64, + ) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { let mut output = [0; 64]; let mut sha3 = tiny_keccak::Sha3::v512(); sha3.update(input); @@ -59,7 +65,7 @@ mod tests { use super::*; #[test] - fn test_empty_input() -> std::result::Result<(), ExitError> { + fn test_empty_input() -> std::result::Result<(), PrecompileFailure> { let input: [u8; 0] = []; let expected = b"\ \xa7\xff\xc6\xf8\xbf\x1e\xd7\x66\x51\xc1\x47\x56\xa0\x61\xd6\x62\ @@ -80,7 +86,7 @@ mod tests { } #[test] - fn hello_sha3_256() -> std::result::Result<(), ExitError> { + fn hello_sha3_256() -> std::result::Result<(), PrecompileFailure> { let input = b"hello"; let expected = b"\ \x33\x38\xbe\x69\x4f\x50\xc5\xf3\x38\x81\x49\x86\xcd\xf0\x68\x64\ @@ -101,7 +107,7 @@ mod tests { } #[test] - fn long_string_sha3_256() -> std::result::Result<(), ExitError> { + fn long_string_sha3_256() -> std::result::Result<(), PrecompileFailure> { let input = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; let expected = b"\ \xbd\xe3\xf2\x69\x17\x5e\x1d\xcd\xa1\x38\x48\x27\x8a\xa6\x04\x6b\ @@ -122,7 +128,7 @@ mod tests { } #[test] - fn long_string_sha3_512() -> std::result::Result<(), ExitError> { + fn long_string_sha3_512() -> std::result::Result<(), PrecompileFailure> { let input = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; let expected = b"\ \xf3\x2a\x94\x23\x55\x13\x51\xdf\x0a\x07\xc0\xb8\xc2\x0e\xb9\x72\ diff --git a/frame/evm/precompile/simple/src/lib.rs b/frame/evm/precompile/simple/src/lib.rs index b3b4a0e3ac..e4e6a44e22 100644 --- a/frame/evm/precompile/simple/src/lib.rs +++ b/frame/evm/precompile/simple/src/lib.rs @@ -21,7 +21,7 @@ extern crate alloc; use alloc::vec::Vec; use core::cmp::min; -use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile}; +use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile, PrecompileFailure}; /// The identity precompile. pub struct Identity; @@ -30,7 +30,10 @@ impl LinearCostPrecompile for Identity { const BASE: u64 = 15; const WORD: u64 = 3; - fn execute(input: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + fn execute( + input: &[u8], + _: u64, + ) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { Ok((ExitSucceed::Returned, input.to_vec())) } } @@ -42,7 +45,10 @@ impl LinearCostPrecompile for ECRecover { const BASE: u64 = 3000; const WORD: u64 = 0; - fn execute(i: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + fn execute( + i: &[u8], + _: u64, + ) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { let mut input = [0u8; 128]; input[..min(i.len(), 128)].copy_from_slice(&i[..min(i.len(), 128)]); @@ -77,7 +83,7 @@ impl LinearCostPrecompile for Ripemd160 { fn execute( input: &[u8], _cost: u64, - ) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + ) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { use ripemd160::Digest; let mut ret = [0u8; 32]; @@ -96,7 +102,7 @@ impl LinearCostPrecompile for Sha256 { fn execute( input: &[u8], _cost: u64, - ) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + ) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { let ret = sp_io::hashing::sha2_256(input); Ok((ExitSucceed::Returned, ret.to_vec())) } @@ -110,7 +116,10 @@ impl LinearCostPrecompile for ECRecoverPublicKey { const BASE: u64 = 3000; const WORD: u64 = 0; - fn execute(i: &[u8], _: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError> { + fn execute( + i: &[u8], + _: u64, + ) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure> { let mut input = [0u8; 128]; input[..min(i.len(), 128)].copy_from_slice(&i[..min(i.len(), 128)]); @@ -122,8 +131,11 @@ impl LinearCostPrecompile for ECRecoverPublicKey { sig[32..64].copy_from_slice(&input[96..128]); sig[64] = input[63]; - let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg) - .map_err(|_| ExitError::Other("Public key recover failed".into()))?; + let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg).map_err(|_| { + PrecompileFailure::Error { + exit_status: ExitError::Other("Public key recover failed".into()), + } + })?; Ok((ExitSucceed::Returned, pubkey.to_vec())) } diff --git a/frame/evm/src/lib.rs b/frame/evm/src/lib.rs index bc63c95dcb..5d82bb1e6f 100644 --- a/frame/evm/src/lib.rs +++ b/frame/evm/src/lib.rs @@ -63,10 +63,10 @@ mod tests; pub mod benchmarks; pub use crate::runner::Runner; -pub use evm::{ExitError, ExitFatal, ExitReason, ExitRevert, ExitSucceed}; +pub use evm::{Context, ExitError, ExitFatal, ExitReason, ExitRevert, ExitSucceed}; pub use fp_evm::{ Account, CallInfo, CreateInfo, ExecutionInfo, LinearCostPrecompile, Log, Precompile, - PrecompileSet, Vicinity, + PrecompileFailure, PrecompileOutput, PrecompileResult, PrecompileSet, Vicinity, }; #[cfg(feature = "std")] @@ -126,7 +126,8 @@ pub mod pallet { /// The overarching event type. type Event: From> + IsType<::Event>; /// Precompiles associated with this EVM engine. - type Precompiles: PrecompileSet; + type PrecompilesType: PrecompileSet; + type PrecompilesValue: Get; /// Chain ID of EVM. type ChainId: Get; /// The block gas limit. Can be a simple constant, or an adjustment algorithm in another pallet. @@ -179,8 +180,10 @@ pub mod pallet { input: Vec, value: U256, gas_limit: u64, - gas_price: U256, + max_fee_per_gas: U256, + max_priority_fee_per_gas: Option, nonce: Option, + access_list: Vec<(H160, Vec)>, ) -> DispatchResultWithPostInfo { T::CallOrigin::ensure_address_origin(&source, origin)?; @@ -190,8 +193,10 @@ pub mod pallet { input, value, gas_limit, - Some(gas_price), + Some(max_fee_per_gas), + max_priority_fee_per_gas, nonce, + access_list, T::config(), )?; @@ -221,8 +226,10 @@ pub mod pallet { init: Vec, value: U256, gas_limit: u64, - gas_price: U256, + max_fee_per_gas: U256, + max_priority_fee_per_gas: Option, nonce: Option, + access_list: Vec<(H160, Vec)>, ) -> DispatchResultWithPostInfo { T::CallOrigin::ensure_address_origin(&source, origin)?; @@ -231,8 +238,10 @@ pub mod pallet { init, value, gas_limit, - Some(gas_price), + Some(max_fee_per_gas), + max_priority_fee_per_gas, nonce, + access_list, T::config(), )?; @@ -270,8 +279,10 @@ pub mod pallet { salt: H256, value: U256, gas_limit: u64, - gas_price: U256, + max_fee_per_gas: U256, + max_priority_fee_per_gas: Option, nonce: Option, + access_list: Vec<(H160, Vec)>, ) -> DispatchResultWithPostInfo { T::CallOrigin::ensure_address_origin(&source, origin)?; @@ -281,8 +292,10 @@ pub mod pallet { salt, value, gas_limit, - Some(gas_price), + Some(max_fee_per_gas), + max_priority_fee_per_gas, nonce, + access_list, T::config(), )?; @@ -655,6 +668,9 @@ pub trait OnChargeEVMTransaction { corrected_fee: U256, already_withdrawn: Self::LiquidityInfo, ); + + /// Introduced in EIP1559 to handle the priority tip payment to the block Author. + fn pay_priority_fee(tip: U256); } /// Implements the transaction payment for a pallet implementing the `Currency` @@ -736,6 +752,11 @@ where OU::on_unbalanced(adjusted_paid); } } + + fn pay_priority_fee(tip: U256) { + let account_id = T::AddressMapping::into_account_id(>::find_author()); + let _ = C::deposit_into_existing(&account_id, tip.low_u128().unique_saturated_into()); + } } /// Implementation for () does not specify what to do with imbalance @@ -763,4 +784,8 @@ impl OnChargeEVMTransaction for () ) { ::Currency, ()> as OnChargeEVMTransaction>::correct_and_deposit_fee(who, corrected_fee, already_withdrawn) } + + fn pay_priority_fee(tip: U256) { + ::Currency, ()> as OnChargeEVMTransaction>::pay_priority_fee(tip); + } } diff --git a/frame/evm/src/mock.rs b/frame/evm/src/mock.rs index a56485e82b..16f19849a8 100644 --- a/frame/evm/src/mock.rs +++ b/frame/evm/src/mock.rs @@ -101,7 +101,7 @@ impl pallet_timestamp::Config for Test { pub struct FixedGasPrice; impl FeeCalculator for FixedGasPrice { fn min_gas_price() -> U256 { - 0.into() + 1_000_000_000u128.into() } } @@ -127,7 +127,8 @@ impl crate::Config for Test { type Runner = crate::runner::stack::Runner; type Event = Event; - type Precompiles = (); + type PrecompilesType = (); + type PrecompilesValue = (); type ChainId = (); type BlockGasLimit = (); type OnChargeTransaction = (); diff --git a/frame/evm/src/runner/mod.rs b/frame/evm/src/runner/mod.rs index 55329c87a3..bc0de56fc5 100644 --- a/frame/evm/src/runner/mod.rs +++ b/frame/evm/src/runner/mod.rs @@ -31,8 +31,10 @@ pub trait Runner { input: Vec, value: U256, gas_limit: u64, - gas_price: Option, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, nonce: Option, + access_list: Vec<(H160, Vec)>, config: &evm::Config, ) -> Result; @@ -41,8 +43,10 @@ pub trait Runner { init: Vec, value: U256, gas_limit: u64, - gas_price: Option, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, nonce: Option, + access_list: Vec<(H160, Vec)>, config: &evm::Config, ) -> Result; @@ -52,8 +56,10 @@ pub trait Runner { salt: H256, value: U256, gas_limit: u64, - gas_price: Option, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, nonce: Option, + access_list: Vec<(H160, Vec)>, config: &evm::Config, ) -> Result; } diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index ebb2bb60fa..2ef6fcf785 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -19,11 +19,11 @@ use crate::{ runner::Runner as RunnerT, AccountCodes, AccountStorages, AddressMapping, BlockHashMapping, - Config, Error, Event, FeeCalculator, OnChargeEVMTransaction, Pallet, PrecompileSet, + Config, Error, Event, FeeCalculator, OnChargeEVMTransaction, Pallet, }; use evm::{ backend::Backend as BackendT, - executor::{StackExecutor, StackState as StackStateT, StackSubstateMetadata}, + executor::stack::{Accessed, StackExecutor, StackState as StackStateT, StackSubstateMetadata}, ExitError, ExitReason, Transfer, }; use fp_evm::{CallInfo, CreateInfo, ExecutionInfo, Log, Vicinity}; @@ -43,45 +43,62 @@ pub struct Runner { impl Runner { /// Execute an EVM operation. - pub fn execute<'config, F, R>( + pub fn execute<'config, 'precompiles, F, R>( source: H160, value: U256, gas_limit: u64, - gas_price: Option, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, nonce: Option, config: &'config evm::Config, + precompiles: &'precompiles T::PrecompilesType, f: F, ) -> Result, Error> where F: FnOnce( - &mut StackExecutor<'config, SubstrateStackState<'_, 'config, T>>, + &mut StackExecutor< + 'config, + 'precompiles, + SubstrateStackState<'_, 'config, T>, + T::PrecompilesType, + >, ) -> (ExitReason, R), { + let base_fee = T::FeeCalculator::min_gas_price(); // Gas price check is skipped when performing a gas estimation. - let gas_price = match gas_price { - Some(gas_price) => { - ensure!( - gas_price >= T::FeeCalculator::min_gas_price(), - Error::::GasPriceTooLow - ); - gas_price + let max_fee_per_gas = match max_fee_per_gas { + Some(max_fee_per_gas) => { + ensure!(max_fee_per_gas >= base_fee, Error::::GasPriceTooLow); + max_fee_per_gas } None => Default::default(), }; let vicinity = Vicinity { - gas_price, + gas_price: max_fee_per_gas, origin: source, }; let metadata = StackSubstateMetadata::new(gas_limit, &config); let state = SubstrateStackState::new(&vicinity, metadata); - let mut executor = - StackExecutor::new_with_precompile(state, config, T::Precompiles::execute); + let mut executor = StackExecutor::new_with_precompiles(state, config, precompiles); - let total_fee = gas_price + // After eip-1559 we make sure the account can pay both the evm execution and priority fees. + let max_base_fee = max_fee_per_gas .checked_mul(U256::from(gas_limit)) .ok_or(Error::::FeeOverflow)?; + let max_priority_fee = if let Some(max_priority_fee) = max_priority_fee_per_gas { + max_priority_fee + .checked_mul(U256::from(gas_limit)) + .ok_or(Error::::FeeOverflow)? + } else { + U256::zero() + }; + + let total_fee = max_base_fee + .checked_add(max_priority_fee) + .ok_or(Error::::FeeOverflow)?; + let total_payment = value .checked_add(total_fee) .ok_or(Error::::PaymentOverflow)?; @@ -94,7 +111,6 @@ impl Runner { if let Some(nonce) = nonce { ensure!(source_account.nonce == nonce, Error::::InvalidNonce); } - // Deduct fee from the `source` account. let fee = T::OnChargeTransaction::withdraw_fee(&source, total_fee)?; @@ -102,7 +118,19 @@ impl Runner { let (reason, retv) = f(&mut executor); let used_gas = U256::from(executor.used_gas()); - let actual_fee = executor.fee(gas_price); + let (actual_fee, actual_priority_fee) = + if let Some(max_priority_fee) = max_priority_fee_per_gas { + let actual_priority_fee = max_priority_fee + .checked_mul(U256::from(used_gas)) + .ok_or(Error::::FeeOverflow)?; + let actual_fee = executor + .fee(base_fee) + .checked_add(actual_priority_fee) + .unwrap_or(U256::max_value()); + (actual_fee, Some(actual_priority_fee)) + } else { + (executor.fee(base_fee), None) + }; log::debug!( target: "evm", "Execution {:?} [source: {:?}, value: {}, gas_limit: {}, actual_fee: {}]", @@ -112,9 +140,31 @@ impl Runner { gas_limit, actual_fee ); - - // Refund fees to the `source` account if deducted more before, + // The difference between initially withdrawn and the actual cost is refunded. + // + // Considered the following request: + // +-----------+---------+--------------+ + // | Gas_limit | Max_Fee | Max_Priority | + // +-----------+---------+--------------+ + // | 20 | 10 | 6 | + // +-----------+---------+--------------+ + // + // And execution: + // +----------+----------+ + // | Gas_used | Base_Fee | + // +----------+----------+ + // | 5 | 2 | + // +----------+----------+ + // + // Initially withdrawn (10 + 6) * 20 = 320. + // Actual cost (2 + 6) * 5 = 40. + // Refunded 320 - 40 = 280. + // Tip 5 * 6 = 30. + // Burned 320 - (280 + 30) = 10. Which is equivalent to gas_used * base_fee. T::OnChargeTransaction::correct_and_deposit_fee(&source, actual_fee, fee); + if let Some(actual_priority_fee) = actual_priority_fee { + T::OnChargeTransaction::pay_priority_fee(actual_priority_fee); + } let state = executor.into_state(); @@ -162,18 +212,23 @@ impl RunnerT for Runner { input: Vec, value: U256, gas_limit: u64, - gas_price: Option, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, nonce: Option, + access_list: Vec<(H160, Vec)>, config: &evm::Config, ) -> Result { + let precompiles = T::PrecompilesValue::get(); Self::execute( source, value, gas_limit, - gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, nonce, config, - |executor| executor.transact_call(source, target, value, input, gas_limit), + &precompiles, + |executor| executor.transact_call(source, target, value, input, gas_limit, access_list), ) } @@ -182,21 +237,26 @@ impl RunnerT for Runner { init: Vec, value: U256, gas_limit: u64, - gas_price: Option, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, nonce: Option, + access_list: Vec<(H160, Vec)>, config: &evm::Config, ) -> Result { + let precompiles = T::PrecompilesValue::get(); Self::execute( source, value, gas_limit, - gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, nonce, config, + &precompiles, |executor| { let address = executor.create_address(evm::CreateScheme::Legacy { caller: source }); ( - executor.transact_create(source, value, init, gas_limit), + executor.transact_create(source, value, init, gas_limit, access_list), address, ) }, @@ -209,18 +269,23 @@ impl RunnerT for Runner { salt: H256, value: U256, gas_limit: u64, - gas_price: Option, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, nonce: Option, + access_list: Vec<(H160, Vec)>, config: &evm::Config, ) -> Result { + let precompiles = T::PrecompilesValue::get(); let code_hash = H256::from_slice(Keccak256::digest(&init).as_slice()); Self::execute( source, value, gas_limit, - gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, nonce, config, + &precompiles, |executor| { let address = executor.create_address(evm::CreateScheme::Create2 { caller: source, @@ -228,7 +293,7 @@ impl RunnerT for Runner { salt, }); ( - executor.transact_create2(source, value, init, salt, gas_limit), + executor.transact_create2(source, value, init, salt, gas_limit, access_list), address, ) }, @@ -319,6 +384,18 @@ impl<'config> SubstrateStackSubstate<'config> { data, }); } + + fn recursive_is_cold bool>(&self, f: &F) -> bool { + let local_is_accessed = self.metadata.accessed().as_ref().map(f).unwrap_or(false); + if local_is_accessed { + false + } else { + self.parent + .as_ref() + .map(|p| p.recursive_is_cold(f)) + .unwrap_or(true) + } + } } /// Substrate backend for EVM. @@ -410,6 +487,10 @@ impl<'vicinity, 'config, T: Config> BackendT for SubstrateStackState<'vicinity, fn original_storage(&self, _address: H160, _index: H256) -> Option { None } + + fn block_base_fee_per_gas(&self) -> sp_core::U256 { + T::FeeCalculator::min_gas_price() + } } impl<'vicinity, 'config, T: Config> StackStateT<'config> @@ -523,4 +604,14 @@ impl<'vicinity, 'config, T: Config> StackStateT<'config> // only empty and non-empty accounts. This avoids many of the // subtle issues in EIP-161. } + + fn is_cold(&self, address: H160) -> bool { + self.substate + .recursive_is_cold(&|a| a.accessed_addresses.contains(&address)) + } + + fn is_storage_cold(&self, address: H160, key: H256) -> bool { + self.substate + .recursive_is_cold(&|a: &Accessed| a.accessed_storage.contains(&(address, key))) + } } diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index 6855701710..6c676a17fc 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -57,10 +57,25 @@ pub fn new_test_ext() -> sp_io::TestExternalities { ], }, ); + accounts.insert( + H160::default(), // root + GenesisAccount { + nonce: U256::from(1), + balance: U256::max_value(), + storage: Default::default(), + code: vec![], + }, + ); - pallet_balances::GenesisConfig::::default() - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig:: { + // Create the block author account with some balance. + balances: vec![( + H160::from_str("0x1234500000000000000000000000000000000000").unwrap(), + 12345, + )], + } + .assimilate_storage(&mut t) + .expect("Pallet balances storage can be assimilated"); GenesisBuild::::assimilate_storage(&crate::GenesisConfig { accounts }, &mut t).unwrap(); t.into() } @@ -75,8 +90,10 @@ fn fail_call_return_ok() { Vec::new(), U256::default(), 1000000, - U256::default(), + U256::from(1_000_000_000), + None, None, + Vec::new(), )); assert_ok!(EVM::call( @@ -86,8 +103,10 @@ fn fail_call_return_ok() { Vec::new(), U256::default(), 1000000, - U256::default(), + U256::from(1_000_000_000), None, + None, + Vec::new(), )); }); } @@ -121,21 +140,23 @@ fn ed_0_refund_patch_works() { let evm_addr = H160::from_str("1000000000000000000000000000000000000003").unwrap(); let substrate_addr = ::AddressMapping::into_account_id(evm_addr); - let _ = ::Currency::deposit_creating(&substrate_addr, 21777); - assert_eq!(Balances::free_balance(&substrate_addr), 21777); + let _ = ::Currency::deposit_creating(&substrate_addr, 21_777_000_000_000); + assert_eq!(Balances::free_balance(&substrate_addr), 21_777_000_000_000); let _ = EVM::call( Origin::root(), evm_addr, H160::from_str("1000000000000000000000000000000000000001").unwrap(), Vec::new(), - U256::from(1), + U256::from(1_000_000_000), 21776, - U256::from(1), + U256::from(1_000_000_000), + None, Some(U256::from(0)), + Vec::new(), ); // All that was due, was refunded. - assert_eq!(Balances::free_balance(&substrate_addr), 776); + assert_eq!(Balances::free_balance(&substrate_addr), 776_000_000_000); }); } @@ -204,3 +225,106 @@ fn reducible_balance() { assert_eq!(reducible_balance, (genesis_balance - to_lock + existential)); }); } + +#[test] +fn author_should_get_tip() { + new_test_ext().execute_with(|| { + let author = EVM::find_author(); + let before_tip = EVM::account_basic(&author).balance; + let _ = EVM::call( + Origin::root(), + H160::default(), + H160::from_str("1000000000000000000000000000000000000001").unwrap(), + Vec::new(), + U256::from(1), + 1000000, + U256::from(1_000_000_000), + Some(U256::from(1)), + None, + Vec::new(), + ); + let after_tip = EVM::account_basic(&author).balance; + assert_eq!(after_tip, (before_tip + 21000)); + }); +} + +#[test] +fn author_same_balance_without_tip() { + new_test_ext().execute_with(|| { + let author = EVM::find_author(); + let before_tip = EVM::account_basic(&author).balance; + let _ = EVM::call( + Origin::root(), + H160::default(), + H160::from_str("1000000000000000000000000000000000000001").unwrap(), + Vec::new(), + U256::default(), + 1000000, + U256::default(), + None, + None, + Vec::new(), + ); + let after_tip = EVM::account_basic(&author).balance; + assert_eq!(after_tip, before_tip); + }); +} + +#[test] +fn refunds_should_work() { + new_test_ext().execute_with(|| { + let before_call = EVM::account_basic(&H160::default()).balance; + // Gas price is not part of the actual fee calculations anymore, only the base fee. + // + // Because we first deduct max_fee_per_gas * gas_limit (2_000_000_000 * 1000000) we need + // to ensure that the difference (max fee VS base fee) is refunded. + let _ = EVM::call( + Origin::root(), + H160::default(), + H160::from_str("1000000000000000000000000000000000000001").unwrap(), + Vec::new(), + U256::from(1), + 1000000, + U256::from(2_000_000_000), + None, + None, + Vec::new(), + ); + let total_cost = + (U256::from(21_000) * ::FeeCalculator::min_gas_price()) + U256::from(1); + let after_call = EVM::account_basic(&H160::default()).balance; + assert_eq!(after_call, before_call - total_cost); + }); +} + +#[test] +fn refunds_and_priority_should_work() { + new_test_ext().execute_with(|| { + let author = EVM::find_author(); + let before_tip = EVM::account_basic(&author).balance; + let before_call = EVM::account_basic(&H160::default()).balance; + let tip = 5; + // The tip is deducted but never refunded to the caller. + let _ = EVM::call( + Origin::root(), + H160::default(), + H160::from_str("1000000000000000000000000000000000000001").unwrap(), + Vec::new(), + U256::from(1), + 1000000, + U256::from(2_000_000_000), + Some(U256::from(tip)), + None, + Vec::new(), + ); + let tip = tip * 21000; + let total_cost = (U256::from(21_000) * ::FeeCalculator::min_gas_price()) + + U256::from(1) + + U256::from(tip); + let after_call = EVM::account_basic(&H160::default()).balance; + assert_eq!(after_call, before_call - total_cost); + + let after_tip = EVM::account_basic(&author).balance; + assert_eq!(after_tip, (before_tip + tip)); + }); +} diff --git a/frame/evm/test-vector-support/Cargo.toml b/frame/evm/test-vector-support/Cargo.toml index fedab71622..ffd2bae325 100644 --- a/frame/evm/test-vector-support/Cargo.toml +++ b/frame/evm/test-vector-support/Cargo.toml @@ -12,7 +12,7 @@ description = "Test vector support for EVM pallet." hex = { version = "0.4.0", optional = true } serde = { version = "1.0.101", optional = true, features = ["derive"] } serde_json = { version = "1.0", optional = true } -evm = { version = "0.30.0", default-features = false, features = ["with-codec"] } +evm = { version = "0.33.0", default-features = false, features = ["with-codec"] } fp-evm = { version = "3.0.0-dev", default-features = false, path = "../../../primitives/evm" } [features] diff --git a/frame/evm/test-vector-support/src/lib.rs b/frame/evm/test-vector-support/src/lib.rs index d02b9efa51..715916d201 100644 --- a/frame/evm/test-vector-support/src/lib.rs +++ b/frame/evm/test-vector-support/src/lib.rs @@ -55,7 +55,7 @@ pub fn test_precompile_test_vectors( apparent_value: From::from(0), }; - match P::execute(&input, Some(cost), &context) { + match P::execute(&input, Some(cost), &context, false) { Ok(result) => { let as_hex: String = hex::encode(result.output); assert_eq!( diff --git a/primitives/consensus/Cargo.toml b/primitives/consensus/Cargo.toml index 1b77242342..0e5dbea353 100644 --- a/primitives/consensus/Cargo.toml +++ b/primitives/consensus/Cargo.toml @@ -13,7 +13,7 @@ sp-std = { version = "4.0.0-dev", default-features = false, git = "https://githu sp-runtime = { version = "4.0.0-dev", default-features = false, git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" } sp-core = { version = "4.0.0-dev", default-features = false, git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" } codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } -ethereum = { version = "0.9.0", default-features = false, features = ["with-codec"] } +ethereum = { version = "0.10.0", default-features = false, features = ["with-codec"] } rlp = { version = "0.5", default-features = false } sha3 = { version = "0.8", default-features = false } diff --git a/primitives/consensus/src/lib.rs b/primitives/consensus/src/lib.rs index 15e22bb706..3f0b0c8097 100644 --- a/primitives/consensus/src/lib.rs +++ b/primitives/consensus/src/lib.rs @@ -47,7 +47,7 @@ impl Log { #[derive(Decode, Encode, Clone, PartialEq, Eq)] pub enum PreLog { #[codec(index = 3)] - Block(ethereum::BlockV0), + Block(ethereum::BlockV2), } #[derive(Decode, Encode, Clone, PartialEq, Eq)] @@ -55,7 +55,7 @@ pub enum PostLog { #[codec(index = 1)] Hashes(Hashes), #[codec(index = 2)] - Block(ethereum::BlockV0), + Block(ethereum::BlockV2), } #[derive(Decode, Encode, Clone, PartialEq, Eq)] @@ -67,7 +67,7 @@ pub struct Hashes { } impl Hashes { - pub fn from_block(block: ethereum::BlockV0) -> Self { + pub fn from_block(block: ethereum::BlockV2) -> Self { let mut transaction_hashes = Vec::new(); for t in &block.transactions { diff --git a/primitives/evm/Cargo.toml b/primitives/evm/Cargo.toml index df3277f669..a4849c11b5 100644 --- a/primitives/evm/Cargo.toml +++ b/primitives/evm/Cargo.toml @@ -17,8 +17,7 @@ sp-core = { version = "4.0.0-dev", git = "https://github.com/purestake/substrate sp-std = { version = "4.0.0-dev", git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13", default-features = false } serde = { version = "1.0.101", optional = true, features = ["derive"] } codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -evm = { version = "0.30.0", default-features = false, features = ["with-codec"] } -impl-trait-for-tuples = "0.1" +evm = { version = "0.33.0", default-features = false, features = ["with-codec"] } [features] default = ["std"] diff --git a/primitives/evm/src/lib.rs b/primitives/evm/src/lib.rs index c6b64b88a7..cc3f6b052d 100644 --- a/primitives/evm/src/lib.rs +++ b/primitives/evm/src/lib.rs @@ -28,8 +28,8 @@ use sp_std::vec::Vec; pub use evm::backend::{Basic as Account, Log}; pub use precompile::{ - Context, ExitError, ExitSucceed, LinearCostPrecompile, Precompile, PrecompileOutput, - PrecompileSet, + Context, ExitError, ExitSucceed, LinearCostPrecompile, Precompile, PrecompileFailure, + PrecompileOutput, PrecompileResult, PrecompileSet, }; #[derive(Clone, Eq, PartialEq, Encode, Decode, Default)] diff --git a/primitives/evm/src/precompile.rs b/primitives/evm/src/precompile.rs index 33c0346d3f..babd071c87 100644 --- a/primitives/evm/src/precompile.rs +++ b/primitives/evm/src/precompile.rs @@ -15,25 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub use evm::{executor::PrecompileOutput, Context, ExitError, ExitSucceed}; -use impl_trait_for_tuples::impl_for_tuples; -use sp_core::H160; +pub use evm::{ + executor::stack::{PrecompileFailure, PrecompileOutput, PrecompileSet}, + Context, ExitError, ExitSucceed, +}; use sp_std::vec::Vec; -/// Custom precompiles to be used by EVM engine. -pub trait PrecompileSet { - /// Try to execute the code address as precompile. If the code address is not - /// a precompile or the precompile is not yet available, return `None`. - /// Otherwise, calculate the amount of gas needed with given `input` and - /// `target_gas`. Return `Some(Ok(status, output, gas_used))` if the execution - /// is successful. Otherwise return `Some(Err(_))`. - fn execute( - address: H160, - input: &[u8], - target_gas: Option, - context: &Context, - ) -> Option>; -} +pub type PrecompileResult = Result; /// One single precompile used by EVM engine. pub trait Precompile { @@ -44,46 +32,22 @@ pub trait Precompile { input: &[u8], target_gas: Option, context: &Context, - ) -> core::result::Result; -} - -#[impl_for_tuples(16)] -#[tuple_types_no_default_trait_bound] -impl PrecompileSet for Tuple { - for_tuples!( where #( Tuple: Precompile )* ); - - fn execute( - address: H160, - input: &[u8], - target_gas: Option, - context: &Context, - ) -> Option> { - let mut index = 0; - - for_tuples!( #( - index += 1; - if address == H160::from_low_u64_be(index) { - return Some(Tuple::execute(input, target_gas, context)) - } - )* ); - - None - } + is_static: bool, + ) -> PrecompileResult; } pub trait LinearCostPrecompile { const BASE: u64; const WORD: u64; - fn execute(input: &[u8], cost: u64) -> core::result::Result<(ExitSucceed, Vec), ExitError>; + fn execute( + input: &[u8], + cost: u64, + ) -> core::result::Result<(ExitSucceed, Vec), PrecompileFailure>; } impl Precompile for T { - fn execute( - input: &[u8], - target_gas: Option, - _: &Context, - ) -> core::result::Result { + fn execute(input: &[u8], target_gas: Option, _: &Context, _: bool) -> PrecompileResult { let cost = ensure_linear_cost(target_gas, input.len() as u64, T::BASE, T::WORD)?; let (exit_status, output) = T::execute(input, cost)?; @@ -102,17 +66,22 @@ fn ensure_linear_cost( len: u64, base: u64, word: u64, -) -> Result { +) -> Result { let cost = base - .checked_add( - word.checked_mul(len.saturating_add(31) / 32) - .ok_or(ExitError::OutOfGas)?, - ) - .ok_or(ExitError::OutOfGas)?; + .checked_add(word.checked_mul(len.saturating_add(31) / 32).ok_or( + PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }, + )?) + .ok_or(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + })?; if let Some(target_gas) = target_gas { if cost > target_gas { - return Err(ExitError::OutOfGas); + return Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }); } } diff --git a/primitives/rpc/Cargo.toml b/primitives/rpc/Cargo.toml index f2fe285611..931b9db825 100644 --- a/primitives/rpc/Cargo.toml +++ b/primitives/rpc/Cargo.toml @@ -10,7 +10,7 @@ license = "Apache-2.0" sp-core = { version = "4.0.0-dev", default-features = false, git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" } sp-api = { version = "4.0.0-dev", default-features = false, git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" } fp-evm = { version = "3.0.0-dev", default-features = false, path = "../../primitives/evm" } -ethereum = { version = "0.9.0", default-features = false, features = ["with-codec"] } +ethereum = { version = "0.10.0", default-features = false, features = ["with-codec"] } ethereum-types = { version = "0.12", default-features = false } codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } sp-runtime = { version = "4.0.0-dev", default-features = false, git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" } @@ -30,4 +30,5 @@ std = [ "sp-runtime/std", "sp-std/std", "sp-io/std", + "scale-info/std", ] diff --git a/primitives/rpc/src/lib.rs b/primitives/rpc/src/lib.rs index d51bf59cb0..c603f12a06 100644 --- a/primitives/rpc/src/lib.rs +++ b/primitives/rpc/src/lib.rs @@ -18,7 +18,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode}; -use ethereum::{BlockV0 as EthereumBlock, Log}; +use ethereum::Log; use ethereum_types::Bloom; use sp_core::{H160, H256, U256}; use sp_runtime::traits::Block as BlockT; @@ -51,6 +51,7 @@ impl Default for TransactionStatus { sp_api::decl_runtime_apis! { /// API necessary for Ethereum-compatibility layer. + #[api_version(2)] pub trait EthereumRuntimeRPCApi { /// Returns runtime defined pallet_evm::ChainId. fn chain_id() -> u64; @@ -65,6 +66,7 @@ sp_api::decl_runtime_apis! { /// For a given account address and index, returns pallet_evm::AccountStorages. fn storage_at(address: H160, index: U256) -> H256; /// Returns a frame_ethereum::call response. If `estimate` is true, + #[changed_in(2)] fn call( from: H160, to: H160, @@ -75,7 +77,19 @@ sp_api::decl_runtime_apis! { nonce: Option, estimate: bool, ) -> Result; + fn call( + from: H160, + to: H160, + data: Vec, + value: U256, + gas_limit: U256, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, + nonce: Option, + estimate: bool, + ) -> Result; /// Returns a frame_ethereum::create response. + #[changed_in(2)] fn create( from: H160, data: Vec, @@ -85,25 +99,50 @@ sp_api::decl_runtime_apis! { nonce: Option, estimate: bool, ) -> Result; + fn create( + from: H160, + data: Vec, + value: U256, + gas_limit: U256, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, + nonce: Option, + estimate: bool, + ) -> Result; + /// Return the current block. Legacy. + #[changed_in(2)] + fn current_block() -> Option; /// Return the current block. - fn current_block() -> Option; + fn current_block() -> Option; /// Return the current receipt. fn current_receipts() -> Option>; /// Return the current transaction status. fn current_transaction_statuses() -> Option>; + /// Return all the current data for a block in a single runtime call. Legacy. + #[changed_in(2)] + fn current_all() -> ( + Option, + Option>, + Option> + ); /// Return all the current data for a block in a single runtime call. fn current_all() -> ( - Option, + Option, Option>, Option> ); - /// Receives a `Vec` and filters all the ethereum transactions. + /// Receives a `Vec` and filters all the ethereum transactions. Legacy. + #[changed_in(2)] fn extrinsic_filter( xts: Vec<::Extrinsic>, ) -> Vec; + /// Receives a `Vec` and filters all the ethereum transactions. + fn extrinsic_filter( + xts: Vec<::Extrinsic>, + ) -> Vec; } } pub trait ConvertTransaction { - fn convert_transaction(&self, transaction: ethereum::TransactionV0) -> E; + fn convert_transaction(&self, transaction: ethereum::TransactionV2) -> E; } diff --git a/primitives/self-contained/Cargo.toml b/primitives/self-contained/Cargo.toml index aaed050093..04805110fb 100644 --- a/primitives/self-contained/Cargo.toml +++ b/primitives/self-contained/Cargo.toml @@ -12,7 +12,7 @@ documentation = "https://docs.rs/fp-ethereum" [dependencies] serde = { version = "1.0.101", optional = true, features = ["derive"] } codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } -ethereum = { version = "0.9.0", default-features = false, features = ["with-codec"] } +ethereum = { version = "0.10.0", default-features = false, features = ["with-codec"] } sp-core = { version = "4.0.0-dev", git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13", default-features = false } sp-runtime = { version = "4.0.0-dev", git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13", default-features = false } sp-io = { version = "4.0.0-dev", git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13", default-features = false } diff --git a/template/node/Cargo.toml b/template/node/Cargo.toml index 612c19a0b3..66799bcc38 100644 --- a/template/node/Cargo.toml +++ b/template/node/Cargo.toml @@ -68,6 +68,7 @@ fc-mapping-sync = { path = "../../client/mapping-sync" } pallet-evm = { path = "../../frame/evm" } pallet-ethereum = { path = "../../frame/ethereum" } pallet-dynamic-fee = { path = "../../frame/dynamic-fee" } +pallet-base-fee = { path = "../../frame/base-fee" } [build-dependencies] substrate-build-script-utils = { git = "https://github.com/purestake/substrate", branch = "moonbeam-polkadot-v0.9.13" } diff --git a/template/node/src/chain_spec.rs b/template/node/src/chain_spec.rs index 94d03a6622..f46cc578c7 100644 --- a/template/node/src/chain_spec.rs +++ b/template/node/src/chain_spec.rs @@ -196,5 +196,6 @@ fn testnet_genesis( }, ethereum: EthereumConfig {}, dynamic_fee: Default::default(), + base_fee: Default::default(), } } diff --git a/template/node/src/rpc.rs b/template/node/src/rpc.rs index d75ca075a2..3c39661ed0 100644 --- a/template/node/src/rpc.rs +++ b/template/node/src/rpc.rs @@ -3,7 +3,8 @@ use std::sync::Arc; use fc_rpc::{ - EthBlockDataCache, OverrideHandle, RuntimeApiStorageOverride, SchemaV1Override, StorageOverride, + EthBlockDataCache, OverrideHandle, RuntimeApiStorageOverride, SchemaV1Override, + SchemaV2Override, StorageOverride, }; use fc_rpc_core::types::FilterPool; use frontier_template_runtime::{opaque::Block, AccountId, Balance, Hash, Index}; @@ -113,6 +114,11 @@ where Box::new(SchemaV1Override::new(client.clone())) as Box + Send + Sync>, ); + overrides_map.insert( + EthereumStorageSchema::V2, + Box::new(SchemaV2Override::new(client.clone())) + as Box + Send + Sync>, + ); let overrides = Arc::new(OverrideHandle { schemas: overrides_map, diff --git a/template/node/src/service.rs b/template/node/src/service.rs index 69f0489d1b..c3900b5bfc 100644 --- a/template/node/src/service.rs +++ b/template/node/src/service.rs @@ -27,7 +27,7 @@ use sp_core::U256; use sp_inherents::{InherentData, InherentIdentifier}; use std::{ cell::RefCell, - collections::{BTreeMap, HashMap}, + collections::BTreeMap, sync::{Arc, Mutex}, time::Duration, }; @@ -442,7 +442,11 @@ pub fn new_full(mut config: Configuration, cli: &Cli) -> Result> FindAuthor for FindAuthorTruncated { parameter_types! { pub const ChainId: u64 = 42; pub BlockGasLimit: U256 = U256::from(u32::max_value()); + pub PrecompilesValue: FrontierPrecompiles = FrontierPrecompiles::<_>::new(); } impl pallet_evm::Config for Runtime { - type FeeCalculator = pallet_dynamic_fee::Pallet; + type FeeCalculator = BaseFee; type GasWeightMapping = (); type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping; type CallOrigin = EnsureAddressTruncated; @@ -309,16 +314,8 @@ impl pallet_evm::Config for Runtime { type Currency = Balances; type Event = Event; type Runner = pallet_evm::runner::stack::Runner; - type Precompiles = ( - pallet_evm_precompile_simple::ECRecover, - pallet_evm_precompile_simple::Sha256, - pallet_evm_precompile_simple::Ripemd160, - pallet_evm_precompile_simple::Identity, - pallet_evm_precompile_modexp::Modexp, - pallet_evm_precompile_simple::ECRecoverPublicKey, - pallet_evm_precompile_sha3fips::Sha3FIPS256, - pallet_evm_precompile_sha3fips::Sha3FIPS512, - ); + type PrecompilesType = FrontierPrecompiles; + type PrecompilesValue = PrecompilesValue; type ChainId = ChainId; type BlockGasLimit = BlockGasLimit; type OnChargeTransaction = (); @@ -338,6 +335,21 @@ impl pallet_dynamic_fee::Config for Runtime { type MinGasPriceBoundDivisor = BoundDivision; } +pub struct BaseFeeThreshold; +impl pallet_base_fee::BaseFeeThreshold for BaseFeeThreshold { + fn lower() -> Permill { + Permill::zero() + } + fn upper() -> Permill { + Permill::from_parts(1_000_000) + } +} + +impl pallet_base_fee::Config for Runtime { + type Event = Event; + type Threshold = BaseFeeThreshold; +} + impl pallet_randomness_collective_flip::Config for Runtime {} // Create the runtime by composing the FRAME pallets that were previously configured. @@ -358,6 +370,7 @@ construct_runtime!( Ethereum: pallet_ethereum::{Pallet, Call, Storage, Event, Config, Origin}, EVM: pallet_evm::{Pallet, Config, Call, Storage, Event}, DynamicFee: pallet_dynamic_fee::{Pallet, Call, Storage, Config, Inherent}, + BaseFee: pallet_base_fee::{Pallet, Call, Storage, Config, Event}, } ); @@ -573,7 +586,8 @@ impl_runtime_apis! { data: Vec, value: U256, gas_limit: U256, - gas_price: Option, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, nonce: Option, estimate: bool, ) -> Result { @@ -591,8 +605,10 @@ impl_runtime_apis! { data, value, gas_limit.low_u64(), - gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, nonce, + Vec::new(), config.as_ref().unwrap_or(::config()), ).map_err(|err| err.into()) } @@ -602,7 +618,8 @@ impl_runtime_apis! { data: Vec, value: U256, gas_limit: U256, - gas_price: Option, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, nonce: Option, estimate: bool, ) -> Result { @@ -619,8 +636,10 @@ impl_runtime_apis! { data, value, gas_limit.low_u64(), - gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, nonce, + Vec::new(), config.as_ref().unwrap_or(::config()), ).map_err(|err| err.into()) } @@ -653,7 +672,7 @@ impl_runtime_apis! { xts: Vec<::Extrinsic>, ) -> Vec { xts.into_iter().filter_map(|xt| match xt.0.function { - Call::Ethereum(transact {transaction}) => Some(transaction), + Call::Ethereum(transact { transaction }) => Some(transaction), _ => None }).collect::>() } diff --git a/template/runtime/src/precompiles.rs b/template/runtime/src/precompiles.rs new file mode 100644 index 0000000000..17a4ec2b9e --- /dev/null +++ b/template/runtime/src/precompiles.rs @@ -0,0 +1,62 @@ +use pallet_evm::{Context, Precompile, PrecompileResult, PrecompileSet}; +use sp_core::H160; +use sp_std::marker::PhantomData; + +use pallet_evm_precompile_modexp::Modexp; +use pallet_evm_precompile_sha3fips::Sha3FIPS256; +use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; + +pub struct FrontierPrecompiles(PhantomData); + +impl FrontierPrecompiles +where + R: pallet_evm::Config, +{ + pub fn new() -> Self { + Self(Default::default()) + } + pub fn used_addresses() -> sp_std::vec::Vec { + sp_std::vec![1, 2, 3, 4, 5, 1024, 1025] + .into_iter() + .map(|x| hash(x)) + .collect() + } +} +impl PrecompileSet for FrontierPrecompiles +where + R: pallet_evm::Config, +{ + fn execute( + &self, + address: H160, + input: &[u8], + target_gas: Option, + context: &Context, + is_static: bool, + ) -> Option { + match address { + // Ethereum precompiles : + a if a == hash(1) => Some(ECRecover::execute(input, target_gas, context, is_static)), + a if a == hash(2) => Some(Sha256::execute(input, target_gas, context, is_static)), + a if a == hash(3) => Some(Ripemd160::execute(input, target_gas, context, is_static)), + a if a == hash(4) => Some(Identity::execute(input, target_gas, context, is_static)), + a if a == hash(5) => Some(Modexp::execute(input, target_gas, context, is_static)), + // Non-Frontier specific nor Ethereum precompiles : + a if a == hash(1024) => { + Some(Sha3FIPS256::execute(input, target_gas, context, is_static)) + } + a if a == hash(1025) => Some(ECRecoverPublicKey::execute( + input, target_gas, context, is_static, + )), + _ => None, + } + } + + fn is_precompile(&self, address: H160) -> bool { + Self::used_addresses().contains(&address) + } +} + +fn hash(a: u64) -> H160 { + H160::from_low_u64_be(a) +} diff --git a/ts-tests/package-lock.json b/ts-tests/package-lock.json index b3a9bf1cc4..1fb14763e5 100644 --- a/ts-tests/package-lock.json +++ b/ts-tests/package-lock.json @@ -4269,4 +4269,4 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" } } -} +} \ No newline at end of file diff --git a/ts-tests/package.json b/ts-tests/package.json index ca7602bc34..da146dd65a 100644 --- a/ts-tests/package.json +++ b/ts-tests/package.json @@ -19,6 +19,7 @@ "truffle": "^5.1.62", "ts-node": "^8.10.2", "typescript": "^3.9.6", - "web3": "^1.3.4" + "web3": "^1.3.4", + "ethers": "^5.4.6" } } diff --git a/ts-tests/tests/test-balance.ts b/ts-tests/tests/test-balance.ts index 24a2bf33a7..5573f1c54d 100644 --- a/ts-tests/tests/test-balance.ts +++ b/ts-tests/tests/test-balance.ts @@ -20,12 +20,13 @@ describeWithFrontier("Frontier RPC (Balance)", (context) => { from: GENESIS_ACCOUNT, to: TEST_ACCOUNT, value: "0x200", // Must me higher than ExistentialDeposit (500) - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", gas: "0x100000", }, GENESIS_ACCOUNT_PRIVATE_KEY); await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]); await createAndFinalizeBlock(context.web3); - expect(await context.web3.eth.getBalance(GENESIS_ACCOUNT)).to.equal("340282366920938463463374607431768189443"); + // 340282366920938463463374607431768210955 - (21000 * 1000000000) + 512; + expect(await context.web3.eth.getBalance(GENESIS_ACCOUNT)).to.equal("340282366920938463463374586431768210443"); expect(await context.web3.eth.getBalance(TEST_ACCOUNT)).to.equal("12"); }); -}); +}); \ No newline at end of file diff --git a/ts-tests/tests/test-bloom.ts b/ts-tests/tests/test-bloom.ts index f252884398..a6e99b4098 100644 --- a/ts-tests/tests/test-bloom.ts +++ b/ts-tests/tests/test-bloom.ts @@ -17,7 +17,7 @@ describeWithFrontier("Frontier RPC (Bloom)", (context) => { from: GENESIS_ACCOUNT, data: TEST_CONTRACT_BYTECODE, value: "0x00", - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", gas: "0x100000", }, GENESIS_ACCOUNT_PRIVATE_KEY diff --git a/ts-tests/tests/test-contract-methods.ts b/ts-tests/tests/test-contract-methods.ts index ab142f0424..861a3ee875 100644 --- a/ts-tests/tests/test-contract-methods.ts +++ b/ts-tests/tests/test-contract-methods.ts @@ -19,7 +19,7 @@ describeWithFrontier("Frontier RPC (Contract Methods)", (context) => { from: GENESIS_ACCOUNT, data: TEST_CONTRACT_BYTECODE, value: "0x00", - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", gas: "0x100000", }, GENESIS_ACCOUNT_PRIVATE_KEY @@ -40,7 +40,7 @@ describeWithFrontier("Frontier RPC (Contract Methods)", (context) => { it("should return contract method result", async function () { const contract = new context.web3.eth.Contract(TEST_CONTRACT_ABI, FIRST_CONTRACT_ADDRESS, { from: GENESIS_ACCOUNT, - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", }); expect(await contract.methods.multiply(3).call()).to.equal("21"); @@ -49,7 +49,7 @@ describeWithFrontier("Frontier RPC (Contract Methods)", (context) => { // Solidity `block.number` is expected to return the same height at which the runtime call was made. const contract = new context.web3.eth.Contract(TEST_CONTRACT_ABI, FIRST_CONTRACT_ADDRESS, { from: GENESIS_ACCOUNT, - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", }); let block = await context.web3.eth.getBlock("latest"); expect(await contract.methods.currentBlock().call()).to.eq(block.number.toString()); @@ -63,7 +63,7 @@ describeWithFrontier("Frontier RPC (Contract Methods)", (context) => { // Solidity `blockhash` is expected to return the ethereum block hash at a given height. const contract = new context.web3.eth.Contract(TEST_CONTRACT_ABI, FIRST_CONTRACT_ADDRESS, { from: GENESIS_ACCOUNT, - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", }); let number = (await context.web3.eth.getBlock("latest")).number; let last = number + 256; @@ -81,7 +81,7 @@ describeWithFrontier("Frontier RPC (Contract Methods)", (context) => { it("should get correct environmental block gaslimit", async function () { const contract = new context.web3.eth.Contract(TEST_CONTRACT_ABI, FIRST_CONTRACT_ADDRESS, { from: GENESIS_ACCOUNT, - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", }); // Max u32 expect(await contract.methods.gasLimit().call()).to.eq('4294967295'); @@ -91,7 +91,7 @@ describeWithFrontier("Frontier RPC (Contract Methods)", (context) => { it.skip("should fail for missing parameters", async function () { const contract = new context.web3.eth.Contract([{ ...TEST_CONTRACT_ABI[0], inputs: [] }], FIRST_CONTRACT_ADDRESS, { from: GENESIS_ACCOUNT, - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", }); await contract.methods .multiply() @@ -116,7 +116,7 @@ describeWithFrontier("Frontier RPC (Contract Methods)", (context) => { FIRST_CONTRACT_ADDRESS, { from: GENESIS_ACCOUNT, - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", } ); await contract.methods @@ -132,7 +132,7 @@ describeWithFrontier("Frontier RPC (Contract Methods)", (context) => { const contract = new context.web3.eth.Contract( [{ ...TEST_CONTRACT_ABI[0], inputs: [{ internalType: "address", name: "a", type: "address" }] }], FIRST_CONTRACT_ADDRESS, - { from: GENESIS_ACCOUNT, gasPrice: "0x01" } + { from: GENESIS_ACCOUNT, gasPrice: "0x3B9ACA00" } ); await contract.methods .multiply("0x0123456789012345678901234567890123456789") diff --git a/ts-tests/tests/test-contract-storage.ts b/ts-tests/tests/test-contract-storage.ts index 156af3c17e..39572ed1af 100644 --- a/ts-tests/tests/test-contract-storage.ts +++ b/ts-tests/tests/test-contract-storage.ts @@ -22,7 +22,7 @@ describeWithFrontier("Frontier RPC (Contract)", (context) => { from: GENESIS_ACCOUNT, data: TEST_CONTRACT_BYTECODE, value: "0x00", - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", gas: "0x100000", }, GENESIS_ACCOUNT_PRIVATE_KEY @@ -57,7 +57,7 @@ describeWithFrontier("Frontier RPC (Contract)", (context) => { "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" ).encodeABI(), value: "0x00", - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", gas: "0x500000", }, GENESIS_ACCOUNT_PRIVATE_KEY diff --git a/ts-tests/tests/test-contract.ts b/ts-tests/tests/test-contract.ts index b589fc7b4d..367d72cc60 100644 --- a/ts-tests/tests/test-contract.ts +++ b/ts-tests/tests/test-contract.ts @@ -20,7 +20,7 @@ describeWithFrontier("Frontier RPC (Contract)", (context) => { from: GENESIS_ACCOUNT, data: TEST_CONTRACT_BYTECODE, value: "0x00", - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", gas: "0x100000", }, GENESIS_ACCOUNT_PRIVATE_KEY diff --git a/ts-tests/tests/test-filter-api.ts b/ts-tests/tests/test-filter-api.ts index 06d8052f4d..0795309df4 100644 --- a/ts-tests/tests/test-filter-api.ts +++ b/ts-tests/tests/test-filter-api.ts @@ -18,7 +18,7 @@ describeWithFrontier("Frontier RPC (EthFilterApi)", (context) => { from: GENESIS_ACCOUNT, data: TEST_CONTRACT_BYTECODE, value: "0x00", - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", gas: "0x100000", }, GENESIS_ACCOUNT_PRIVATE_KEY diff --git a/ts-tests/tests/test-gas.ts b/ts-tests/tests/test-gas.ts index 2f0e133860..4e1ac995cb 100644 --- a/ts-tests/tests/test-gas.ts +++ b/ts-tests/tests/test-gas.ts @@ -44,7 +44,7 @@ describeWithFrontier("Frontier RPC (Gas)", (context) => { it("eth_estimateGas for contract call", async function () { const contract = new context.web3.eth.Contract(TEST_CONTRACT_ABI, FIRST_CONTRACT_ADDRESS, { from: GENESIS_ACCOUNT, - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", }); expect(await contract.methods.multiply(3).estimateGas()).to.equal(21204); diff --git a/ts-tests/tests/test-log-filtering.ts b/ts-tests/tests/test-log-filtering.ts index 910d1340c7..e9b9d7cfb1 100644 --- a/ts-tests/tests/test-log-filtering.ts +++ b/ts-tests/tests/test-log-filtering.ts @@ -19,7 +19,7 @@ describeWithFrontier("Frontier RPC (Log filtering)", (context) => { from: GENESIS_ACCOUNT, data: TEST_CONTRACT_BYTECODE, value: "0x00", - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", gas: "0x100000", }, GENESIS_ACCOUNT_PRIVATE_KEY diff --git a/ts-tests/tests/test-nonce.ts b/ts-tests/tests/test-nonce.ts index 51260a13c9..439cfc9174 100644 --- a/ts-tests/tests/test-nonce.ts +++ b/ts-tests/tests/test-nonce.ts @@ -14,7 +14,7 @@ describeWithFrontier("Frontier RPC (Nonce)", (context) => { from: GENESIS_ACCOUNT, to: TEST_ACCOUNT, value: "0x200", // Must me higher than ExistentialDeposit (500) - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", gas: "0x100000", }, GENESIS_ACCOUNT_PRIVATE_KEY); diff --git a/ts-tests/tests/test-pending-pool.ts b/ts-tests/tests/test-pending-pool.ts index c727b0b2a4..bdeecf1d11 100644 --- a/ts-tests/tests/test-pending-pool.ts +++ b/ts-tests/tests/test-pending-pool.ts @@ -18,7 +18,7 @@ describeWithFrontier("Frontier RPC (Pending Pool)", (context) => { from: GENESIS_ACCOUNT, data: TEST_CONTRACT_BYTECODE, value: "0x00", - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", gas: "0x100000", }, GENESIS_ACCOUNT_PRIVATE_KEY @@ -32,8 +32,8 @@ describeWithFrontier("Frontier RPC (Pending Pool)", (context) => { blockNumber: null, hash: tx_hash, publicKey: "0x624f720eae676a04111631c9ca338c11d0f5a80ee42210c6be72983ceb620fbf645a96f951529fa2d70750432d11b7caba5270c4d677255be90b3871c8c58069", - r: "0x5431b25e8100a21ced6af01868357b19d58b94afa6f57dc7cbf81f4a922ddecc", - s: "0x22e05530d015ea702ffb37af313e691ce4423565c2734c267edd3c74aea0a010", + r: "0x8e3759de96b00f8a05a95c24fa905963f86a82a0038cca0fde035762fb2d24f7", + s: "0x7131a2c265463f4bb063504f924df4d3d14bdad9cdfff8391041ea78295d186b", v: "0x77", }); @@ -43,8 +43,8 @@ describeWithFrontier("Frontier RPC (Pending Pool)", (context) => { expect(processed_transaction).to.include({ hash: tx_hash, publicKey: "0x624f720eae676a04111631c9ca338c11d0f5a80ee42210c6be72983ceb620fbf645a96f951529fa2d70750432d11b7caba5270c4d677255be90b3871c8c58069", - r: "0x5431b25e8100a21ced6af01868357b19d58b94afa6f57dc7cbf81f4a922ddecc", - s: "0x22e05530d015ea702ffb37af313e691ce4423565c2734c267edd3c74aea0a010", + r: "0x8e3759de96b00f8a05a95c24fa905963f86a82a0038cca0fde035762fb2d24f7", + s: "0x7131a2c265463f4bb063504f924df4d3d14bdad9cdfff8391041ea78295d186b", v: "0x77", }); }); diff --git a/ts-tests/tests/test-precompiles.ts b/ts-tests/tests/test-precompiles.ts index 1a0ba048fc..12d1155dac 100644 --- a/ts-tests/tests/test-precompiles.ts +++ b/ts-tests/tests/test-precompiles.ts @@ -20,7 +20,7 @@ describeWithFrontier("Frontier RPC (Precompile)", (context) => { from: GENESIS_ACCOUNT, data: TEST_CONTRACT_BYTECODE, value: "0x00", - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", gas: "0x100000", }, GENESIS_ACCOUNT_PRIVATE_KEY @@ -52,7 +52,7 @@ describeWithFrontier("Frontier RPC (Precompile)", (context) => { to: '0000000000000000000000000000000000000005', data: `0x${hash.toString()}${sigPart}`, value: "0x00", - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", gas: "0x100000", }, GENESIS_ACCOUNT_PRIVATE_KEY @@ -60,7 +60,7 @@ describeWithFrontier("Frontier RPC (Precompile)", (context) => { const contract = new context.web3.eth.Contract(TEST_CONTRACT_ABI, FIRST_CONTRACT_ADDRESS, { from: GENESIS_ACCOUNT, - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", }); await contract.methods diff --git a/ts-tests/tests/test-revert-reason.ts b/ts-tests/tests/test-revert-reason.ts index bfd281a3a1..e6fb735b2d 100644 --- a/ts-tests/tests/test-revert-reason.ts +++ b/ts-tests/tests/test-revert-reason.ts @@ -21,7 +21,7 @@ describeWithFrontier("Frontier RPC (Revert Reason)", (context) => { from: GENESIS_ACCOUNT, data: REVERT_W_MESSAGE_BYTECODE, value: "0x00", - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", gas: "0x100000", }, GENESIS_ACCOUNT_PRIVATE_KEY @@ -35,7 +35,7 @@ describeWithFrontier("Frontier RPC (Revert Reason)", (context) => { it("should fail with revert reason", async function () { const contract = new context.web3.eth.Contract(TEST_CONTRACT_ABI, contractAddress, { from: GENESIS_ACCOUNT, - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", }); try { await contract.methods.max10(30).call(); diff --git a/ts-tests/tests/test-revert-receipt.ts b/ts-tests/tests/test-revert-receipt.ts index 4865a44fd5..3949556071 100644 --- a/ts-tests/tests/test-revert-receipt.ts +++ b/ts-tests/tests/test-revert-receipt.ts @@ -21,14 +21,14 @@ describeWithFrontier("Frontier RPC (Constructor Revert)", (context) => { it("should provide a tx receipt after successful deployment", async function () { this.timeout(15000); - const GOOD_TX_HASH = '0xae813c533aac0719fbca4db6e3bb05cfb5859bdeaaa7dc5c9dbd24083301be8d'; + const GOOD_TX_HASH = '0xe73cae1b1105e805ec524b7ffdc4144041e72ff5fb539757ab2d4eef255bfe2d'; const tx = await context.web3.eth.accounts.signTransaction( { from: GENESIS_ACCOUNT, data: GOOD_BYTECODE, value: "0x00", - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", gas: "0x100000", }, GENESIS_ACCOUNT_PRIVATE_KEY @@ -51,7 +51,7 @@ describeWithFrontier("Frontier RPC (Constructor Revert)", (context) => { from: '0x6be02d1d3665660d22ff9624b7be0551ee1ac91b', gasUsed: 67231, to: null, - transactionHash: '0xae813c533aac0719fbca4db6e3bb05cfb5859bdeaaa7dc5c9dbd24083301be8d', + transactionHash: GOOD_TX_HASH, transactionIndex: 0, status: true }); @@ -61,14 +61,14 @@ describeWithFrontier("Frontier RPC (Constructor Revert)", (context) => { this.timeout(15000); // Transaction hash depends on which nonce we're using //const FAIL_TX_HASH = '0x89a956c4631822f407b3af11f9251796c276655860c892919f848699ed570a8d'; //nonce 1 - const FAIL_TX_HASH = '0x640df9deb183d565addc45bdc8f95b30c7c03ce7e69df49456be9929352e4347'; //nonce 2 + const FAIL_TX_HASH = '0x0aad023a79ccfe0290b8cb47a807720bf4ffcf60225f3fa786eefd95b538d87d'; //nonce 2 const tx = await context.web3.eth.accounts.signTransaction( { from: GENESIS_ACCOUNT, data: FAIL_BYTECODE, value: "0x00", - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", gas: "0x100000", }, GENESIS_ACCOUNT_PRIVATE_KEY @@ -90,7 +90,7 @@ describeWithFrontier("Frontier RPC (Constructor Revert)", (context) => { from: '0x6be02d1d3665660d22ff9624b7be0551ee1ac91b', gasUsed: 54600, to: null, - transactionHash: '0x640df9deb183d565addc45bdc8f95b30c7c03ce7e69df49456be9929352e4347', + transactionHash: FAIL_TX_HASH, transactionIndex: 0, status: false }); diff --git a/ts-tests/tests/test-subscription.ts b/ts-tests/tests/test-subscription.ts index 837fe03749..f77f583cb7 100644 --- a/ts-tests/tests/test-subscription.ts +++ b/ts-tests/tests/test-subscription.ts @@ -20,7 +20,7 @@ describeWithFrontier("Frontier RPC (Subscription)", (context) => { from: GENESIS_ACCOUNT, data: TEST_CONTRACT_BYTECODE, value: "0x00", - gasPrice: "0x01", + gasPrice: "0x3B9ACA00", gas: "0x1000000", }, GENESIS_ACCOUNT_PRIVATE_KEY diff --git a/ts-tests/tests/test-transaction-priority.ts b/ts-tests/tests/test-transaction-priority.ts index ee399b7a86..70c8b41188 100644 --- a/ts-tests/tests/test-transaction-priority.ts +++ b/ts-tests/tests/test-transaction-priority.ts @@ -28,16 +28,16 @@ describeWithFrontier("Frontier RPC (Priority)", (context) => { await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]); return tx; } - + step("should prioritize transaction with the higher gasPrice", async function () { this.timeout(15000); - const gasPrices = ["0x03","0x01","0x06","0x08","0x03","0x09","0x04","0x07","0x05"]; + const gasPrices = ["0x3B9ACA01","0x3B9ACA00","0x3B9ACA04","0x3B9ACA06","0x3B9ACA01","0x3B9ACA07","0x3B9ACA02","0x3B9ACA05","0x3B9ACA03"]; for(var gasPrice of gasPrices) { await sendTransaction(context, gasPrice); } await createAndFinalizeBlock(context.web3); const block = await context.web3.eth.getBlock("latest",true); expect(block.transactions.length).to.be.eq(1); - expect(block.transactions[0].gasPrice).to.be.eq('9'); + expect(block.transactions[0].gasPrice).to.be.eq('1000000007'); }); }); diff --git a/ts-tests/tests/test-transaction-version.ts b/ts-tests/tests/test-transaction-version.ts new file mode 100644 index 0000000000..94434d45e8 --- /dev/null +++ b/ts-tests/tests/test-transaction-version.ts @@ -0,0 +1,59 @@ +import { ethers } from "ethers"; +import { expect } from "chai"; +import { step } from "mocha-steps"; + +import { createAndFinalizeBlock, describeWithFrontier, customRequest } from "./util"; + +// We use ethers library in this test as apparently web3js's types are not fully EIP-1559 compliant yet. +describeWithFrontier("Frontier RPC (Transaction Version)", (context) => { + + const GENESIS_ACCOUNT = "0x6be02d1d3665660d22ff9624b7be0551ee1ac91b"; + const GENESIS_ACCOUNT_PRIVATE_KEY = "0x99B3C12287537E38C90A9219D4CB074A89A16E9CDB20BF85728EBD97C343E342"; + + const TEST_CONTRACT_BYTECODE = + "0x608060405234801561001057600080fd5b50610041337fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61004660201b60201c565b610291565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156100e9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f45524332303a206d696e7420746f20746865207a65726f20616464726573730081525060200191505060405180910390fd5b6101028160025461020960201b610c7c1790919060201c565b60028190555061015d816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461020960201b610c7c1790919060201c565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b600080828401905083811015610287576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b610e3a806102a06000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806370a082311161005b57806370a08231146101fd578063a457c2d714610255578063a9059cbb146102bb578063dd62ed3e1461032157610088565b8063095ea7b31461008d57806318160ddd146100f357806323b872dd146101115780633950935114610197575b600080fd5b6100d9600480360360408110156100a357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610399565b604051808215151515815260200191505060405180910390f35b6100fb6103b7565b6040518082815260200191505060405180910390f35b61017d6004803603606081101561012757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506103c1565b604051808215151515815260200191505060405180910390f35b6101e3600480360360408110156101ad57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061049a565b604051808215151515815260200191505060405180910390f35b61023f6004803603602081101561021357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061054d565b6040518082815260200191505060405180910390f35b6102a16004803603604081101561026b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610595565b604051808215151515815260200191505060405180910390f35b610307600480360360408110156102d157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610662565b604051808215151515815260200191505060405180910390f35b6103836004803603604081101561033757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610680565b6040518082815260200191505060405180910390f35b60006103ad6103a6610707565b848461070f565b6001905092915050565b6000600254905090565b60006103ce848484610906565b61048f846103da610707565b61048a85604051806060016040528060288152602001610d7060289139600160008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610440610707565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610bbc9092919063ffffffff16565b61070f565b600190509392505050565b60006105436104a7610707565b8461053e85600160006104b8610707565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610c7c90919063ffffffff16565b61070f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60006106586105a2610707565b8461065385604051806060016040528060258152602001610de160259139600160006105cc610707565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610bbc9092919063ffffffff16565b61070f565b6001905092915050565b600061067661066f610707565b8484610906565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610795576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180610dbd6024913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561081b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180610d286022913960400191505060405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040518082815260200191505060405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180610d986025913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610a12576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526023815260200180610d056023913960400191505060405180910390fd5b610a7d81604051806060016040528060268152602001610d4a602691396000808773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610bbc9092919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610b10816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610c7c90919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b6000838311158290610c69576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b83811015610c2e578082015181840152602081019050610c13565b50505050905090810190601f168015610c5b5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060008385039050809150509392505050565b600080828401905083811015610cfa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b809150509291505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa265627a7a72315820c7a5ffabf642bda14700b2de42f8c57b36621af020441df825de45fd2b3e1c5c64736f6c63430005100032"; + + async function sendTransaction(context, payload: any) { + let signer = new ethers.Wallet(GENESIS_ACCOUNT_PRIVATE_KEY, context.ethersjs); + // Ethers internally matches the locally calculated transaction hash against the one returned as a response. + // Test would fail in case of mismatch. + const tx = await signer.sendTransaction(payload); + return tx; + } + + step("should handle EIP-2930 transaction type 1", async function () { + const tx_hash = (await sendTransaction(context, { + from: GENESIS_ACCOUNT, + data: TEST_CONTRACT_BYTECODE, + value: "0x00", + gasPrice: "0x3B9ACA00", + type: 1, + accessList: [], + nonce: 0, + gasLimit: "0x100000", + chainId: 42 + })).hash; + await createAndFinalizeBlock(context.web3); + const latest = await context.web3.eth.getBlock("latest"); + expect(latest.transactions.length).to.be.eq(1); + expect(latest.transactions[0]).to.be.eq(tx_hash); + }); + + step("should handle EIP-1559 transaction type 2", async function () { + const tx_hash = (await sendTransaction(context, { + from: GENESIS_ACCOUNT, + data: TEST_CONTRACT_BYTECODE, + value: "0x00", + maxFeePerGas: "0x3B9ACA00", + maxPriorityFeePerGas: "0x01", + accessList: [], + nonce: 1, + gasLimit: "0x100000", + chainId: 42 + })).hash; + await createAndFinalizeBlock(context.web3); + const latest = await context.web3.eth.getBlock("latest"); + expect(latest.transactions.length).to.be.eq(1); + expect(latest.transactions[0]).to.be.eq(tx_hash); + }); +}); diff --git a/ts-tests/tests/util.ts b/ts-tests/tests/util.ts index 112a3f9ec0..6ebcd85163 100644 --- a/ts-tests/tests/util.ts +++ b/ts-tests/tests/util.ts @@ -1,4 +1,5 @@ import Web3 from "web3"; +import { ethers } from "ethers"; import { JsonRpcResponse } from "web3-core-helpers"; import { spawn, ChildProcess } from "child_process"; @@ -55,7 +56,7 @@ export async function createAndFinalizeBlockNowait(web3: Web3) { } } -export async function startFrontierNode(provider?: string): Promise<{ web3: Web3; binary: ChildProcess }> { +export async function startFrontierNode(provider?: string): Promise<{ web3: Web3; binary: ChildProcess, ethersjs: ethers.providers.JsonRpcProvider }> { var web3; if (!provider || provider == 'http') { web3 = new Web3(`http://localhost:${RPC_PORT}`); @@ -128,18 +129,24 @@ export async function startFrontierNode(provider?: string): Promise<{ web3: Web3 web3 = new Web3(`ws://localhost:${WS_PORT}`); } - return { web3, binary }; + let ethersjs = new ethers.providers.StaticJsonRpcProvider(`http://localhost:${RPC_PORT}`, { + chainId: 42, + name: "frontier-dev", + }); + + return { web3, binary, ethersjs }; } export function describeWithFrontier(title: string, cb: (context: { web3: Web3 }) => void, provider?: string) { describe(title, () => { - let context: { web3: Web3 } = { web3: null }; + let context: { web3: Web3, ethersjs: ethers.providers.JsonRpcProvider } = { web3: null, ethersjs: null }; let binary: ChildProcess; // Making sure the Frontier node has started before("Starting Frontier Test Node", async function () { this.timeout(SPAWNING_TIME); const init = await startFrontierNode(provider); context.web3 = init.web3; + context.ethersjs = init.ethersjs; binary = init.binary; });