diff --git a/Cargo.lock b/Cargo.lock index 4c4d0344bf222..0fe24dbadbb97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -134,6 +134,63 @@ dependencies = [ "winnow 0.7.10", ] +[[package]] +name = "alloy-eip2124" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "crc", + "serde", + "thiserror 2.0.12", +] + +[[package]] +name = "alloy-eip2930" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b82752a889170df67bbb36d42ca63c531eb16274f0d7299ae2a680facba17bd" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d4769c6ffddca380b0070d71c8b7f30bed375543fe76bb2f74ec0acf4b7cd16" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "k256", + "serde", + "thiserror 2.0.12", +] + +[[package]] +name = "alloy-eips" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f35887da30b5fc50267109a3c61cd63e6ca1f45967983641053a40ee83468c1" +dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "auto_impl", + "c-kzg", + "derive_more 2.0.1", + "either", + "serde", + "sha2 0.10.9", +] + [[package]] name = "alloy-json-abi" version = "1.1.2" @@ -148,9 +205,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.1.2" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c35fc4b03ace65001676358ffbbaefe2a2b27ee50fe777c345082c7c888be8" +checksum = "bc9485c56de23438127a731a6b4c87803d49faf1a7068dcd1d8768aca3a9edb9" dependencies = [ "alloy-rlp", "bytes", @@ -175,13 +232,35 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.3" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc0fac0fc16baf1f63f78b47c3d24718f3619b0714076f6a02957d808d52cbef" +checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" dependencies = [ + "alloy-rlp-derive", "arrayvec 0.7.4", "bytes", - "smol_str", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" +dependencies = [ + "proc-macro2 1.0.95", + "quote 1.0.40", + "syn 2.0.98", +] + +[[package]] +name = "alloy-serde" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8d2c52adebf3e6494976c8542fbdf12f10123b26e11ad56f77274c16a2a039" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", ] [[package]] @@ -428,6 +507,18 @@ dependencies = [ "ark-std 0.4.0", ] +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-r1cs-std", + "ark-std 0.5.0", +] + [[package]] name = "ark-bw6-761" version = "0.4.0" @@ -724,6 +815,35 @@ dependencies = [ "rayon", ] +[[package]] +name = "ark-r1cs-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-relations", + "ark-std 0.5.0", + "educe", + "num-bigint", + "num-integer", + "num-traits", + "tracing", +] + +[[package]] +name = "ark-relations" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384" +dependencies = [ + "ark-ff 0.5.0", + "ark-std 0.5.0", + "tracing", + "tracing-subscriber 0.2.25", +] + [[package]] name = "ark-scale" version = "0.0.12" @@ -869,7 +989,7 @@ dependencies = [ "digest 0.10.7", "rand_chacha 0.3.1", "rayon", - "sha2 0.10.8", + "sha2 0.10.9", "w3f-ring-proof", "zeroize", ] @@ -1701,16 +1821,25 @@ dependencies = [ "url", ] +[[package]] +name = "aurora-engine-modexp" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518bc5745a6264b5fd7b09dffb9667e400ee9e2bbe18555fac75e1fe9afa0df9" +dependencies = [ + "hex", + "num", +] + [[package]] name = "auto_impl" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ - "proc-macro-error", "proc-macro2 1.0.95", "quote 1.0.40", - "syn 1.0.109", + "syn 2.0.98", ] [[package]] @@ -1730,6 +1859,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + [[package]] name = "backoff" version = "0.4.0" @@ -1851,7 +1986,7 @@ dependencies = [ "k256", "rand_core 0.6.4", "ripemd", - "sha2 0.10.8", + "sha2 0.10.9", "subtle 2.5.0", "zeroize", ] @@ -1922,9 +2057,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" dependencies = [ "serde", ] @@ -2041,6 +2176,18 @@ dependencies = [ "log", ] +[[package]] +name = "blst" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + [[package]] name = "bounded-collections" version = "0.1.9" @@ -2797,7 +2944,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ - "sha2 0.10.8", + "sha2 0.10.9", "tinyvec", ] @@ -2874,6 +3021,21 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "c-kzg" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7318cfa722931cb5fe0838b98d3ce5621e75f6a6408abc21721d80de9223f2e4" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + [[package]] name = "c2-chacha" version = "0.3.3" @@ -2930,9 +3092,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.1.24" +version = "1.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" dependencies = [ "jobserver", "libc", @@ -4670,7 +4832,7 @@ dependencies = [ "sp-io", "sp-maybe-compressed-blob", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.18", ] [[package]] @@ -5376,9 +5538,9 @@ dependencies = [ [[package]] name = "derive-where" -version = "1.2.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" +checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" dependencies = [ "proc-macro2 1.0.95", "quote 1.0.40", @@ -5714,7 +5876,7 @@ dependencies = [ "ed25519", "rand_core 0.6.4", "serde", - "sha2 0.10.8", + "sha2 0.10.9", "subtle 2.5.0", "zeroize", ] @@ -5730,7 +5892,7 @@ dependencies = [ "hashbrown 0.14.5", "hex", "rand_core 0.6.4", - "sha2 0.10.8", + "sha2 0.10.9", "zeroize", ] @@ -5748,9 +5910,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" dependencies = [ "serde", ] @@ -6729,7 +6891,7 @@ dependencies = [ "sp-runtime", "sp-statement-store", "tempfile", - "tracing-subscriber", + "tracing-subscriber 0.3.18", ] [[package]] @@ -7298,7 +7460,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fda788993cc341f69012feba8bf45c0ba4f3291fcc08e214b4d5a7332d88aff" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.2", "libc", "libgit2-sys", "log", @@ -7409,6 +7571,16 @@ dependencies = [ "testnet-parachains-constants", ] +[[package]] +name = "gmp-mpfr-sys" +version = "1.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66d61197a68f6323b9afa616cf83d55d69191e1bf364d4eb7d35ae18defe776" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "governance-westend-integration-tests" version = "0.0.0" @@ -8792,7 +8964,7 @@ dependencies = [ "elliptic-curve", "once_cell", "serdect", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] @@ -9269,7 +9441,7 @@ dependencies = [ "multihash 0.19.1", "quick-protobuf", "rand 0.8.5", - "sha2 0.10.8", + "sha2 0.10.9", "thiserror 1.0.65", "tracing", "zeroize", @@ -9295,7 +9467,7 @@ dependencies = [ "quick-protobuf", "quick-protobuf-codec", "rand 0.8.5", - "sha2 0.10.8", + "sha2 0.10.9", "smallvec", "thiserror 1.0.65", "tracing", @@ -9360,7 +9532,7 @@ dependencies = [ "once_cell", "quick-protobuf", "rand 0.8.5", - "sha2 0.10.8", + "sha2 0.10.9", "snow", "static_assertions", "thiserror 1.0.65", @@ -9754,7 +9926,7 @@ dependencies = [ "prost-build", "rand 0.8.5", "serde", - "sha2 0.10.8", + "sha2 0.10.9", "simple-dns", "smallvec", "snow", @@ -9805,7 +9977,7 @@ dependencies = [ "generator", "scoped-tls", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.18", ] [[package]] @@ -10290,7 +10462,7 @@ dependencies = [ "core2", "digest 0.10.7", "multihash-derive", - "sha2 0.10.8", + "sha2 0.10.9", "sha3", "unsigned-varint 0.7.2", ] @@ -10493,7 +10665,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.2", "cfg-if", "libc", ] @@ -10504,7 +10676,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.2", "cfg-if", "cfg_aliases 0.2.1", "libc", @@ -10728,11 +10900,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -10842,6 +11013,28 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +dependencies = [ + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.95", + "quote 1.0.40", + "syn 2.0.98", +] + [[package]] name = "num_threads" version = "0.1.7" @@ -10930,7 +11123,7 @@ version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.2", "cfg-if", "foreign-types", "libc", @@ -11038,6 +11231,18 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.9", +] + [[package]] name = "pallet-ah-ops" version = "0.1.0" @@ -12892,6 +13097,7 @@ dependencies = [ "pretty_assertions", "rand 0.8.5", "rand_pcg", + "revm", "ripemd", "rlp 0.6.1", "scale-info", @@ -12954,10 +13160,13 @@ dependencies = [ name = "pallet-revive-fixtures" version = "0.1.0" dependencies = [ + "alloy-core", "anyhow", "cargo_metadata", + "hex", "pallet-revive-uapi", "polkavm-linker 0.27.0", + "serde_json", "sp-core 28.0.0", "sp-io", "toml", @@ -14544,7 +14753,7 @@ checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" dependencies = [ "once_cell", "pest", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] @@ -14557,6 +14766,48 @@ dependencies = [ "indexmap", ] +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2 1.0.95", + "quote 1.0.40", + "syn 2.0.98", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.1", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -17279,6 +17530,15 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -17427,7 +17687,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.2", "chrono", "flate2", "hex", @@ -17442,7 +17702,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.2", "chrono", "hex", ] @@ -17504,7 +17764,7 @@ checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.6.0", + "bitflags 2.9.2", "lazy_static", "num-traits", "rand 0.8.5", @@ -17989,7 +18249,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.2", ] [[package]] @@ -18281,6 +18541,195 @@ dependencies = [ "serde_json", ] +[[package]] +name = "revm" +version = "27.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6bf82101a1ad8a2b637363a37aef27f88b4efc8a6e24c72bf5f64923dc5532" +dependencies = [ + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database", + "revm-database-interface", + "revm-handler", + "revm-inspector", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-bytecode" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d800e6c2119457ded5b0af71634eb2468040bf97de468eee5a730272a106da0" +dependencies = [ + "bitvec", + "phf", + "revm-primitives", + "serde", +] + +[[package]] +name = "revm-context" +version = "8.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd508416a35a4d8a9feaf5ccd06ac6d6661cd31ee2dc0252f9f7316455d71f9" +dependencies = [ + "cfg-if", + "derive-where", + "revm-bytecode", + "revm-context-interface", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-context-interface" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc90302642d21c8f93e0876e201f3c5f7913c4fcb66fb465b0fd7b707dfe1c79" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "auto_impl", + "either", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-database" +version = "7.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40000c7d917c865f6c232a78581b78e70c43f52db17282bd1b52d4f0565bc8a2" +dependencies = [ + "alloy-eips", + "revm-bytecode", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-database-interface" +version = "7.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ccea7a168cba1196b1e57dd3e22c36047208c135f600f8e58cbe7d49957dba" +dependencies = [ + "auto_impl", + "either", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-handler" +version = "8.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1529c8050e663be64010e80ec92bf480315d21b1f2dbf65540028653a621b27d" +dependencies = [ + "auto_impl", + "derive-where", + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database-interface", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-inspector" +version = "8.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78db140e332489094ef314eaeb0bd1849d6d01172c113ab0eb6ea8ab9372926" +dependencies = [ + "auto_impl", + "either", + "revm-context", + "revm-database-interface", + "revm-handler", + "revm-interpreter", + "revm-primitives", + "revm-state", + "serde", + "serde_json", +] + +[[package]] +name = "revm-interpreter" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff9d7d9d71e8a33740b277b602165b6e3d25fff091ba3d7b5a8d373bf55f28a7" +dependencies = [ + "revm-bytecode", + "revm-context-interface", + "revm-primitives", + "serde", +] + +[[package]] +name = "revm-precompile" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cee3f336b83621294b4cfe84d817e3eef6f3d0fce00951973364cc7f860424d" +dependencies = [ + "ark-bls12-381 0.5.0", + "ark-bn254", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "arrayref", + "aurora-engine-modexp", + "c-kzg", + "cfg-if", + "k256", + "libsecp256k1", + "once_cell", + "p256", + "revm-primitives", + "ripemd", + "rug", + "secp256k1 0.31.1", + "sha2 0.10.9", +] + +[[package]] +name = "revm-primitives" +version = "20.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa29d9da06fe03b249b6419b33968ecdf92ad6428e2f012dc57bcd619b5d94e" +dependencies = [ + "alloy-primitives", + "num_enum", + "once_cell", + "serde", +] + +[[package]] +name = "revm-state" +version = "7.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d7f39ea56df3bfbb3c81c99b1f028d26f205b6004156baffbf1a4f84b46cfa" +dependencies = [ + "bitflags 2.9.2", + "revm-bytecode", + "revm-primitives", + "serde", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -18668,6 +19117,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "rug" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4207e8d668e5b8eb574bda8322088ccd0d7782d3d03c7e8d562e82ed82bdcbc3" +dependencies = [ + "az", + "gmp-mpfr-sys", + "libc", + "libm", +] + [[package]] name = "ruint" version = "1.15.0" @@ -18781,7 +19242,7 @@ version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.2", "errno", "libc", "linux-raw-sys 0.4.14", @@ -18794,7 +19255,7 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.2", "errno", "libc", "linux-raw-sys 0.9.4", @@ -19657,7 +20118,7 @@ dependencies = [ "substrate-test-runtime", "tempfile", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.18", "wat", ] @@ -20431,7 +20892,7 @@ dependencies = [ "thiserror 1.0.65", "tracing", "tracing-log", - "tracing-subscriber", + "tracing-subscriber 0.3.18", ] [[package]] @@ -20486,7 +20947,7 @@ dependencies = [ "tokio", "tokio-stream", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.18", "zombienet-configuration", "zombienet-sdk", ] @@ -20832,7 +21293,7 @@ dependencies = [ "merlin", "rand_core 0.6.4", "serde_bytes", - "sha2 0.10.8", + "sha2 0.10.9", "subtle 2.5.0", "zeroize", ] @@ -20864,7 +21325,7 @@ dependencies = [ "password-hash", "pbkdf2", "salsa20", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] @@ -20921,6 +21382,17 @@ dependencies = [ "secp256k1-sys 0.10.1", ] +[[package]] +name = "secp256k1" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" +dependencies = [ + "bitcoin_hashes 0.14.0", + "rand 0.9.0", + "secp256k1-sys 0.11.0", +] + [[package]] name = "secp256k1-sys" version = "0.9.2" @@ -20939,6 +21411,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + [[package]] name = "secrecy" version = "0.8.0" @@ -20964,7 +21445,7 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.2", "core-foundation", "core-foundation-sys", "libc", @@ -21231,9 +21712,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -21323,7 +21804,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee851d0e5e7af3721faea1843e8015e820a234f81fda3dea9247e15bac9a86a" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.2", ] [[package]] @@ -21432,15 +21913,6 @@ dependencies = [ "futures-lite 2.3.0", ] -[[package]] -name = "smol_str" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" -dependencies = [ - "serde", -] - [[package]] name = "smoldot" version = "0.11.0" @@ -21483,7 +21955,7 @@ dependencies = [ "schnorrkel 0.10.2", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "sha3", "siphasher 0.3.11", "slab", @@ -21537,7 +22009,7 @@ dependencies = [ "schnorrkel 0.11.4", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "sha3", "siphasher 1.0.1", "slab", @@ -21640,7 +22112,7 @@ dependencies = [ "rand_core 0.6.4", "ring 0.17.8", "rustc_version 0.4.0", - "sha2 0.10.8", + "sha2 0.10.9", "subtle 2.5.0", ] @@ -22605,7 +23077,7 @@ dependencies = [ "secrecy 0.8.0", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "sp-crypto-hashing 0.1.0", "sp-debug-derive 14.0.0", "sp-externalities 0.25.0", @@ -22716,7 +23188,7 @@ dependencies = [ "byteorder", "criterion", "digest 0.10.7", - "sha2 0.10.8", + "sha2 0.10.9", "sha3", "sp-crypto-hashing-proc-macro", "twox-hash", @@ -22731,7 +23203,7 @@ dependencies = [ "blake2b_simd", "byteorder", "digest 0.10.7", - "sha2 0.10.8", + "sha2 0.10.9", "sha3", "twox-hash", ] @@ -23155,7 +23627,7 @@ dependencies = [ "parity-scale-codec", "rand 0.8.5", "scale-info", - "sha2 0.10.8", + "sha2 0.10.9", "sp-api", "sp-application-crypto", "sp-core 28.0.0", @@ -23232,7 +23704,7 @@ dependencies = [ "regex", "tracing", "tracing-core", - "tracing-subscriber", + "tracing-subscriber 0.3.18", ] [[package]] @@ -23244,7 +23716,7 @@ dependencies = [ "parity-scale-codec", "tracing", "tracing-core", - "tracing-subscriber", + "tracing-subscriber 0.3.18", ] [[package]] @@ -23450,7 +23922,7 @@ dependencies = [ "percent-encoding", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "smallvec", "sqlformat", "thiserror 1.0.65", @@ -23488,7 +23960,7 @@ dependencies = [ "quote 1.0.40", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "sqlx-core", "sqlx-mysql", "sqlx-postgres", @@ -23507,7 +23979,7 @@ checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.6.0", + "bitflags 2.9.2", "byteorder", "bytes", "crc", @@ -23532,7 +24004,7 @@ dependencies = [ "rsa", "serde", "sha1", - "sha2 0.10.8", + "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", @@ -23549,7 +24021,7 @@ checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.6.0", + "bitflags 2.9.2", "byteorder", "crc", "dotenvy", @@ -23570,7 +24042,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", @@ -23928,7 +24400,7 @@ dependencies = [ "pbkdf2", "rustc-hex", "schnorrkel 0.11.4", - "sha2 0.10.8", + "sha2 0.10.9", "zeroize", ] @@ -23941,7 +24413,7 @@ dependencies = [ "hmac 0.12.1", "pbkdf2", "schnorrkel 0.11.4", - "sha2 0.10.8", + "sha2 0.10.9", "zeroize", ] @@ -24253,7 +24725,7 @@ dependencies = [ "tokio", "tokio-util", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.18", ] [[package]] @@ -24613,7 +25085,7 @@ dependencies = [ "secrecy 0.10.3", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "subxt-core 0.38.0", "zeroize", ] @@ -24641,7 +25113,7 @@ dependencies = [ "secrecy 0.10.3", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "subxt-core 0.41.0", "thiserror 2.0.12", @@ -24870,7 +25342,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.2", "core-foundation", "system-configuration-sys 0.6.0", ] @@ -24995,7 +25467,7 @@ checksum = "1e33b98a582ea0be1168eba097538ee8dd4bbe0f2b01b22ac92ea30054e5be7b" dependencies = [ "env_logger 0.11.3", "test-log-macros", - "tracing-subscriber", + "tracing-subscriber 0.3.18", ] [[package]] @@ -25553,7 +26025,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ "base64 0.21.7", - "bitflags 2.6.0", + "bitflags 2.9.2", "bytes", "futures-core", "futures-util", @@ -25573,7 +26045,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.2", "bytes", "http 1.1.0", "http-body 1.0.0", @@ -25671,6 +26143,15 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.18" @@ -26135,7 +26616,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rand_core 0.6.4", - "sha2 0.10.8", + "sha2 0.10.9", "sha3", "zeroize", ] @@ -26473,7 +26954,7 @@ version = "0.235.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "161296c618fa2d63f6ed5fffd1112937e803cb9ec71b32b01a76321555660917" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.2", "hashbrown 0.15.3", "indexmap", "semver 1.0.18", @@ -26508,7 +26989,7 @@ checksum = "b6fe976922a16af3b0d67172c473d1fd4f1aa5d0af9c8ba6538c741f3af686f4" dependencies = [ "addr2line 0.24.2", "anyhow", - "bitflags 2.6.0", + "bitflags 2.9.2", "bumpalo", "cc", "cfg-if", @@ -26592,7 +27073,7 @@ dependencies = [ "rustix 1.0.8", "serde", "serde_derive", - "sha2 0.10.8", + "sha2 0.10.9", "toml", "windows-sys 0.59.0", "zstd 0.13.3", @@ -27318,7 +27799,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.2", ] [[package]] @@ -27894,7 +28375,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "sha2 0.10.8", + "sha2 0.10.9", "sp-core 35.0.0", "subxt 0.38.1", "subxt-signer 0.38.0", @@ -27938,7 +28419,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "sha2 0.10.8", + "sha2 0.10.9", "tar", "thiserror 1.0.65", "tokio", diff --git a/Cargo.toml b/Cargo.toml index ea58a96bab296..fb0d30d88e43b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -928,7 +928,7 @@ node-rpc = { path = "substrate/bin/node/rpc" } node-testing = { path = "substrate/bin/node/testing" } nohash-hasher = { version = "0.2.0" } novelpoly = { version = "2.0.0", package = "reed-solomon-novelpoly" } -num-bigint = { version = "0.4.3", default-features = false } +num-bigint = { version = "0.4.6", default-features = false } num-format = { version = "0.4.3" } num-integer = { version = "0.1.46", default-features = false } num-rational = { version = "0.4.1" } @@ -1190,6 +1190,7 @@ regex = { version = "1.10.2" } relay-substrate-client = { path = "bridges/relays/client-substrate" } relay-utils = { path = "bridges/relays/utils" } remote-externalities = { path = "substrate/utils/frame/remote-externalities", default-features = false, package = "frame-remote-externalities" } +revm = { version = "27.0.2", default-features = false } ripemd = { version = "0.1.3", default-features = false } rlp = { version = "0.6.1", default-features = false } rococo-emulated-chain = { path = "cumulus/parachains/integration-tests/emulated/chains/relays/rococo" } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs index 8f48a3171916e..3d65d3b168293 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -85,6 +85,16 @@ const ALICE: [u8; 32] = [1u8; 32]; const BOB: [u8; 32] = [2u8; 32]; const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32]; +const ERC20_PVM: &[u8] = + include_bytes!("../../../../../../substrate/frame/revive/fixtures/erc20/erc20.polkavm"); + +const FAKE_ERC20_PVM: &[u8] = + include_bytes!("../../../../../../substrate/frame/revive/fixtures/erc20/fake_erc20.polkavm"); + +const EXPENSIVE_ERC20_PVM: &[u8] = include_bytes!( + "../../../../../../substrate/frame/revive/fixtures/erc20/expensive_erc20.polkavm" +); + parameter_types! { pub Governance: GovernanceOrigin = GovernanceOrigin::Origin(RuntimeOrigin::root()); } @@ -1670,10 +1680,7 @@ fn withdraw_and_deposit_erc20s() { assert_ok!(Revive::map_account(RuntimeOrigin::signed(sender.clone()))); assert_ok!(Revive::map_account(RuntimeOrigin::signed(beneficiary.clone()))); - let code = include_bytes!( - "../../../../../../substrate/frame/revive/fixtures/contracts/erc20.polkavm" - ) - .to_vec(); + let code = ERC20_PVM.to_vec(); let initial_amount_u256 = U256::from(1_000_000_000_000u128); let constructor_data = sol_data::Uint::<256>::abi_encode(&initial_amount_u256); @@ -1832,10 +1839,7 @@ fn smart_contract_does_not_return_bool_fails() { assert_ok!(Revive::map_account(RuntimeOrigin::signed(beneficiary.clone()))); // This contract implements the ERC20 interface for `transfer` except it returns a uint256. - let code = include_bytes!( - "../../../../../../substrate/frame/revive/fixtures/contracts/fake_erc20.polkavm" - ) - .to_vec(); + let code = FAKE_ERC20_PVM.to_vec(); let initial_amount_u256 = U256::from(1_000_000_000_000u128); let constructor_data = sol_data::Uint::<256>::abi_encode(&initial_amount_u256); @@ -1888,10 +1892,7 @@ fn expensive_erc20_runs_out_of_gas() { assert_ok!(Revive::map_account(RuntimeOrigin::signed(beneficiary.clone()))); // This contract does a lot more storage writes in `transfer`. - let code = include_bytes!( - "../../../../../../substrate/frame/revive/fixtures/contracts/expensive_erc20.polkavm" - ) - .to_vec(); + let code = EXPENSIVE_ERC20_PVM.to_vec(); let initial_amount_u256 = U256::from(1_000_000_000_000u128); let constructor_data = sol_data::Uint::<256>::abi_encode(&initial_amount_u256); diff --git a/prdoc/pr_9501.prdoc b/prdoc/pr_9501.prdoc new file mode 100644 index 0000000000000..995095f0819c0 --- /dev/null +++ b/prdoc/pr_9501.prdoc @@ -0,0 +1,13 @@ +title: '[revive] revm move existing files' +doc: +- audience: Runtime Dev + description: |- + - Move exisiting files in pallet-revive to accomodate the upcoming EVM backend + - Add solc/resolc compilation feature for fixtures +crates: +- name: asset-hub-westend-runtime + bump: patch +- name: pallet-revive + bump: patch +- name: pallet-revive-fixtures + bump: patch diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index a2be0cd34f862..6a2078dd50c43 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -35,6 +35,7 @@ polkavm = { version = "0.27.0", default-features = false } polkavm-common = { version = "0.27.0", default-features = false, features = ["alloc"] } rand = { workspace = true, optional = true } rand_pcg = { workspace = true, optional = true } +revm = { workspace = true } rlp = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = ["alloc", "derive"], workspace = true, default-features = false } @@ -98,6 +99,7 @@ std = [ "polkavm-common/std", "polkavm/std", "rand?/std", + "revm/std", "ripemd/std", "rlp/std", "scale-info/std", diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml index f1a4d7d1969f5..820a9b7952a55 100644 --- a/substrate/frame/revive/fixtures/Cargo.toml +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -16,6 +16,7 @@ exclude-from-umbrella = true workspace = true [dependencies] +alloy-core = { workspace = true, default-features = true, features = ["sol-types"], optional = true } anyhow = { workspace = true, default-features = true, optional = true } sp-core = { workspace = true, default-features = true, optional = true } sp-io = { workspace = true, default-features = true, optional = true } @@ -23,11 +24,20 @@ sp-io = { workspace = true, default-features = true, optional = true } [build-dependencies] anyhow = { workspace = true, default-features = true } cargo_metadata = { workspace = true } +hex = { workspace = true, features = ["alloc"] } pallet-revive-uapi = { workspace = true } polkavm-linker = { version = "0.27.0" } +serde_json = { workspace = true } toml = { workspace = true } [features] default = ["std"] # only when std is enabled all fixtures are available -std = ["anyhow", "sp-core", "sp-io"] +std = [ + "alloy-core", + "anyhow", + "hex/std", + "serde_json/std", + "sp-core", + "sp-io", +] diff --git a/substrate/frame/revive/fixtures/build.rs b/substrate/frame/revive/fixtures/build.rs index ce9215a165d21..a3030a66afad0 100644 --- a/substrate/frame/revive/fixtures/build.rs +++ b/substrate/frame/revive/fixtures/build.rs @@ -30,15 +30,24 @@ const OVERRIDE_STRIP_ENV_VAR: &str = "PALLET_REVIVE_FIXTURES_STRIP"; const OVERRIDE_OPTIMIZE_ENV_VAR: &str = "PALLET_REVIVE_FIXTURES_OPTIMIZE"; /// A contract entry. +#[derive(Clone)] struct Entry { /// The path to the contract source file. path: PathBuf, + /// The type of the contract (rust or solidity). + contract_type: ContractType, +} + +#[derive(Clone, Copy)] +enum ContractType { + Rust, + Solidity, } impl Entry { /// Create a new contract entry from the given path. - fn new(path: PathBuf) -> Self { - Self { path } + fn new(path: PathBuf, contract_type: ContractType) -> Self { + Self { path, contract_type } } /// Return the path to the contract source file. @@ -55,9 +64,12 @@ impl Entry { .expect("name is valid unicode; qed") } - /// Return the name of the polkavm file. + /// Return the name of the bytecode file. fn out_filename(&self) -> String { - format!("{}.polkavm", self.name()) + match self.contract_type { + ContractType::Rust => format!("{}.polkavm", self.name()), + ContractType::Solidity => format!("{}.resolc.polkavm", self.name()), + } } } @@ -67,16 +79,18 @@ fn collect_entries(contracts_dir: &Path) -> Vec { .expect("src dir exists; qed") .filter_map(|file| { let path = file.expect("file exists; qed").path(); - if path.extension().map_or(true, |ext| ext != "rs") { - return None - } + let extension = path.extension(); - Some(Entry::new(path)) + match extension.and_then(|ext| ext.to_str()) { + Some("rs") => Some(Entry::new(path, ContractType::Rust)), + Some("sol") => Some(Entry::new(path, ContractType::Solidity)), + _ => None, + } }) .collect::>() } -/// Create a `Cargo.toml` to compile the given contract entries. +/// Create a `Cargo.toml` to compile the given Rust contract entries. fn create_cargo_toml<'a>( fixtures_dir: &Path, entries: impl Iterator, @@ -168,7 +182,7 @@ fn invoke_build(current_dir: &Path) -> Result<()> { let build_res = build_command.output().expect("failed to execute process"); if build_res.status.success() { - return Ok(()) + return Ok(()); } let stderr = String::from_utf8_lossy(&build_res.stderr); @@ -192,15 +206,166 @@ fn post_process(input_path: &Path, output_path: &Path) -> Result<()> { Ok(()) } -/// Write the compiled contracts to the given output directory. +/// Compile a Solidity contract using standard JSON interface. +fn compile_with_standard_json( + compiler: &str, + contracts_dir: &Path, + solidity_entries: &[&Entry], +) -> Result { + let mut input_json = serde_json::json!({ + "language": "Solidity", + "sources": {}, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "outputSelection": + + serde_json::json!({ + "*": { + "*": ["evm.bytecode"] + } + }), + + } + }); + + // Add all Solidity files to the input + for entry in solidity_entries { + let source_code = fs::read_to_string(entry.path()) + .with_context(|| format!("Failed to read Solidity source: {}", entry.path()))?; + + let file_key = entry.path().split('/').last().unwrap_or(entry.name()); + input_json["sources"][file_key] = serde_json::json!({ + "content": source_code + }); + } + + let compiler_output = Command::new(compiler) + .current_dir(contracts_dir) + .arg("--standard-json") + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .with_context(|| { + format!("Failed to execute {}. Make sure {} is installed.", compiler, compiler) + })?; + + let mut stdin = compiler_output.stdin.as_ref().unwrap(); + stdin + .write_all(input_json.to_string().as_bytes()) + .with_context(|| format!("Failed to write to {} stdin", compiler))?; + let _ = stdin; + + let compiler_result = compiler_output + .wait_with_output() + .with_context(|| format!("Failed to wait for {} output", compiler))?; + + if !compiler_result.status.success() { + let stderr = String::from_utf8_lossy(&compiler_result.stderr); + bail!("{} compilation failed: {}", compiler, stderr); + } + + // Parse JSON output + let compiler_json: serde_json::Value = serde_json::from_slice(&compiler_result.stdout) + .with_context(|| format!("Failed to parse {} JSON output", compiler))?; + + // Abort on errors + if let Some(errors) = compiler_json.get("errors") { + if errors + .as_array() + .unwrap() + .iter() + .any(|object| object.get("severity").unwrap().as_str().unwrap() == "error") + { + bail!( + "failed to compile the Solidity fixtures: {}", + serde_json::to_string_pretty(errors)? + ); + } + } + + Ok(compiler_json) +} + +/// Extract bytecode from compiler JSON output and write binary files. +fn extract_and_write_bytecode( + compiler_json: &serde_json::Value, + out_dir: &Path, + file_suffix: &str, +) -> Result<()> { + if let Some(contracts) = compiler_json["contracts"].as_object() { + for (_file_key, file_contracts) in contracts { + if let Some(contract_map) = file_contracts.as_object() { + for (contract_name, contract_data) in contract_map { + // Navigate through the JSON path to find the bytecode + let mut current = contract_data; + for path_segment in ["evm", "bytecode", "object"] { + if let Some(next) = current.get(path_segment) { + current = next; + } else { + // Skip if path doesn't exist (e.g., contract has no bytecode) + continue; + } + } + + if let Some(bytecode_obj) = current.as_str() { + let bytecode_hex = bytecode_obj.strip_prefix("0x").unwrap_or(bytecode_obj); + let binary_content = hex::decode(bytecode_hex).map_err(|e| { + anyhow::anyhow!("Failed to decode hex for {contract_name}: {e}") + })?; + + let out_path = out_dir.join(format!("{}{}", contract_name, file_suffix)); + fs::write(&out_path, binary_content).with_context(|| { + format!("Failed to write {out_path:?} for {contract_name}") + })?; + } + } + } + } + } + Ok(()) +} + +/// Compile Solidity contracts using both solc and resolc. +fn compile_solidity_contracts( + contracts_dir: &Path, + out_dir: &Path, + entries: &[Entry], +) -> Result<()> { + let solidity_entries: Vec<_> = entries + .iter() + .filter(|entry| matches!(entry.contract_type, ContractType::Solidity)) + .collect(); + + if solidity_entries.is_empty() { + return Ok(()); + } + + // Compile with solc for EVM bytecode + let json = compile_with_standard_json("solc", contracts_dir, &solidity_entries)?; + extract_and_write_bytecode(&json, out_dir, ".sol.bin")?; + + // Compile with resolc for PVM bytecode + let json = compile_with_standard_json("resolc", contracts_dir, &solidity_entries)?; + extract_and_write_bytecode(&json, out_dir, ".resolc.polkavm")?; + + Ok(()) +} + +/// Write the compiled Rust contracts to the given output directory. fn write_output(build_dir: &Path, out_dir: &Path, entries: Vec) -> Result<()> { for entry in entries { - post_process( - &build_dir - .join("target/riscv64emac-unknown-none-polkavm/release") - .join(entry.name()), - &out_dir.join(entry.out_filename()), - )?; + if matches!(entry.contract_type, ContractType::Rust) { + post_process( + &build_dir + .join("target/riscv64emac-unknown-none-polkavm/release") + .join(entry.name()), + &out_dir.join(entry.out_filename()), + )?; + } } Ok(()) @@ -211,7 +376,7 @@ fn create_out_dir() -> Result { let temp_dir: PathBuf = env::var("OUT_DIR").context("Failed to fetch `OUT_DIR` env variable")?.into(); - // this is set in case the user has overriden the target directory + // this is set in case the user has overridden the target directory let out_dir = if let Ok(path) = env::var("CARGO_TARGET_DIR") { let path = PathBuf::from(path); @@ -263,25 +428,46 @@ fn create_out_dir() -> Result { .context(format!("Failed to create output directory: {})", out_dir.display(),))?; } - // write the location of the out dir so it can be found later + Ok(out_dir) +} + +/// Generate the fixture_location.rs file with macros and sol! definitions. +fn generate_fixture_location(temp_dir: &Path, out_dir: &Path, entries: &[Entry]) -> Result<()> { let mut file = fs::File::create(temp_dir.join("fixture_location.rs")) .context("Failed to create fixture_location.rs")?; + write!( file, r#" #[allow(dead_code)] const FIXTURE_DIR: &str = "{0}"; + + #[macro_export] macro_rules! fixture {{ ($name: literal) => {{ include_bytes!(concat!("{0}", "/", $name, ".polkavm")) }}; }} + + #[macro_export] + macro_rules! fixture_resolc {{ + ($name: literal) => {{ + include_bytes!(concat!("{0}", "/", $name, ".resolc.polkavm")) + }}; + }} "#, out_dir.display() ) .context("Failed to write to fixture_location.rs")?; - Ok(out_dir) + // Generate sol! macros for Solidity contracts + for entry in entries.iter().filter(|e| matches!(e.contract_type, ContractType::Solidity)) { + let relative_path = format!("contracts/{}", entry.path().split('/').last().unwrap()); + writeln!(file, r#"alloy_core::sol!("{}");"#, relative_path) + .context("Failed to write sol! macro to fixture_location.rs")?; + } + + Ok(()) } pub fn main() -> Result<()> { @@ -305,12 +491,28 @@ pub fn main() -> Result<()> { let entries = collect_entries(&contracts_dir); if entries.is_empty() { - return Ok(()) + return Ok(()); } - create_cargo_toml(&fixtures_dir, entries.iter(), &build_dir)?; - invoke_build(&build_dir)?; - write_output(&build_dir, &out_dir, entries)?; + // Compile Rust contracts + let rust_entries: Vec<_> = entries + .iter() + .filter(|e| matches!(e.contract_type, ContractType::Rust)) + .collect(); + if !rust_entries.is_empty() { + create_cargo_toml(&fixtures_dir, rust_entries.into_iter(), &build_dir)?; + invoke_build(&build_dir)?; + write_output(&build_dir, &out_dir, entries.clone())?; + } + + // Compile Solidity contracts + compile_solidity_contracts(&contracts_dir, &out_dir, &entries)?; + + let temp_dir: PathBuf = + env::var("OUT_DIR").context("Failed to fetch `OUT_DIR` env variable")?.into(); + + // Generate fixture_location.rs with sol! macros + generate_fixture_location(&temp_dir, &out_dir, &entries)?; Ok(()) } diff --git a/substrate/frame/revive/fixtures/contracts/dummy.polkavm b/substrate/frame/revive/fixtures/contracts/dummy.polkavm deleted file mode 100644 index d970e700ce564..0000000000000 Binary files a/substrate/frame/revive/fixtures/contracts/dummy.polkavm and /dev/null differ diff --git a/substrate/frame/revive/fixtures/contracts/dummy.sol b/substrate/frame/revive/fixtures/contracts/dummy.sol deleted file mode 100644 index e64031b0e0210..0000000000000 --- a/substrate/frame/revive/fixtures/contracts/dummy.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.2 <0.9.0; - -contract Simple { - uint256 number; - - function store(uint256 num) public { - number = num; - } - - function retrieve() public view returns (uint256) { - return number; - } -} \ No newline at end of file diff --git a/substrate/frame/revive/fixtures/contracts/fake_erc20.sol b/substrate/frame/revive/fixtures/contracts/fake_erc20.sol deleted file mode 100644 index 1c6d0aca5c8c2..0000000000000 --- a/substrate/frame/revive/fixtures/contracts/fake_erc20.sol +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -contract MyToken { - mapping(address account => uint256) private _balances; - - uint256 private _totalSupply; - - constructor(uint256 total) { - // We mint `total` tokens to the creator of this contract, as - // a sort of genesis. - _mint(msg.sender, total); - } - - function transfer(address to, uint256 value) public virtual returns (uint256) { - address owner = msg.sender; - _transfer(owner, to, value); - return 1243657816489523; - } - - function _transfer(address from, address to, uint256 value) internal { - _update(from, to, value); - } - - function _update(address from, address to, uint256 value) internal virtual { - if (from == address(0)) { - // Overflow check required: The rest of the code assumes that totalSupply never overflows - _totalSupply += value; - } else { - uint256 fromBalance = _balances[from]; - unchecked { - // Overflow not possible: value <= fromBalance <= totalSupply. - _balances[from] = fromBalance - value; - } - } - - if (to == address(0)) { - unchecked { - // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply. - _totalSupply -= value; - } - } else { - unchecked { - // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256. - _balances[to] += value; - } - } - } - - function _mint(address account, uint256 value) internal { - _update(address(0), account, value); - } -} - diff --git a/substrate/frame/revive/fixtures/contracts/erc20.polkavm b/substrate/frame/revive/fixtures/erc20/erc20.polkavm similarity index 100% rename from substrate/frame/revive/fixtures/contracts/erc20.polkavm rename to substrate/frame/revive/fixtures/erc20/erc20.polkavm diff --git a/substrate/frame/revive/fixtures/contracts/erc20.sol b/substrate/frame/revive/fixtures/erc20/erc20.sol similarity index 100% rename from substrate/frame/revive/fixtures/contracts/erc20.sol rename to substrate/frame/revive/fixtures/erc20/erc20.sol diff --git a/substrate/frame/revive/fixtures/contracts/expensive_erc20.polkavm b/substrate/frame/revive/fixtures/erc20/expensive_erc20.polkavm similarity index 100% rename from substrate/frame/revive/fixtures/contracts/expensive_erc20.polkavm rename to substrate/frame/revive/fixtures/erc20/expensive_erc20.polkavm diff --git a/substrate/frame/revive/fixtures/contracts/expensive_erc20.sol b/substrate/frame/revive/fixtures/erc20/expensive_erc20.sol similarity index 100% rename from substrate/frame/revive/fixtures/contracts/expensive_erc20.sol rename to substrate/frame/revive/fixtures/erc20/expensive_erc20.sol diff --git a/substrate/frame/revive/fixtures/contracts/fake_erc20.polkavm b/substrate/frame/revive/fixtures/erc20/fake_erc20.polkavm similarity index 100% rename from substrate/frame/revive/fixtures/contracts/fake_erc20.polkavm rename to substrate/frame/revive/fixtures/erc20/fake_erc20.polkavm diff --git a/substrate/frame/revive/fixtures/src/lib.rs b/substrate/frame/revive/fixtures/src/lib.rs index 8d6a8236cd742..022dcb9b43f09 100644 --- a/substrate/frame/revive/fixtures/src/lib.rs +++ b/substrate/frame/revive/fixtures/src/lib.rs @@ -22,16 +22,47 @@ extern crate alloc; // generated file that tells us where to find the fixtures include!(concat!(env!("OUT_DIR"), "/fixture_location.rs")); -/// Load a given polkavm module and returns a polkavm binary contents along with its hash. +/// Enum for different fixture types +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FixtureType { + /// Polkavm (compiled Rust contracts) + Rust, + /// Resolc (compiled Solidity contracts to Polkavm) + Resolc, + /// Solc (compiled Solidity contracts to EVM bytecode) + Solc, +} + #[cfg(feature = "std")] -pub fn compile_module(fixture_name: &str) -> anyhow::Result<(Vec, sp_core::H256)> { +impl FixtureType { + fn file_extension(&self) -> &'static str { + match self { + Self::Rust => ".polkavm", + Self::Resolc => ".resolc.polkavm", + Self::Solc => ".sol.bin", + } + } +} + +/// Load a fixture module with the specified type and return binary contents along with its hash. +#[cfg(feature = "std")] +pub fn compile_module_with_type( + fixture_name: &str, + fixture_type: FixtureType, +) -> anyhow::Result<(Vec, sp_core::H256)> { let out_dir: std::path::PathBuf = FIXTURE_DIR.into(); - let fixture_path = out_dir.join(format!("{fixture_name}.polkavm")); + let fixture_path = out_dir.join(format!("{fixture_name}{}", fixture_type.file_extension())); let binary = std::fs::read(fixture_path)?; let code_hash = sp_io::hashing::keccak_256(&binary); Ok((binary, sp_core::H256(code_hash))) } +/// Load a given polkavm module and returns a polkavm binary contents along with its hash. +#[cfg(feature = "std")] +pub fn compile_module(fixture_name: &str) -> anyhow::Result<(Vec, sp_core::H256)> { + compile_module_with_type(fixture_name, FixtureType::Rust) +} + /// Fixtures used in runtime benchmarks. /// /// We explicitly include those fixtures into the binary to make them diff --git a/substrate/frame/revive/src/benchmarking.rs b/substrate/frame/revive/src/benchmarking.rs index 84819744b2ffb..8d7fd221ec6cc 100644 --- a/substrate/frame/revive/src/benchmarking.rs +++ b/substrate/frame/revive/src/benchmarking.rs @@ -27,6 +27,7 @@ use crate::{ self, run::builtin as run_builtin_precompile, BenchmarkSystem, BuiltinPrecompile, ISystem, }, storage::WriteOutcome, + vm::pvm, Pallet as Contracts, *, }; use alloc::{vec, vec::Vec}; @@ -79,7 +80,7 @@ macro_rules! build_runtime( let $contract = setup.contract(); let input = setup.data(); let (mut ext, _) = setup.ext(); - let mut $runtime = crate::vm::Runtime::<_, [u8]>::new(&mut ext, input); + let mut $runtime = $crate::vm::pvm::Runtime::<_, [u8]>::new(&mut ext, input); }; ); @@ -654,7 +655,7 @@ mod benchmarks { let mut setup = CallSetup::::default(); setup.set_origin(Origin::Root); let (mut ext, _) = setup.ext(); - let mut runtime = crate::vm::Runtime::new(&mut ext, vec![]); + let mut runtime = pvm::Runtime::new(&mut ext, vec![]); let result; #[block] @@ -793,7 +794,7 @@ mod benchmarks { let (mut ext, _) = setup.ext(); ext.override_export(crate::exec::ExportedFunction::Constructor); - let mut runtime = crate::vm::Runtime::<_, [u8]>::new(&mut ext, input); + let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, input); let result; #[block] @@ -833,7 +834,7 @@ mod benchmarks { fn seal_return_data_size() { let mut setup = CallSetup::::default(); let (mut ext, _) = setup.ext(); - let mut runtime = crate::vm::Runtime::new(&mut ext, vec![]); + let mut runtime = pvm::Runtime::new(&mut ext, vec![]); let mut memory = memory!(vec![],); *runtime.ext().last_frame_output_mut() = ExecReturnValue { data: vec![42; 256], ..Default::default() }; @@ -849,7 +850,7 @@ mod benchmarks { fn seal_call_data_size() { let mut setup = CallSetup::::default(); let (mut ext, _) = setup.ext(); - let mut runtime = crate::vm::Runtime::new(&mut ext, vec![42u8; 128 as usize]); + let mut runtime = pvm::Runtime::new(&mut ext, vec![42u8; 128 as usize]); let mut memory = memory!(vec![0u8; 4],); let result; #[block] @@ -962,7 +963,7 @@ mod benchmarks { let (mut ext, _) = setup.ext(); ext.set_block_number(BlockNumberFor::::from(1u32)); - let mut runtime = crate::vm::Runtime::<_, [u8]>::new(&mut ext, input); + let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, input); let block_hash = H256::from([1; 32]); frame_system::BlockHash::::insert( @@ -1013,7 +1014,7 @@ mod benchmarks { fn seal_copy_to_contract(n: Linear<0, { limits::code::BLOB_BYTES - 4 }>) { let mut setup = CallSetup::::default(); let (mut ext, _) = setup.ext(); - let mut runtime = crate::vm::Runtime::new(&mut ext, vec![]); + let mut runtime = pvm::Runtime::new(&mut ext, vec![]); let mut memory = memory!(n.encode(), vec![0u8; n as usize],); let result; #[block] @@ -1036,7 +1037,7 @@ mod benchmarks { fn seal_call_data_load() { let mut setup = CallSetup::::default(); let (mut ext, _) = setup.ext(); - let mut runtime = crate::vm::Runtime::new(&mut ext, vec![42u8; 32]); + let mut runtime = pvm::Runtime::new(&mut ext, vec![42u8; 32]); let mut memory = memory!(vec![0u8; 32],); let result; #[block] @@ -1051,7 +1052,7 @@ mod benchmarks { fn seal_call_data_copy(n: Linear<0, { limits::code::BLOB_BYTES }>) { let mut setup = CallSetup::::default(); let (mut ext, _) = setup.ext(); - let mut runtime = crate::vm::Runtime::new(&mut ext, vec![42u8; n as usize]); + let mut runtime = pvm::Runtime::new(&mut ext, vec![42u8; n as usize]); let mut memory = memory!(vec![0u8; n as usize],); let result; #[block] @@ -1072,7 +1073,10 @@ mod benchmarks { result = runtime.bench_seal_return(memory.as_mut_slice(), 0, 0, n); } - assert!(matches!(result, Err(crate::vm::TrapReason::Return(crate::vm::ReturnData { .. })))); + assert!(matches!( + result, + Err(crate::vm::pvm::TrapReason::Return(crate::vm::pvm::ReturnData { .. })) + )); } #[benchmark(pov_mode = Measured)] @@ -1087,7 +1091,7 @@ mod benchmarks { result = runtime.bench_terminate(memory.as_mut_slice(), 0); } - assert!(matches!(result, Err(crate::vm::TrapReason::Termination))); + assert!(matches!(result, Err(crate::vm::pvm::TrapReason::Termination))); Ok(()) } @@ -1391,7 +1395,7 @@ mod benchmarks { let value = Some(vec![42u8; max_value_len as _]); let mut setup = CallSetup::::default(); let (mut ext, _) = setup.ext(); - let mut runtime = crate::vm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]); runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; let result; #[block] @@ -1414,7 +1418,7 @@ mod benchmarks { let mut setup = CallSetup::::default(); setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES); let (mut ext, _) = setup.ext(); - let mut runtime = crate::vm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]); runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; let result; #[block] @@ -1436,7 +1440,7 @@ mod benchmarks { let mut setup = CallSetup::::default(); let (mut ext, _) = setup.ext(); - let mut runtime = crate::vm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]); runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; runtime .ext() @@ -1462,7 +1466,7 @@ mod benchmarks { let mut setup = CallSetup::::default(); setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES); let (mut ext, _) = setup.ext(); - let mut runtime = crate::vm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]); runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; runtime .ext() @@ -1489,7 +1493,7 @@ mod benchmarks { let mut setup = CallSetup::::default(); setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES); let (mut ext, _) = setup.ext(); - let mut runtime = crate::vm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]); runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; runtime.ext().transient_storage().start_transaction(); runtime @@ -1702,7 +1706,7 @@ mod benchmarks { setup.set_balance(value + 1u32.into() + Pallet::::min_balance()); let (mut ext, _) = setup.ext(); - let mut runtime = crate::vm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]); let mut memory = memory!(callee_bytes, deposit_bytes, value_bytes,); let result; @@ -1759,7 +1763,7 @@ mod benchmarks { setup.set_storage_deposit_limit(deposit); let (mut ext, _) = setup.ext(); - let mut runtime = crate::vm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]); let mut memory = memory!(callee_bytes, deposit_bytes, value_bytes, input_bytes,); let mut do_benchmark = || { @@ -1805,7 +1809,7 @@ mod benchmarks { setup.set_origin(Origin::from_account_id(setup.contract().account_id.clone())); let (mut ext, _) = setup.ext(); - let mut runtime = crate::vm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]); let mut memory = memory!(address_bytes, deposit_bytes,); let result; @@ -1856,7 +1860,7 @@ mod benchmarks { let account_id = &setup.contract().account_id.clone(); let (mut ext, _) = setup.ext(); - let mut runtime = crate::vm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]); let input = vec![42u8; i as _]; let input_len = hash_bytes.len() as u32 + input.len() as u32; @@ -2076,7 +2080,9 @@ mod benchmarks { fn bn128_add() { use hex_literal::hex; let input = hex!("089142debb13c461f61523586a60732d8b69c5b38a3380a74da7b2961d867dbf2d5fc7bbc013c16d7945f190b232eacc25da675c0eb093fe6b9f1b4b4e107b3625f8c89ea3437f44f8fc8b6bfbb6312074dc6f983809a5e809ff4e1d076dd5850b38c7ced6e4daef9c4347f370d6d8b58f4b1d8dc61a3c59d651a0644a2a27cf").to_vec(); - let expected = hex!("0a6678fd675aa4d8f0d03a1feb921a27f38ebdcb860cc083653519655acd6d79172fd5b3b2bfdd44e43bcec3eace9347608f9f0a16f1e184cb3f52e6f259cbeb"); + let expected = hex!( + "0a6678fd675aa4d8f0d03a1feb921a27f38ebdcb860cc083653519655acd6d79172fd5b3b2bfdd44e43bcec3eace9347608f9f0a16f1e184cb3f52e6f259cbeb" + ); let mut call_setup = CallSetup::::default(); let (mut ext, _) = call_setup.ext(); @@ -2094,7 +2100,9 @@ mod benchmarks { fn bn128_mul() { use hex_literal::hex; let input = hex!("089142debb13c461f61523586a60732d8b69c5b38a3380a74da7b2961d867dbf2d5fc7bbc013c16d7945f190b232eacc25da675c0eb093fe6b9f1b4b4e107b36ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").to_vec(); - let expected = hex!("0bf982b98a2757878c051bfe7eee228b12bc69274b918f08d9fcb21e9184ddc10b17c77cbf3c19d5d27e18cbd4a8c336afb488d0e92c18d56e64dd4ea5c437e6"); + let expected = hex!( + "0bf982b98a2757878c051bfe7eee228b12bc69274b918f08d9fcb21e9184ddc10b17c77cbf3c19d5d27e18cbd4a8c336afb488d0e92c18d56e64dd4ea5c437e6" + ); let mut call_setup = CallSetup::::default(); let (mut ext, _) = call_setup.ext(); @@ -2161,7 +2169,9 @@ mod benchmarks { #[benchmark(pov_mode = Measured)] fn blake2f(n: Linear<0, 1200>) { use hex_literal::hex; - let input = hex!("48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"); + let input = hex!( + "48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001" + ); let input = n.to_be_bytes().to_vec().into_iter().chain(input.to_vec()).collect::>(); let mut call_setup = CallSetup::::default(); let (mut ext, _) = call_setup.ext(); diff --git a/substrate/frame/revive/src/call_builder.rs b/substrate/frame/revive/src/call_builder.rs index 683cd5d5db803..1213dc874d4e5 100644 --- a/substrate/frame/revive/src/call_builder.rs +++ b/substrate/frame/revive/src/call_builder.rs @@ -31,7 +31,7 @@ use crate::{ limits, storage::meter::Meter, transient_storage::MeterEntry, - vm::{PreparedCall, Runtime}, + vm::pvm::{PreparedCall, Runtime}, AccountInfo, BalanceOf, BalanceWithDust, BumpNonce, Code, CodeInfoOf, Config, ContractBlob, ContractInfo, DepositLimit, Error, GasMeter, MomentOf, Origin, Pallet as Contracts, PristineCode, Weight, diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index ec277d562f825..4cd9c6d93dca0 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -61,6 +61,9 @@ use sp_runtime::{ #[cfg(test)] mod tests; +#[cfg(test)] +pub mod mock_ext; + pub type AccountIdOf = ::AccountId; pub type MomentOf = <::Time as Time>::Moment; pub type ExecResult = Result; @@ -474,6 +477,11 @@ pub trait Executable: Sized { /// The code hash of the executable. fn code_hash(&self) -> &H256; + + /// Returns true if the executable is a PVM blob. + fn is_pvm(&self) -> bool { + self.code().starts_with(&polkavm_common::program::BLOB_MAGIC) + } } /// The complete call stack of a contract execution. @@ -567,6 +575,13 @@ impl, Env> ExecutableOrPrecompile { } } + fn is_pvm(&self) -> bool { + match self { + Self::Executable(e) => e.is_pvm(), + _ => false, + } + } + fn as_precompile(&self) -> Option<&PrecompileInstance> { if let Self::Precompile { instance, .. } = self { Some(instance) @@ -1087,6 +1102,7 @@ where ) -> Result<(), ExecError> { let frame = self.top_frame(); let entry_point = frame.entry_point; + let is_pvm = executable.is_pvm(); if_tracing(|tracer| { tracer.enter_child_span( @@ -1157,12 +1173,14 @@ where >::inc_account_nonce(caller.account_id()?); } // The incremented refcount should be visible to the constructor. - >::increment_refcount( - *executable - .as_executable() - .expect("Precompiles cannot be instantiated; qed") - .code_hash(), - )?; + if is_pvm { + >::increment_refcount( + *executable + .as_executable() + .expect("Precompiles cannot be instantiated; qed") + .code_hash(), + )?; + } } // Every non delegate call or instantiate also optionally transfers the balance. @@ -1229,7 +1247,8 @@ where // The deposit we charge for a contract depends on the size of the immutable data. // Hence we need to delay charging the base deposit after execution. if entry_point == ExportedFunction::Constructor { - let deposit = frame.contract_info().update_base_deposit(code_deposit); + let contract_info = frame.contract_info(); + let deposit = contract_info.update_base_deposit(code_deposit); frame .nested_storage .charge_deposit(frame.account_id.clone(), StorageDeposit::Charge(deposit)); @@ -2075,6 +2094,8 @@ mod sealing { use super::*; pub trait Sealed {} - impl<'a, T: Config, E> Sealed for Stack<'a, T, E> {} + + #[cfg(test)] + impl sealing::Sealed for mock_ext::MockExt {} } diff --git a/substrate/frame/revive/src/exec/mock_ext.rs b/substrate/frame/revive/src/exec/mock_ext.rs new file mode 100644 index 0000000000000..63b10c60eedda --- /dev/null +++ b/substrate/frame/revive/src/exec/mock_ext.rs @@ -0,0 +1,259 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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(test)] + +use crate::{ + exec::{AccountIdOf, ExecError, Ext, Key, Origin, PrecompileExt, PrecompileWithInfoExt}, + gas::GasMeter, + precompiles::Diff, + storage::{ContractInfo, WriteOutcome}, + transient_storage::TransientStorage, + Config, ExecReturnValue, ImmutableData, +}; +use alloc::vec::Vec; +use core::marker::PhantomData; +use frame_support::{dispatch::DispatchResult, weights::Weight}; +use sp_core::{H160, H256, U256}; +use sp_runtime::DispatchError; + +/// Mock implementation of the Ext trait that panics for all methods +pub struct MockExt { + gas_meter: GasMeter, + _phantom: PhantomData, +} + +impl PrecompileExt for MockExt { + type T = T; + + fn call( + &mut self, + _gas_limit: Weight, + _deposit_limit: U256, + _to: &H160, + _value: U256, + _input_data: Vec, + _allows_reentry: bool, + _read_only: bool, + ) -> Result<(), ExecError> { + panic!("MockExt::call") + } + + fn get_transient_storage(&self, _key: &Key) -> Option> { + panic!("MockExt::get_transient_storage") + } + + fn get_transient_storage_size(&self, _key: &Key) -> Option { + panic!("MockExt::get_transient_storage_size") + } + + fn set_transient_storage( + &mut self, + _key: &Key, + _value: Option>, + _take_old: bool, + ) -> Result { + panic!("MockExt::set_transient_storage") + } + + fn caller(&self) -> Origin { + panic!("MockExt::caller") + } + + fn origin(&self) -> &Origin { + panic!("MockExt::origin") + } + + fn code_hash(&self, _address: &H160) -> H256 { + panic!("MockExt::code_hash") + } + + fn code_size(&self, _address: &H160) -> u64 { + panic!("MockExt::code_size") + } + + fn caller_is_origin(&self) -> bool { + panic!("MockExt::caller_is_origin") + } + + fn caller_is_root(&self) -> bool { + panic!("MockExt::caller_is_root") + } + + fn account_id(&self) -> &AccountIdOf { + panic!("MockExt::account_id") + } + + fn balance(&self) -> U256 { + panic!("MockExt::balance") + } + + fn balance_of(&self, _address: &H160) -> U256 { + panic!("MockExt::balance_of") + } + + fn value_transferred(&self) -> U256 { + panic!("MockExt::value_transferred") + } + + fn now(&self) -> U256 { + panic!("MockExt::now") + } + + fn minimum_balance(&self) -> U256 { + panic!("MockExt::minimum_balance") + } + + fn deposit_event(&mut self, _topics: Vec, _data: Vec) { + panic!("MockExt::deposit_event") + } + + fn block_number(&self) -> U256 { + panic!("MockExt::block_number") + } + + fn block_hash(&self, _block_number: U256) -> Option { + panic!("MockExt::block_hash") + } + + fn block_author(&self) -> Option { + panic!("MockExt::block_author") + } + + fn max_value_size(&self) -> u32 { + panic!("MockExt::max_value_size") + } + + fn get_weight_price(&self, _weight: Weight) -> U256 { + panic!("MockExt::get_weight_price") + } + + fn gas_meter(&self) -> &GasMeter { + &self.gas_meter + } + + fn gas_meter_mut(&mut self) -> &mut GasMeter { + &mut self.gas_meter + } + + fn ecdsa_recover( + &self, + _signature: &[u8; 65], + _message_hash: &[u8; 32], + ) -> Result<[u8; 33], ()> { + panic!("MockExt::ecdsa_recover") + } + + fn sr25519_verify(&self, _signature: &[u8; 64], _message: &[u8], _pub_key: &[u8; 32]) -> bool { + panic!("MockExt::sr25519_verify") + } + + fn ecdsa_to_eth_address(&self, _pk: &[u8; 33]) -> Result<[u8; 20], ()> { + panic!("MockExt::ecdsa_to_eth_address") + } + + #[cfg(any(test, feature = "runtime-benchmarks"))] + fn contract_info(&mut self) -> &mut ContractInfo { + panic!("MockExt::contract_info") + } + + #[cfg(any(feature = "runtime-benchmarks", test))] + fn transient_storage(&mut self) -> &mut TransientStorage { + panic!("MockExt::transient_storage") + } + + fn is_read_only(&self) -> bool { + panic!("MockExt::is_read_only") + } + + fn last_frame_output(&self) -> &ExecReturnValue { + panic!("MockExt::last_frame_output") + } + + fn last_frame_output_mut(&mut self) -> &mut ExecReturnValue { + panic!("MockExt::last_frame_output_mut") + } +} + +impl PrecompileWithInfoExt for MockExt { + fn get_storage(&mut self, _key: &Key) -> Option> { + panic!("MockExt::get_storage") + } + + fn get_storage_size(&mut self, _key: &Key) -> Option { + panic!("MockExt::get_storage_size") + } + + fn set_storage( + &mut self, + _key: &Key, + _value: Option>, + _take_old: bool, + ) -> Result { + panic!("MockExt::set_storage") + } + + fn charge_storage(&mut self, _diff: &Diff) {} + + fn instantiate( + &mut self, + _gas_limit: Weight, + _deposit_limit: U256, + _code: H256, + _value: U256, + _input_data: Vec, + _salt: Option<&[u8; 32]>, + ) -> Result { + panic!("MockExt::instantiate") + } +} + +impl Ext for MockExt { + fn delegate_call( + &mut self, + _gas_limit: Weight, + _deposit_limit: U256, + _address: H160, + _input_data: Vec, + ) -> Result<(), ExecError> { + panic!("MockExt::delegate_call") + } + + fn terminate(&mut self, _beneficiary: &H160) -> DispatchResult { + panic!("MockExt::terminate") + } + + fn own_code_hash(&mut self) -> &H256 { + panic!("MockExt::own_code_hash") + } + + fn set_code_hash(&mut self, _hash: H256) -> DispatchResult { + panic!("MockExt::set_code_hash") + } + + fn immutable_data_len(&mut self) -> u32 { + panic!("MockExt::immutable_data_len") + } + + fn get_immutable_data(&mut self) -> Result { + panic!("MockExt::get_immutable_data") + } + + fn set_immutable_data(&mut self, _data: ImmutableData) -> Result<(), DispatchError> { + panic!("MockExt::set_immutable_data") + } +} diff --git a/substrate/frame/revive/src/exec/tests.rs b/substrate/frame/revive/src/exec/tests.rs index 381abc7c26117..9b1995e2db7ef 100644 --- a/substrate/frame/revive/src/exec/tests.rs +++ b/substrate/frame/revive/src/exec/tests.rs @@ -176,6 +176,10 @@ impl Executable for MockExecutable { self.code_hash.as_ref() } + fn is_pvm(&self) -> bool { + true + } + fn code_hash(&self) -> &H256 { &self.code_hash } diff --git a/substrate/frame/revive/src/impl_fungibles.rs b/substrate/frame/revive/src/impl_fungibles.rs index 55c42a5091098..404690c6765b4 100644 --- a/substrate/frame/revive/src/impl_fungibles.rs +++ b/substrate/frame/revive/src/impl_fungibles.rs @@ -302,13 +302,14 @@ mod tests { AccountInfoOf, Code, }; use frame_support::assert_ok; + const ERC20_PVM_CODE: &[u8] = include_bytes!("../fixtures/erc20/erc20.polkavm"); #[test] fn call_erc20_contract() { ExtBuilder::default().existential_deposit(1).build().execute_with(|| { let _ = <::Currency as fungible::Mutate<_>>::set_balance(&ALICE, 1_000_000); - let code = include_bytes!("../fixtures/contracts/erc20.polkavm").to_vec(); + let code = ERC20_PVM_CODE.to_vec(); let amount = EU256::from(1000); let constructor_data = sol_data::Uint::<256>::abi_encode(&amount); let Contract { addr, .. } = BareInstantiateBuilder::::bare_instantiate( @@ -333,7 +334,7 @@ mod tests { ExtBuilder::default().existential_deposit(1).build().execute_with(|| { let _ = <::Currency as fungible::Mutate<_>>::set_balance(&ALICE, 1_000_000); - let code = include_bytes!("../fixtures/contracts/erc20.polkavm").to_vec(); + let code = ERC20_PVM_CODE.to_vec(); let amount = 1000; let constructor_data = sol_data::Uint::<256>::abi_encode(&EU256::from(amount)); let Contract { addr, .. } = BareInstantiateBuilder::::bare_instantiate( @@ -353,7 +354,7 @@ mod tests { ExtBuilder::default().existential_deposit(1).build().execute_with(|| { let _ = <::Currency as fungible::Mutate<_>>::set_balance(&ALICE, 1_000_000); - let code = include_bytes!("../fixtures/contracts/erc20.polkavm").to_vec(); + let code = ERC20_PVM_CODE.to_vec(); let amount = 1000; let constructor_data = sol_data::Uint::<256>::abi_encode(&EU256::from(amount)); let Contract { addr, .. } = BareInstantiateBuilder::::bare_instantiate( @@ -371,7 +372,7 @@ mod tests { ExtBuilder::default().existential_deposit(1).build().execute_with(|| { let _ = <::Currency as fungible::Mutate<_>>::set_balance(&ALICE, 1_000_000); - let code = include_bytes!("../fixtures/contracts/erc20.polkavm").to_vec(); + let code = ERC20_PVM_CODE.to_vec(); let amount = 1000; let constructor_data = sol_data::Uint::<256>::abi_encode(&(EU256::from(amount * 2))); let Contract { addr, .. } = BareInstantiateBuilder::::bare_instantiate( @@ -407,7 +408,7 @@ mod tests { &checking_account, 1_000_000, ); - let code = include_bytes!("../fixtures/contracts/erc20.polkavm").to_vec(); + let code = ERC20_PVM_CODE.to_vec(); let amount = 1000; let constructor_data = sol_data::Uint::<256>::abi_encode(&EU256::from(amount)); // We're instantiating the contract with the `CheckingAccount` so it has `amount` in it. diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 8b2d7f76e716a..11c69bfdfe9e3 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -102,7 +102,7 @@ pub use sp_runtime; pub use weights::WeightInfo; #[cfg(doc)] -pub use crate::vm::SyscallDoc; +pub use crate::vm::pvm::SyscallDoc; pub type BalanceOf = <::Currency as Inspect<::AccountId>>::Balance; @@ -1135,9 +1135,9 @@ where if_tracing(|t| t.instantiate_code(&code, salt.as_ref())); let (executable, upload_deposit) = match code { - Code::Upload(code) => { + Code::Upload(code) if code.starts_with(&polkavm_common::program::BLOB_MAGIC) => { let upload_account = T::UploadOrigin::ensure_origin(origin)?; - let (executable, upload_deposit) = Self::try_upload_code( + let (executable, upload_deposit) = Self::try_upload_pvm_code( upload_account, code, storage_deposit_limit, @@ -1146,6 +1146,7 @@ where storage_deposit_limit.saturating_reduce(upload_deposit); (executable, upload_deposit) }, + Code::Upload(_code) => return Err(>::CodeRejected.into()), Code::Existing(code_hash) => (ContractBlob::from_storage(code_hash, &mut gas_meter)?, Default::default()), }; @@ -1245,10 +1246,10 @@ where err == Error::::StorageDepositLimitExhausted.into() { let balance = Self::evm_balance(&from); - return Err(EthTransactError::Message( - format!("insufficient funds for gas * price + value: address {from:?} have {balance} (supplied gas {})", - tx.gas.unwrap_or_default())) - ); + return Err(EthTransactError::Message(format!( + "insufficient funds for gas * price + value: address {from:?} have {balance} (supplied gas {})", + tx.gas.unwrap_or_default() + ))); } return Err(EthTransactError::Message(format!( @@ -1331,16 +1332,20 @@ where // A contract deployment None => { // Extract code and data from the input. - let (code, data) = match polkavm::ProgramBlob::blob_length(&input) { - Some(blob_len) => blob_len - .try_into() - .ok() - .and_then(|blob_len| (input.split_at_checked(blob_len))) - .unwrap_or_else(|| (&input[..], &[][..])), - _ => { - log::debug!(target: LOG_TARGET, "Failed to extract polkavm blob length"); - (&input[..], &[][..]) - }, + let (code, data) = if input.starts_with(&polkavm_common::program::BLOB_MAGIC) { + match polkavm::ProgramBlob::blob_length(&input) { + Some(blob_len) => blob_len + .try_into() + .ok() + .and_then(|blob_len| (input.split_at_checked(blob_len))) + .unwrap_or_else(|| (&input[..], &[][..])), + _ => { + log::debug!(target: LOG_TARGET, "Failed to extract polkavm blob length"); + (&input[..], &[][..]) + }, + } + } else { + return Err(EthTransactError::Message("Invalid transaction".into())); }; // Dry run the call. @@ -1501,7 +1506,8 @@ where storage_deposit_limit: BalanceOf, ) -> CodeUploadResult> { let origin = T::UploadOrigin::ensure_origin(origin)?; - let (module, deposit) = Self::try_upload_code(origin, code, storage_deposit_limit, false)?; + let (module, deposit) = + Self::try_upload_pvm_code(origin, code, storage_deposit_limit, false)?; Ok(CodeUploadReturnValue { code_hash: *module.code_hash(), deposit }) } @@ -1528,13 +1534,13 @@ where } /// Uploads new code and returns the Vm binary contract blob and deposit amount collected. - fn try_upload_code( + fn try_upload_pvm_code( origin: T::AccountId, code: Vec, storage_deposit_limit: BalanceOf, skip_transfer: bool, ) -> Result<(ContractBlob, BalanceOf), DispatchError> { - let mut module = ContractBlob::from_code(code, origin)?; + let mut module = ContractBlob::from_pvm_code(code, origin)?; let deposit = module.store_code(skip_transfer)?; ensure!(storage_deposit_limit >= deposit, >::StorageDepositLimitExhausted); Ok((module, deposit)) diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 0cee2e3d9499a..9b3a3c10a932c 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -17,49 +17,24 @@ mod pallet_dummy; mod precompiles; +mod pvm; -use self::test_utils::{ensure_stored, expected_deposit}; use crate::{ - self as pallet_revive, - address::{create1, create2, AddressMapper}, - evm::{runtime::GAS_PRICE, CallTrace, CallTracer, CallType, GenericTransaction}, - exec::Key, - limits, - storage::DeletionQueueManager, - test_utils::{builder::Contract, *}, - tests::test_utils::{get_contract, get_contract_checked}, - tracing::trace, - weights::WeightInfo, - AccountId32Mapper, AccountInfo, AccountInfoOf, BalanceOf, BalanceWithDust, BumpNonce, Code, - CodeInfoOf, Config, ContractInfo, DeletionQueueCounter, DepositLimit, Error, EthTransactError, - HoldReason, Origin, Pallet, PristineCode, StorageDeposit, H160, + self as pallet_revive, test_utils::*, AccountId32Mapper, BalanceOf, BalanceWithDust, + CodeInfoOf, Config, Origin, Pallet, }; -use assert_matches::assert_matches; -use codec::Encode; use frame_support::{ - assert_err, assert_err_ignore_postinfo, assert_noop, assert_ok, derive_impl, + assert_ok, derive_impl, pallet_prelude::EnsureOrigin, parameter_types, - storage::child, - traits::{ - fungible::{BalancedHold, Inspect, Mutate, MutateHold}, - tokens::Preservation, - ConstU32, ConstU64, FindAuthor, OnIdle, OnInitialize, StorageVersion, - }, - weights::{constants::WEIGHT_REF_TIME_PER_SECOND, FixedFee, IdentityFee, Weight, WeightMeter}, + traits::{ConstU32, ConstU64, FindAuthor, StorageVersion}, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, FixedFee, IdentityFee, Weight}, }; -use frame_system::{EventRecord, Phase}; -use pallet_revive_fixtures::compile_module; -use pallet_revive_uapi::{ReturnErrorCode as RuntimeReturnCode, ReturnFlags}; use pallet_transaction_payment::{ConstFeeMultiplier, Multiplier}; -use pretty_assertions::{assert_eq, assert_ne}; -use sp_core::{Get, U256}; -use sp_io::hashing::blake2_256; use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::{ - testing::H256, - traits::{BlakeTwo256, Convert, IdentityLookup, One, Zero}, - AccountId32, BuildStorage, DispatchError, Perbill, TokenError, + traits::{BlakeTwo256, Convert, IdentityLookup, One}, + AccountId32, BuildStorage, Perbill, }; type Block = frame_system::mocking::MockBlock; @@ -78,12 +53,14 @@ frame_support::construct_runtime!( } ); +#[macro_export] macro_rules! assert_return_code { ( $x:expr , $y:expr $(,)? ) => {{ assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32); }}; } +#[macro_export] macro_rules! assert_refcount { ( $code_hash:expr , $should:expr $(,)? ) => {{ let is = crate::CodeInfoOf::::get($code_hash).map(|m| m.refcount()).unwrap(); @@ -194,7 +171,7 @@ pub mod test_utils { } } -mod builder { +pub(crate) mod builder { use super::Test; use crate::{ test_utils::{builder::*, ALICE}, @@ -457,4844 +434,3 @@ impl Default for Origin { Self::Signed(ALICE) } } - -#[test] -fn transfer_with_dust_works() { - struct TestCase { - description: &'static str, - from_balance: BalanceWithDust, - to_balance: BalanceWithDust, - amount: BalanceWithDust, - expected_from_balance: BalanceWithDust, - expected_to_balance: BalanceWithDust, - total_issuance_diff: i64, - } - - let plank: u32 = ::NativeToEthRatio::get(); - - let test_cases = vec![ - TestCase { - description: "without dust", - from_balance: BalanceWithDust::new_unchecked::(100, 0), - to_balance: BalanceWithDust::new_unchecked::(0, 0), - amount: BalanceWithDust::new_unchecked::(1, 0), - expected_from_balance: BalanceWithDust::new_unchecked::(99, 0), - expected_to_balance: BalanceWithDust::new_unchecked::(1, 0), - total_issuance_diff: 0, - }, - TestCase { - description: "with dust", - from_balance: BalanceWithDust::new_unchecked::(100, 0), - to_balance: BalanceWithDust::new_unchecked::(0, 0), - amount: BalanceWithDust::new_unchecked::(1, 10), - expected_from_balance: BalanceWithDust::new_unchecked::(98, plank - 10), - expected_to_balance: BalanceWithDust::new_unchecked::(1, 10), - total_issuance_diff: 1, - }, - TestCase { - description: "just dust", - from_balance: BalanceWithDust::new_unchecked::(100, 0), - to_balance: BalanceWithDust::new_unchecked::(0, 0), - amount: BalanceWithDust::new_unchecked::(0, 10), - expected_from_balance: BalanceWithDust::new_unchecked::(99, plank - 10), - expected_to_balance: BalanceWithDust::new_unchecked::(0, 10), - total_issuance_diff: 1, - }, - TestCase { - description: "with existing dust", - from_balance: BalanceWithDust::new_unchecked::(100, 5), - to_balance: BalanceWithDust::new_unchecked::(0, plank - 5), - amount: BalanceWithDust::new_unchecked::(1, 10), - expected_from_balance: BalanceWithDust::new_unchecked::(98, plank - 5), - expected_to_balance: BalanceWithDust::new_unchecked::(2, 5), - total_issuance_diff: 0, - }, - TestCase { - description: "with enough existing dust", - from_balance: BalanceWithDust::new_unchecked::(100, 10), - to_balance: BalanceWithDust::new_unchecked::(0, plank - 10), - amount: BalanceWithDust::new_unchecked::(1, 10), - expected_from_balance: BalanceWithDust::new_unchecked::(99, 0), - expected_to_balance: BalanceWithDust::new_unchecked::(2, 0), - total_issuance_diff: -1, - }, - TestCase { - description: "receiver dust less than 1 plank", - from_balance: BalanceWithDust::new_unchecked::(100, plank / 10), - to_balance: BalanceWithDust::new_unchecked::(0, plank / 2), - amount: BalanceWithDust::new_unchecked::(1, plank / 10 * 3), - expected_from_balance: BalanceWithDust::new_unchecked::(98, plank / 10 * 8), - expected_to_balance: BalanceWithDust::new_unchecked::(1, plank / 10 * 8), - total_issuance_diff: 1, - }, - ]; - - for TestCase { - description, - from_balance, - to_balance, - amount, - expected_from_balance, - expected_to_balance, - total_issuance_diff, - } in test_cases.into_iter() - { - ExtBuilder::default().build().execute_with(|| { - test_utils::set_balance_with_dust(&ALICE_ADDR, from_balance); - test_utils::set_balance_with_dust(&BOB_ADDR, to_balance); - - let total_issuance = ::Currency::total_issuance(); - let evm_value = Pallet::::convert_native_to_evm(amount); - - let (value, dust) = amount.deconstruct(); - assert_eq!(Pallet::::has_dust(evm_value), !dust.is_zero()); - assert_eq!(Pallet::::has_balance(evm_value), !value.is_zero()); - - let result = - builder::bare_call(BOB_ADDR).evm_value(evm_value).build_and_unwrap_result(); - assert_eq!(result, Default::default(), "{description} tx failed"); - - assert_eq!( - Pallet::::evm_balance(&ALICE_ADDR), - Pallet::::convert_native_to_evm(expected_from_balance), - "{description}: invalid from balance" - ); - - assert_eq!( - Pallet::::evm_balance(&BOB_ADDR), - Pallet::::convert_native_to_evm(expected_to_balance), - "{description}: invalid to balance" - ); - - assert_eq!( - total_issuance as i64 - total_issuance_diff, - ::Currency::total_issuance() as i64, - "{description}: total issuance should match" - ); - }); - } -} - -#[test] -fn eth_call_transfer_with_dust_works() { - let (binary, _) = compile_module("dummy").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); - - let balance = - Pallet::::convert_native_to_evm(BalanceWithDust::new_unchecked::(100, 10)); - assert_ok!(builder::eth_call(addr).value(balance).build()); - - assert_eq!(Pallet::::evm_balance(&addr), balance); - }); -} - -#[test] -fn contract_call_transfer_with_dust_works() { - let (binary_caller, _code_hash_caller) = compile_module("call_with_value").unwrap(); - let (binary_callee, _code_hash_callee) = compile_module("dummy").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(binary_caller)) - .native_value(200) - .build_and_unwrap_contract(); - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); - - let balance = - Pallet::::convert_native_to_evm(BalanceWithDust::new_unchecked::(100, 10)); - assert_ok!(builder::call(addr_caller).data((balance, addr_callee).encode()).build()); - - assert_eq!(Pallet::::evm_balance(&addr_callee), balance); - }); -} - -#[test] -fn deposit_limit_enforced_on_plain_transfer() { - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let _ = ::Currency::set_balance(&BOB, 1_000_000); - - // sending balance to a new account should fail when the limit is lower than the ed - let result = builder::bare_call(CHARLIE_ADDR) - .native_value(1) - .storage_deposit_limit(190.into()) - .build(); - assert_err!(result.result, >::StorageDepositLimitExhausted); - assert_eq!(result.storage_deposit, StorageDeposit::Charge(0)); - assert_eq!(test_utils::get_balance(&CHARLIE), 0); - - // works when the account is prefunded - let result = builder::bare_call(BOB_ADDR) - .native_value(1) - .storage_deposit_limit(0.into()) - .build(); - assert_ok!(result.result); - assert_eq!(result.storage_deposit, StorageDeposit::Charge(0)); - assert_eq!(test_utils::get_balance(&BOB), 1_000_001); - - // also works allowing enough deposit - let result = builder::bare_call(CHARLIE_ADDR) - .native_value(1) - .storage_deposit_limit(200.into()) - .build(); - assert_ok!(result.result); - assert_eq!(result.storage_deposit, StorageDeposit::Charge(200)); - assert_eq!(test_utils::get_balance(&CHARLIE), 201); - }); -} - -#[test] -fn instantiate_and_call_and_deposit_event() { - let (binary, code_hash) = compile_module("event_and_return_on_deploy").unwrap(); - - ExtBuilder::default().existential_deposit(1).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let min_balance = Contracts::min_balance(); - let value = 100; - - // We determine the storage deposit limit after uploading because it depends on ALICEs - // free balance which is changed by uploading a module. - assert_ok!(Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - binary, - deposit_limit::(), - )); - - // Drop previous events - initialize_block(2); - - // Check at the end to get hash on error easily - let Contract { addr, account_id } = builder::bare_instantiate(Code::Existing(code_hash)) - .native_value(value) - .build_and_unwrap_contract(); - assert!(AccountInfoOf::::contains_key(&addr)); - - let hold_balance = test_utils::contract_base_deposit(&addr); - - assert_eq!( - System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::System(frame_system::Event::NewAccount { - account: account_id.clone() - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { - account: account_id.clone(), - free_balance: min_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: account_id.clone(), - amount: min_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: account_id.clone(), - amount: value, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::ContractEmitted { - contract: addr, - data: vec![1, 2, 3, 4], - topics: vec![H256::repeat_byte(42)], - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::TransferAndHold { - reason: ::RuntimeHoldReason::Contracts( - HoldReason::StorageDepositReserve, - ), - source: ALICE, - dest: account_id.clone(), - transferred: hold_balance, - }), - topics: vec![], - }, - ] - ); - }); -} - -#[test] -fn create1_address_from_extrinsic() { - let (binary, code_hash) = compile_module("dummy").unwrap(); - - ExtBuilder::default().existential_deposit(1).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - assert_ok!(Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - binary.clone(), - deposit_limit::(), - )); - - assert_eq!(System::account_nonce(&ALICE), 0); - System::inc_account_nonce(&ALICE); - - for nonce in 1..3 { - let Contract { addr, .. } = builder::bare_instantiate(Code::Existing(code_hash)) - .salt(None) - .build_and_unwrap_contract(); - assert!(AccountInfoOf::::contains_key(&addr)); - assert_eq!( - addr, - create1(&::AddressMapper::to_address(&ALICE), nonce - 1) - ); - } - assert_eq!(System::account_nonce(&ALICE), 3); - - for nonce in 3..6 { - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary.clone())) - .salt(None) - .build_and_unwrap_contract(); - assert!(AccountInfoOf::::contains_key(&addr)); - assert_eq!( - addr, - create1(&::AddressMapper::to_address(&ALICE), nonce - 1) - ); - } - assert_eq!(System::account_nonce(&ALICE), 6); - }); -} - -#[test] -fn deposit_event_max_value_limit() { - let (binary, _code_hash) = compile_module("event_size").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Create - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) - .native_value(30_000) - .build_and_unwrap_contract(); - - // Call contract with allowed storage value. - assert_ok!(builder::call(addr) - .gas_limit(GAS_LIMIT.set_ref_time(GAS_LIMIT.ref_time() * 2)) // we are copying a huge buffer, - .data(limits::PAYLOAD_BYTES.encode()) - .build()); - - // Call contract with too large a storage value. - assert_err_ignore_postinfo!( - builder::call(addr).data((limits::PAYLOAD_BYTES + 1).encode()).build(), - Error::::ValueTooLarge, - ); - }); -} - -// Fail out of fuel (ref_time weight) in the engine. -#[test] -fn run_out_of_fuel_engine() { - let (binary, _code_hash) = compile_module("run_out_of_gas").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) - .native_value(100 * min_balance) - .build_and_unwrap_contract(); - - // Call the contract with a fixed gas limit. It must run out of gas because it just - // loops forever. - assert_err_ignore_postinfo!( - builder::call(addr) - .gas_limit(Weight::from_parts(10_000_000_000, u64::MAX)) - .build(), - Error::::OutOfGas, - ); - }); -} - -// Fail out of fuel (ref_time weight) in the host. -#[test] -fn run_out_of_fuel_host() { - use crate::precompiles::Precompile; - use alloy_core::sol_types::SolInterface; - use precompiles::{INoInfo, NoInfo}; - - let precompile_addr = H160(NoInfo::::MATCHER.base_address()); - let input = INoInfo::INoInfoCalls::consumeMaxGas(INoInfo::consumeMaxGasCall {}).abi_encode(); - - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); - let result = builder::bare_call(precompile_addr).data(input).build().result; - assert_err!(result, >::OutOfGas); - }); -} - -#[test] -fn gas_syncs_work() { - let (code, _code_hash) = compile_module("caller_is_origin_n").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let contract = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - let result = builder::bare_call(contract.addr).data(0u32.encode()).build(); - assert_ok!(result.result); - let engine_consumed_noop = result.gas_consumed.ref_time(); - - let result = builder::bare_call(contract.addr).data(1u32.encode()).build(); - assert_ok!(result.result); - let gas_consumed_once = result.gas_consumed.ref_time(); - let host_consumed_once = ::WeightInfo::seal_caller_is_origin().ref_time(); - let engine_consumed_once = gas_consumed_once - host_consumed_once - engine_consumed_noop; - - let result = builder::bare_call(contract.addr).data(2u32.encode()).build(); - assert_ok!(result.result); - let gas_consumed_twice = result.gas_consumed.ref_time(); - let host_consumed_twice = host_consumed_once * 2; - let engine_consumed_twice = gas_consumed_twice - host_consumed_twice - engine_consumed_noop; - - // Second contract just repeats first contract's instructions twice. - // If runtime syncs gas with the engine properly, this should pass. - assert_eq!(engine_consumed_twice, engine_consumed_once * 2); - }); -} - -/// Check that contracts with the same account id have different trie ids. -/// Check the `Nonce` storage item for more information. -#[test] -fn instantiate_unique_trie_id() { - let (binary, code_hash) = compile_module("self_destruct").unwrap(); - - ExtBuilder::default().existential_deposit(500).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - Contracts::upload_code(RuntimeOrigin::signed(ALICE), binary, deposit_limit::()) - .unwrap(); - - // Instantiate the contract and store its trie id for later comparison. - let Contract { addr, .. } = - builder::bare_instantiate(Code::Existing(code_hash)).build_and_unwrap_contract(); - let trie_id = get_contract(&addr).trie_id; - - // Try to instantiate it again without termination should yield an error. - assert_err_ignore_postinfo!( - builder::instantiate(code_hash).build(), - >::DuplicateContract, - ); - - // Terminate the contract. - assert_ok!(builder::call(addr).build()); - - // Re-Instantiate after termination. - assert_ok!(builder::instantiate(code_hash).build()); - - // Trie ids shouldn't match or we might have a collision - assert_ne!(trie_id, get_contract(&addr).trie_id); - }); -} - -#[test] -fn storage_work() { - let (code, _code_hash) = compile_module("storage").unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let min_balance = Contracts::min_balance(); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) - .native_value(min_balance * 100) - .build_and_unwrap_contract(); - - builder::bare_call(addr).build_and_unwrap_result(); - }); -} - -#[test] -fn storage_max_value_limit() { - let (binary, _code_hash) = compile_module("storage_size").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Create - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) - .native_value(30_000) - .build_and_unwrap_contract(); - get_contract(&addr); - - // Call contract with allowed storage value. - assert_ok!(builder::call(addr) - .gas_limit(GAS_LIMIT.set_ref_time(GAS_LIMIT.ref_time() * 2)) // we are copying a huge buffer - .data(limits::PAYLOAD_BYTES.encode()) - .build()); - - // Call contract with too large a storage value. - assert_err_ignore_postinfo!( - builder::call(addr).data((limits::PAYLOAD_BYTES + 1).encode()).build(), - Error::::ValueTooLarge, - ); - }); -} - -#[test] -fn clear_storage_on_zero_value() { - let (code, _code_hash) = compile_module("clear_storage_on_zero_value").unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let min_balance = Contracts::min_balance(); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) - .native_value(min_balance * 100) - .build_and_unwrap_contract(); - - builder::bare_call(addr).build_and_unwrap_result(); - }); -} - -#[test] -fn transient_storage_work() { - let (code, _code_hash) = compile_module("transient_storage").unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let min_balance = Contracts::min_balance(); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) - .native_value(min_balance * 100) - .build_and_unwrap_contract(); - - builder::bare_call(addr).build_and_unwrap_result(); - }); -} - -#[test] -fn transient_storage_limit_in_call() { - let (binary_caller, _code_hash_caller) = - compile_module("create_transient_storage_and_call").unwrap(); - let (binary_callee, _code_hash_callee) = compile_module("set_transient_storage").unwrap(); - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create both contracts: Constructors do nothing. - let Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(binary_caller)).build_and_unwrap_contract(); - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); - - // Call contracts with storage values within the limit. - // Caller and Callee contracts each set a transient storage value of size 100. - assert_ok!(builder::call(addr_caller) - .data((100u32, 100u32, &addr_callee).encode()) - .build(),); - - // Call a contract with a storage value that is too large. - // Limit exceeded in the caller contract. - assert_err_ignore_postinfo!( - builder::call(addr_caller) - .data((4u32 * 1024u32, 200u32, &addr_callee).encode()) - .build(), - >::OutOfTransientStorage, - ); - - // Call a contract with a storage value that is too large. - // Limit exceeded in the callee contract. - assert_err_ignore_postinfo!( - builder::call(addr_caller) - .data((50u32, 4 * 1024u32, &addr_callee).encode()) - .build(), - >::ContractTrapped - ); - }); -} - -#[test] -fn deploy_and_call_other_contract() { - let (caller_binary, _caller_code_hash) = compile_module("caller_contract").unwrap(); - let (callee_binary, callee_code_hash) = compile_module("return_with_data").unwrap(); - let code_load_weight = crate::vm::code_load_weight(callee_binary.len() as u32); - - ExtBuilder::default().existential_deposit(1).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - - // Create - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr: caller_addr, account_id: caller_account } = - builder::bare_instantiate(Code::Upload(caller_binary)) - .native_value(100_000) - .build_and_unwrap_contract(); - - let callee_addr = create2( - &caller_addr, - &callee_binary, - &[0, 1, 34, 51, 68, 85, 102, 119], // hard coded in binary - &[0u8; 32], - ); - let callee_account = ::AddressMapper::to_account_id(&callee_addr); - - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - callee_binary, - deposit_limit::(), - ) - .unwrap(); - - // Drop previous events - initialize_block(2); - - // Call BOB contract, which attempts to instantiate and call the callee contract and - // makes various assertions on the results from those calls. - assert_ok!(builder::call(caller_addr) - .data( - (callee_code_hash, code_load_weight.ref_time(), code_load_weight.proof_size()) - .encode() - ) - .build()); - - assert_eq!( - System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::System(frame_system::Event::NewAccount { - account: callee_account.clone() - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { - account: callee_account.clone(), - free_balance: min_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: callee_account.clone(), - amount: min_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: caller_account.clone(), - to: callee_account.clone(), - amount: 32768 // hardcoded in binary - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: caller_account.clone(), - to: callee_account.clone(), - amount: 32768, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::TransferAndHold { - reason: ::RuntimeHoldReason::Contracts( - HoldReason::StorageDepositReserve, - ), - source: ALICE, - dest: callee_account.clone(), - transferred: 555, - }), - topics: vec![], - }, - ] - ); - }); -} - -#[test] -fn delegate_call() { - let (caller_binary, _caller_code_hash) = compile_module("delegate_call").unwrap(); - let (callee_binary, _callee_code_hash) = compile_module("delegate_call_lib").unwrap(); - - ExtBuilder::default().existential_deposit(500).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Instantiate the 'caller' - let Contract { addr: caller_addr, .. } = - builder::bare_instantiate(Code::Upload(caller_binary)) - .native_value(300_000) - .build_and_unwrap_contract(); - - // Instantiate the 'callee' - let Contract { addr: callee_addr, .. } = - builder::bare_instantiate(Code::Upload(callee_binary)) - .native_value(100_000) - .build_and_unwrap_contract(); - - assert_ok!(builder::call(caller_addr) - .value(1337) - .data((callee_addr, u64::MAX, u64::MAX).encode()) - .build()); - }); -} - -#[test] -fn delegate_call_non_existant_is_noop() { - let (caller_binary, _caller_code_hash) = compile_module("delegate_call_simple").unwrap(); - - ExtBuilder::default().existential_deposit(500).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Instantiate the 'caller' - let Contract { addr: caller_addr, .. } = - builder::bare_instantiate(Code::Upload(caller_binary)) - .native_value(300_000) - .build_and_unwrap_contract(); - - assert_ok!(builder::call(caller_addr) - .value(1337) - .data((BOB_ADDR, u64::MAX, u64::MAX).encode()) - .build()); - - assert_eq!(test_utils::get_balance(&BOB_FALLBACK), 0); - }); -} - -#[test] -fn delegate_call_with_weight_limit() { - let (caller_binary, _caller_code_hash) = compile_module("delegate_call").unwrap(); - let (callee_binary, _callee_code_hash) = compile_module("delegate_call_lib").unwrap(); - - ExtBuilder::default().existential_deposit(500).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Instantiate the 'caller' - let Contract { addr: caller_addr, .. } = - builder::bare_instantiate(Code::Upload(caller_binary)) - .native_value(300_000) - .build_and_unwrap_contract(); - - // Instantiate the 'callee' - let Contract { addr: callee_addr, .. } = - builder::bare_instantiate(Code::Upload(callee_binary)) - .native_value(100_000) - .build_and_unwrap_contract(); - - // fails, not enough weight - assert_err!( - builder::bare_call(caller_addr) - .native_value(1337) - .data((callee_addr, 100u64, 100u64).encode()) - .build() - .result, - Error::::ContractTrapped, - ); - - assert_ok!(builder::call(caller_addr) - .value(1337) - .data((callee_addr, 500_000_000u64, 100_000u64).encode()) - .build()); - }); -} - -#[test] -fn delegate_call_with_deposit_limit() { - let (caller_binary, _caller_code_hash) = compile_module("delegate_call_deposit_limit").unwrap(); - let (callee_binary, _callee_code_hash) = compile_module("delegate_call_lib").unwrap(); - - ExtBuilder::default().existential_deposit(500).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Instantiate the 'caller' - let Contract { addr: caller_addr, .. } = - builder::bare_instantiate(Code::Upload(caller_binary)) - .native_value(300_000) - .build_and_unwrap_contract(); - - // Instantiate the 'callee' - let Contract { addr: callee_addr, .. } = - builder::bare_instantiate(Code::Upload(callee_binary)) - .native_value(100_000) - .build_and_unwrap_contract(); - - // Delegate call will write 1 storage and deposit of 2 (1 item) + 32 (bytes) is required. - // + 32 + 16 for blake2_128concat - // Fails, not enough deposit - let ret = builder::bare_call(caller_addr) - .native_value(1337) - .data((callee_addr, 81u64).encode()) - .build_and_unwrap_result(); - assert_return_code!(ret, RuntimeReturnCode::OutOfResources); - - assert_ok!(builder::call(caller_addr) - .value(1337) - .data((callee_addr, 82u64).encode()) - .build()); - }); -} - -#[test] -fn transfer_expendable_cannot_kill_account() { - let (binary, _code_hash) = compile_module("dummy").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Instantiate the BOB contract. - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) - .native_value(1_000) - .build_and_unwrap_contract(); - - // Check that the BOB contract has been instantiated. - get_contract(&addr); - - let account = ::AddressMapper::to_account_id(&addr); - let total_balance = ::Currency::total_balance(&account); - - assert_eq!( - test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account), - test_utils::contract_base_deposit(&addr) - ); - - // Some or the total balance is held, so it can't be transferred. - assert_err!( - <::Currency as Mutate>::transfer( - &account, - &ALICE, - total_balance, - Preservation::Expendable, - ), - TokenError::FundsUnavailable, - ); - - assert_eq!(::Currency::total_balance(&account), total_balance); - }); -} - -#[test] -fn cannot_self_destruct_through_draining() { - let (binary, _code_hash) = compile_module("drain").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let value = 1_000; - let min_balance = Contracts::min_balance(); - - // Instantiate the BOB contract. - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) - .native_value(value) - .build_and_unwrap_contract(); - let account = ::AddressMapper::to_account_id(&addr); - - // Check that the BOB contract has been instantiated. - get_contract(&addr); - - // Call BOB which makes it send all funds to the zero address - // The contract code asserts that the transfer fails with the correct error code - assert_ok!(builder::call(addr).build()); - - // Make sure the account wasn't remove by sending all free balance away. - assert_eq!( - ::Currency::total_balance(&account), - value + test_utils::contract_base_deposit(&addr) + min_balance, - ); - }); -} - -#[test] -fn cannot_self_destruct_through_storage_refund_after_price_change() { - let (binary, _code_hash) = compile_module("store_call").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let min_balance = Contracts::min_balance(); - - // Instantiate the BOB contract. - let contract = builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); - let info_deposit = test_utils::contract_base_deposit(&contract.addr); - - // Check that the contract has been instantiated and has the minimum balance - assert_eq!(get_contract(&contract.addr).total_deposit(), info_deposit); - assert_eq!(get_contract(&contract.addr).extra_deposit(), 0); - assert_eq!( - ::Currency::total_balance(&contract.account_id), - info_deposit + min_balance - ); - - // Create 100 (16 + 32 bytes for key for blake128 concat) bytes of storage with a - // price of per byte and a single storage item of price 2 - assert_ok!(builder::call(contract.addr).data(100u32.to_le_bytes().to_vec()).build()); - assert_eq!(get_contract(&contract.addr).total_deposit(), info_deposit + 100 + 16 + 32 + 2); - - // Increase the byte price and trigger a refund. This should not have any influence - // because the removal is pro rata and exactly those 100 bytes should have been - // removed as we didn't delete the key. - DEPOSIT_PER_BYTE.with(|c| *c.borrow_mut() = 500); - assert_ok!(builder::call(contract.addr).data(0u32.to_le_bytes().to_vec()).build()); - - // Make sure the account wasn't removed by the refund - assert_eq!( - ::Currency::total_balance(&contract.account_id), - get_contract(&contract.addr).total_deposit() + min_balance, - ); - // + 1 because due to fixed point arithmetic we can sometimes refund - // one unit to little - assert_eq!(get_contract(&contract.addr).extra_deposit(), 16 + 32 + 2 + 1); - }); -} - -#[test] -fn cannot_self_destruct_while_live() { - let (binary, _code_hash) = compile_module("self_destruct").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Instantiate the BOB contract. - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) - .native_value(100_000) - .build_and_unwrap_contract(); - - // Check that the BOB contract has been instantiated. - get_contract(&addr); - - // Call BOB with input data, forcing it make a recursive call to itself to - // self-destruct, resulting in a trap. - assert_err_ignore_postinfo!( - builder::call(addr).data(vec![0]).build(), - Error::::ContractTrapped, - ); - - // Check that BOB is still there. - get_contract(&addr); - }); -} - -#[test] -fn self_destruct_works() { - let (binary, code_hash) = compile_module("self_destruct").unwrap(); - ExtBuilder::default().existential_deposit(1_000).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let _ = ::Currency::set_balance(&DJANGO_FALLBACK, 1_000_000); - let min_balance = Contracts::min_balance(); - - // Instantiate the BOB contract. - let contract = builder::bare_instantiate(Code::Upload(binary)) - .native_value(100_000) - .build_and_unwrap_contract(); - - let hold_balance = test_utils::contract_base_deposit(&contract.addr); - - // Check that the BOB contract has been instantiated. - let _ = get_contract(&contract.addr); - - // Drop all previous events - initialize_block(2); - - // Call BOB without input data which triggers termination. - assert_matches!(builder::call(contract.addr).build(), Ok(_)); - - // Check that code is still there but refcount dropped to zero. - assert_refcount!(&code_hash, 0); - - // Check that account is gone - assert!(get_contract_checked(&contract.addr).is_none()); - assert_eq!(::Currency::total_balance(&contract.account_id), 0); - - // Check that the beneficiary (django) got remaining balance. - assert_eq!( - ::Currency::free_balance(DJANGO_FALLBACK), - 1_000_000 + 100_000 + min_balance - ); - - // Check that the Alice is missing Django's benefit. Within ALICE's total balance - // there's also the code upload deposit held. - assert_eq!( - ::Currency::total_balance(&ALICE), - 1_000_000 - (100_000 + min_balance) - ); - - pretty_assertions::assert_eq!( - System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::TransferOnHold { - reason: ::RuntimeHoldReason::Contracts( - HoldReason::StorageDepositReserve, - ), - source: contract.account_id.clone(), - dest: ALICE, - amount: hold_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::System(frame_system::Event::KilledAccount { - account: contract.account_id.clone() - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: contract.account_id.clone(), - to: DJANGO_FALLBACK, - amount: 100_000 + min_balance, - }), - topics: vec![], - }, - ], - ); - }); -} - -// This tests that one contract cannot prevent another from self-destructing by sending it -// additional funds after it has been drained. -#[test] -fn destroy_contract_and_transfer_funds() { - let (callee_binary, callee_code_hash) = compile_module("self_destruct").unwrap(); - let (caller_binary, _caller_code_hash) = compile_module("destroy_and_transfer").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Create code hash for bob to instantiate - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - callee_binary.clone(), - deposit_limit::(), - ) - .unwrap(); - - // This deploys the BOB contract, which in turn deploys the CHARLIE contract during - // construction. - let Contract { addr: addr_bob, .. } = - builder::bare_instantiate(Code::Upload(caller_binary)) - .native_value(200_000) - .data(callee_code_hash.as_ref().to_vec()) - .build_and_unwrap_contract(); - - // Check that the CHARLIE contract has been instantiated. - let salt = [47; 32]; // hard coded in fixture. - let addr_charlie = create2(&addr_bob, &callee_binary, &[], &salt); - get_contract(&addr_charlie); - - // Call BOB, which calls CHARLIE, forcing CHARLIE to self-destruct. - assert_ok!(builder::call(addr_bob).data(addr_charlie.encode()).build()); - - // Check that CHARLIE has moved on to the great beyond (ie. died). - assert!(get_contract_checked(&addr_charlie).is_none()); - }); -} - -#[test] -fn cannot_self_destruct_in_constructor() { - let (binary, _) = compile_module("self_destructing_constructor").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Fail to instantiate the BOB because the constructor calls seal_terminate. - assert_err_ignore_postinfo!( - builder::instantiate_with_code(binary).value(100_000).build(), - Error::::TerminatedInConstructor, - ); - }); -} - -#[test] -fn crypto_hash_keccak_256() { - let (binary, _code_hash) = compile_module("crypto_hash_keccak_256").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Instantiate the CRYPTO_HASH_KECCAK_256 contract. - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) - .native_value(100_000) - .build_and_unwrap_contract(); - // Perform the call. - let input = b"_DEAD_BEEF"; - use sp_io::hashing::*; - // Wraps a hash function into a more dynamic form usable for testing. - macro_rules! dyn_hash_fn { - ($name:ident) => { - Box::new(|input| $name(input).as_ref().to_vec().into_boxed_slice()) - }; - } - // The hash function and its associated output byte lengths. - let hash_fn: Box Box<[u8]>> = dyn_hash_fn!(keccak_256); - let expected_size: usize = 32; - // Test the hash function for the input: "_DEAD_BEEF" - let result = builder::bare_call(addr).data(input.to_vec()).build_and_unwrap_result(); - assert!(!result.did_revert()); - let expected = hash_fn(input.as_ref()); - assert_eq!(&result.data[..expected_size], &*expected); - }) -} - -#[test] -fn transfer_return_code() { - let (binary, _code_hash) = compile_module("transfer_return_code").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let contract = builder::bare_instantiate(Code::Upload(binary)) - .native_value(min_balance * 100) - .build_and_unwrap_contract(); - - // Contract has only the minimal balance so any transfer will fail. - ::Currency::set_balance(&contract.account_id, min_balance); - let result = builder::bare_call(contract.addr).build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::TransferFailed); - }); -} - -#[test] -fn call_return_code() { - use test_utils::u256_bytes; - - let (caller_code, _caller_hash) = compile_module("call_return_code").unwrap(); - let (callee_code, _callee_hash) = compile_module("ok_trap_revert").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); - - let bob = builder::bare_instantiate(Code::Upload(caller_code)) - .native_value(min_balance * 100) - .build_and_unwrap_contract(); - - // BOB cannot pay the ed which is needed to pull DJANGO into existence - // this does trap the caller instead of returning an error code - // reasoning is that this error state does not exist on eth where - // ed does not exist. We hide this fact from the contract. - let result = builder::bare_call(bob.addr) - .data((DJANGO_ADDR, u256_bytes(1)).encode()) - .origin(RuntimeOrigin::signed(BOB)) - .build(); - assert_err!(result.result, >::StorageDepositNotEnoughFunds); - - // Contract calls into Django which is no valid contract - // This will be a balance transfer into a new account - // with more than the contract has which will make the transfer fail - let value = Pallet::::convert_native_to_evm(min_balance * 200); - let result = builder::bare_call(bob.addr) - .data( - AsRef::<[u8]>::as_ref(&DJANGO_ADDR) - .iter() - .chain(&value.to_little_endian()) - .cloned() - .collect(), - ) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::TransferFailed); - - // Sending below the minimum balance should result in success. - // The ED is charged from the call origin. - let alice_before = test_utils::get_balance(&ALICE_FALLBACK); - assert_eq!(test_utils::get_balance(&DJANGO_FALLBACK), 0); - - let value = Pallet::::convert_native_to_evm(1u64); - let result = builder::bare_call(bob.addr) - .data( - AsRef::<[u8]>::as_ref(&DJANGO_ADDR) - .iter() - .chain(&value.to_little_endian()) - .cloned() - .collect(), - ) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::Success); - assert_eq!(test_utils::get_balance(&DJANGO_FALLBACK), min_balance + 1); - assert_eq!(test_utils::get_balance(&ALICE_FALLBACK), alice_before - min_balance); - - let django = builder::bare_instantiate(Code::Upload(callee_code)) - .origin(RuntimeOrigin::signed(CHARLIE)) - .native_value(min_balance * 100) - .build_and_unwrap_contract(); - - // Sending more than the contract has will make the transfer fail. - let value = Pallet::::convert_native_to_evm(min_balance * 300); - let result = builder::bare_call(bob.addr) - .data( - AsRef::<[u8]>::as_ref(&django.addr) - .iter() - .chain(&value.to_little_endian()) - .chain(&0u32.to_le_bytes()) - .cloned() - .collect(), - ) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::TransferFailed); - - // Contract has enough balance but callee reverts because "1" is passed. - ::Currency::set_balance(&bob.account_id, min_balance + 1000); - let value = Pallet::::convert_native_to_evm(5u64); - let result = builder::bare_call(bob.addr) - .data( - AsRef::<[u8]>::as_ref(&django.addr) - .iter() - .chain(&value.to_little_endian()) - .chain(&1u32.to_le_bytes()) - .cloned() - .collect(), - ) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::CalleeReverted); - - // Contract has enough balance but callee traps because "2" is passed. - let result = builder::bare_call(bob.addr) - .data( - AsRef::<[u8]>::as_ref(&django.addr) - .iter() - .chain(&value.to_little_endian()) - .chain(&2u32.to_le_bytes()) - .cloned() - .collect(), - ) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); - }); -} - -#[test] -fn instantiate_return_code() { - let (caller_code, _caller_hash) = compile_module("instantiate_return_code").unwrap(); - let (callee_code, callee_hash) = compile_module("ok_trap_revert").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); - let callee_hash = callee_hash.as_ref().to_vec(); - - assert_ok!(builder::instantiate_with_code(callee_code).value(min_balance * 100).build()); - - let contract = builder::bare_instantiate(Code::Upload(caller_code)) - .native_value(min_balance * 100) - .build_and_unwrap_contract(); - - // bob cannot pay the ED to create the contract as he has no money - // this traps the caller rather than returning an error - let result = builder::bare_call(contract.addr) - .data(callee_hash.iter().chain(&0u32.to_le_bytes()).cloned().collect()) - .origin(RuntimeOrigin::signed(BOB)) - .build(); - assert_err!(result.result, >::StorageDepositNotEnoughFunds); - - // Contract has only the minimal balance so any transfer will fail. - ::Currency::set_balance(&contract.account_id, min_balance); - let result = builder::bare_call(contract.addr) - .data(callee_hash.iter().chain(&0u32.to_le_bytes()).cloned().collect()) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::TransferFailed); - - // Contract has enough balance but the passed code hash is invalid - ::Currency::set_balance(&contract.account_id, min_balance + 10_000); - let result = builder::bare_call(contract.addr).data(vec![0; 36]).build(); - assert_err!(result.result, >::CodeNotFound); - - // Contract has enough balance but callee reverts because "1" is passed. - let result = builder::bare_call(contract.addr) - .data(callee_hash.iter().chain(&1u32.to_le_bytes()).cloned().collect()) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::CalleeReverted); - - // Contract has enough balance but callee traps because "2" is passed. - let result = builder::bare_call(contract.addr) - .data(callee_hash.iter().chain(&2u32.to_le_bytes()).cloned().collect()) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); - - // Contract instantiation succeeds - let result = builder::bare_call(contract.addr) - .data(callee_hash.iter().chain(&0u32.to_le_bytes()).cloned().collect()) - .build_and_unwrap_result(); - assert_return_code!(result, 0); - - // Contract instantiation fails because the same salt is being used again. - let result = builder::bare_call(contract.addr) - .data(callee_hash.iter().chain(&0u32.to_le_bytes()).cloned().collect()) - .build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::DuplicateContractAddress); - }); -} - -#[test] -fn lazy_removal_works() { - let (code, _hash) = compile_module("self_destruct").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let contract = builder::bare_instantiate(Code::Upload(code)) - .native_value(min_balance * 100) - .build_and_unwrap_contract(); - - let info = get_contract(&contract.addr); - let trie = &info.child_trie_info(); - - // Put value into the contracts child trie - child::put(trie, &[99], &42); - - // Terminate the contract - assert_ok!(builder::call(contract.addr).build()); - - // Contract info should be gone - assert!(!>::contains_key(&contract.addr)); - - // But value should be still there as the lazy removal did not run, yet. - assert_matches!(child::get(trie, &[99]), Some(42)); - - // Run the lazy removal - Contracts::on_idle(System::block_number(), Weight::MAX); - - // Value should be gone now - assert_matches!(child::get::(trie, &[99]), None); - }); -} - -#[test] -fn lazy_batch_removal_works() { - let (code, _hash) = compile_module("self_destruct").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - let mut tries: Vec = vec![]; - - for i in 0..3u8 { - let contract = builder::bare_instantiate(Code::Upload(code.clone())) - .native_value(min_balance * 100) - .salt(Some([i; 32])) - .build_and_unwrap_contract(); - - let info = get_contract(&contract.addr); - let trie = &info.child_trie_info(); - - // Put value into the contracts child trie - child::put(trie, &[99], &42); - - // Terminate the contract. Contract info should be gone, but value should be still - // there as the lazy removal did not run, yet. - assert_ok!(builder::call(contract.addr).build()); - - assert!(!>::contains_key(&contract.addr)); - assert_matches!(child::get(trie, &[99]), Some(42)); - - tries.push(trie.clone()) - } - - // Run single lazy removal - Contracts::on_idle(System::block_number(), Weight::MAX); - - // The single lazy removal should have removed all queued tries - for trie in tries.iter() { - assert_matches!(child::get::(trie, &[99]), None); - } - }); -} - -#[test] -fn ref_time_left_api_works() { - let (code, _) = compile_module("ref_time_left").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create fixture: Constructor calls ref_time_left twice and asserts it to decrease - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - // Call the contract: It echoes back the ref_time returned by the ref_time_left API. - let received = builder::bare_call(addr).build_and_unwrap_result(); - assert_eq!(received.flags, ReturnFlags::empty()); - - let returned_value = u64::from_le_bytes(received.data[..8].try_into().unwrap()); - assert!(returned_value > 0); - assert!(returned_value < GAS_LIMIT.ref_time()); - }); -} - -#[test] -fn lazy_removal_partial_remove_works() { - let (code, _hash) = compile_module("self_destruct").unwrap(); - - // We create a contract with some extra keys above the weight limit - let extra_keys = 7u32; - let mut meter = WeightMeter::with_limit(Weight::from_parts(5_000_000_000, 100 * 1024)); - let (weight_per_key, max_keys) = ContractInfo::::deletion_budget(&meter); - let vals: Vec<_> = (0..max_keys + extra_keys) - .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode())) - .collect(); - - let mut ext = ExtBuilder::default().existential_deposit(50).build(); - - let trie = ext.execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) - .native_value(min_balance * 100) - .build_and_unwrap_contract(); - - let info = get_contract(&addr); - - // Put value into the contracts child trie - for val in &vals { - info.write(&Key::Fix(val.0), Some(val.2.clone()), None, false).unwrap(); - } - AccountInfo::::insert_contract(&addr, info.clone()); - - // Terminate the contract - assert_ok!(builder::call(addr).build()); - - // Contract info should be gone - assert!(!>::contains_key(&addr)); - - let trie = info.child_trie_info(); - - // But value should be still there as the lazy removal did not run, yet. - for val in &vals { - assert_eq!(child::get::(&trie, &blake2_256(&val.0)), Some(val.1)); - } - - trie.clone() - }); - - // The lazy removal limit only applies to the backend but not to the overlay. - // This commits all keys from the overlay to the backend. - ext.commit_all().unwrap(); - - ext.execute_with(|| { - // Run the lazy removal - ContractInfo::::process_deletion_queue_batch(&mut meter); - - // Weight should be exhausted because we could not even delete all keys - assert!(!meter.can_consume(weight_per_key)); - - let mut num_deleted = 0u32; - let mut num_remaining = 0u32; - - for val in &vals { - match child::get::(&trie, &blake2_256(&val.0)) { - None => num_deleted += 1, - Some(x) if x == val.1 => num_remaining += 1, - Some(_) => panic!("Unexpected value in contract storage"), - } - } - - // All but one key is removed - assert_eq!(num_deleted + num_remaining, vals.len() as u32); - assert_eq!(num_deleted, max_keys); - assert_eq!(num_remaining, extra_keys); - }); -} - -#[test] -fn lazy_removal_does_no_run_on_low_remaining_weight() { - let (code, _hash) = compile_module("self_destruct").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) - .native_value(min_balance * 100) - .build_and_unwrap_contract(); - - let info = get_contract(&addr); - let trie = &info.child_trie_info(); - - // Put value into the contracts child trie - child::put(trie, &[99], &42); - - // Terminate the contract - assert_ok!(builder::call(addr).build()); - - // Contract info should be gone - assert!(!>::contains_key(&addr)); - - // But value should be still there as the lazy removal did not run, yet. - assert_matches!(child::get(trie, &[99]), Some(42)); - - // Assign a remaining weight which is too low for a successful deletion of the contract - let low_remaining_weight = - <::WeightInfo as WeightInfo>::on_process_deletion_queue_batch(); - - // Run the lazy removal - Contracts::on_idle(System::block_number(), low_remaining_weight); - - // Value should still be there, since remaining weight was too low for removal - assert_matches!(child::get::(trie, &[99]), Some(42)); - - // Run the lazy removal while deletion_queue is not full - Contracts::on_initialize(System::block_number()); - - // Value should still be there, since deletion_queue was not full - assert_matches!(child::get::(trie, &[99]), Some(42)); - - // Run on_idle with max remaining weight, this should remove the value - Contracts::on_idle(System::block_number(), Weight::MAX); - - // Value should be gone - assert_matches!(child::get::(trie, &[99]), None); - }); -} - -#[test] -fn lazy_removal_does_not_use_all_weight() { - let (code, _hash) = compile_module("self_destruct").unwrap(); - - let mut meter = WeightMeter::with_limit(Weight::from_parts(5_000_000_000, 100 * 1024)); - let mut ext = ExtBuilder::default().existential_deposit(50).build(); - - let (trie, vals, weight_per_key) = ext.execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) - .native_value(min_balance * 100) - .build_and_unwrap_contract(); - - let info = get_contract(&addr); - let (weight_per_key, max_keys) = ContractInfo::::deletion_budget(&meter); - assert!(max_keys > 0); - - // We create a contract with one less storage item than we can remove within the limit - let vals: Vec<_> = (0..max_keys - 1) - .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode())) - .collect(); - - // Put value into the contracts child trie - for val in &vals { - info.write(&Key::Fix(val.0), Some(val.2.clone()), None, false).unwrap(); - } - AccountInfo::::insert_contract(&addr, info.clone()); - - // Terminate the contract - assert_ok!(builder::call(addr).build()); - - // Contract info should be gone - assert!(!>::contains_key(&addr)); - - let trie = info.child_trie_info(); - - // But value should be still there as the lazy removal did not run, yet. - for val in &vals { - assert_eq!(child::get::(&trie, &blake2_256(&val.0)), Some(val.1)); - } - - (trie, vals, weight_per_key) - }); - - // The lazy removal limit only applies to the backend but not to the overlay. - // This commits all keys from the overlay to the backend. - ext.commit_all().unwrap(); - - ext.execute_with(|| { - // Run the lazy removal - ContractInfo::::process_deletion_queue_batch(&mut meter); - let base_weight = - <::WeightInfo as WeightInfo>::on_process_deletion_queue_batch(); - assert_eq!(meter.consumed(), weight_per_key.mul(vals.len() as _) + base_weight); - - // All the keys are removed - for val in vals { - assert_eq!(child::get::(&trie, &blake2_256(&val.0)), None); - } - }); -} - -#[test] -fn deletion_queue_ring_buffer_overflow() { - let (code, _hash) = compile_module("self_destruct").unwrap(); - let mut ext = ExtBuilder::default().existential_deposit(50).build(); - - // setup the deletion queue with custom counters - ext.execute_with(|| { - let queue = DeletionQueueManager::from_test_values(u32::MAX - 1, u32::MAX - 1); - >::set(queue); - }); - - // commit the changes to the storage - ext.commit_all().unwrap(); - - ext.execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - let mut tries: Vec = vec![]; - - // add 3 contracts to the deletion queue - for i in 0..3u8 { - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code.clone())) - .native_value(min_balance * 100) - .salt(Some([i; 32])) - .build_and_unwrap_contract(); - - let info = get_contract(&addr); - let trie = &info.child_trie_info(); - - // Put value into the contracts child trie - child::put(trie, &[99], &42); - - // Terminate the contract. Contract info should be gone, but value should be still - // there as the lazy removal did not run, yet. - assert_ok!(builder::call(addr).build()); - - assert!(!>::contains_key(&addr)); - assert_matches!(child::get(trie, &[99]), Some(42)); - - tries.push(trie.clone()) - } - - // Run single lazy removal - Contracts::on_idle(System::block_number(), Weight::MAX); - - // The single lazy removal should have removed all queued tries - for trie in tries.iter() { - assert_matches!(child::get::(trie, &[99]), None); - } - - // insert and delete counter values should go from u32::MAX - 1 to 1 - assert_eq!(>::get().as_test_tuple(), (1, 1)); - }) -} -#[test] -fn refcounter() { - let (binary, code_hash) = compile_module("self_destruct").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let min_balance = Contracts::min_balance(); - - // Create two contracts with the same code and check that they do in fact share it. - let Contract { addr: addr0, .. } = builder::bare_instantiate(Code::Upload(binary.clone())) - .native_value(min_balance * 100) - .salt(Some([0; 32])) - .build_and_unwrap_contract(); - let Contract { addr: addr1, .. } = builder::bare_instantiate(Code::Upload(binary.clone())) - .native_value(min_balance * 100) - .salt(Some([1; 32])) - .build_and_unwrap_contract(); - assert_refcount!(code_hash, 2); - - // Sharing should also work with the usual instantiate call - let Contract { addr: addr2, .. } = builder::bare_instantiate(Code::Existing(code_hash)) - .native_value(min_balance * 100) - .salt(Some([2; 32])) - .build_and_unwrap_contract(); - assert_refcount!(code_hash, 3); - - // Terminating one contract should decrement the refcount - assert_ok!(builder::call(addr0).build()); - assert_refcount!(code_hash, 2); - - // remove another one - assert_ok!(builder::call(addr1).build()); - assert_refcount!(code_hash, 1); - - // Pristine code should still be there - PristineCode::::get(code_hash).unwrap(); - - // remove the last contract - assert_ok!(builder::call(addr2).build()); - assert_refcount!(code_hash, 0); - - // refcount is `0` but code should still exists because it needs to be removed manually - assert!(crate::PristineCode::::contains_key(&code_hash)); - }); -} - -#[test] -fn gas_estimation_for_subcalls() { - let (caller_code, _caller_hash) = compile_module("call_with_limit").unwrap(); - let (dummy_code, _callee_hash) = compile_module("dummy").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 2_000 * min_balance); - - let Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(caller_code)) - .native_value(min_balance * 100) - .build_and_unwrap_contract(); - - let Contract { addr: addr_dummy, .. } = builder::bare_instantiate(Code::Upload(dummy_code)) - .native_value(min_balance * 100) - .build_and_unwrap_contract(); - - // Run the test for all of those weight limits for the subcall - let weights = [ - Weight::MAX, - GAS_LIMIT, - GAS_LIMIT * 2, - GAS_LIMIT / 5, - Weight::from_parts(u64::MAX, GAS_LIMIT.proof_size()), - Weight::from_parts(GAS_LIMIT.ref_time(), u64::MAX), - ]; - - let (sub_addr, sub_input) = (addr_dummy.as_ref(), vec![]); - - for weight in weights { - let input: Vec = sub_addr - .iter() - .cloned() - .chain(weight.ref_time().to_le_bytes()) - .chain(weight.proof_size().to_le_bytes()) - .chain(sub_input.clone()) - .collect(); - - // Call in order to determine the gas that is required for this call - let result_orig = builder::bare_call(addr_caller).data(input.clone()).build(); - assert_ok!(&result_orig.result); - assert_eq!(result_orig.gas_required, result_orig.gas_consumed); - - // Make the same call using the estimated gas. Should succeed. - let result = builder::bare_call(addr_caller) - .gas_limit(result_orig.gas_required) - .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) - .data(input.clone()) - .build(); - assert_ok!(&result.result); - - // Check that it fails with too little ref_time - let result = builder::bare_call(addr_caller) - .gas_limit(result_orig.gas_required.sub_ref_time(1)) - .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) - .data(input.clone()) - .build(); - assert_err!(result.result, >::OutOfGas); - - // Check that it fails with too little proof_size - let result = builder::bare_call(addr_caller) - .gas_limit(result_orig.gas_required.sub_proof_size(1)) - .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) - .data(input.clone()) - .build(); - assert_err!(result.result, >::OutOfGas); - } - }); -} - -#[test] -fn call_runtime_reentrancy_guarded() { - use crate::precompiles::Precompile; - use alloy_core::sol_types::SolInterface; - use precompiles::{INoInfo, NoInfo}; - - let precompile_addr = H160(NoInfo::::MATCHER.base_address()); - - let (callee_code, _callee_hash) = compile_module("dummy").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); - - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(callee_code)) - .native_value(min_balance * 100) - .salt(Some([1; 32])) - .build_and_unwrap_contract(); - - // Call pallet_revive call() dispatchable - let call = RuntimeCall::Contracts(crate::Call::call { - dest: addr_callee, - value: 0, - gas_limit: GAS_LIMIT / 3, - storage_deposit_limit: deposit_limit::(), - data: vec![], - }) - .encode(); - - // Call runtime to re-enter back to contracts engine by - // calling dummy contract - let result = builder::bare_call(precompile_addr) - .data( - INoInfo::INoInfoCalls::callRuntime(INoInfo::callRuntimeCall { call: call.into() }) - .abi_encode(), - ) - .build(); - // Call to runtime should fail because of the re-entrancy guard - assert_err!(result.result, >::ReenteredPallet); - }); -} - -#[test] -fn sr25519_verify() { - let (binary, _code_hash) = compile_module("sr25519_verify").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Instantiate the sr25519_verify contract. - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) - .native_value(100_000) - .build_and_unwrap_contract(); - - let call_with = |message: &[u8; 11]| { - // Alice's signature for "hello world" - #[rustfmt::skip] - let signature: [u8; 64] = [ - 184, 49, 74, 238, 78, 165, 102, 252, 22, 92, 156, 176, 124, 118, 168, 116, 247, - 99, 0, 94, 2, 45, 9, 170, 73, 222, 182, 74, 60, 32, 75, 64, 98, 174, 69, 55, 83, - 85, 180, 98, 208, 75, 231, 57, 205, 62, 4, 105, 26, 136, 172, 17, 123, 99, 90, 255, - 228, 54, 115, 63, 30, 207, 205, 131, - ]; - - // Alice's public key - #[rustfmt::skip] - let public_key: [u8; 32] = [ - 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, - 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, - ]; - - let mut params = vec![]; - params.extend_from_slice(&signature); - params.extend_from_slice(&public_key); - params.extend_from_slice(message); - - builder::bare_call(addr).data(params).build_and_unwrap_result() - }; - - // verification should succeed for "hello world" - assert_return_code!(call_with(&b"hello world"), RuntimeReturnCode::Success); - - // verification should fail for other messages - assert_return_code!(call_with(&b"hello worlD"), RuntimeReturnCode::Sr25519VerifyFailed); - }); -} - -#[test] -fn upload_code_works() { - let (binary, code_hash) = compile_module("dummy").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Drop previous events - initialize_block(2); - - assert!(!PristineCode::::contains_key(&code_hash)); - assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), binary, 1_000,)); - // Ensure the contract was stored and get expected deposit amount to be reserved. - expected_deposit(ensure_stored(code_hash)); - }); -} - -#[test] -fn upload_code_limit_too_low() { - let (binary, _code_hash) = compile_module("dummy").unwrap(); - let deposit_expected = expected_deposit(binary.len()); - let deposit_insufficient = deposit_expected.saturating_sub(1); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Drop previous events - initialize_block(2); - - assert_noop!( - Contracts::upload_code(RuntimeOrigin::signed(ALICE), binary, deposit_insufficient,), - >::StorageDepositLimitExhausted, - ); - - assert_eq!(System::events(), vec![]); - }); -} - -#[test] -fn upload_code_not_enough_balance() { - let (binary, _code_hash) = compile_module("dummy").unwrap(); - let deposit_expected = expected_deposit(binary.len()); - let deposit_insufficient = deposit_expected.saturating_sub(1); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, deposit_insufficient); - - // Drop previous events - initialize_block(2); - - assert_noop!( - Contracts::upload_code(RuntimeOrigin::signed(ALICE), binary, 1_000,), - >::StorageDepositNotEnoughFunds, - ); - - assert_eq!(System::events(), vec![]); - }); -} - -#[test] -fn remove_code_works() { - let (binary, code_hash) = compile_module("dummy").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Drop previous events - initialize_block(2); - - assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), binary, 1_000,)); - // Ensure the contract was stored and get expected deposit amount to be reserved. - expected_deposit(ensure_stored(code_hash)); - assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash)); - }); -} - -#[test] -fn remove_code_wrong_origin() { - let (binary, code_hash) = compile_module("dummy").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Drop previous events - initialize_block(2); - - assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), binary, 1_000,)); - // Ensure the contract was stored and get expected deposit amount to be reserved. - expected_deposit(ensure_stored(code_hash)); - - assert_noop!( - Contracts::remove_code(RuntimeOrigin::signed(BOB), code_hash), - sp_runtime::traits::BadOrigin, - ); - }); -} - -#[test] -fn remove_code_in_use() { - let (binary, code_hash) = compile_module("dummy").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - assert_ok!(builder::instantiate_with_code(binary).build()); - - // Drop previous events - initialize_block(2); - - assert_noop!( - Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), - >::CodeInUse, - ); - - assert_eq!(System::events(), vec![]); - }); -} - -#[test] -fn remove_code_not_found() { - let (_binary, code_hash) = compile_module("dummy").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Drop previous events - initialize_block(2); - - assert_noop!( - Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), - >::CodeNotFound, - ); - - assert_eq!(System::events(), vec![]); - }); -} - -#[test] -fn instantiate_with_zero_balance_works() { - let (binary, code_hash) = compile_module("dummy").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let min_balance = Contracts::min_balance(); - - // Drop previous events - initialize_block(2); - - // Instantiate the BOB contract. - let Contract { addr, account_id } = - builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); - - // Ensure the contract was stored and get expected deposit amount to be reserved. - expected_deposit(ensure_stored(code_hash)); - - // Make sure the account exists even though no free balance was send - assert_eq!(::Currency::free_balance(&account_id), min_balance); - assert_eq!( - ::Currency::total_balance(&account_id), - min_balance + test_utils::contract_base_deposit(&addr) - ); - - assert_eq!( - System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Held { - reason: ::RuntimeHoldReason::Contracts( - HoldReason::CodeUploadDepositReserve, - ), - who: ALICE, - amount: 776, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::System(frame_system::Event::NewAccount { - account: account_id.clone(), - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { - account: account_id.clone(), - free_balance: min_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: account_id.clone(), - amount: min_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::TransferAndHold { - reason: ::RuntimeHoldReason::Contracts( - HoldReason::StorageDepositReserve, - ), - source: ALICE, - dest: account_id, - transferred: 336, - }), - topics: vec![], - }, - ] - ); - }); -} - -#[test] -fn instantiate_with_below_existential_deposit_works() { - let (binary, code_hash) = compile_module("dummy").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let min_balance = Contracts::min_balance(); - let value = 50; - - // Drop previous events - initialize_block(2); - - // Instantiate the BOB contract. - let Contract { addr, account_id } = builder::bare_instantiate(Code::Upload(binary)) - .native_value(value) - .build_and_unwrap_contract(); - - // Ensure the contract was stored and get expected deposit amount to be reserved. - expected_deposit(ensure_stored(code_hash)); - // Make sure the account exists even though not enough free balance was send - assert_eq!(::Currency::free_balance(&account_id), min_balance + value); - assert_eq!( - ::Currency::total_balance(&account_id), - min_balance + value + test_utils::contract_base_deposit(&addr) - ); - - assert_eq!( - System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Held { - reason: ::RuntimeHoldReason::Contracts( - HoldReason::CodeUploadDepositReserve, - ), - who: ALICE, - amount: 776, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::System(frame_system::Event::NewAccount { - account: account_id.clone() - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { - account: account_id.clone(), - free_balance: min_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: account_id.clone(), - amount: min_balance, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: account_id.clone(), - amount: 50, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::TransferAndHold { - reason: ::RuntimeHoldReason::Contracts( - HoldReason::StorageDepositReserve, - ), - source: ALICE, - dest: account_id.clone(), - transferred: 336, - }), - topics: vec![], - }, - ] - ); - }); -} - -#[test] -fn storage_deposit_works() { - let (binary, _code_hash) = compile_module("multi_store").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - let Contract { addr, account_id } = - builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); - - let mut deposit = test_utils::contract_base_deposit(&addr); - - // Drop previous events - initialize_block(2); - - // Create storage - assert_ok!(builder::call(addr).value(42).data((50u32, 20u32).encode()).build()); - // 4 is for creating 2 storage items - // 48 is for each of the keys - let charged0 = 4 + 50 + 20 + 48 + 48; - deposit += charged0; - assert_eq!(get_contract(&addr).total_deposit(), deposit); - - // Add more storage (but also remove some) - assert_ok!(builder::call(addr).data((100u32, 10u32).encode()).build()); - let charged1 = 50 - 10; - deposit += charged1; - assert_eq!(get_contract(&addr).total_deposit(), deposit); - - // Remove more storage (but also add some) - assert_ok!(builder::call(addr).data((10u32, 20u32).encode()).build()); - // -1 for numeric instability - let refunded0 = 90 - 10 - 1; - deposit -= refunded0; - assert_eq!(get_contract(&addr).total_deposit(), deposit); - - assert_eq!( - System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: account_id.clone(), - amount: 42, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::TransferAndHold { - reason: ::RuntimeHoldReason::Contracts( - HoldReason::StorageDepositReserve, - ), - source: ALICE, - dest: account_id.clone(), - transferred: charged0, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::TransferAndHold { - reason: ::RuntimeHoldReason::Contracts( - HoldReason::StorageDepositReserve, - ), - source: ALICE, - dest: account_id.clone(), - transferred: charged1, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::TransferOnHold { - reason: ::RuntimeHoldReason::Contracts( - HoldReason::StorageDepositReserve, - ), - source: account_id.clone(), - dest: ALICE, - amount: refunded0, - }), - topics: vec![], - }, - ] - ); - }); -} - -#[test] -fn storage_deposit_callee_works() { - let (binary_caller, _code_hash_caller) = compile_module("call").unwrap(); - let (binary_callee, _code_hash_callee) = compile_module("store_call").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create both contracts: Constructors do nothing. - let Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(binary_caller)).build_and_unwrap_contract(); - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); - - assert_ok!(builder::call(addr_caller).data((100u32, &addr_callee).encode()).build()); - - let callee = get_contract(&addr_callee); - let deposit = DepositPerByte::get() * 100 + DepositPerItem::get() * 1 + 48; - - assert_eq!(Pallet::::evm_balance(&addr_caller), U256::zero()); - assert_eq!( - callee.total_deposit(), - deposit + test_utils::contract_base_deposit(&addr_callee) - ); - }); -} - -#[test] -fn set_code_extrinsic() { - let (binary, code_hash) = compile_module("dummy").unwrap(); - let (new_binary, new_code_hash) = compile_module("crypto_hash_keccak_256").unwrap(); - - assert_ne!(code_hash, new_code_hash); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); - - assert_ok!(Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - new_binary, - deposit_limit::(), - )); - - // Drop previous events - initialize_block(2); - - assert_eq!(get_contract(&addr).code_hash, code_hash); - assert_refcount!(&code_hash, 1); - assert_refcount!(&new_code_hash, 0); - - // only root can execute this extrinsic - assert_noop!( - Contracts::set_code(RuntimeOrigin::signed(ALICE), addr, new_code_hash), - sp_runtime::traits::BadOrigin, - ); - assert_eq!(get_contract(&addr).code_hash, code_hash); - assert_refcount!(&code_hash, 1); - assert_refcount!(&new_code_hash, 0); - assert_eq!(System::events(), vec![]); - - // contract must exist - assert_noop!( - Contracts::set_code(RuntimeOrigin::root(), BOB_ADDR, new_code_hash), - >::ContractNotFound, - ); - assert_eq!(get_contract(&addr).code_hash, code_hash); - assert_refcount!(&code_hash, 1); - assert_refcount!(&new_code_hash, 0); - assert_eq!(System::events(), vec![]); - - // new code hash must exist - assert_noop!( - Contracts::set_code(RuntimeOrigin::root(), addr, Default::default()), - >::CodeNotFound, - ); - assert_eq!(get_contract(&addr).code_hash, code_hash); - assert_refcount!(&code_hash, 1); - assert_refcount!(&new_code_hash, 0); - assert_eq!(System::events(), vec![]); - - // successful call - assert_ok!(Contracts::set_code(RuntimeOrigin::root(), addr, new_code_hash)); - assert_eq!(get_contract(&addr).code_hash, new_code_hash); - assert_refcount!(&code_hash, 0); - assert_refcount!(&new_code_hash, 1); - }); -} - -#[test] -fn slash_cannot_kill_account() { - let (binary, _code_hash) = compile_module("dummy").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let value = 700; - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let min_balance = Contracts::min_balance(); - - let Contract { addr, account_id } = builder::bare_instantiate(Code::Upload(binary)) - .native_value(value) - .build_and_unwrap_contract(); - - // Drop previous events - initialize_block(2); - - let info_deposit = test_utils::contract_base_deposit(&addr); - - assert_eq!( - test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account_id), - info_deposit - ); - - assert_eq!( - ::Currency::total_balance(&account_id), - info_deposit + value + min_balance - ); - - // Try to destroy the account of the contract by slashing the total balance. - // The account does not get destroyed because slashing only affects the balance held - // under certain `reason`. Slashing can for example happen if the contract takes part - // in staking. - let _ = ::Currency::slash( - &HoldReason::StorageDepositReserve.into(), - &account_id, - ::Currency::total_balance(&account_id), - ); - - // Slashing only removed the balance held. - assert_eq!(::Currency::total_balance(&account_id), value + min_balance); - }); -} - -#[test] -fn contract_reverted() { - let (binary, code_hash) = compile_module("return_with_data").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let flags = ReturnFlags::REVERT; - let buffer = [4u8, 8, 15, 16, 23, 42]; - let input = (flags.bits(), buffer).encode(); - - // We just upload the code for later use - assert_ok!(Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - binary.clone(), - deposit_limit::(), - )); - - // Calling extrinsic: revert leads to an error - assert_err_ignore_postinfo!( - builder::instantiate(code_hash).data(input.clone()).build(), - >::ContractReverted, - ); - - // Calling extrinsic: revert leads to an error - assert_err_ignore_postinfo!( - builder::instantiate_with_code(binary).data(input.clone()).build(), - >::ContractReverted, - ); - - // Calling directly: revert leads to success but the flags indicate the error - // This is just a different way of transporting the error that allows the read out - // the `data` which is only there on success. Obviously, the contract isn't - // instantiated. - let result = builder::bare_instantiate(Code::Existing(code_hash)) - .data(input.clone()) - .build_and_unwrap_result(); - assert_eq!(result.result.flags, flags); - assert_eq!(result.result.data, buffer); - assert!(!>::contains_key(result.addr)); - - // Pass empty flags and therefore successfully instantiate the contract for later use. - let Contract { addr, .. } = builder::bare_instantiate(Code::Existing(code_hash)) - .data(ReturnFlags::empty().bits().encode()) - .build_and_unwrap_contract(); - - // Calling extrinsic: revert leads to an error - assert_err_ignore_postinfo!( - builder::call(addr).data(input.clone()).build(), - >::ContractReverted, - ); - - // Calling directly: revert leads to success but the flags indicate the error - let result = builder::bare_call(addr).data(input).build_and_unwrap_result(); - assert_eq!(result.flags, flags); - assert_eq!(result.data, buffer); - }); -} - -#[test] -fn set_code_hash() { - let (binary, _) = compile_module("set_code_hash").unwrap(); - let (new_binary, new_code_hash) = compile_module("new_set_code_hash_contract").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Instantiate the 'caller' - let Contract { addr: contract_addr, .. } = builder::bare_instantiate(Code::Upload(binary)) - .native_value(300_000) - .build_and_unwrap_contract(); - // upload new code - assert_ok!(Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - new_binary.clone(), - deposit_limit::(), - )); - - System::reset_events(); - - // First call sets new code_hash and returns 1 - let result = builder::bare_call(contract_addr) - .data(new_code_hash.as_ref().to_vec()) - .build_and_unwrap_result(); - assert_return_code!(result, 1); - - // Second calls new contract code that returns 2 - let result = builder::bare_call(contract_addr).build_and_unwrap_result(); - assert_return_code!(result, 2); - }); -} - -#[test] -fn storage_deposit_limit_is_enforced() { - let (binary, _code_hash) = compile_module("store_call").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let min_balance = Contracts::min_balance(); - - // Setting insufficient storage_deposit should fail. - assert_err!( - builder::bare_instantiate(Code::Upload(binary.clone())) - // expected deposit is 2 * ed + 3 for the call - .storage_deposit_limit((2 * min_balance + 3 - 1).into()) - .build() - .result, - >::StorageDepositLimitExhausted, - ); - - // Instantiate the BOB contract. - let Contract { addr, account_id } = - builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); - - let info_deposit = test_utils::contract_base_deposit(&addr); - // Check that the BOB contract has been instantiated and has the minimum balance - assert_eq!(get_contract(&addr).total_deposit(), info_deposit); - assert_eq!( - ::Currency::total_balance(&account_id), - info_deposit + min_balance - ); - - // Create 1 byte of storage with a price of per byte, - // setting insufficient deposit limit, as it requires 3 Balance: - // 2 for the item added + 1 (value) + 48 (key) - assert_err_ignore_postinfo!( - builder::call(addr) - .storage_deposit_limit(50) - .data(1u32.to_le_bytes().to_vec()) - .build(), - >::StorageDepositLimitExhausted, - ); - - // now with enough limit - assert_ok!(builder::call(addr) - .storage_deposit_limit(51) - .data(1u32.to_le_bytes().to_vec()) - .build()); - - // Use 4 more bytes of the storage for the same item, which requires 4 Balance. - // Should fail as DefaultDepositLimit is 3 and hence isn't enough. - assert_err_ignore_postinfo!( - builder::call(addr) - .storage_deposit_limit(3) - .data(5u32.to_le_bytes().to_vec()) - .build(), - >::StorageDepositLimitExhausted, - ); - }); -} - -#[test] -fn deposit_limit_in_nested_calls() { - let (binary_caller, _code_hash_caller) = compile_module("create_storage_and_call").unwrap(); - let (binary_callee, _code_hash_callee) = compile_module("store_call").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create both contracts: Constructors do nothing. - let Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(binary_caller)).build_and_unwrap_contract(); - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); - - // Create 100 bytes of storage with a price of per byte - // This is 100 Balance + 2 Balance for the item - // 48 for the key - assert_ok!(builder::call(addr_callee) - .storage_deposit_limit(102 + 48) - .data(100u32.to_le_bytes().to_vec()) - .build()); - - // We do not remove any storage but add a storage item of 12 bytes in the caller - // contract. This would cost 12 + 2 + 72 = 86 Balance. - // The nested call doesn't get a special limit, which is set by passing `u64::MAX` to it. - // This should fail as the specified parent's limit is less than the cost: 13 < - // 14. - assert_err_ignore_postinfo!( - builder::call(addr_caller) - .storage_deposit_limit(85) - .data((100u32, &addr_callee, U256::MAX).encode()) - .build(), - >::StorageDepositLimitExhausted, - ); - - // Now we specify the parent's limit high enough to cover the caller's storage - // additions. However, we use a single byte more in the callee, hence the storage - // deposit should be 87 Balance. - // The nested call doesn't get a special limit, which is set by passing `u64::MAX` to it. - // This should fail as the specified parent's limit is less than the cost: 86 < 87 - assert_err_ignore_postinfo!( - builder::call(addr_caller) - .storage_deposit_limit(86) - .data((101u32, &addr_callee, &U256::MAX).encode()) - .build(), - >::StorageDepositLimitExhausted, - ); - - // The parents storage deposit limit doesn't matter as the sub calls limit - // is enforced eagerly. However, we set a special deposit limit of 1 Balance for the - // nested call. This should fail as callee adds up 2 bytes to the storage, meaning - // that the nested call should have a deposit limit of at least 2 Balance. The - // sub-call should be rolled back, which is covered by the next test case. - let ret = builder::bare_call(addr_caller) - .storage_deposit_limit(DepositLimit::Balance(u64::MAX)) - .data((102u32, &addr_callee, U256::from(1u64)).encode()) - .build_and_unwrap_result(); - assert_return_code!(ret, RuntimeReturnCode::OutOfResources); - - // Refund in the callee contract but not enough to cover the Balance required by the - // caller. Note that if previous sub-call wouldn't roll back, this call would pass - // making the test case fail. We don't set a special limit for the nested call here. - assert_err_ignore_postinfo!( - builder::call(addr_caller) - .storage_deposit_limit(0) - .data((87u32, &addr_callee, &U256::MAX.to_little_endian()).encode()) - .build(), - >::StorageDepositLimitExhausted, - ); - - let _ = ::Currency::set_balance(&ALICE, 511); - - // Require more than the sender's balance. - // Limit the sub call to little balance so it should fail in there - let ret = builder::bare_call(addr_caller) - .data((416, &addr_callee, U256::from(1u64)).encode()) - .build_and_unwrap_result(); - assert_return_code!(ret, RuntimeReturnCode::OutOfResources); - - // Free up enough storage in the callee so that the caller can create a new item - // We set the special deposit limit of 1 Balance for the nested call, which isn't - // enforced as callee frees up storage. This should pass. - assert_ok!(builder::call(addr_caller) - .storage_deposit_limit(1) - .data((0u32, &addr_callee, U256::from(1u64)).encode()) - .build()); - }); -} - -#[test] -fn deposit_limit_in_nested_instantiate() { - let (binary_caller, _code_hash_caller) = - compile_module("create_storage_and_instantiate").unwrap(); - let (binary_callee, code_hash_callee) = compile_module("store_deploy").unwrap(); - const ED: u64 = 5; - ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let _ = ::Currency::set_balance(&BOB, 1_000_000); - // Create caller contract - let Contract { addr: addr_caller, account_id: caller_id } = - builder::bare_instantiate(Code::Upload(binary_caller)) - .native_value(10_000) // this balance is later passed to the deployed contract - .build_and_unwrap_contract(); - // Deploy a contract to get its occupied storage size - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary_callee)) - .data(vec![0, 0, 0, 0]) - .build_and_unwrap_contract(); - - // This is the deposit we expect to be charged just for instantiatiting the callee. - // - // - callee_info_len + 2 for storing the new contract info - // - the deposit for depending on a code hash - // - ED for deployed contract account - // - 2 for the storage item of 0 bytes being created in the callee constructor - // - 48 for the key - let callee_min_deposit = { - let callee_info_len = - AccountInfo::::load_contract(&addr).unwrap().encoded_size() as u64; - let code_deposit = test_utils::lockup_deposit(&code_hash_callee); - callee_info_len + code_deposit + 2 + ED + 2 + 48 - }; - - // The parent just stores an item of the passed size so at least - // we need to pay for the item itself. - let caller_min_deposit = callee_min_deposit + 2 + 48; - - // Fail in callee. - // - // We still fail in the sub call because we enforce limits on return from a contract. - // Sub calls return first to they are checked first. - let ret = builder::bare_call(addr_caller) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(DepositLimit::Balance(0)) - .data((&code_hash_callee, 100u32, &U256::MAX.to_little_endian()).encode()) - .build_and_unwrap_result(); - assert_return_code!(ret, RuntimeReturnCode::OutOfResources); - // The charges made on instantiation should be rolled back. - assert_eq!(::Currency::free_balance(&BOB), 1_000_000); - - // Fail in the caller. - // - // For that we need to supply enough storage deposit so that the sub call - // succeeds but the parent call runs out of storage. - let ret = builder::bare_call(addr_caller) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(DepositLimit::Balance(callee_min_deposit)) - .data((&code_hash_callee, 0u32, &U256::MAX.to_little_endian()).encode()) - .build(); - assert_err!(ret.result, >::StorageDepositLimitExhausted); - // The charges made on the instantiation should be rolled back. - assert_eq!(::Currency::free_balance(&BOB), 1_000_000); - - // Fail in the callee with bytes. - // - // Same as above but stores one byte in both caller and callee. - let ret = builder::bare_call(addr_caller) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(DepositLimit::Balance(caller_min_deposit + 1)) - .data((&code_hash_callee, 1u32, U256::from(callee_min_deposit)).encode()) - .build_and_unwrap_result(); - assert_return_code!(ret, RuntimeReturnCode::OutOfResources); - // The charges made on the instantiation should be rolled back. - assert_eq!(::Currency::free_balance(&BOB), 1_000_000); - - // Fail in the caller with bytes. - // - // Same as above but stores one byte in both caller and callee. - let ret = builder::bare_call(addr_caller) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(DepositLimit::Balance(callee_min_deposit + 1)) - .data((&code_hash_callee, 1u32, U256::from(callee_min_deposit + 1)).encode()) - .build(); - assert_err!(ret.result, >::StorageDepositLimitExhausted); - // The charges made on the instantiation should be rolled back. - assert_eq!(::Currency::free_balance(&BOB), 1_000_000); - - // Set enough deposit limit for the child instantiate. This should succeed. - let result = builder::bare_call(addr_caller) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit((caller_min_deposit + 2).into()) - .data((&code_hash_callee, 1u32, U256::from(callee_min_deposit + 1)).encode()) - .build(); - - let returned = result.result.unwrap(); - assert!(!returned.did_revert()); - - // All balance of the caller except ED has been transferred to the callee. - // No deposit has been taken from it. - assert_eq!(::Currency::free_balance(&caller_id), ED); - // Get address of the deployed contract. - let addr_callee = H160::from_slice(&returned.data[0..20]); - let callee_account_id = ::AddressMapper::to_account_id(&addr_callee); - // 10_000 should be sent to callee from the caller contract, plus ED to be sent from the - // origin. - assert_eq!(::Currency::free_balance(&callee_account_id), 10_000 + ED); - // The origin should be charged with what the outer call consumed - assert_eq!( - ::Currency::free_balance(&BOB), - 1_000_000 - (caller_min_deposit + 2), - ); - assert_eq!(result.storage_deposit.charge_or_zero(), (caller_min_deposit + 2)) - }); -} - -#[test] -fn deposit_limit_honors_liquidity_restrictions() { - let (binary, _code_hash) = compile_module("store_call").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let bobs_balance = 1_000; - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let _ = ::Currency::set_balance(&BOB, bobs_balance); - let min_balance = Contracts::min_balance(); - - // Instantiate the BOB contract. - let Contract { addr, account_id } = - builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); - - let info_deposit = test_utils::contract_base_deposit(&addr); - // Check that the contract has been instantiated and has the minimum balance - assert_eq!(get_contract(&addr).total_deposit(), info_deposit); - assert_eq!( - ::Currency::total_balance(&account_id), - info_deposit + min_balance - ); - - // check that the hold is honored - ::Currency::hold( - &HoldReason::CodeUploadDepositReserve.into(), - &BOB, - bobs_balance - min_balance, - ) - .unwrap(); - assert_err_ignore_postinfo!( - builder::call(addr) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(10_000) - .data(100u32.to_le_bytes().to_vec()) - .build(), - >::StorageDepositNotEnoughFunds, - ); - assert_eq!(::Currency::free_balance(&BOB), min_balance); - }); -} - -#[test] -fn deposit_limit_honors_existential_deposit() { - let (binary, _code_hash) = compile_module("store_call").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let _ = ::Currency::set_balance(&BOB, 300); - let min_balance = Contracts::min_balance(); - - // Instantiate the BOB contract. - let Contract { addr, account_id } = - builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); - - let info_deposit = test_utils::contract_base_deposit(&addr); - - // Check that the contract has been instantiated and has the minimum balance - assert_eq!(get_contract(&addr).total_deposit(), info_deposit); - assert_eq!( - ::Currency::total_balance(&account_id), - min_balance + info_deposit - ); - - // check that the deposit can't bring the account below the existential deposit - assert_err_ignore_postinfo!( - builder::call(addr) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(10_000) - .data(100u32.to_le_bytes().to_vec()) - .build(), - >::StorageDepositNotEnoughFunds, - ); - assert_eq!(::Currency::free_balance(&BOB), 300); - }); -} - -#[test] -fn native_dependency_deposit_works() { - let (binary, code_hash) = compile_module("set_code_hash").unwrap(); - let (dummy_binary, dummy_code_hash) = compile_module("dummy").unwrap(); - - // Test with both existing and uploaded code - for code in [Code::Upload(binary.clone()), Code::Existing(code_hash)] { - ExtBuilder::default().build().execute_with(|| { - let _ = Balances::set_balance(&ALICE, 1_000_000); - let lockup_deposit_percent = CodeHashLockupDepositPercent::get(); - - // Upload the dummy contract, - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - dummy_binary.clone(), - deposit_limit::(), - ) - .unwrap(); - - // Upload `set_code_hash` contracts if using Code::Existing. - let add_upload_deposit = match code { - Code::Existing(_) => { - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - binary.clone(), - deposit_limit::(), - ) - .unwrap(); - false - }, - Code::Upload(_) => true, - }; - - // Instantiate the set_code_hash contract. - let res = builder::bare_instantiate(code).build(); - - let addr = res.result.unwrap().addr; - let account_id = ::AddressMapper::to_account_id(&addr); - let base_deposit = test_utils::contract_base_deposit(&addr); - let upload_deposit = test_utils::get_code_deposit(&code_hash); - let extra_deposit = add_upload_deposit.then(|| upload_deposit).unwrap_or_default(); - - assert_eq!( - res.storage_deposit.charge_or_zero(), - extra_deposit + base_deposit + Contracts::min_balance() - ); - - // call set_code_hash - builder::bare_call(addr) - .data(dummy_code_hash.encode()) - .build_and_unwrap_result(); - - // Check updated storage_deposit due to code size changes - let deposit_diff = lockup_deposit_percent - .mul_ceil(test_utils::get_code_deposit(&code_hash)) - - lockup_deposit_percent.mul_ceil(test_utils::get_code_deposit(&dummy_code_hash)); - let new_base_deposit = test_utils::contract_base_deposit(&addr); - assert_ne!(deposit_diff, 0); - assert_eq!(base_deposit - new_base_deposit, deposit_diff); - - assert_eq!( - test_utils::get_balance_on_hold( - &HoldReason::StorageDepositReserve.into(), - &account_id - ), - new_base_deposit - ); - }); - } -} - -#[test] -fn block_hash_works() { - let (code, _) = compile_module("block_hash").unwrap(); - - ExtBuilder::default().existential_deposit(1).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - // The genesis config sets to the block number to 1 - let block_hash = [1; 32]; - frame_system::BlockHash::::insert( - &crate::BlockNumberFor::::from(0u32), - ::Hash::from(&block_hash), - ); - assert_ok!(builder::call(addr) - .data((U256::zero(), H256::from(block_hash)).encode()) - .build()); - - // A block number out of range returns the zero value - assert_ok!(builder::call(addr).data((U256::from(1), H256::zero()).encode()).build()); - }); -} - -#[test] -fn block_author_works() { - let (code, _) = compile_module("block_author").unwrap(); - - ExtBuilder::default().existential_deposit(1).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - // The fixture asserts the input to match the find_author API method output. - assert_ok!(builder::call(addr).data(EVE_ADDR.encode()).build()); - }); -} - -#[test] -fn root_cannot_upload_code() { - let (binary, _) = compile_module("dummy").unwrap(); - - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - Contracts::upload_code(RuntimeOrigin::root(), binary, deposit_limit::()), - DispatchError::BadOrigin, - ); - }); -} - -#[test] -fn root_cannot_remove_code() { - let (_, code_hash) = compile_module("dummy").unwrap(); - - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - Contracts::remove_code(RuntimeOrigin::root(), code_hash), - DispatchError::BadOrigin, - ); - }); -} - -#[test] -fn signed_cannot_set_code() { - let (_, code_hash) = compile_module("dummy").unwrap(); - - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - Contracts::set_code(RuntimeOrigin::signed(ALICE), BOB_ADDR, code_hash), - DispatchError::BadOrigin, - ); - }); -} - -#[test] -fn none_cannot_call_code() { - ExtBuilder::default().build().execute_with(|| { - assert_err_ignore_postinfo!( - builder::call(BOB_ADDR).origin(RuntimeOrigin::none()).build(), - DispatchError::BadOrigin, - ); - }); -} - -#[test] -fn root_can_call() { - let (binary, _) = compile_module("dummy").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); - - // Call the contract. - assert_ok!(builder::call(addr).origin(RuntimeOrigin::root()).build()); - }); -} - -#[test] -fn root_cannot_instantiate_with_code() { - let (binary, _) = compile_module("dummy").unwrap(); - - ExtBuilder::default().build().execute_with(|| { - assert_err_ignore_postinfo!( - builder::instantiate_with_code(binary).origin(RuntimeOrigin::root()).build(), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn root_cannot_instantiate() { - let (_, code_hash) = compile_module("dummy").unwrap(); - - ExtBuilder::default().build().execute_with(|| { - assert_err_ignore_postinfo!( - builder::instantiate(code_hash).origin(RuntimeOrigin::root()).build(), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn only_upload_origin_can_upload() { - let (binary, _) = compile_module("dummy").unwrap(); - UploadAccount::set(Some(ALICE)); - ExtBuilder::default().build().execute_with(|| { - let _ = Balances::set_balance(&ALICE, 1_000_000); - let _ = Balances::set_balance(&BOB, 1_000_000); - - assert_err!( - Contracts::upload_code(RuntimeOrigin::root(), binary.clone(), deposit_limit::(),), - DispatchError::BadOrigin - ); - - assert_err!( - Contracts::upload_code( - RuntimeOrigin::signed(BOB), - binary.clone(), - deposit_limit::(), - ), - DispatchError::BadOrigin - ); - - // Only alice is allowed to upload contract code. - assert_ok!(Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - binary.clone(), - deposit_limit::(), - )); - }); -} - -#[test] -fn only_instantiation_origin_can_instantiate() { - let (code, code_hash) = compile_module("dummy").unwrap(); - InstantiateAccount::set(Some(ALICE)); - ExtBuilder::default().build().execute_with(|| { - let _ = Balances::set_balance(&ALICE, 1_000_000); - let _ = Balances::set_balance(&BOB, 1_000_000); - - assert_err_ignore_postinfo!( - builder::instantiate_with_code(code.clone()) - .origin(RuntimeOrigin::root()) - .build(), - DispatchError::BadOrigin - ); - - assert_err_ignore_postinfo!( - builder::instantiate_with_code(code.clone()) - .origin(RuntimeOrigin::signed(BOB)) - .build(), - DispatchError::BadOrigin - ); - - // Only Alice can instantiate - assert_ok!(builder::instantiate_with_code(code).build()); - - // Bob cannot instantiate with either `instantiate_with_code` or `instantiate`. - assert_err_ignore_postinfo!( - builder::instantiate(code_hash).origin(RuntimeOrigin::signed(BOB)).build(), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn balance_of_api() { - let (binary, _code_hash) = compile_module("balance_of").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = Balances::set_balance(&ALICE, 1_000_000); - let _ = Balances::set_balance(&ALICE_FALLBACK, 1_000_000); - - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(binary.to_vec())).build_and_unwrap_contract(); - - // The fixture asserts a non-zero returned free balance of the account; - // The ALICE_FALLBACK account is endowed; - // Hence we should not revert - assert_ok!(builder::call(addr).data(ALICE_ADDR.0.to_vec()).build()); - - // The fixture asserts a non-zero returned free balance of the account; - // The ETH_BOB account is not endowed; - // Hence we should revert - assert_err_ignore_postinfo!( - builder::call(addr).data(BOB_ADDR.0.to_vec()).build(), - >::ContractTrapped - ); - }); -} - -#[test] -fn balance_api_returns_free_balance() { - let (binary, _code_hash) = compile_module("balance").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Instantiate the BOB contract without any extra balance. - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(binary.to_vec())).build_and_unwrap_contract(); - - let value = 0; - // Call BOB which makes it call the balance runtime API. - // The contract code asserts that the returned balance is 0. - assert_ok!(builder::call(addr).value(value).build()); - - let value = 1; - // Calling with value will trap the contract. - assert_err_ignore_postinfo!( - builder::call(addr).value(value).build(), - >::ContractTrapped - ); - }); -} - -#[test] -fn call_depth_is_enforced() { - let (binary, _code_hash) = compile_module("recurse").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - let extra_recursions = 1024; - - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(binary.to_vec())).build_and_unwrap_contract(); - - // takes the number of recursions - // returns the number of left over recursions - assert_eq!( - u32::from_le_bytes( - builder::bare_call(addr) - .data((limits::CALL_STACK_DEPTH + extra_recursions).encode()) - .build_and_unwrap_result() - .data - .try_into() - .unwrap() - ), - // + 1 because when the call depth is reached the caller contract is trapped without - // the ability to return any data. hence the last call frame is untracked. - extra_recursions + 1, - ); - }); -} - -#[test] -fn gas_consumed_is_linear_for_nested_calls() { - let (code, _code_hash) = compile_module("recurse").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - let [gas_0, gas_1, gas_2, gas_max] = { - [0u32, 1u32, 2u32, limits::CALL_STACK_DEPTH] - .iter() - .map(|i| { - let result = builder::bare_call(addr).data(i.encode()).build(); - assert_eq!( - u32::from_le_bytes(result.result.unwrap().data.try_into().unwrap()), - 0 - ); - result.gas_consumed - }) - .collect::>() - .try_into() - .unwrap() - }; - - let gas_per_recursion = gas_2.checked_sub(&gas_1).unwrap(); - assert_eq!(gas_max, gas_0 + gas_per_recursion * limits::CALL_STACK_DEPTH as u64); - }); -} - -#[test] -fn read_only_call_cannot_store() { - let (binary_caller, _code_hash_caller) = compile_module("read_only_call").unwrap(); - let (binary_callee, _code_hash_callee) = compile_module("store_call").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create both contracts: Constructors do nothing. - let Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(binary_caller)).build_and_unwrap_contract(); - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); - - // Read-only call fails when modifying storage. - assert_err_ignore_postinfo!( - builder::call(addr_caller).data((&addr_callee, 100u32).encode()).build(), - >::ContractTrapped - ); - }); -} - -#[test] -fn read_only_call_cannot_transfer() { - let (binary_caller, _code_hash_caller) = compile_module("call_with_flags_and_value").unwrap(); - let (binary_callee, _code_hash_callee) = compile_module("dummy").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create both contracts: Constructors do nothing. - let Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(binary_caller)).build_and_unwrap_contract(); - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); - - // Read-only call fails when a non-zero value is set. - assert_err_ignore_postinfo!( - builder::call(addr_caller) - .data( - (addr_callee, pallet_revive_uapi::CallFlags::READ_ONLY.bits(), 100u64).encode() - ) - .build(), - >::StateChangeDenied - ); - }); -} - -#[test] -fn read_only_subsequent_call_cannot_store() { - let (binary_read_only_caller, _code_hash_caller) = compile_module("read_only_call").unwrap(); - let (binary_caller, _code_hash_caller) = compile_module("call_with_flags_and_value").unwrap(); - let (binary_callee, _code_hash_callee) = compile_module("store_call").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create contracts: Constructors do nothing. - let Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(binary_read_only_caller)) - .build_and_unwrap_contract(); - let Contract { addr: addr_subsequent_caller, .. } = - builder::bare_instantiate(Code::Upload(binary_caller)).build_and_unwrap_contract(); - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); - - // Subsequent call input. - let input = (&addr_callee, pallet_revive_uapi::CallFlags::empty().bits(), 0u64, 100u32); - - // Read-only call fails when modifying storage. - assert_err_ignore_postinfo!( - builder::call(addr_caller) - .data((&addr_subsequent_caller, input).encode()) - .build(), - >::ContractTrapped - ); - }); -} - -#[test] -fn read_only_call_works() { - let (binary_caller, _code_hash_caller) = compile_module("read_only_call").unwrap(); - let (binary_callee, _code_hash_callee) = compile_module("dummy").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create both contracts: Constructors do nothing. - let Contract { addr: addr_caller, .. } = - builder::bare_instantiate(Code::Upload(binary_caller)).build_and_unwrap_contract(); - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); - - assert_ok!(builder::call(addr_caller).data(addr_callee.encode()).build()); - }); -} - -#[test] -fn create1_with_value_works() { - let (code, code_hash) = compile_module("create1_with_value").unwrap(); - let value = 42; - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create the contract: Constructor does nothing. - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - // Call the contract: Deploys itself using create1 and the expected value - assert_ok!(builder::call(addr).value(value).data(code_hash.encode()).build()); - - // We should see the expected balance at the expected account - let address = crate::address::create1(&addr, 1); - let account_id = ::AddressMapper::to_account_id(&address); - let usable_balance = ::Currency::usable_balance(&account_id); - assert_eq!(usable_balance, value); - }); -} - -#[test] -fn gas_price_api_works() { - let (code, _) = compile_module("gas_price").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create fixture: Constructor does nothing - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - // Call the contract: It echoes back the value returned by the gas price API. - let received = builder::bare_call(addr).build_and_unwrap_result(); - assert_eq!(received.flags, ReturnFlags::empty()); - assert_eq!(u64::from_le_bytes(received.data[..].try_into().unwrap()), u64::from(GAS_PRICE)); - }); -} - -#[test] -fn base_fee_api_works() { - let (code, _) = compile_module("base_fee").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create fixture: Constructor does nothing - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - // Call the contract: It echoes back the value returned by the base fee API. - let received = builder::bare_call(addr).build_and_unwrap_result(); - assert_eq!(received.flags, ReturnFlags::empty()); - assert_eq!(U256::from_little_endian(received.data[..].try_into().unwrap()), U256::zero()); - }); -} - -#[test] -fn call_data_size_api_works() { - let (code, _) = compile_module("call_data_size").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create fixture: Constructor does nothing - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - // Call the contract: It echoes back the value returned by the call data size API. - let received = builder::bare_call(addr).build_and_unwrap_result(); - assert_eq!(received.flags, ReturnFlags::empty()); - assert_eq!(u64::from_le_bytes(received.data.try_into().unwrap()), 0); - - let received = builder::bare_call(addr).data(vec![1; 256]).build_and_unwrap_result(); - assert_eq!(received.flags, ReturnFlags::empty()); - assert_eq!(u64::from_le_bytes(received.data.try_into().unwrap()), 256); - }); -} - -#[test] -fn call_data_copy_api_works() { - let (code, _) = compile_module("call_data_copy").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create fixture: Constructor does nothing - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - // Call fixture: Expects an input of [255; 32] and executes tests. - assert_ok!(builder::call(addr).data(vec![255; 32]).build()); - }); -} - -#[test] -fn static_data_limit_is_enforced() { - let (oom_rw_trailing, _) = compile_module("oom_rw_trailing").unwrap(); - let (oom_rw_included, _) = compile_module("oom_rw_included").unwrap(); - let (oom_ro, _) = compile_module("oom_ro").unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let _ = Balances::set_balance(&ALICE, 1_000_000); - - assert_err!( - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - oom_rw_trailing, - deposit_limit::(), - ), - >::StaticMemoryTooLarge - ); - - assert_err!( - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - oom_rw_included, - deposit_limit::(), - ), - >::BlobTooLarge - ); - - assert_err!( - Contracts::upload_code(RuntimeOrigin::signed(ALICE), oom_ro, deposit_limit::(),), - >::BlobTooLarge - ); - }); -} - -#[test] -fn call_diverging_out_len_works() { - let (code, _) = compile_module("call_diverging_out_len").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create the contract: Constructor does nothing - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - // Call the contract: It will issue calls and deploys, asserting on - // correct output if the supplied output length was smaller than - // than what the callee returned. - assert_ok!(builder::call(addr).build()); - }); -} - -#[test] -fn chain_id_works() { - let (code, _) = compile_module("chain_id").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - let chain_id = U256::from(::ChainId::get()); - let received = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_result(); - assert_eq!(received.result.data, chain_id.encode()); - }); -} - -#[test] -fn call_data_load_api_works() { - let (code, _) = compile_module("call_data_load").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create fixture: Constructor does nothing - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - // Call the contract: It reads a byte for the offset and then returns - // what call data load returned using this byte as the offset. - let input = (3u8, U256::max_value(), U256::max_value()).encode(); - let received = builder::bare_call(addr).data(input).build().result.unwrap(); - assert_eq!(received.flags, ReturnFlags::empty()); - assert_eq!(U256::from_little_endian(&received.data), U256::max_value()); - - // Edge case - let input = (2u8, U256::from(255).to_big_endian()).encode(); - let received = builder::bare_call(addr).data(input).build().result.unwrap(); - assert_eq!(received.flags, ReturnFlags::empty()); - assert_eq!(U256::from_little_endian(&received.data), U256::from(65280)); - - // Edge case - let received = builder::bare_call(addr).data(vec![1]).build().result.unwrap(); - assert_eq!(received.flags, ReturnFlags::empty()); - assert_eq!(U256::from_little_endian(&received.data), U256::zero()); - - // OOB case - let input = (42u8).encode(); - let received = builder::bare_call(addr).data(input).build().result.unwrap(); - assert_eq!(received.flags, ReturnFlags::empty()); - assert_eq!(U256::from_little_endian(&received.data), U256::zero()); - - // No calldata should return the zero value - let received = builder::bare_call(addr).build().result.unwrap(); - assert_eq!(received.flags, ReturnFlags::empty()); - assert_eq!(U256::from_little_endian(&received.data), U256::zero()); - }); -} - -#[test] -fn return_data_api_works() { - let (code_return_data_api, _) = compile_module("return_data_api").unwrap(); - let (code_return_with_data, hash_return_with_data) = - compile_module("return_with_data").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Upload the io echoing fixture for later use - assert_ok!(Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - code_return_with_data, - deposit_limit::(), - )); - - // Create fixture: Constructor does nothing - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code_return_data_api)) - .build_and_unwrap_contract(); - - // Call the contract: It will issue calls and deploys, asserting on - assert_ok!(builder::call(addr) - .value(10 * 1024) - .data(hash_return_with_data.encode()) - .build()); - }); -} - -#[test] -fn immutable_data_works() { - let (code, _) = compile_module("immutable_data").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - let data = [0xfe; 8]; - - // Create fixture: Constructor sets the immtuable data - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) - .data(data.to_vec()) - .build_and_unwrap_contract(); - - let contract = test_utils::get_contract(&addr); - let account = ::AddressMapper::to_account_id(&addr); - let actual_deposit = - test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account); - - assert_eq!(contract.immutable_data_len(), data.len() as u32); - - // Storing immmutable data charges storage deposit; verify it explicitly. - assert_eq!(actual_deposit, test_utils::contract_base_deposit(&addr)); - - // make sure it is also recorded in the base deposit - assert_eq!( - test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account), - contract.storage_base_deposit(), - ); - - // Call the contract: Asserts the input to equal the immutable data - assert_ok!(builder::call(addr).data(data.to_vec()).build()); - }); -} - -#[test] -fn sbrk_cannot_be_deployed() { - let (code, _) = compile_module("sbrk").unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let _ = Balances::set_balance(&ALICE, 1_000_000); - - assert_err!( - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - code.clone(), - deposit_limit::(), - ), - >::InvalidInstruction - ); - - assert_err!( - builder::bare_instantiate(Code::Upload(code)).build().result, - >::InvalidInstruction - ); - }); -} - -#[test] -fn overweight_basic_block_cannot_be_deployed() { - let (code, _) = compile_module("basic_block").unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let _ = Balances::set_balance(&ALICE, 1_000_000); - - assert_err!( - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - code.clone(), - deposit_limit::(), - ), - >::BasicBlockTooLarge - ); - - assert_err!( - builder::bare_instantiate(Code::Upload(code)).build().result, - >::BasicBlockTooLarge - ); - }); -} - -#[test] -fn origin_api_works() { - let (code, _) = compile_module("origin").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create fixture: Constructor does nothing - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - // Call the contract: Asserts the origin API to work as expected - assert_ok!(builder::call(addr).build()); - }); -} - -#[test] -fn code_hash_works() { - use crate::precompiles::{Precompile, EVM_REVERT}; - use precompiles::NoInfo; - - let builtin_precompile = H160(NoInfo::::MATCHER.base_address()); - let primitive_precompile = H160::from_low_u64_be(1); - - let (code_hash_code, self_code_hash) = compile_module("code_hash").unwrap(); - let (dummy_code, code_hash) = compile_module("dummy").unwrap(); - - ExtBuilder::default().existential_deposit(1).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code_hash_code)).build_and_unwrap_contract(); - let Contract { addr: dummy_addr, .. } = - builder::bare_instantiate(Code::Upload(dummy_code)).build_and_unwrap_contract(); - - // code hash of dummy contract - assert_ok!(builder::call(addr).data((dummy_addr, code_hash).encode()).build()); - // code hash of itself - assert_ok!(builder::call(addr).data((addr, self_code_hash).encode()).build()); - // code hash of primitive pre-compile (exist but have no bytecode) - assert_ok!(builder::call(addr) - .data((primitive_precompile, crate::exec::EMPTY_CODE_HASH).encode()) - .build()); - // code hash of normal pre-compile (do have a bytecode) - assert_ok!(builder::call(addr) - .data((builtin_precompile, sp_io::hashing::keccak_256(&EVM_REVERT)).encode()) - .build()); - - // EOA doesn't exists - assert_err!( - builder::bare_call(addr) - .data((BOB_ADDR, crate::exec::EMPTY_CODE_HASH).encode()) - .build() - .result, - Error::::ContractTrapped - ); - // non-existing will return zero - assert_ok!(builder::call(addr).data((BOB_ADDR, H256::zero()).encode()).build()); - - // create EOA - let _ = ::Currency::set_balance( - &::AddressMapper::to_account_id(&BOB_ADDR), - 1_000_000, - ); - - // EOA returns empty code hash - assert_ok!(builder::call(addr) - .data((BOB_ADDR, crate::exec::EMPTY_CODE_HASH).encode()) - .build()); - }); -} - -#[test] -fn code_size_works() { - let (tester_code, _) = compile_module("extcodesize").unwrap(); - let tester_code_len = tester_code.len() as u64; - - let (dummy_code, _) = compile_module("dummy").unwrap(); - let dummy_code_len = dummy_code.len() as u64; - - ExtBuilder::default().existential_deposit(1).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - let Contract { addr: tester_addr, .. } = - builder::bare_instantiate(Code::Upload(tester_code)).build_and_unwrap_contract(); - let Contract { addr: dummy_addr, .. } = - builder::bare_instantiate(Code::Upload(dummy_code)).build_and_unwrap_contract(); - - // code size of another contract address - assert_ok!(builder::call(tester_addr).data((dummy_addr, dummy_code_len).encode()).build()); - - // code size of own contract address - assert_ok!(builder::call(tester_addr) - .data((tester_addr, tester_code_len).encode()) - .build()); - - // code size of non contract accounts - assert_ok!(builder::call(tester_addr).data(([8u8; 20], 0u64).encode()).build()); - }); -} - -#[test] -fn origin_must_be_mapped() { - let (code, hash) = compile_module("dummy").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - ::Currency::set_balance(&ALICE, 1_000_000); - ::Currency::set_balance(&EVE, 1_000_000); - - let eve = RuntimeOrigin::signed(EVE); - - // alice can instantiate as she doesn't need a mapping - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - // without a mapping eve can neither call nor instantiate - assert_err!( - builder::bare_call(addr).origin(eve.clone()).build().result, - >::AccountUnmapped - ); - assert_err!( - builder::bare_instantiate(Code::Existing(hash)) - .origin(eve.clone()) - .build() - .result, - >::AccountUnmapped - ); - - // after mapping eve is usable as an origin - >::map_account(eve.clone()).unwrap(); - assert_ok!(builder::bare_call(addr).origin(eve.clone()).build().result); - assert_ok!(builder::bare_instantiate(Code::Existing(hash)).origin(eve).build().result); - }); -} - -#[test] -fn mapped_address_works() { - let (code, _) = compile_module("terminate_and_send_to_argument").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - ::Currency::set_balance(&ALICE, 1_000_000); - - // without a mapping everything will be send to the fallback account - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code.clone())).build_and_unwrap_contract(); - assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 0); - builder::bare_call(addr).data(EVE_ADDR.encode()).build_and_unwrap_result(); - assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 100); - - // after mapping it will be sent to the real eve account - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - // need some balance to pay for the map deposit - ::Currency::set_balance(&EVE, 1_000); - >::map_account(RuntimeOrigin::signed(EVE)).unwrap(); - builder::bare_call(addr).data(EVE_ADDR.encode()).build_and_unwrap_result(); - assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 100); - assert_eq!(::Currency::total_balance(&EVE), 1_100); - }); -} - -#[test] -fn recovery_works() { - let (code, _) = compile_module("terminate_and_send_to_argument").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - ::Currency::set_balance(&ALICE, 1_000_000); - - // eve puts her AccountId20 as argument to terminate but forgot to register - // her AccountId32 first so now the funds are trapped in her fallback account - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code.clone())).build_and_unwrap_contract(); - assert_eq!(::Currency::total_balance(&EVE), 0); - assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 0); - builder::bare_call(addr).data(EVE_ADDR.encode()).build_and_unwrap_result(); - assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 100); - assert_eq!(::Currency::total_balance(&EVE), 0); - - let call = RuntimeCall::Balances(pallet_balances::Call::transfer_all { - dest: EVE, - keep_alive: false, - }); - - // she now uses the recovery function to move all funds from the fallback - // account to her real account - >::dispatch_as_fallback_account(RuntimeOrigin::signed(EVE), Box::new(call)) - .unwrap(); - assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 0); - assert_eq!(::Currency::total_balance(&EVE), 100); - }); -} - -#[test] -fn skip_transfer_works() { - let (code_caller, _) = compile_module("call").unwrap(); - let (code, _) = compile_module("store_call").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - ::Currency::set_balance(&ALICE, 1_000_000); - ::Currency::set_balance(&BOB, 0); - - // when gas is some (transfers enabled): bob has no money: fail - assert_err!( - Pallet::::dry_run_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - input: code.clone().into(), - gas: Some(1u32.into()), - ..Default::default() - }, - Weight::MAX, - |_, _| 0u64, - ), - EthTransactError::Message(format!( - "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" - )) - ); - - // no gas specified (all transfers are skipped): even without money bob can deploy - assert_ok!(Pallet::::dry_run_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - input: code.clone().into(), - ..Default::default() - }, - Weight::MAX, - |_, _| 0u64, - )); - - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - let Contract { addr: caller_addr, .. } = - builder::bare_instantiate(Code::Upload(code_caller)).build_and_unwrap_contract(); - - // call directly: fails with enabled transfers - assert_err!( - Pallet::::dry_run_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - to: Some(addr), - input: 0u32.encode().into(), - gas: Some(1u32.into()), - ..Default::default() - }, - Weight::MAX, - |_, _| 0u64, - ), - EthTransactError::Message(format!( - "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" - )) - ); - - // fails to call through other contract - // we didn't roll back the storage changes done by the previous - // call. So the item already exists. We simply increase the size of - // the storage item to incur some deposits (which bob can't pay). - assert!(Pallet::::dry_run_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - to: Some(caller_addr), - input: (1u32, &addr).encode().into(), - gas: Some(1u32.into()), - ..Default::default() - }, - Weight::MAX, - |_, _| 0u64, - ) - .is_err(),); - - // works when no gas is specified (skip transfer) - assert_ok!(Pallet::::dry_run_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - to: Some(addr), - input: 2u32.encode().into(), - ..Default::default() - }, - Weight::MAX, - |_, _| 0u64, - )); - - // call through contract works when transfers are skipped - assert_ok!(Pallet::::dry_run_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - to: Some(caller_addr), - input: (3u32, &addr).encode().into(), - ..Default::default() - }, - Weight::MAX, - |_, _| 0u64, - )); - - // works with transfers enabled if we don't incur a storage cost - // we shrink the item so its actually a refund - assert_ok!(Pallet::::dry_run_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - to: Some(caller_addr), - input: (2u32, &addr).encode().into(), - gas: Some(1u32.into()), - ..Default::default() - }, - Weight::MAX, - |_, _| 0u64, - )); - - // fails when trying to increase the storage item size - assert!(Pallet::::dry_run_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - to: Some(caller_addr), - input: (3u32, &addr).encode().into(), - gas: Some(1u32.into()), - ..Default::default() - }, - Weight::MAX, - |_, _| 0u64, - ) - .is_err()); - }); -} - -#[test] -fn gas_limit_api_works() { - let (code, _) = compile_module("gas_limit").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - - // Create fixture: Constructor does nothing - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - // Call the contract: It echoes back the value returned by the gas limit API. - let received = builder::bare_call(addr).build_and_unwrap_result(); - assert_eq!(received.flags, ReturnFlags::empty()); - assert_eq!( - u64::from_le_bytes(received.data[..].try_into().unwrap()), - ::BlockWeights::get().max_block.ref_time() - ); - }); -} - -#[test] -fn unknown_syscall_rejected() { - let (code, _) = compile_module("unknown_syscall").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - ::Currency::set_balance(&ALICE, 1_000_000); - - assert_err!( - builder::bare_instantiate(Code::Upload(code)).build().result, - >::CodeRejected, - ) - }); -} - -#[test] -fn unstable_interface_rejected() { - let (code, _) = compile_module("unstable_interface").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - ::Currency::set_balance(&ALICE, 1_000_000); - - Test::set_unstable_interface(false); - assert_err!( - builder::bare_instantiate(Code::Upload(code.clone())).build().result, - >::CodeRejected, - ); - - Test::set_unstable_interface(true); - assert_ok!(builder::bare_instantiate(Code::Upload(code)).build().result); - }); -} - -#[test] -fn tracing_works_for_transfers() { - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 100_000_000); - let mut tracer = CallTracer::new(Default::default(), |_| U256::zero()); - trace(&mut tracer, || { - builder::bare_call(BOB_ADDR).evm_value(10.into()).build_and_unwrap_result(); - }); - - let trace = tracer.collect_trace(); - assert_eq!( - trace, - Some(CallTrace { - from: ALICE_ADDR, - to: BOB_ADDR, - value: Some(U256::from(10)), - call_type: CallType::Call, - ..Default::default() - }) - ) - }); -} - -#[test] -fn call_tracing_works() { - use crate::evm::*; - use CallType::*; - let (code, _code_hash) = compile_module("tracing").unwrap(); - let (binary_callee, _) = compile_module("tracing_callee").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 100_000_000); - - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); - - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).evm_value(10_000_000.into()).build_and_unwrap_contract(); - - - let tracer_configs = vec![ - CallTracerConfig{ with_logs: false, only_top_call: false}, - CallTracerConfig{ with_logs: false, only_top_call: false}, - CallTracerConfig{ with_logs: false, only_top_call: true}, - ]; - - // Verify that the first trace report the same weight reported by bare_call - // TODO: fix tracing ( https://github.com/paritytech/polkadot-sdk/issues/8362 ) - /* - let mut tracer = CallTracer::new(false, |w| w); - let gas_used = trace(&mut tracer, || { - builder::bare_call(addr).data((3u32, addr_callee).encode()).build().gas_consumed - }); - let trace = tracer.collect_trace().unwrap(); - assert_eq!(&trace.gas_used, &gas_used); - */ - - // Discarding gas usage, check that traces reported are correct - for config in tracer_configs { - let logs = if config.with_logs { - vec![ - CallLog { - address: addr, - topics: Default::default(), - data: b"before".to_vec().into(), - position: 0, - }, - CallLog { - address: addr, - topics: Default::default(), - data: b"after".to_vec().into(), - position: 1, - }, - ] - } else { - vec![] - }; - - let calls = if config.only_top_call { - vec![] - } else { - vec![ - CallTrace { - from: addr, - to: addr_callee, - input: 2u32.encode().into(), - output: hex_literal::hex!( - "08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a546869732066756e6374696f6e20616c77617973206661696c73000000000000" - ).to_vec().into(), - revert_reason: Some("revert: This function always fails".to_string()), - error: Some("execution reverted".to_string()), - call_type: Call, - value: Some(U256::from(0)), - ..Default::default() - }, - CallTrace { - from: addr, - to: addr, - input: (2u32, addr_callee).encode().into(), - call_type: Call, - logs: logs.clone(), - value: Some(U256::from(0)), - calls: vec![ - CallTrace { - from: addr, - to: addr_callee, - input: 1u32.encode().into(), - output: Default::default(), - error: Some("ContractTrapped".to_string()), - call_type: Call, - value: Some(U256::from(0)), - ..Default::default() - }, - CallTrace { - from: addr, - to: addr, - input: (1u32, addr_callee).encode().into(), - call_type: Call, - logs: logs.clone(), - value: Some(U256::from(0)), - calls: vec![ - CallTrace { - from: addr, - to: addr_callee, - input: 0u32.encode().into(), - output: 0u32.to_le_bytes().to_vec().into(), - call_type: Call, - value: Some(U256::from(0)), - ..Default::default() - }, - CallTrace { - from: addr, - to: addr, - input: (0u32, addr_callee).encode().into(), - call_type: Call, - value: Some(U256::from(0)), - calls: vec![ - CallTrace { - from: addr, - to: BOB_ADDR, - value: Some(U256::from(100)), - call_type: CallType::Call, - ..Default::default() - } - ], - ..Default::default() - }, - ], - ..Default::default() - }, - ], - ..Default::default() - }, - ] - }; - - let mut tracer = CallTracer::new(config, |_| U256::zero()); - trace(&mut tracer, || { - builder::bare_call(addr).data((3u32, addr_callee).encode()).build() - }); - - let trace = tracer.collect_trace(); - let expected_trace = CallTrace { - from: ALICE_ADDR, - to: addr, - input: (3u32, addr_callee).encode().into(), - call_type: Call, - logs: logs.clone(), - value: Some(U256::from(0)), - calls: calls, - ..Default::default() - }; - - assert_eq!( - trace, - expected_trace.into(), - ); - } - }); -} - -#[test] -fn create_call_tracing_works() { - use crate::evm::*; - let (code, code_hash) = compile_module("create2_with_value").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 100_000_000); - - let mut tracer = CallTracer::new(Default::default(), |_| U256::zero()); - - let Contract { addr, .. } = trace(&mut tracer, || { - builder::bare_instantiate(Code::Upload(code.clone())) - .evm_value(100.into()) - .salt(None) - .build_and_unwrap_contract() - }); - - let call_trace = tracer.collect_trace().unwrap(); - assert_eq!( - call_trace, - CallTrace { - from: ALICE_ADDR, - to: addr, - value: Some(100.into()), - input: Bytes(code.clone()), - call_type: CallType::Create, - ..Default::default() - } - ); - - let mut tracer = CallTracer::new(Default::default(), |_| U256::zero()); - let data = b"garbage"; - let input = (code_hash, data).encode(); - trace(&mut tracer, || { - assert_ok!(builder::call(addr).data(input.clone()).build()); - }); - - let call_trace = tracer.collect_trace().unwrap(); - let child_addr = crate::address::create2(&addr, &code, data, &[1u8; 32]); - - assert_eq!( - call_trace, - CallTrace { - from: ALICE_ADDR, - to: addr, - value: Some(0.into()), - input: input.clone().into(), - calls: vec![CallTrace { - from: addr, - input: input.clone().into(), - to: child_addr, - value: Some(0.into()), - call_type: CallType::Create2, - ..Default::default() - },], - ..Default::default() - } - ); - }); -} - -#[test] -fn prestate_tracing_works() { - use crate::evm::*; - use alloc::collections::BTreeMap; - - let (dummy_code, _) = compile_module("dummy").unwrap(); - let (code, _) = compile_module("tracing").unwrap(); - let (callee_code, _) = compile_module("tracing_callee").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 100_000_000); - - let Contract { addr: addr_callee, .. } = - builder::bare_instantiate(Code::Upload(callee_code.clone())) - .build_and_unwrap_contract(); - - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code.clone())) - .native_value(10) - .build_and_unwrap_contract(); - - // redact balance so that tests are resilient to weight changes - let alice_redacted_balance = Some(U256::from(1)); - - let test_cases: Vec<(Box, _, _)> = vec![ - ( - Box::new(|| { - builder::bare_call(addr) - .data((3u32, addr_callee).encode()) - .build_and_unwrap_result(); - }), - PrestateTracerConfig { - diff_mode: false, - disable_storage: false, - disable_code: false, - }, - PrestateTrace::Prestate(BTreeMap::from([ - ( - ALICE_ADDR, - PrestateTraceInfo { - balance: alice_redacted_balance, - nonce: Some(2), - ..Default::default() - }, - ), - ( - BOB_ADDR, - PrestateTraceInfo { balance: Some(U256::from(0u64)), ..Default::default() }, - ), - ( - addr_callee, - PrestateTraceInfo { - balance: Some(U256::from(0u64)), - code: Some(Bytes(callee_code.clone())), - nonce: Some(1), - ..Default::default() - }, - ), - ( - addr, - PrestateTraceInfo { - balance: Some(U256::from(10_000_000u64)), - code: Some(Bytes(code.clone())), - nonce: Some(1), - ..Default::default() - }, - ), - ])), - ), - ( - Box::new(|| { - builder::bare_call(addr) - .data((3u32, addr_callee).encode()) - .build_and_unwrap_result(); - }), - PrestateTracerConfig { - diff_mode: true, - disable_storage: false, - disable_code: false, - }, - PrestateTrace::DiffMode { - pre: BTreeMap::from([ - ( - BOB_ADDR, - PrestateTraceInfo { - balance: Some(U256::from(100u64)), - ..Default::default() - }, - ), - ( - addr, - PrestateTraceInfo { - balance: Some(U256::from(9_999_900u64)), - code: Some(Bytes(code.clone())), - nonce: Some(1), - ..Default::default() - }, - ), - ]), - post: BTreeMap::from([ - ( - BOB_ADDR, - PrestateTraceInfo { - balance: Some(U256::from(200u64)), - ..Default::default() - }, - ), - ( - addr, - PrestateTraceInfo { - balance: Some(U256::from(9_999_800u64)), - ..Default::default() - }, - ), - ]), - }, - ), - ( - Box::new(|| { - builder::bare_instantiate(Code::Upload(dummy_code.clone())) - .salt(None) - .build_and_unwrap_result(); - }), - PrestateTracerConfig { - diff_mode: true, - disable_storage: false, - disable_code: false, - }, - PrestateTrace::DiffMode { - pre: BTreeMap::from([( - ALICE_ADDR, - PrestateTraceInfo { - balance: alice_redacted_balance, - nonce: Some(2), - ..Default::default() - }, - )]), - post: BTreeMap::from([ - ( - ALICE_ADDR, - PrestateTraceInfo { - balance: alice_redacted_balance, - nonce: Some(3), - ..Default::default() - }, - ), - ( - create1(&ALICE_ADDR, 1), - PrestateTraceInfo { - code: Some(dummy_code.clone().into()), - balance: Some(U256::from(0)), - nonce: Some(1), - ..Default::default() - }, - ), - ]), - }, - ), - ]; - - for (exec_call, config, expected_trace) in test_cases.into_iter() { - let mut tracer = PrestateTracer::::new(config); - trace(&mut tracer, || { - exec_call(); - }); - - let mut trace = tracer.collect_trace(); - - // redact alice balance - match trace { - PrestateTrace::DiffMode { ref mut pre, ref mut post } => { - pre.get_mut(&ALICE_ADDR).map(|info| { - info.balance = alice_redacted_balance; - }); - post.get_mut(&ALICE_ADDR).map(|info| { - info.balance = alice_redacted_balance; - }); - }, - PrestateTrace::Prestate(ref mut pre) => { - pre.get_mut(&ALICE_ADDR).map(|info| { - info.balance = alice_redacted_balance; - }); - }, - } - - assert_eq!(trace, expected_trace); - } - }); -} - -#[test] -fn unknown_precompiles_revert() { - let (code, _code_hash) = compile_module("read_only_call").unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - let cases: Vec<(H160, Box)> = vec![( - H160::from_low_u64_be(0x0a), - Box::new(|result| { - assert_err!(result, >::UnsupportedPrecompileAddress); - }), - )]; - - for (callee_addr, assert_result) in cases { - let result = - builder::bare_call(addr).data((callee_addr, [0u8; 0]).encode()).build().result; - assert_result(result); - } - }); -} - -#[test] -fn pure_precompile_works() { - use hex_literal::hex; - - let cases = vec![ - ( - "ECRecover", - H160::from_low_u64_be(1), - hex!("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549").to_vec(), - hex!("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b").to_vec(), - ), - ( - "Sha256", - H160::from_low_u64_be(2), - hex!("ec07171c4f0f0e2b").to_vec(), - hex!("d0591ea667763c69a5f5a3bae657368ea63318b2c9c8349cccaf507e3cbd7c7a").to_vec(), - ), - ( - "Ripemd160", - H160::from_low_u64_be(3), - hex!("ec07171c4f0f0e2b").to_vec(), - hex!("000000000000000000000000a9c5ebaf7589fd8acfd542c3a008956de84fbeb7").to_vec(), - ), - ( - "Identity", - H160::from_low_u64_be(4), - [42u8; 128].to_vec(), - [42u8; 128].to_vec(), - ), - ( - "Modexp", - H160::from_low_u64_be(5), - hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002003fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f").to_vec(), - hex!("0000000000000000000000000000000000000000000000000000000000000001").to_vec(), - ), - ( - "Bn128Add", - H160::from_low_u64_be(6), - hex!("18b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f3726607c2b7f58a84bd6145f00c9c2bc0bb1a187f20ff2c92963a88019e7c6a014eed06614e20c147e940f2d70da3f74c9a17df361706a4485c742bd6788478fa17d7").to_vec(), - hex!("2243525c5efd4b9c3d3c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703301d1d33be6da8e509df21cc35964723180eed7532537db9ae5e7d48f195c915").to_vec(), - ), - ( - "Bn128Mul", - H160::from_low_u64_be(7), - hex!("2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb721611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb20400000000000000000000000000000000000000000000000011138ce750fa15c2").to_vec(), - hex!("070a8d6a982153cae4be29d434e8faef8a47b274a053f5a4ee2a6c9c13c31e5c031b8ce914eba3a9ffb989f9cdd5b0f01943074bf4f0f315690ec3cec6981afc").to_vec(), - ), - ( - "Bn128Pairing", - H160::from_low_u64_be(8), - hex!("1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f593034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf704bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416782bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa").to_vec(), - hex!("0000000000000000000000000000000000000000000000000000000000000001").to_vec(), - ), - ( - "Blake2F", - H160::from_low_u64_be(9), - hex!("0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001").to_vec(), - hex!("08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b").to_vec(), - ), - ]; - - for (description, precompile_addr, input, output) in cases { - let (code, _code_hash) = compile_module("call_and_return").unwrap(); - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) - .native_value(1_000) - .build_and_unwrap_contract(); - - let result = builder::bare_call(addr) - .data( - (&precompile_addr, 100u64) - .encode() - .into_iter() - .chain(input) - .collect::>(), - ) - .build_and_unwrap_result(); - - assert_eq!( - Pallet::::evm_balance(&precompile_addr), - U256::from(100), - "{description}: unexpected balance" - ); - assert_eq!( - alloy_core::hex::encode(result.data), - alloy_core::hex::encode(output), - "{description} Unexpected output for precompile: {precompile_addr:?}", - ); - assert_eq!(result.flags, ReturnFlags::empty()); - }); - } -} - -#[test] -fn precompiles_work() { - use crate::precompiles::Precompile; - use alloy_core::sol_types::{Panic, PanicKind, Revert, SolError, SolInterface, SolValue}; - use precompiles::{INoInfo, NoInfo}; - - let precompile_addr = H160(NoInfo::::MATCHER.base_address()); - - let cases = vec![ - ( - INoInfo::INoInfoCalls::identity(INoInfo::identityCall { number: 42u64.into() }) - .abi_encode(), - 42u64.abi_encode(), - RuntimeReturnCode::Success, - ), - ( - INoInfo::INoInfoCalls::reverts(INoInfo::revertsCall { error: "panic".to_string() }) - .abi_encode(), - Revert::from("panic").abi_encode(), - RuntimeReturnCode::CalleeReverted, - ), - ( - INoInfo::INoInfoCalls::panics(INoInfo::panicsCall {}).abi_encode(), - Panic::from(PanicKind::Assert).abi_encode(), - RuntimeReturnCode::CalleeReverted, - ), - ( - INoInfo::INoInfoCalls::errors(INoInfo::errorsCall {}).abi_encode(), - Vec::new(), - RuntimeReturnCode::CalleeTrapped, - ), - // passing non decodeable input reverts with solidity panic - ( - b"invalid".to_vec(), - Panic::from(PanicKind::ResourceError).abi_encode(), - RuntimeReturnCode::CalleeReverted, - ), - ( - INoInfo::INoInfoCalls::passData(INoInfo::passDataCall { - inputLen: limits::CALLDATA_BYTES, - }) - .abi_encode(), - Vec::new(), - RuntimeReturnCode::Success, - ), - ( - INoInfo::INoInfoCalls::passData(INoInfo::passDataCall { - inputLen: limits::CALLDATA_BYTES + 1, - }) - .abi_encode(), - Vec::new(), - RuntimeReturnCode::CalleeTrapped, - ), - ( - INoInfo::INoInfoCalls::returnData(INoInfo::returnDataCall { - returnLen: limits::CALLDATA_BYTES - 4, - }) - .abi_encode(), - vec![42u8; limits::CALLDATA_BYTES as usize - 4], - RuntimeReturnCode::Success, - ), - ( - INoInfo::INoInfoCalls::returnData(INoInfo::returnDataCall { - returnLen: limits::CALLDATA_BYTES + 1, - }) - .abi_encode(), - vec![], - RuntimeReturnCode::CalleeTrapped, - ), - ]; - - for (input, output, error_code) in cases { - let (code, _code_hash) = compile_module("call_and_returncode").unwrap(); - ExtBuilder::default().build().execute_with(|| { - let id = ::AddressMapper::to_account_id(&precompile_addr); - let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) - .native_value(1000) - .build_and_unwrap_contract(); - - let result = builder::bare_call(addr) - .data( - (&precompile_addr, 0u64).encode().into_iter().chain(input).collect::>(), - ) - .build_and_unwrap_result(); - - // no account or contract info should be created for a NoInfo pre-compile - assert!(test_utils::get_contract_checked(&precompile_addr).is_none()); - assert!(!System::account_exists(&id)); - assert_eq!(Pallet::::evm_balance(&precompile_addr), U256::zero()); - - assert_eq!(result.flags, ReturnFlags::empty()); - assert_eq!(u32::from_le_bytes(result.data[..4].try_into().unwrap()), error_code as u32); - assert_eq!( - &result.data[4..], - &output, - "Unexpected output for precompile: {precompile_addr:?}", - ); - }); - } -} - -#[test] -fn precompiles_with_info_creates_contract() { - use crate::precompiles::Precompile; - use alloy_core::sol_types::SolInterface; - use precompiles::{IWithInfo, WithInfo}; - - let precompile_addr = H160(WithInfo::::MATCHER.base_address()); - - let cases = vec![( - IWithInfo::IWithInfoCalls::dummy(IWithInfo::dummyCall {}).abi_encode(), - Vec::::new(), - RuntimeReturnCode::Success, - )]; - - for (input, output, error_code) in cases { - let (code, _code_hash) = compile_module("call_and_returncode").unwrap(); - ExtBuilder::default().build().execute_with(|| { - let id = ::AddressMapper::to_account_id(&precompile_addr); - let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) - .native_value(1000) - .build_and_unwrap_contract(); - - let result = builder::bare_call(addr) - .data( - (&precompile_addr, 0u64).encode().into_iter().chain(input).collect::>(), - ) - .build_and_unwrap_result(); - - // a pre-compile with contract info should create an account on first call - assert!(test_utils::get_contract_checked(&precompile_addr).is_some()); - assert!(System::account_exists(&id)); - assert_eq!(Pallet::::evm_balance(&precompile_addr), U256::from(0)); - - assert_eq!(result.flags, ReturnFlags::empty()); - assert_eq!(u32::from_le_bytes(result.data[..4].try_into().unwrap()), error_code as u32); - assert_eq!( - &result.data[4..], - &output, - "Unexpected output for precompile: {precompile_addr:?}", - ); - }); - } -} - -#[test] -fn bump_nonce_once_works() { - let (code, hash) = compile_module("dummy").unwrap(); - - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - frame_system::Account::::mutate(&ALICE, |account| account.nonce = 1); - - let _ = ::Currency::set_balance(&BOB, 1_000_000); - frame_system::Account::::mutate(&BOB, |account| account.nonce = 1); - - builder::bare_instantiate(Code::Upload(code.clone())) - .origin(RuntimeOrigin::signed(ALICE)) - .bump_nonce(BumpNonce::Yes) - .salt(None) - .build_and_unwrap_result(); - assert_eq!(System::account_nonce(&ALICE), 2); - - // instantiate again is ok - let result = builder::bare_instantiate(Code::Existing(hash)) - .origin(RuntimeOrigin::signed(ALICE)) - .bump_nonce(BumpNonce::Yes) - .salt(None) - .build() - .result; - assert!(result.is_ok()); - - builder::bare_instantiate(Code::Upload(code.clone())) - .origin(RuntimeOrigin::signed(BOB)) - .bump_nonce(BumpNonce::No) - .salt(None) - .build_and_unwrap_result(); - assert_eq!(System::account_nonce(&BOB), 1); - - // instantiate again should fail - let err = builder::bare_instantiate(Code::Upload(code)) - .origin(RuntimeOrigin::signed(BOB)) - .bump_nonce(BumpNonce::No) - .salt(None) - .build() - .result - .unwrap_err(); - - assert_eq!(err, >::DuplicateContract.into()); - }); -} - -#[test] -fn code_size_for_precompiles_works() { - use crate::precompiles::Precompile; - use precompiles::NoInfo; - - let builtin_precompile = H160(NoInfo::::MATCHER.base_address()); - let primitive_precompile = H160::from_low_u64_be(1); - - let (code, _code_hash) = compile_module("extcodesize").unwrap(); - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) - .native_value(1000) - .build_and_unwrap_contract(); - - // the primitive pre-compiles return 0 code size on eth - builder::bare_call(addr) - .data((&primitive_precompile, 0u64).encode()) - .build_and_unwrap_result(); - - // other precompiles should return the minimal evm revert code - builder::bare_call(addr) - .data((&builtin_precompile, 5u64).encode()) - .build_and_unwrap_result(); - }); -} - -#[test] -fn call_data_limit_is_enforced_subcalls() { - let (code, _code_hash) = compile_module("call_with_input_size").unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - let cases: Vec<(u32, Box)> = vec![ - ( - 0_u32, - Box::new(|result| { - assert_ok!(result); - }), - ), - ( - 1_u32, - Box::new(|result| { - assert_ok!(result); - }), - ), - ( - limits::CALLDATA_BYTES, - Box::new(|result| { - assert_ok!(result); - }), - ), - ( - limits::CALLDATA_BYTES + 1, - Box::new(|result| { - assert_err!(result, >::CallDataTooLarge); - }), - ), - ]; - - for (callee_input_size, assert_result) in cases { - let result = builder::bare_call(addr).data(callee_input_size.encode()).build().result; - assert_result(result); - } - }); -} - -#[test] -fn call_data_limit_is_enforced_root_call() { - let (code, _code_hash) = compile_module("dummy").unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - let cases: Vec<(H160, u32, Box)> = vec![ - ( - addr, - 0_u32, - Box::new(|result| { - assert_ok!(result); - }), - ), - ( - addr, - 1_u32, - Box::new(|result| { - assert_ok!(result); - }), - ), - ( - addr, - limits::CALLDATA_BYTES, - Box::new(|result| { - assert_ok!(result); - }), - ), - ( - addr, - limits::CALLDATA_BYTES + 1, - Box::new(|result| { - assert_err!(result, >::CallDataTooLarge); - }), - ), - ( - // limit is not enforced when tx calls EOA - BOB_ADDR, - limits::CALLDATA_BYTES + 1, - Box::new(|result| { - assert_ok!(result); - }), - ), - ]; - - for (addr, callee_input_size, assert_result) in cases { - let result = builder::bare_call(addr) - .data(vec![42; callee_input_size as usize]) - .build() - .result; - assert_result(result); - } - }); -} - -#[test] -fn return_data_limit_is_enforced() { - let (code, _code_hash) = compile_module("return_sized").unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); - let Contract { addr, .. } = - builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); - - let cases: Vec<(u32, Box)> = vec![ - ( - 1_u32, - Box::new(|result| { - assert_ok!(result); - }), - ), - ( - limits::CALLDATA_BYTES, - Box::new(|result| { - assert_ok!(result); - }), - ), - ( - limits::CALLDATA_BYTES + 1, - Box::new(|result| { - assert_err!(result, >::ReturnDataTooLarge); - }), - ), - ]; - - for (return_size, assert_result) in cases { - let result = builder::bare_call(addr).data(return_size.encode()).build().result; - assert_result(result); - } - }); -} diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs new file mode 100644 index 0000000000000..81aab74f72f2f --- /dev/null +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -0,0 +1,4892 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! The pallet-revive PVM specific integration test suite. + +use super::{ + precompiles, + precompiles::{INoInfo, NoInfo}, +}; +use crate::{ + address::{create1, create2, AddressMapper}, + assert_refcount, assert_return_code, + evm::{runtime::GAS_PRICE, CallTrace, CallTracer, CallType, GenericTransaction}, + exec::Key, + limits, + storage::DeletionQueueManager, + test_utils::builder::Contract, + tests::{ + builder, initialize_block, test_utils::*, Balances, CodeHashLockupDepositPercent, + Contracts, DepositPerByte, DepositPerItem, ExtBuilder, InstantiateAccount, RuntimeCall, + RuntimeEvent, RuntimeOrigin, System, Test, UploadAccount, DEPOSIT_PER_BYTE, *, + }, + tracing::trace, + weights::WeightInfo, + AccountInfo, AccountInfoOf, BalanceWithDust, BumpNonce, Code, Config, ContractInfo, + DeletionQueueCounter, DepositLimit, Error, EthTransactError, HoldReason, Pallet, PristineCode, + StorageDeposit, H160, +}; +use assert_matches::assert_matches; +use codec::Encode; +use frame_support::{ + assert_err, assert_err_ignore_postinfo, assert_noop, assert_ok, + storage::child, + traits::{ + fungible::{BalancedHold, Inspect, Mutate, MutateHold}, + tokens::Preservation, + OnIdle, OnInitialize, + }, + weights::{Weight, WeightMeter}, +}; +use frame_system::{EventRecord, Phase}; +use pallet_revive_fixtures::compile_module; +use pallet_revive_uapi::{ReturnErrorCode as RuntimeReturnCode, ReturnFlags}; +use pretty_assertions::{assert_eq, assert_ne}; +use sp_core::{Get, U256}; +use sp_io::hashing::blake2_256; +use sp_runtime::{testing::H256, traits::Zero, AccountId32, DispatchError, TokenError}; + +#[test] +fn transfer_with_dust_works() { + struct TestCase { + description: &'static str, + from_balance: BalanceWithDust, + to_balance: BalanceWithDust, + amount: BalanceWithDust, + expected_from_balance: BalanceWithDust, + expected_to_balance: BalanceWithDust, + total_issuance_diff: i64, + } + + let plank: u32 = ::NativeToEthRatio::get(); + + let test_cases = vec![ + TestCase { + description: "without dust", + from_balance: BalanceWithDust::new_unchecked::(100, 0), + to_balance: BalanceWithDust::new_unchecked::(0, 0), + amount: BalanceWithDust::new_unchecked::(1, 0), + expected_from_balance: BalanceWithDust::new_unchecked::(99, 0), + expected_to_balance: BalanceWithDust::new_unchecked::(1, 0), + total_issuance_diff: 0, + }, + TestCase { + description: "with dust", + from_balance: BalanceWithDust::new_unchecked::(100, 0), + to_balance: BalanceWithDust::new_unchecked::(0, 0), + amount: BalanceWithDust::new_unchecked::(1, 10), + expected_from_balance: BalanceWithDust::new_unchecked::(98, plank - 10), + expected_to_balance: BalanceWithDust::new_unchecked::(1, 10), + total_issuance_diff: 1, + }, + TestCase { + description: "just dust", + from_balance: BalanceWithDust::new_unchecked::(100, 0), + to_balance: BalanceWithDust::new_unchecked::(0, 0), + amount: BalanceWithDust::new_unchecked::(0, 10), + expected_from_balance: BalanceWithDust::new_unchecked::(99, plank - 10), + expected_to_balance: BalanceWithDust::new_unchecked::(0, 10), + total_issuance_diff: 1, + }, + TestCase { + description: "with existing dust", + from_balance: BalanceWithDust::new_unchecked::(100, 5), + to_balance: BalanceWithDust::new_unchecked::(0, plank - 5), + amount: BalanceWithDust::new_unchecked::(1, 10), + expected_from_balance: BalanceWithDust::new_unchecked::(98, plank - 5), + expected_to_balance: BalanceWithDust::new_unchecked::(2, 5), + total_issuance_diff: 0, + }, + TestCase { + description: "with enough existing dust", + from_balance: BalanceWithDust::new_unchecked::(100, 10), + to_balance: BalanceWithDust::new_unchecked::(0, plank - 10), + amount: BalanceWithDust::new_unchecked::(1, 10), + expected_from_balance: BalanceWithDust::new_unchecked::(99, 0), + expected_to_balance: BalanceWithDust::new_unchecked::(2, 0), + total_issuance_diff: -1, + }, + TestCase { + description: "receiver dust less than 1 plank", + from_balance: BalanceWithDust::new_unchecked::(100, plank / 10), + to_balance: BalanceWithDust::new_unchecked::(0, plank / 2), + amount: BalanceWithDust::new_unchecked::(1, plank / 10 * 3), + expected_from_balance: BalanceWithDust::new_unchecked::(98, plank / 10 * 8), + expected_to_balance: BalanceWithDust::new_unchecked::(1, plank / 10 * 8), + total_issuance_diff: 1, + }, + ]; + + for TestCase { + description, + from_balance, + to_balance, + amount, + expected_from_balance, + expected_to_balance, + total_issuance_diff, + } in test_cases.into_iter() + { + ExtBuilder::default().build().execute_with(|| { + set_balance_with_dust(&ALICE_ADDR, from_balance); + set_balance_with_dust(&BOB_ADDR, to_balance); + + let total_issuance = ::Currency::total_issuance(); + let evm_value = Pallet::::convert_native_to_evm(amount); + + let (value, dust) = amount.deconstruct(); + assert_eq!(Pallet::::has_dust(evm_value), !dust.is_zero()); + assert_eq!(Pallet::::has_balance(evm_value), !value.is_zero()); + + let result = + builder::bare_call(BOB_ADDR).evm_value(evm_value).build_and_unwrap_result(); + assert_eq!(result, Default::default(), "{description} tx failed"); + + assert_eq!( + Pallet::::evm_balance(&ALICE_ADDR), + Pallet::::convert_native_to_evm(expected_from_balance), + "{description}: invalid from balance" + ); + + assert_eq!( + Pallet::::evm_balance(&BOB_ADDR), + Pallet::::convert_native_to_evm(expected_to_balance), + "{description}: invalid to balance" + ); + + assert_eq!( + total_issuance as i64 - total_issuance_diff, + ::Currency::total_issuance() as i64, + "{description}: total issuance should match" + ); + }); + } +} + +#[test] +fn eth_call_transfer_with_dust_works() { + let (binary, _) = compile_module("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); + + let balance = + Pallet::::convert_native_to_evm(BalanceWithDust::new_unchecked::(100, 10)); + assert_ok!(builder::eth_call(addr).value(balance).build()); + + assert_eq!(Pallet::::evm_balance(&addr), balance); + }); +} + +#[test] +fn contract_call_transfer_with_dust_works() { + let (binary_caller, _code_hash_caller) = compile_module("call_with_value").unwrap(); + let (binary_callee, _code_hash_callee) = compile_module("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(binary_caller)) + .native_value(200) + .build_and_unwrap_contract(); + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); + + let balance = + Pallet::::convert_native_to_evm(BalanceWithDust::new_unchecked::(100, 10)); + assert_ok!(builder::call(addr_caller).data((balance, addr_callee).encode()).build()); + + assert_eq!(Pallet::::evm_balance(&addr_callee), balance); + }); +} + +#[test] +fn deposit_limit_enforced_on_plain_transfer() { + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&BOB, 1_000_000); + + // sending balance to a new account should fail when the limit is lower than the ed + let result = builder::bare_call(CHARLIE_ADDR) + .native_value(1) + .storage_deposit_limit(190.into()) + .build(); + assert_err!(result.result, >::StorageDepositLimitExhausted); + assert_eq!(result.storage_deposit, StorageDeposit::Charge(0)); + assert_eq!(get_balance(&CHARLIE), 0); + + // works when the account is prefunded + let result = builder::bare_call(BOB_ADDR) + .native_value(1) + .storage_deposit_limit(0.into()) + .build(); + assert_ok!(result.result); + assert_eq!(result.storage_deposit, StorageDeposit::Charge(0)); + assert_eq!(get_balance(&BOB), 1_000_001); + + // also works allowing enough deposit + let result = builder::bare_call(CHARLIE_ADDR) + .native_value(1) + .storage_deposit_limit(200.into()) + .build(); + assert_ok!(result.result); + assert_eq!(result.storage_deposit, StorageDeposit::Charge(200)); + assert_eq!(get_balance(&CHARLIE), 201); + }); +} + +#[test] +fn instantiate_and_call_and_deposit_event() { + let (binary, code_hash) = compile_module("event_and_return_on_deploy").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + let value = 100; + + // We determine the storage deposit limit after uploading because it depends on ALICEs + // free balance which is changed by uploading a module. + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + binary, + deposit_limit::(), + )); + + // Drop previous events + initialize_block(2); + + // Check at the end to get hash on error easily + let Contract { addr, account_id } = builder::bare_instantiate(Code::Existing(code_hash)) + .native_value(value) + .build_and_unwrap_contract(); + assert!(AccountInfoOf::::contains_key(&addr)); + + let hold_balance = contract_base_deposit(&addr); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: account_id.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: account_id.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: account_id.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: account_id.clone(), + amount: value, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::ContractEmitted { + contract: addr, + data: vec![1, 2, 3, 4], + topics: vec![H256::repeat_byte(42)], + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: ALICE_ADDR, + contract: addr + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::TransferAndHold { + reason: ::RuntimeHoldReason::Contracts( + HoldReason::StorageDepositReserve, + ), + source: ALICE, + dest: account_id.clone(), + transferred: hold_balance, + }), + topics: vec![], + }, + ] + ); + }); +} + +#[test] +fn create1_address_from_extrinsic() { + let (binary, code_hash) = compile_module("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + binary.clone(), + deposit_limit::(), + )); + + assert_eq!(System::account_nonce(&ALICE), 0); + System::inc_account_nonce(&ALICE); + + for nonce in 1..3 { + let Contract { addr, .. } = builder::bare_instantiate(Code::Existing(code_hash)) + .salt(None) + .build_and_unwrap_contract(); + assert!(AccountInfoOf::::contains_key(&addr)); + assert_eq!( + addr, + create1(&::AddressMapper::to_address(&ALICE), nonce - 1) + ); + } + assert_eq!(System::account_nonce(&ALICE), 3); + + for nonce in 3..6 { + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary.clone())) + .salt(None) + .build_and_unwrap_contract(); + assert!(AccountInfoOf::::contains_key(&addr)); + assert_eq!( + addr, + create1(&::AddressMapper::to_address(&ALICE), nonce - 1) + ); + } + assert_eq!(System::account_nonce(&ALICE), 6); + }); +} + +#[test] +fn deposit_event_max_value_limit() { + let (binary, _code_hash) = compile_module("event_size").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) + .native_value(30_000) + .build_and_unwrap_contract(); + + // Call contract with allowed storage value. + assert_ok!(builder::call(addr) + .gas_limit(GAS_LIMIT.set_ref_time(GAS_LIMIT.ref_time() * 2)) // we are copying a huge buffer, + .data(limits::PAYLOAD_BYTES.encode()) + .build()); + + // Call contract with too large a storage value. + assert_err_ignore_postinfo!( + builder::call(addr).data((limits::PAYLOAD_BYTES + 1).encode()).build(), + Error::::ValueTooLarge, + ); + }); +} + +// Fail out of fuel (ref_time weight) in the engine. +#[test] +fn run_out_of_fuel_engine() { + let (binary, _code_hash) = compile_module("run_out_of_gas").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) + .native_value(100 * min_balance) + .build_and_unwrap_contract(); + + // Call the contract with a fixed gas limit. It must run out of gas because it just + // loops forever. + assert_err_ignore_postinfo!( + builder::call(addr) + .gas_limit(Weight::from_parts(10_000_000_000, u64::MAX)) + .build(), + Error::::OutOfGas, + ); + }); +} + +// Fail out of fuel (ref_time weight) in the host. +#[test] +fn run_out_of_fuel_host() { + use crate::precompiles::Precompile; + use alloy_core::sol_types::SolInterface; + + let precompile_addr = H160(NoInfo::::MATCHER.base_address()); + let input = INoInfo::INoInfoCalls::consumeMaxGas(INoInfo::consumeMaxGasCall {}).abi_encode(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let result = builder::bare_call(precompile_addr).data(input).build().result; + assert_err!(result, >::OutOfGas); + }); +} + +#[test] +fn gas_syncs_work() { + let (code, _code_hash) = compile_module("caller_is_origin_n").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let contract = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let result = builder::bare_call(contract.addr).data(0u32.encode()).build(); + assert_ok!(result.result); + let engine_consumed_noop = result.gas_consumed.ref_time(); + + let result = builder::bare_call(contract.addr).data(1u32.encode()).build(); + assert_ok!(result.result); + let gas_consumed_once = result.gas_consumed.ref_time(); + let host_consumed_once = ::WeightInfo::seal_caller_is_origin().ref_time(); + let engine_consumed_once = gas_consumed_once - host_consumed_once - engine_consumed_noop; + + let result = builder::bare_call(contract.addr).data(2u32.encode()).build(); + assert_ok!(result.result); + let gas_consumed_twice = result.gas_consumed.ref_time(); + let host_consumed_twice = host_consumed_once * 2; + let engine_consumed_twice = gas_consumed_twice - host_consumed_twice - engine_consumed_noop; + + // Second contract just repeats first contract's instructions twice. + // If runtime syncs gas with the engine properly, this should pass. + assert_eq!(engine_consumed_twice, engine_consumed_once * 2); + }); +} + +/// Check that contracts with the same account id have different trie ids. +/// Check the `Nonce` storage item for more information. +#[test] +fn instantiate_unique_trie_id() { + let (binary, code_hash) = compile_module("self_destruct").unwrap(); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + Contracts::upload_code(RuntimeOrigin::signed(ALICE), binary, deposit_limit::()) + .unwrap(); + + // Instantiate the contract and store its trie id for later comparison. + let Contract { addr, .. } = + builder::bare_instantiate(Code::Existing(code_hash)).build_and_unwrap_contract(); + let trie_id = get_contract(&addr).trie_id; + + // Try to instantiate it again without termination should yield an error. + assert_err_ignore_postinfo!( + builder::instantiate(code_hash).build(), + >::DuplicateContract, + ); + + // Terminate the contract. + assert_ok!(builder::call(addr).build()); + + // Re-Instantiate after termination. + assert_ok!(builder::instantiate(code_hash).build()); + + // Trie ids shouldn't match or we might have a collision + assert_ne!(trie_id, get_contract(&addr).trie_id); + }); +} + +#[test] +fn storage_work() { + let (code, _code_hash) = compile_module("storage").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .native_value(min_balance * 100) + .build_and_unwrap_contract(); + + builder::bare_call(addr).build_and_unwrap_result(); + }); +} + +#[test] +fn storage_max_value_limit() { + let (binary, _code_hash) = compile_module("storage_size").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) + .native_value(30_000) + .build_and_unwrap_contract(); + get_contract(&addr); + + // Call contract with allowed storage value. + assert_ok!(builder::call(addr) + .gas_limit(GAS_LIMIT.set_ref_time(GAS_LIMIT.ref_time() * 2)) // we are copying a huge buffer + .data(limits::PAYLOAD_BYTES.encode()) + .build()); + + // Call contract with too large a storage value. + assert_err_ignore_postinfo!( + builder::call(addr).data((limits::PAYLOAD_BYTES + 1).encode()).build(), + Error::::ValueTooLarge, + ); + }); +} + +#[test] +fn clear_storage_on_zero_value() { + let (code, _code_hash) = compile_module("clear_storage_on_zero_value").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .native_value(min_balance * 100) + .build_and_unwrap_contract(); + + builder::bare_call(addr).build_and_unwrap_result(); + }); +} + +#[test] +fn transient_storage_work() { + let (code, _code_hash) = compile_module("transient_storage").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .native_value(min_balance * 100) + .build_and_unwrap_contract(); + + builder::bare_call(addr).build_and_unwrap_result(); + }); +} + +#[test] +fn transient_storage_limit_in_call() { + let (binary_caller, _code_hash_caller) = + compile_module("create_transient_storage_and_call").unwrap(); + let (binary_callee, _code_hash_callee) = compile_module("set_transient_storage").unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(binary_caller)).build_and_unwrap_contract(); + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); + + // Call contracts with storage values within the limit. + // Caller and Callee contracts each set a transient storage value of size 100. + assert_ok!(builder::call(addr_caller) + .data((100u32, 100u32, &addr_callee).encode()) + .build(),); + + // Call a contract with a storage value that is too large. + // Limit exceeded in the caller contract. + assert_err_ignore_postinfo!( + builder::call(addr_caller) + .data((4u32 * 1024u32, 200u32, &addr_callee).encode()) + .build(), + >::OutOfTransientStorage, + ); + + // Call a contract with a storage value that is too large. + // Limit exceeded in the callee contract. + assert_err_ignore_postinfo!( + builder::call(addr_caller) + .data((50u32, 4 * 1024u32, &addr_callee).encode()) + .build(), + >::ContractTrapped + ); + }); +} + +#[test] +fn deploy_and_call_other_contract() { + let (caller_binary, _caller_code_hash) = compile_module("caller_contract").unwrap(); + let (callee_binary, callee_code_hash) = compile_module("return_with_data").unwrap(); + let code_load_weight = crate::vm::code_load_weight(callee_binary.len() as u32); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + + // Create + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let Contract { addr: caller_addr, account_id: caller_account } = + builder::bare_instantiate(Code::Upload(caller_binary)) + .native_value(100_000) + .build_and_unwrap_contract(); + + let callee_addr = create2( + &caller_addr, + &callee_binary, + &[0, 1, 34, 51, 68, 85, 102, 119], // hard coded in binary + &[0u8; 32], + ); + let callee_account = ::AddressMapper::to_account_id(&callee_addr); + + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + callee_binary, + deposit_limit::(), + ) + .unwrap(); + + // Drop previous events + initialize_block(2); + + // Call BOB contract, which attempts to instantiate and call the callee contract and + // makes various assertions on the results from those calls. + assert_ok!(builder::call(caller_addr) + .data( + (callee_code_hash, code_load_weight.ref_time(), code_load_weight.proof_size()) + .encode() + ) + .build()); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: callee_account.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: callee_account.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: callee_account.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: caller_account.clone(), + to: callee_account.clone(), + amount: 32768 // hardcoded in binary + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: caller_account.clone(), + to: callee_account.clone(), + amount: 32768, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::TransferAndHold { + reason: ::RuntimeHoldReason::Contracts( + HoldReason::StorageDepositReserve, + ), + source: ALICE, + dest: callee_account.clone(), + transferred: 555, + }), + topics: vec![], + }, + ] + ); + }); +} + +#[test] +fn delegate_call() { + let (caller_binary, _caller_code_hash) = compile_module("delegate_call").unwrap(); + let (callee_binary, _callee_code_hash) = compile_module("delegate_call_lib").unwrap(); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the 'caller' + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_binary)) + .native_value(300_000) + .build_and_unwrap_contract(); + + // Instantiate the 'callee' + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_binary)) + .native_value(100_000) + .build_and_unwrap_contract(); + + assert_ok!(builder::call(caller_addr) + .value(1337) + .data((callee_addr, u64::MAX, u64::MAX).encode()) + .build()); + }); +} + +#[test] +fn delegate_call_non_existant_is_noop() { + let (caller_binary, _caller_code_hash) = compile_module("delegate_call_simple").unwrap(); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the 'caller' + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_binary)) + .native_value(300_000) + .build_and_unwrap_contract(); + + assert_ok!(builder::call(caller_addr) + .value(1337) + .data((BOB_ADDR, u64::MAX, u64::MAX).encode()) + .build()); + + assert_eq!(get_balance(&BOB_FALLBACK), 0); + }); +} + +#[test] +fn delegate_call_with_weight_limit() { + let (caller_binary, _caller_code_hash) = compile_module("delegate_call").unwrap(); + let (callee_binary, _callee_code_hash) = compile_module("delegate_call_lib").unwrap(); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the 'caller' + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_binary)) + .native_value(300_000) + .build_and_unwrap_contract(); + + // Instantiate the 'callee' + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_binary)) + .native_value(100_000) + .build_and_unwrap_contract(); + + // fails, not enough weight + assert_err!( + builder::bare_call(caller_addr) + .native_value(1337) + .data((callee_addr, 100u64, 100u64).encode()) + .build() + .result, + Error::::ContractTrapped, + ); + + assert_ok!(builder::call(caller_addr) + .value(1337) + .data((callee_addr, 500_000_000u64, 100_000u64).encode()) + .build()); + }); +} + +#[test] +fn delegate_call_with_deposit_limit() { + let (caller_binary, _caller_code_hash) = compile_module("delegate_call_deposit_limit").unwrap(); + let (callee_binary, _callee_code_hash) = compile_module("delegate_call_lib").unwrap(); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the 'caller' + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_binary)) + .native_value(300_000) + .build_and_unwrap_contract(); + + // Instantiate the 'callee' + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_binary)) + .native_value(100_000) + .build_and_unwrap_contract(); + + // Delegate call will write 1 storage and deposit of 2 (1 item) + 32 (bytes) is required. + // + 32 + 16 for blake2_128concat + // Fails, not enough deposit + let ret = builder::bare_call(caller_addr) + .native_value(1337) + .data((callee_addr, 81u64).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); + + assert_ok!(builder::call(caller_addr) + .value(1337) + .data((callee_addr, 82u64).encode()) + .build()); + }); +} + +#[test] +fn transfer_expendable_cannot_kill_account() { + let (binary, _code_hash) = compile_module("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the BOB contract. + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) + .native_value(1_000) + .build_and_unwrap_contract(); + + // Check that the BOB contract has been instantiated. + get_contract(&addr); + + let account = ::AddressMapper::to_account_id(&addr); + let total_balance = ::Currency::total_balance(&account); + + assert_eq!( + get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account), + contract_base_deposit(&addr) + ); + + // Some or the total balance is held, so it can't be transferred. + assert_err!( + <::Currency as Mutate>::transfer( + &account, + &ALICE, + total_balance, + Preservation::Expendable, + ), + TokenError::FundsUnavailable, + ); + + assert_eq!(::Currency::total_balance(&account), total_balance); + }); +} + +#[test] +fn cannot_self_destruct_through_draining() { + let (binary, _code_hash) = compile_module("drain").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let value = 1_000; + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) + .native_value(value) + .build_and_unwrap_contract(); + let account = ::AddressMapper::to_account_id(&addr); + + // Check that the BOB contract has been instantiated. + get_contract(&addr); + + // Call BOB which makes it send all funds to the zero address + // The contract code asserts that the transfer fails with the correct error code + assert_ok!(builder::call(addr).build()); + + // Make sure the account wasn't remove by sending all free balance away. + assert_eq!( + ::Currency::total_balance(&account), + value + contract_base_deposit(&addr) + min_balance, + ); + }); +} + +#[test] +fn cannot_self_destruct_through_storage_refund_after_price_change() { + let (binary, _code_hash) = compile_module("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let contract = builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); + let info_deposit = contract_base_deposit(&contract.addr); + + // Check that the contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&contract.addr).total_deposit(), info_deposit); + assert_eq!(get_contract(&contract.addr).extra_deposit(), 0); + assert_eq!( + ::Currency::total_balance(&contract.account_id), + info_deposit + min_balance + ); + + // Create 100 (16 + 32 bytes for key for blake128 concat) bytes of storage with a + // price of per byte and a single storage item of price 2 + assert_ok!(builder::call(contract.addr).data(100u32.to_le_bytes().to_vec()).build()); + assert_eq!(get_contract(&contract.addr).total_deposit(), info_deposit + 100 + 16 + 32 + 2); + + // Increase the byte price and trigger a refund. This should not have any influence + // because the removal is pro rata and exactly those 100 bytes should have been + // removed as we didn't delete the key. + DEPOSIT_PER_BYTE.with(|c| *c.borrow_mut() = 500); + assert_ok!(builder::call(contract.addr).data(0u32.to_le_bytes().to_vec()).build()); + + // Make sure the account wasn't removed by the refund + assert_eq!( + ::Currency::total_balance(&contract.account_id), + get_contract(&contract.addr).total_deposit() + min_balance, + ); + // + 1 because due to fixed point arithmetic we can sometimes refund + // one unit to little + assert_eq!(get_contract(&contract.addr).extra_deposit(), 16 + 32 + 2 + 1); + }); +} + +#[test] +fn cannot_self_destruct_while_live() { + let (binary, _code_hash) = compile_module("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the BOB contract. + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) + .native_value(100_000) + .build_and_unwrap_contract(); + + // Check that the BOB contract has been instantiated. + get_contract(&addr); + + // Call BOB with input data, forcing it make a recursive call to itself to + // self-destruct, resulting in a trap. + assert_err_ignore_postinfo!( + builder::call(addr).data(vec![0]).build(), + Error::::ContractTrapped, + ); + + // Check that BOB is still there. + get_contract(&addr); + }); +} + +#[test] +fn self_destruct_works() { + let (binary, code_hash) = compile_module("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(1_000).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&DJANGO_FALLBACK, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let contract = builder::bare_instantiate(Code::Upload(binary)) + .native_value(100_000) + .build_and_unwrap_contract(); + + let hold_balance = contract_base_deposit(&contract.addr); + + // Check that the BOB contract has been instantiated. + let _ = get_contract(&contract.addr); + + // Drop all previous events + initialize_block(2); + + // Call BOB without input data which triggers termination. + assert_matches!(builder::call(contract.addr).build(), Ok(_)); + + // Check that code is still there but refcount dropped to zero. + assert_refcount!(&code_hash, 0); + + // Check that account is gone + assert!(get_contract_checked(&contract.addr).is_none()); + assert_eq!(::Currency::total_balance(&contract.account_id), 0); + + // Check that the beneficiary (django) got remaining balance. + assert_eq!( + ::Currency::free_balance(DJANGO_FALLBACK), + 1_000_000 + 100_000 + min_balance + ); + + // Check that the Alice is missing Django's benefit. Within ALICE's total balance + // there's also the code upload deposit held. + assert_eq!( + ::Currency::total_balance(&ALICE), + 1_000_000 - (100_000 + min_balance) + ); + + pretty_assertions::assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::TransferOnHold { + reason: ::RuntimeHoldReason::Contracts( + HoldReason::StorageDepositReserve, + ), + source: contract.account_id.clone(), + dest: ALICE, + amount: hold_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::KilledAccount { + account: contract.account_id.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: contract.account_id.clone(), + to: DJANGO_FALLBACK, + amount: 100_000 + min_balance, + }), + topics: vec![], + }, + ], + ); + }); +} + +// This tests that one contract cannot prevent another from self-destructing by sending it +// additional funds after it has been drained. +#[test] +fn destroy_contract_and_transfer_funds() { + let (callee_binary, callee_code_hash) = compile_module("self_destruct").unwrap(); + let (caller_binary, _caller_code_hash) = compile_module("destroy_and_transfer").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create code hash for bob to instantiate + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + callee_binary.clone(), + deposit_limit::(), + ) + .unwrap(); + + // This deploys the BOB contract, which in turn deploys the CHARLIE contract during + // construction. + let Contract { addr: addr_bob, .. } = + builder::bare_instantiate(Code::Upload(caller_binary)) + .native_value(200_000) + .data(callee_code_hash.as_ref().to_vec()) + .build_and_unwrap_contract(); + + // Check that the CHARLIE contract has been instantiated. + let salt = [47; 32]; // hard coded in fixture. + let addr_charlie = create2(&addr_bob, &callee_binary, &[], &salt); + get_contract(&addr_charlie); + + // Call BOB, which calls CHARLIE, forcing CHARLIE to self-destruct. + assert_ok!(builder::call(addr_bob).data(addr_charlie.encode()).build()); + + // Check that CHARLIE has moved on to the great beyond (ie. died). + assert!(get_contract_checked(&addr_charlie).is_none()); + }); +} + +#[test] +fn cannot_self_destruct_in_constructor() { + let (binary, _) = compile_module("self_destructing_constructor").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Fail to instantiate the BOB because the constructor calls seal_terminate. + assert_err_ignore_postinfo!( + builder::instantiate_with_code(binary).value(100_000).build(), + Error::::TerminatedInConstructor, + ); + }); +} + +#[test] +fn crypto_hash_keccak_256() { + let (binary, _code_hash) = compile_module("crypto_hash_keccak_256").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the CRYPTO_HASH_KECCAK_256 contract. + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) + .native_value(100_000) + .build_and_unwrap_contract(); + // Perform the call. + let input = b"_DEAD_BEEF"; + use sp_io::hashing::*; + // Wraps a hash function into a more dynamic form usable for testing. + macro_rules! dyn_hash_fn { + ($name:ident) => { + Box::new(|input| $name(input).as_ref().to_vec().into_boxed_slice()) + }; + } + // The hash function and its associated output byte lengths. + let hash_fn: Box Box<[u8]>> = dyn_hash_fn!(keccak_256); + let expected_size: usize = 32; + // Test the hash function for the input: "_DEAD_BEEF" + let result = builder::bare_call(addr).data(input.to_vec()).build_and_unwrap_result(); + assert!(!result.did_revert()); + let expected = hash_fn(input.as_ref()); + assert_eq!(&result.data[..expected_size], &*expected); + }) +} + +#[test] +fn transfer_return_code() { + let (binary, _code_hash) = compile_module("transfer_return_code").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let contract = builder::bare_instantiate(Code::Upload(binary)) + .native_value(min_balance * 100) + .build_and_unwrap_contract(); + + // Contract has only the minimal balance so any transfer will fail. + ::Currency::set_balance(&contract.account_id, min_balance); + let result = builder::bare_call(contract.addr).build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::TransferFailed); + }); +} + +#[test] +fn call_return_code() { + let (caller_code, _caller_hash) = compile_module("call_return_code").unwrap(); + let (callee_code, _callee_hash) = compile_module("ok_trap_revert").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); + + let bob = builder::bare_instantiate(Code::Upload(caller_code)) + .native_value(min_balance * 100) + .build_and_unwrap_contract(); + + // BOB cannot pay the ed which is needed to pull DJANGO into existence + // this does trap the caller instead of returning an error code + // reasoning is that this error state does not exist on eth where + // ed does not exist. We hide this fact from the contract. + let result = builder::bare_call(bob.addr) + .data((DJANGO_ADDR, u256_bytes(1)).encode()) + .origin(RuntimeOrigin::signed(BOB)) + .build(); + assert_err!(result.result, >::StorageDepositNotEnoughFunds); + + // Contract calls into Django which is no valid contract + // This will be a balance transfer into a new account + // with more than the contract has which will make the transfer fail + let value = Pallet::::convert_native_to_evm(min_balance * 200); + let result = builder::bare_call(bob.addr) + .data( + AsRef::<[u8]>::as_ref(&DJANGO_ADDR) + .iter() + .chain(&value.to_little_endian()) + .cloned() + .collect(), + ) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::TransferFailed); + + // Sending below the minimum balance should result in success. + // The ED is charged from the call origin. + let alice_before = get_balance(&ALICE_FALLBACK); + assert_eq!(get_balance(&DJANGO_FALLBACK), 0); + + let value = Pallet::::convert_native_to_evm(1u64); + let result = builder::bare_call(bob.addr) + .data( + AsRef::<[u8]>::as_ref(&DJANGO_ADDR) + .iter() + .chain(&value.to_little_endian()) + .cloned() + .collect(), + ) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::Success); + assert_eq!(get_balance(&DJANGO_FALLBACK), min_balance + 1); + assert_eq!(get_balance(&ALICE_FALLBACK), alice_before - min_balance); + + let django = builder::bare_instantiate(Code::Upload(callee_code)) + .origin(RuntimeOrigin::signed(CHARLIE)) + .native_value(min_balance * 100) + .build_and_unwrap_contract(); + + // Sending more than the contract has will make the transfer fail. + let value = Pallet::::convert_native_to_evm(min_balance * 300); + let result = builder::bare_call(bob.addr) + .data( + AsRef::<[u8]>::as_ref(&django.addr) + .iter() + .chain(&value.to_little_endian()) + .chain(&0u32.to_le_bytes()) + .cloned() + .collect(), + ) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::TransferFailed); + + // Contract has enough balance but callee reverts because "1" is passed. + ::Currency::set_balance(&bob.account_id, min_balance + 1000); + let value = Pallet::::convert_native_to_evm(5u64); + let result = builder::bare_call(bob.addr) + .data( + AsRef::<[u8]>::as_ref(&django.addr) + .iter() + .chain(&value.to_little_endian()) + .chain(&1u32.to_le_bytes()) + .cloned() + .collect(), + ) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CalleeReverted); + + // Contract has enough balance but callee traps because "2" is passed. + let result = builder::bare_call(bob.addr) + .data( + AsRef::<[u8]>::as_ref(&django.addr) + .iter() + .chain(&value.to_little_endian()) + .chain(&2u32.to_le_bytes()) + .cloned() + .collect(), + ) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); + }); +} + +#[test] +fn instantiate_return_code() { + let (caller_code, _caller_hash) = compile_module("instantiate_return_code").unwrap(); + let (callee_code, callee_hash) = compile_module("ok_trap_revert").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); + let callee_hash = callee_hash.as_ref().to_vec(); + + assert_ok!(builder::instantiate_with_code(callee_code).value(min_balance * 100).build()); + + let contract = builder::bare_instantiate(Code::Upload(caller_code)) + .native_value(min_balance * 100) + .build_and_unwrap_contract(); + + // bob cannot pay the ED to create the contract as he has no money + // this traps the caller rather than returning an error + let result = builder::bare_call(contract.addr) + .data(callee_hash.iter().chain(&0u32.to_le_bytes()).cloned().collect()) + .origin(RuntimeOrigin::signed(BOB)) + .build(); + assert_err!(result.result, >::StorageDepositNotEnoughFunds); + + // Contract has only the minimal balance so any transfer will fail. + ::Currency::set_balance(&contract.account_id, min_balance); + let result = builder::bare_call(contract.addr) + .data(callee_hash.iter().chain(&0u32.to_le_bytes()).cloned().collect()) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::TransferFailed); + + // Contract has enough balance but the passed code hash is invalid + ::Currency::set_balance(&contract.account_id, min_balance + 10_000); + let result = builder::bare_call(contract.addr).data(vec![0; 36]).build(); + assert_err!(result.result, >::CodeNotFound); + + // Contract has enough balance but callee reverts because "1" is passed. + let result = builder::bare_call(contract.addr) + .data(callee_hash.iter().chain(&1u32.to_le_bytes()).cloned().collect()) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CalleeReverted); + + // Contract has enough balance but callee traps because "2" is passed. + let result = builder::bare_call(contract.addr) + .data(callee_hash.iter().chain(&2u32.to_le_bytes()).cloned().collect()) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); + + // Contract instantiation succeeds + let result = builder::bare_call(contract.addr) + .data(callee_hash.iter().chain(&0u32.to_le_bytes()).cloned().collect()) + .build_and_unwrap_result(); + assert_return_code!(result, 0); + + // Contract instantiation fails because the same salt is being used again. + let result = builder::bare_call(contract.addr) + .data(callee_hash.iter().chain(&0u32.to_le_bytes()).cloned().collect()) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::DuplicateContractAddress); + }); +} + +#[test] +fn lazy_removal_works() { + let (code, _hash) = compile_module("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let contract = builder::bare_instantiate(Code::Upload(code)) + .native_value(min_balance * 100) + .build_and_unwrap_contract(); + + let info = get_contract(&contract.addr); + let trie = &info.child_trie_info(); + + // Put value into the contracts child trie + child::put(trie, &[99], &42); + + // Terminate the contract + assert_ok!(builder::call(contract.addr).build()); + + // Contract info should be gone + assert!(!>::contains_key(&contract.addr)); + + // But value should be still there as the lazy removal did not run, yet. + assert_matches!(child::get(trie, &[99]), Some(42)); + + // Run the lazy removal + Contracts::on_idle(System::block_number(), Weight::MAX); + + // Value should be gone now + assert_matches!(child::get::(trie, &[99]), None); + }); +} + +#[test] +fn lazy_batch_removal_works() { + let (code, _hash) = compile_module("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let mut tries: Vec = vec![]; + + for i in 0..3u8 { + let contract = builder::bare_instantiate(Code::Upload(code.clone())) + .native_value(min_balance * 100) + .salt(Some([i; 32])) + .build_and_unwrap_contract(); + + let info = get_contract(&contract.addr); + let trie = &info.child_trie_info(); + + // Put value into the contracts child trie + child::put(trie, &[99], &42); + + // Terminate the contract. Contract info should be gone, but value should be still + // there as the lazy removal did not run, yet. + assert_ok!(builder::call(contract.addr).build()); + + assert!(!>::contains_key(&contract.addr)); + assert_matches!(child::get(trie, &[99]), Some(42)); + + tries.push(trie.clone()) + } + + // Run single lazy removal + Contracts::on_idle(System::block_number(), Weight::MAX); + + // The single lazy removal should have removed all queued tries + for trie in tries.iter() { + assert_matches!(child::get::(trie, &[99]), None); + } + }); +} + +#[test] +fn ref_time_left_api_works() { + let (code, _) = compile_module("ref_time_left").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor calls ref_time_left twice and asserts it to decrease + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It echoes back the ref_time returned by the ref_time_left API. + let received = builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + + let returned_value = u64::from_le_bytes(received.data[..8].try_into().unwrap()); + assert!(returned_value > 0); + assert!(returned_value < GAS_LIMIT.ref_time()); + }); +} + +#[test] +fn lazy_removal_partial_remove_works() { + let (code, _hash) = compile_module("self_destruct").unwrap(); + + // We create a contract with some extra keys above the weight limit + let extra_keys = 7u32; + let mut meter = WeightMeter::with_limit(Weight::from_parts(5_000_000_000, 100 * 1024)); + let (weight_per_key, max_keys) = ContractInfo::::deletion_budget(&meter); + let vals: Vec<_> = (0..max_keys + extra_keys) + .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode())) + .collect(); + + let mut ext = ExtBuilder::default().existential_deposit(50).build(); + + let trie = ext.execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .native_value(min_balance * 100) + .build_and_unwrap_contract(); + + let info = get_contract(&addr); + + // Put value into the contracts child trie + for val in &vals { + info.write(&Key::Fix(val.0), Some(val.2.clone()), None, false).unwrap(); + } + AccountInfo::::insert_contract(&addr, info.clone()); + + // Terminate the contract + assert_ok!(builder::call(addr).build()); + + // Contract info should be gone + assert!(!>::contains_key(&addr)); + + let trie = info.child_trie_info(); + + // But value should be still there as the lazy removal did not run, yet. + for val in &vals { + assert_eq!(child::get::(&trie, &blake2_256(&val.0)), Some(val.1)); + } + + trie.clone() + }); + + // The lazy removal limit only applies to the backend but not to the overlay. + // This commits all keys from the overlay to the backend. + ext.commit_all().unwrap(); + + ext.execute_with(|| { + // Run the lazy removal + ContractInfo::::process_deletion_queue_batch(&mut meter); + + // Weight should be exhausted because we could not even delete all keys + assert!(!meter.can_consume(weight_per_key)); + + let mut num_deleted = 0u32; + let mut num_remaining = 0u32; + + for val in &vals { + match child::get::(&trie, &blake2_256(&val.0)) { + None => num_deleted += 1, + Some(x) if x == val.1 => num_remaining += 1, + Some(_) => panic!("Unexpected value in contract storage"), + } + } + + // All but one key is removed + assert_eq!(num_deleted + num_remaining, vals.len() as u32); + assert_eq!(num_deleted, max_keys); + assert_eq!(num_remaining, extra_keys); + }); +} + +#[test] +fn lazy_removal_does_no_run_on_low_remaining_weight() { + let (code, _hash) = compile_module("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .native_value(min_balance * 100) + .build_and_unwrap_contract(); + + let info = get_contract(&addr); + let trie = &info.child_trie_info(); + + // Put value into the contracts child trie + child::put(trie, &[99], &42); + + // Terminate the contract + assert_ok!(builder::call(addr).build()); + + // Contract info should be gone + assert!(!>::contains_key(&addr)); + + // But value should be still there as the lazy removal did not run, yet. + assert_matches!(child::get(trie, &[99]), Some(42)); + + // Assign a remaining weight which is too low for a successful deletion of the contract + let low_remaining_weight = + <::WeightInfo as WeightInfo>::on_process_deletion_queue_batch(); + + // Run the lazy removal + Contracts::on_idle(System::block_number(), low_remaining_weight); + + // Value should still be there, since remaining weight was too low for removal + assert_matches!(child::get::(trie, &[99]), Some(42)); + + // Run the lazy removal while deletion_queue is not full + Contracts::on_initialize(System::block_number()); + + // Value should still be there, since deletion_queue was not full + assert_matches!(child::get::(trie, &[99]), Some(42)); + + // Run on_idle with max remaining weight, this should remove the value + Contracts::on_idle(System::block_number(), Weight::MAX); + + // Value should be gone + assert_matches!(child::get::(trie, &[99]), None); + }); +} + +#[test] +fn lazy_removal_does_not_use_all_weight() { + let (code, _hash) = compile_module("self_destruct").unwrap(); + + let mut meter = WeightMeter::with_limit(Weight::from_parts(5_000_000_000, 100 * 1024)); + let mut ext = ExtBuilder::default().existential_deposit(50).build(); + + let (trie, vals, weight_per_key) = ext.execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .native_value(min_balance * 100) + .build_and_unwrap_contract(); + + let info = get_contract(&addr); + let (weight_per_key, max_keys) = ContractInfo::::deletion_budget(&meter); + assert!(max_keys > 0); + + // We create a contract with one less storage item than we can remove within the limit + let vals: Vec<_> = (0..max_keys - 1) + .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode())) + .collect(); + + // Put value into the contracts child trie + for val in &vals { + info.write(&Key::Fix(val.0), Some(val.2.clone()), None, false).unwrap(); + } + AccountInfo::::insert_contract(&addr, info.clone()); + + // Terminate the contract + assert_ok!(builder::call(addr).build()); + + // Contract info should be gone + assert!(!>::contains_key(&addr)); + + let trie = info.child_trie_info(); + + // But value should be still there as the lazy removal did not run, yet. + for val in &vals { + assert_eq!(child::get::(&trie, &blake2_256(&val.0)), Some(val.1)); + } + + (trie, vals, weight_per_key) + }); + + // The lazy removal limit only applies to the backend but not to the overlay. + // This commits all keys from the overlay to the backend. + ext.commit_all().unwrap(); + + ext.execute_with(|| { + // Run the lazy removal + ContractInfo::::process_deletion_queue_batch(&mut meter); + let base_weight = + <::WeightInfo as WeightInfo>::on_process_deletion_queue_batch(); + assert_eq!(meter.consumed(), weight_per_key.mul(vals.len() as _) + base_weight); + + // All the keys are removed + for val in vals { + assert_eq!(child::get::(&trie, &blake2_256(&val.0)), None); + } + }); +} + +#[test] +fn deletion_queue_ring_buffer_overflow() { + let (code, _hash) = compile_module("self_destruct").unwrap(); + let mut ext = ExtBuilder::default().existential_deposit(50).build(); + + // setup the deletion queue with custom counters + ext.execute_with(|| { + let queue = DeletionQueueManager::from_test_values(u32::MAX - 1, u32::MAX - 1); + >::set(queue); + }); + + // commit the changes to the storage + ext.commit_all().unwrap(); + + ext.execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let mut tries: Vec = vec![]; + + // add 3 contracts to the deletion queue + for i in 0..3u8 { + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code.clone())) + .native_value(min_balance * 100) + .salt(Some([i; 32])) + .build_and_unwrap_contract(); + + let info = get_contract(&addr); + let trie = &info.child_trie_info(); + + // Put value into the contracts child trie + child::put(trie, &[99], &42); + + // Terminate the contract. Contract info should be gone, but value should be still + // there as the lazy removal did not run, yet. + assert_ok!(builder::call(addr).build()); + + assert!(!>::contains_key(&addr)); + assert_matches!(child::get(trie, &[99]), Some(42)); + + tries.push(trie.clone()) + } + + // Run single lazy removal + Contracts::on_idle(System::block_number(), Weight::MAX); + + // The single lazy removal should have removed all queued tries + for trie in tries.iter() { + assert_matches!(child::get::(trie, &[99]), None); + } + + // insert and delete counter values should go from u32::MAX - 1 to 1 + assert_eq!(>::get().as_test_tuple(), (1, 1)); + }) +} +#[test] +fn refcounter() { + let (binary, code_hash) = compile_module("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Create two contracts with the same code and check that they do in fact share it. + let Contract { addr: addr0, .. } = builder::bare_instantiate(Code::Upload(binary.clone())) + .native_value(min_balance * 100) + .salt(Some([0; 32])) + .build_and_unwrap_contract(); + let Contract { addr: addr1, .. } = builder::bare_instantiate(Code::Upload(binary.clone())) + .native_value(min_balance * 100) + .salt(Some([1; 32])) + .build_and_unwrap_contract(); + assert_refcount!(code_hash, 2); + + // Sharing should also work with the usual instantiate call + let Contract { addr: addr2, .. } = builder::bare_instantiate(Code::Existing(code_hash)) + .native_value(min_balance * 100) + .salt(Some([2; 32])) + .build_and_unwrap_contract(); + assert_refcount!(code_hash, 3); + + // Terminating one contract should decrement the refcount + assert_ok!(builder::call(addr0).build()); + assert_refcount!(code_hash, 2); + + // remove another one + assert_ok!(builder::call(addr1).build()); + assert_refcount!(code_hash, 1); + + // Pristine code should still be there + PristineCode::::get(code_hash).unwrap(); + + // remove the last contract + assert_ok!(builder::call(addr2).build()); + assert_refcount!(code_hash, 0); + + // refcount is `0` but code should still exists because it needs to be removed manually + assert!(crate::PristineCode::::contains_key(&code_hash)); + }); +} + +#[test] +fn gas_estimation_for_subcalls() { + let (caller_code, _caller_hash) = compile_module("call_with_limit").unwrap(); + let (dummy_code, _callee_hash) = compile_module("dummy").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 2_000 * min_balance); + + let Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(caller_code)) + .native_value(min_balance * 100) + .build_and_unwrap_contract(); + + let Contract { addr: addr_dummy, .. } = builder::bare_instantiate(Code::Upload(dummy_code)) + .native_value(min_balance * 100) + .build_and_unwrap_contract(); + + // Run the test for all of those weight limits for the subcall + let weights = [ + Weight::MAX, + GAS_LIMIT, + GAS_LIMIT * 2, + GAS_LIMIT / 5, + Weight::from_parts(u64::MAX, GAS_LIMIT.proof_size()), + Weight::from_parts(GAS_LIMIT.ref_time(), u64::MAX), + ]; + + let (sub_addr, sub_input) = (addr_dummy.as_ref(), vec![]); + + for weight in weights { + let input: Vec = sub_addr + .iter() + .cloned() + .chain(weight.ref_time().to_le_bytes()) + .chain(weight.proof_size().to_le_bytes()) + .chain(sub_input.clone()) + .collect(); + + // Call in order to determine the gas that is required for this call + let result_orig = builder::bare_call(addr_caller).data(input.clone()).build(); + assert_ok!(&result_orig.result); + assert_eq!(result_orig.gas_required, result_orig.gas_consumed); + + // Make the same call using the estimated gas. Should succeed. + let result = builder::bare_call(addr_caller) + .gas_limit(result_orig.gas_required) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) + .data(input.clone()) + .build(); + assert_ok!(&result.result); + + // Check that it fails with too little ref_time + let result = builder::bare_call(addr_caller) + .gas_limit(result_orig.gas_required.sub_ref_time(1)) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) + .data(input.clone()) + .build(); + assert_err!(result.result, >::OutOfGas); + + // Check that it fails with too little proof_size + let result = builder::bare_call(addr_caller) + .gas_limit(result_orig.gas_required.sub_proof_size(1)) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) + .data(input.clone()) + .build(); + assert_err!(result.result, >::OutOfGas); + } + }); +} + +#[test] +fn call_runtime_reentrancy_guarded() { + use crate::precompiles::Precompile; + use alloy_core::sol_types::SolInterface; + use precompiles::{INoInfo, NoInfo}; + + let precompile_addr = H160(NoInfo::::MATCHER.base_address()); + + let (callee_code, _callee_hash) = compile_module("dummy").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); + + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(callee_code)) + .native_value(min_balance * 100) + .salt(Some([1; 32])) + .build_and_unwrap_contract(); + + // Call pallet_revive call() dispatchable + let call = RuntimeCall::Contracts(crate::Call::call { + dest: addr_callee, + value: 0, + gas_limit: GAS_LIMIT / 3, + storage_deposit_limit: deposit_limit::(), + data: vec![], + }) + .encode(); + + // Call runtime to re-enter back to contracts engine by + // calling dummy contract + let result = builder::bare_call(precompile_addr) + .data( + INoInfo::INoInfoCalls::callRuntime(INoInfo::callRuntimeCall { call: call.into() }) + .abi_encode(), + ) + .build(); + // Call to runtime should fail because of the re-entrancy guard + assert_err!(result.result, >::ReenteredPallet); + }); +} + +#[test] +fn sr25519_verify() { + let (binary, _code_hash) = compile_module("sr25519_verify").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the sr25519_verify contract. + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary)) + .native_value(100_000) + .build_and_unwrap_contract(); + + let call_with = |message: &[u8; 11]| { + // Alice's signature for "hello world" + #[rustfmt::skip] + let signature: [u8; 64] = [ + 184, 49, 74, 238, 78, 165, 102, 252, 22, 92, 156, 176, 124, 118, 168, 116, 247, + 99, 0, 94, 2, 45, 9, 170, 73, 222, 182, 74, 60, 32, 75, 64, 98, 174, 69, 55, 83, + 85, 180, 98, 208, 75, 231, 57, 205, 62, 4, 105, 26, 136, 172, 17, 123, 99, 90, 255, + 228, 54, 115, 63, 30, 207, 205, 131, + ]; + + // Alice's public key + #[rustfmt::skip] + let public_key: [u8; 32] = [ + 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, + 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, + ]; + + let mut params = vec![]; + params.extend_from_slice(&signature); + params.extend_from_slice(&public_key); + params.extend_from_slice(message); + + builder::bare_call(addr).data(params).build_and_unwrap_result() + }; + + // verification should succeed for "hello world" + assert_return_code!(call_with(&b"hello world"), RuntimeReturnCode::Success); + + // verification should fail for other messages + assert_return_code!(call_with(&b"hello worlD"), RuntimeReturnCode::Sr25519VerifyFailed); + }); +} + +#[test] +fn upload_code_works() { + let (binary, code_hash) = compile_module("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert!(!PristineCode::::contains_key(&code_hash)); + assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), binary, 1_000,)); + // Ensure the contract was stored and get expected deposit amount to be reserved. + expected_deposit(ensure_stored(code_hash)); + }); +} + +#[test] +fn upload_code_limit_too_low() { + let (binary, _code_hash) = compile_module("dummy").unwrap(); + let deposit_expected = expected_deposit(binary.len()); + let deposit_insufficient = deposit_expected.saturating_sub(1); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::upload_code(RuntimeOrigin::signed(ALICE), binary, deposit_insufficient,), + >::StorageDepositLimitExhausted, + ); + + assert_eq!(System::events(), vec![]); + }); +} + +#[test] +fn upload_code_not_enough_balance() { + let (binary, _code_hash) = compile_module("dummy").unwrap(); + let deposit_expected = expected_deposit(binary.len()); + let deposit_insufficient = deposit_expected.saturating_sub(1); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, deposit_insufficient); + + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::upload_code(RuntimeOrigin::signed(ALICE), binary, 1_000,), + >::StorageDepositNotEnoughFunds, + ); + + assert_eq!(System::events(), vec![]); + }); +} + +#[test] +fn remove_code_works() { + let (binary, code_hash) = compile_module("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), binary, 1_000,)); + // Ensure the contract was stored and get expected deposit amount to be reserved. + expected_deposit(ensure_stored(code_hash)); + assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash)); + }); +} + +#[test] +fn remove_code_wrong_origin() { + let (binary, code_hash) = compile_module("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), binary, 1_000,)); + // Ensure the contract was stored and get expected deposit amount to be reserved. + expected_deposit(ensure_stored(code_hash)); + + assert_noop!( + Contracts::remove_code(RuntimeOrigin::signed(BOB), code_hash), + sp_runtime::traits::BadOrigin, + ); + }); +} + +#[test] +fn remove_code_in_use() { + let (binary, code_hash) = compile_module("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + assert_ok!(builder::instantiate_with_code(binary).build()); + + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), + >::CodeInUse, + ); + + assert_eq!(System::events(), vec![]); + }); +} + +#[test] +fn remove_code_not_found() { + let (_binary, code_hash) = compile_module("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), + >::CodeNotFound, + ); + + assert_eq!(System::events(), vec![]); + }); +} + +#[test] +fn instantiate_with_zero_balance_works() { + let (binary, code_hash) = compile_module("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Drop previous events + initialize_block(2); + + // Instantiate the BOB contract. + let Contract { addr, account_id } = + builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); + + // Ensure the contract was stored and get expected deposit amount to be reserved. + expected_deposit(ensure_stored(code_hash)); + + // Make sure the account exists even though no free balance was send + assert_eq!(::Currency::free_balance(&account_id), min_balance); + assert_eq!( + ::Currency::total_balance(&account_id), + min_balance + contract_base_deposit(&addr) + ); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Held { + reason: ::RuntimeHoldReason::Contracts( + HoldReason::CodeUploadDepositReserve, + ), + who: ALICE, + amount: 776, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: account_id.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: account_id.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: account_id.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: ALICE_ADDR, + contract: addr, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::TransferAndHold { + reason: ::RuntimeHoldReason::Contracts( + HoldReason::StorageDepositReserve, + ), + source: ALICE, + dest: account_id, + transferred: 336, + }), + topics: vec![], + }, + ] + ); + }); +} + +#[test] +fn instantiate_with_below_existential_deposit_works() { + let (binary, code_hash) = compile_module("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + let value = 50; + + // Drop previous events + initialize_block(2); + + // Instantiate the BOB contract. + let Contract { addr, account_id } = builder::bare_instantiate(Code::Upload(binary)) + .native_value(value) + .build_and_unwrap_contract(); + + // Ensure the contract was stored and get expected deposit amount to be reserved. + expected_deposit(ensure_stored(code_hash)); + // Make sure the account exists even though not enough free balance was send + assert_eq!(::Currency::free_balance(&account_id), min_balance + value); + assert_eq!( + ::Currency::total_balance(&account_id), + min_balance + value + contract_base_deposit(&addr) + ); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Held { + reason: ::RuntimeHoldReason::Contracts( + HoldReason::CodeUploadDepositReserve, + ), + who: ALICE, + amount: 776, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: account_id.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: account_id.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: account_id.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: account_id.clone(), + amount: 50, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: ALICE_ADDR, + contract: addr, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::TransferAndHold { + reason: ::RuntimeHoldReason::Contracts( + HoldReason::StorageDepositReserve, + ), + source: ALICE, + dest: account_id.clone(), + transferred: 336, + }), + topics: vec![], + }, + ] + ); + }); +} + +#[test] +fn storage_deposit_works() { + let (binary, _code_hash) = compile_module("multi_store").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr, account_id } = + builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); + + let mut deposit = contract_base_deposit(&addr); + + // Drop previous events + initialize_block(2); + + // Create storage + assert_ok!(builder::call(addr).value(42).data((50u32, 20u32).encode()).build()); + // 4 is for creating 2 storage items + // 48 is for each of the keys + let charged0 = 4 + 50 + 20 + 48 + 48; + deposit += charged0; + assert_eq!(get_contract(&addr).total_deposit(), deposit); + + // Add more storage (but also remove some) + assert_ok!(builder::call(addr).data((100u32, 10u32).encode()).build()); + let charged1 = 50 - 10; + deposit += charged1; + assert_eq!(get_contract(&addr).total_deposit(), deposit); + + // Remove more storage (but also add some) + assert_ok!(builder::call(addr).data((10u32, 20u32).encode()).build()); + // -1 for numeric instability + let refunded0 = 90 - 10 - 1; + deposit -= refunded0; + assert_eq!(get_contract(&addr).total_deposit(), deposit); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: account_id.clone(), + amount: 42, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::TransferAndHold { + reason: ::RuntimeHoldReason::Contracts( + HoldReason::StorageDepositReserve, + ), + source: ALICE, + dest: account_id.clone(), + transferred: charged0, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::TransferAndHold { + reason: ::RuntimeHoldReason::Contracts( + HoldReason::StorageDepositReserve, + ), + source: ALICE, + dest: account_id.clone(), + transferred: charged1, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::TransferOnHold { + reason: ::RuntimeHoldReason::Contracts( + HoldReason::StorageDepositReserve, + ), + source: account_id.clone(), + dest: ALICE, + amount: refunded0, + }), + topics: vec![], + }, + ] + ); + }); +} + +#[test] +fn storage_deposit_callee_works() { + let (binary_caller, _code_hash_caller) = compile_module("call").unwrap(); + let (binary_callee, _code_hash_callee) = compile_module("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(binary_caller)).build_and_unwrap_contract(); + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); + + assert_ok!(builder::call(addr_caller).data((100u32, &addr_callee).encode()).build()); + + let callee = get_contract(&addr_callee); + let deposit = DepositPerByte::get() * 100 + DepositPerItem::get() * 1 + 48; + + assert_eq!(Pallet::::evm_balance(&addr_caller), U256::zero()); + assert_eq!(callee.total_deposit(), deposit + contract_base_deposit(&addr_callee)); + }); +} + +#[test] +fn set_code_extrinsic() { + let (binary, code_hash) = compile_module("dummy").unwrap(); + let (new_binary, new_code_hash) = compile_module("crypto_hash_keccak_256").unwrap(); + + assert_ne!(code_hash, new_code_hash); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); + + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + new_binary, + deposit_limit::(), + )); + + // Drop previous events + initialize_block(2); + + assert_eq!(get_contract(&addr).code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + + // only root can execute this extrinsic + assert_noop!( + Contracts::set_code(RuntimeOrigin::signed(ALICE), addr, new_code_hash), + sp_runtime::traits::BadOrigin, + ); + assert_eq!(get_contract(&addr).code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + assert_eq!(System::events(), vec![]); + + // contract must exist + assert_noop!( + Contracts::set_code(RuntimeOrigin::root(), BOB_ADDR, new_code_hash), + >::ContractNotFound, + ); + assert_eq!(get_contract(&addr).code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + assert_eq!(System::events(), vec![]); + + // new code hash must exist + assert_noop!( + Contracts::set_code(RuntimeOrigin::root(), addr, Default::default()), + >::CodeNotFound, + ); + assert_eq!(get_contract(&addr).code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + assert_eq!(System::events(), vec![]); + + // successful call + assert_ok!(Contracts::set_code(RuntimeOrigin::root(), addr, new_code_hash)); + assert_eq!(get_contract(&addr).code_hash, new_code_hash); + assert_refcount!(&code_hash, 0); + assert_refcount!(&new_code_hash, 1); + }); +} + +#[test] +fn slash_cannot_kill_account() { + let (binary, _code_hash) = compile_module("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let value = 700; + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + let Contract { addr, account_id } = builder::bare_instantiate(Code::Upload(binary)) + .native_value(value) + .build_and_unwrap_contract(); + + // Drop previous events + initialize_block(2); + + let info_deposit = contract_base_deposit(&addr); + + assert_eq!( + get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account_id), + info_deposit + ); + + assert_eq!( + ::Currency::total_balance(&account_id), + info_deposit + value + min_balance + ); + + // Try to destroy the account of the contract by slashing the total balance. + // The account does not get destroyed because slashing only affects the balance held + // under certain `reason`. Slashing can for example happen if the contract takes part + // in staking. + let _ = ::Currency::slash( + &HoldReason::StorageDepositReserve.into(), + &account_id, + ::Currency::total_balance(&account_id), + ); + + // Slashing only removed the balance held. + assert_eq!(::Currency::total_balance(&account_id), value + min_balance); + }); +} + +#[test] +fn contract_reverted() { + let (binary, code_hash) = compile_module("return_with_data").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let flags = ReturnFlags::REVERT; + let buffer = [4u8, 8, 15, 16, 23, 42]; + let input = (flags.bits(), buffer).encode(); + + // We just upload the code for later use + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + binary.clone(), + deposit_limit::(), + )); + + // Calling extrinsic: revert leads to an error + assert_err_ignore_postinfo!( + builder::instantiate(code_hash).data(input.clone()).build(), + >::ContractReverted, + ); + + // Calling extrinsic: revert leads to an error + assert_err_ignore_postinfo!( + builder::instantiate_with_code(binary).data(input.clone()).build(), + >::ContractReverted, + ); + + // Calling directly: revert leads to success but the flags indicate the error + // This is just a different way of transporting the error that allows the read out + // the `data` which is only there on success. Obviously, the contract isn't + // instantiated. + let result = builder::bare_instantiate(Code::Existing(code_hash)) + .data(input.clone()) + .build_and_unwrap_result(); + assert_eq!(result.result.flags, flags); + assert_eq!(result.result.data, buffer); + assert!(!>::contains_key(result.addr)); + + // Pass empty flags and therefore successfully instantiate the contract for later use. + let Contract { addr, .. } = builder::bare_instantiate(Code::Existing(code_hash)) + .data(ReturnFlags::empty().bits().encode()) + .build_and_unwrap_contract(); + + // Calling extrinsic: revert leads to an error + assert_err_ignore_postinfo!( + builder::call(addr).data(input.clone()).build(), + >::ContractReverted, + ); + + // Calling directly: revert leads to success but the flags indicate the error + let result = builder::bare_call(addr).data(input).build_and_unwrap_result(); + assert_eq!(result.flags, flags); + assert_eq!(result.data, buffer); + }); +} + +#[test] +fn set_code_hash() { + let (binary, _) = compile_module("set_code_hash").unwrap(); + let (new_binary, new_code_hash) = compile_module("new_set_code_hash_contract").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the 'caller' + let Contract { addr: contract_addr, .. } = builder::bare_instantiate(Code::Upload(binary)) + .native_value(300_000) + .build_and_unwrap_contract(); + // upload new code + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + new_binary.clone(), + deposit_limit::(), + )); + + System::reset_events(); + + // First call sets new code_hash and returns 1 + let result = builder::bare_call(contract_addr) + .data(new_code_hash.as_ref().to_vec()) + .build_and_unwrap_result(); + assert_return_code!(result, 1); + + // Second calls new contract code that returns 2 + let result = builder::bare_call(contract_addr).build_and_unwrap_result(); + assert_return_code!(result, 2); + }); +} + +#[test] +fn storage_deposit_limit_is_enforced() { + let (binary, _code_hash) = compile_module("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Setting insufficient storage_deposit should fail. + assert_err!( + builder::bare_instantiate(Code::Upload(binary.clone())) + // expected deposit is 2 * ed + 3 for the call + .storage_deposit_limit((2 * min_balance + 3 - 1).into()) + .build() + .result, + >::StorageDepositLimitExhausted, + ); + + // Instantiate the BOB contract. + let Contract { addr, account_id } = + builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); + + let info_deposit = contract_base_deposit(&addr); + // Check that the BOB contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), info_deposit); + assert_eq!( + ::Currency::total_balance(&account_id), + info_deposit + min_balance + ); + + // Create 1 byte of storage with a price of per byte, + // setting insufficient deposit limit, as it requires 3 Balance: + // 2 for the item added + 1 (value) + 48 (key) + assert_err_ignore_postinfo!( + builder::call(addr) + .storage_deposit_limit(50) + .data(1u32.to_le_bytes().to_vec()) + .build(), + >::StorageDepositLimitExhausted, + ); + + // now with enough limit + assert_ok!(builder::call(addr) + .storage_deposit_limit(51) + .data(1u32.to_le_bytes().to_vec()) + .build()); + + // Use 4 more bytes of the storage for the same item, which requires 4 Balance. + // Should fail as DefaultDepositLimit is 3 and hence isn't enough. + assert_err_ignore_postinfo!( + builder::call(addr) + .storage_deposit_limit(3) + .data(5u32.to_le_bytes().to_vec()) + .build(), + >::StorageDepositLimitExhausted, + ); + }); +} + +#[test] +fn deposit_limit_in_nested_calls() { + let (binary_caller, _code_hash_caller) = compile_module("create_storage_and_call").unwrap(); + let (binary_callee, _code_hash_callee) = compile_module("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(binary_caller)).build_and_unwrap_contract(); + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); + + // Create 100 bytes of storage with a price of per byte + // This is 100 Balance + 2 Balance for the item + // 48 for the key + assert_ok!(builder::call(addr_callee) + .storage_deposit_limit(102 + 48) + .data(100u32.to_le_bytes().to_vec()) + .build()); + + // We do not remove any storage but add a storage item of 12 bytes in the caller + // contract. This would cost 12 + 2 + 72 = 86 Balance. + // The nested call doesn't get a special limit, which is set by passing `u64::MAX` to it. + // This should fail as the specified parent's limit is less than the cost: 13 < + // 14. + assert_err_ignore_postinfo!( + builder::call(addr_caller) + .storage_deposit_limit(85) + .data((100u32, &addr_callee, U256::MAX).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + + // Now we specify the parent's limit high enough to cover the caller's storage + // additions. However, we use a single byte more in the callee, hence the storage + // deposit should be 87 Balance. + // The nested call doesn't get a special limit, which is set by passing `u64::MAX` to it. + // This should fail as the specified parent's limit is less than the cost: 86 < 87 + assert_err_ignore_postinfo!( + builder::call(addr_caller) + .storage_deposit_limit(86) + .data((101u32, &addr_callee, &U256::MAX).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + + // The parents storage deposit limit doesn't matter as the sub calls limit + // is enforced eagerly. However, we set a special deposit limit of 1 Balance for the + // nested call. This should fail as callee adds up 2 bytes to the storage, meaning + // that the nested call should have a deposit limit of at least 2 Balance. The + // sub-call should be rolled back, which is covered by the next test case. + let ret = builder::bare_call(addr_caller) + .storage_deposit_limit(DepositLimit::Balance(u64::MAX)) + .data((102u32, &addr_callee, U256::from(1u64)).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); + + // Refund in the callee contract but not enough to cover the Balance required by the + // caller. Note that if previous sub-call wouldn't roll back, this call would pass + // making the test case fail. We don't set a special limit for the nested call here. + assert_err_ignore_postinfo!( + builder::call(addr_caller) + .storage_deposit_limit(0) + .data((87u32, &addr_callee, &U256::MAX.to_little_endian()).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + + let _ = ::Currency::set_balance(&ALICE, 511); + + // Require more than the sender's balance. + // Limit the sub call to little balance so it should fail in there + let ret = builder::bare_call(addr_caller) + .data((416, &addr_callee, U256::from(1u64)).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); + + // Free up enough storage in the callee so that the caller can create a new item + // We set the special deposit limit of 1 Balance for the nested call, which isn't + // enforced as callee frees up storage. This should pass. + assert_ok!(builder::call(addr_caller) + .storage_deposit_limit(1) + .data((0u32, &addr_callee, U256::from(1u64)).encode()) + .build()); + }); +} + +#[test] +fn deposit_limit_in_nested_instantiate() { + let (binary_caller, _code_hash_caller) = + compile_module("create_storage_and_instantiate").unwrap(); + let (binary_callee, code_hash_callee) = compile_module("store_deploy").unwrap(); + const ED: u64 = 5; + ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&BOB, 1_000_000); + // Create caller contract + let Contract { addr: addr_caller, account_id: caller_id } = + builder::bare_instantiate(Code::Upload(binary_caller)) + .native_value(10_000) // this balance is later passed to the deployed contract + .build_and_unwrap_contract(); + // Deploy a contract to get its occupied storage size + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(binary_callee)) + .data(vec![0, 0, 0, 0]) + .build_and_unwrap_contract(); + + // This is the deposit we expect to be charged just for instantiatiting the callee. + // + // - callee_info_len + 2 for storing the new contract info + // - the deposit for depending on a code hash + // - ED for deployed contract account + // - 2 for the storage item of 0 bytes being created in the callee constructor + // - 48 for the key + let callee_min_deposit = { + let callee_info_len = + AccountInfo::::load_contract(&addr).unwrap().encoded_size() as u64; + let code_deposit = lockup_deposit(&code_hash_callee); + callee_info_len + code_deposit + 2 + ED + 2 + 48 + }; + + // The parent just stores an item of the passed size so at least + // we need to pay for the item itself. + let caller_min_deposit = callee_min_deposit + 2 + 48; + + // Fail in callee. + // + // We still fail in the sub call because we enforce limits on return from a contract. + // Sub calls return first to they are checked first. + let ret = builder::bare_call(addr_caller) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(DepositLimit::Balance(0)) + .data((&code_hash_callee, 100u32, &U256::MAX.to_little_endian()).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); + // The charges made on instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Fail in the caller. + // + // For that we need to supply enough storage deposit so that the sub call + // succeeds but the parent call runs out of storage. + let ret = builder::bare_call(addr_caller) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(DepositLimit::Balance(callee_min_deposit)) + .data((&code_hash_callee, 0u32, &U256::MAX.to_little_endian()).encode()) + .build(); + assert_err!(ret.result, >::StorageDepositLimitExhausted); + // The charges made on the instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Fail in the callee with bytes. + // + // Same as above but stores one byte in both caller and callee. + let ret = builder::bare_call(addr_caller) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(DepositLimit::Balance(caller_min_deposit + 1)) + .data((&code_hash_callee, 1u32, U256::from(callee_min_deposit)).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); + // The charges made on the instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Fail in the caller with bytes. + // + // Same as above but stores one byte in both caller and callee. + let ret = builder::bare_call(addr_caller) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(DepositLimit::Balance(callee_min_deposit + 1)) + .data((&code_hash_callee, 1u32, U256::from(callee_min_deposit + 1)).encode()) + .build(); + assert_err!(ret.result, >::StorageDepositLimitExhausted); + // The charges made on the instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Set enough deposit limit for the child instantiate. This should succeed. + let result = builder::bare_call(addr_caller) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit((caller_min_deposit + 2).into()) + .data((&code_hash_callee, 1u32, U256::from(callee_min_deposit + 1)).encode()) + .build(); + + let returned = result.result.unwrap(); + assert!(!returned.did_revert()); + + // All balance of the caller except ED has been transferred to the callee. + // No deposit has been taken from it. + assert_eq!(::Currency::free_balance(&caller_id), ED); + // Get address of the deployed contract. + let addr_callee = H160::from_slice(&returned.data[0..20]); + let callee_account_id = ::AddressMapper::to_account_id(&addr_callee); + // 10_000 should be sent to callee from the caller contract, plus ED to be sent from the + // origin. + assert_eq!(::Currency::free_balance(&callee_account_id), 10_000 + ED); + // The origin should be charged with what the outer call consumed + assert_eq!( + ::Currency::free_balance(&BOB), + 1_000_000 - (caller_min_deposit + 2), + ); + assert_eq!(result.storage_deposit.charge_or_zero(), (caller_min_deposit + 2)) + }); +} + +#[test] +fn deposit_limit_honors_liquidity_restrictions() { + let (binary, _code_hash) = compile_module("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let bobs_balance = 1_000; + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&BOB, bobs_balance); + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let Contract { addr, account_id } = + builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); + + let info_deposit = contract_base_deposit(&addr); + // Check that the contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), info_deposit); + assert_eq!( + ::Currency::total_balance(&account_id), + info_deposit + min_balance + ); + + // check that the hold is honored + ::Currency::hold( + &HoldReason::CodeUploadDepositReserve.into(), + &BOB, + bobs_balance - min_balance, + ) + .unwrap(); + assert_err_ignore_postinfo!( + builder::call(addr) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(10_000) + .data(100u32.to_le_bytes().to_vec()) + .build(), + >::StorageDepositNotEnoughFunds, + ); + assert_eq!(::Currency::free_balance(&BOB), min_balance); + }); +} + +#[test] +fn deposit_limit_honors_existential_deposit() { + let (binary, _code_hash) = compile_module("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&BOB, 300); + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let Contract { addr, account_id } = + builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); + + let info_deposit = contract_base_deposit(&addr); + + // Check that the contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), info_deposit); + assert_eq!( + ::Currency::total_balance(&account_id), + min_balance + info_deposit + ); + + // check that the deposit can't bring the account below the existential deposit + assert_err_ignore_postinfo!( + builder::call(addr) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(10_000) + .data(100u32.to_le_bytes().to_vec()) + .build(), + >::StorageDepositNotEnoughFunds, + ); + assert_eq!(::Currency::free_balance(&BOB), 300); + }); +} + +#[test] +fn native_dependency_deposit_works() { + let (binary, code_hash) = compile_module("set_code_hash").unwrap(); + let (dummy_binary, dummy_code_hash) = compile_module("dummy").unwrap(); + + // Test with both existing and uploaded code + for code in [Code::Upload(binary.clone()), Code::Existing(code_hash)] { + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + let lockup_deposit_percent = CodeHashLockupDepositPercent::get(); + + // Upload the dummy contract, + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + dummy_binary.clone(), + deposit_limit::(), + ) + .unwrap(); + + // Upload `set_code_hash` contracts if using Code::Existing. + let add_upload_deposit = match code { + Code::Existing(_) => { + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + binary.clone(), + deposit_limit::(), + ) + .unwrap(); + false + }, + Code::Upload(_) => true, + }; + + // Instantiate the set_code_hash contract. + let res = builder::bare_instantiate(code).build(); + + let addr = res.result.unwrap().addr; + let account_id = ::AddressMapper::to_account_id(&addr); + let base_deposit = contract_base_deposit(&addr); + let upload_deposit = get_code_deposit(&code_hash); + let extra_deposit = add_upload_deposit.then(|| upload_deposit).unwrap_or_default(); + + assert_eq!( + res.storage_deposit.charge_or_zero(), + extra_deposit + base_deposit + Contracts::min_balance() + ); + + // call set_code_hash + builder::bare_call(addr) + .data(dummy_code_hash.encode()) + .build_and_unwrap_result(); + + // Check updated storage_deposit due to code size changes + let deposit_diff = lockup_deposit_percent.mul_ceil(get_code_deposit(&code_hash)) - + lockup_deposit_percent.mul_ceil(get_code_deposit(&dummy_code_hash)); + let new_base_deposit = contract_base_deposit(&addr); + assert_ne!(deposit_diff, 0); + assert_eq!(base_deposit - new_base_deposit, deposit_diff); + + assert_eq!( + get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account_id), + new_base_deposit + ); + }); + } +} + +#[test] +fn block_hash_works() { + let (code, _) = compile_module("block_hash").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // The genesis config sets to the block number to 1 + let block_hash = [1; 32]; + frame_system::BlockHash::::insert( + &crate::BlockNumberFor::::from(0u32), + ::Hash::from(&block_hash), + ); + assert_ok!(builder::call(addr) + .data((U256::zero(), H256::from(block_hash)).encode()) + .build()); + + // A block number out of range returns the zero value + assert_ok!(builder::call(addr).data((U256::from(1), H256::zero()).encode()).build()); + }); +} + +#[test] +fn block_author_works() { + let (code, _) = compile_module("block_author").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // The fixture asserts the input to match the find_author API method output. + assert_ok!(builder::call(addr).data(EVE_ADDR.encode()).build()); + }); +} + +#[test] +fn root_cannot_upload_code() { + let (binary, _) = compile_module("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Contracts::upload_code(RuntimeOrigin::root(), binary, deposit_limit::()), + DispatchError::BadOrigin, + ); + }); +} + +#[test] +fn root_cannot_remove_code() { + let (_, code_hash) = compile_module("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Contracts::remove_code(RuntimeOrigin::root(), code_hash), + DispatchError::BadOrigin, + ); + }); +} + +#[test] +fn signed_cannot_set_code() { + let (_, code_hash) = compile_module("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Contracts::set_code(RuntimeOrigin::signed(ALICE), BOB_ADDR, code_hash), + DispatchError::BadOrigin, + ); + }); +} + +#[test] +fn none_cannot_call_code() { + ExtBuilder::default().build().execute_with(|| { + assert_err_ignore_postinfo!( + builder::call(BOB_ADDR).origin(RuntimeOrigin::none()).build(), + DispatchError::BadOrigin, + ); + }); +} + +#[test] +fn root_can_call() { + let (binary, _) = compile_module("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract(); + + // Call the contract. + assert_ok!(builder::call(addr).origin(RuntimeOrigin::root()).build()); + }); +} + +#[test] +fn root_cannot_instantiate_with_code() { + let (binary, _) = compile_module("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_err_ignore_postinfo!( + builder::instantiate_with_code(binary).origin(RuntimeOrigin::root()).build(), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn root_cannot_instantiate() { + let (_, code_hash) = compile_module("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_err_ignore_postinfo!( + builder::instantiate(code_hash).origin(RuntimeOrigin::root()).build(), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn only_upload_origin_can_upload() { + let (binary, _) = compile_module("dummy").unwrap(); + UploadAccount::set(Some(ALICE)); + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + let _ = Balances::set_balance(&BOB, 1_000_000); + + assert_err!( + Contracts::upload_code(RuntimeOrigin::root(), binary.clone(), deposit_limit::(),), + DispatchError::BadOrigin + ); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(BOB), + binary.clone(), + deposit_limit::(), + ), + DispatchError::BadOrigin + ); + + // Only alice is allowed to upload contract code. + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + binary.clone(), + deposit_limit::(), + )); + }); +} + +#[test] +fn only_instantiation_origin_can_instantiate() { + let (code, code_hash) = compile_module("dummy").unwrap(); + InstantiateAccount::set(Some(ALICE)); + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + let _ = Balances::set_balance(&BOB, 1_000_000); + + assert_err_ignore_postinfo!( + builder::instantiate_with_code(code.clone()) + .origin(RuntimeOrigin::root()) + .build(), + DispatchError::BadOrigin + ); + + assert_err_ignore_postinfo!( + builder::instantiate_with_code(code.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .build(), + DispatchError::BadOrigin + ); + + // Only Alice can instantiate + assert_ok!(builder::instantiate_with_code(code).build()); + + // Bob cannot instantiate with either `instantiate_with_code` or `instantiate`. + assert_err_ignore_postinfo!( + builder::instantiate(code_hash).origin(RuntimeOrigin::signed(BOB)).build(), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn balance_of_api() { + let (binary, _code_hash) = compile_module("balance_of").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + let _ = Balances::set_balance(&ALICE_FALLBACK, 1_000_000); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(binary.to_vec())).build_and_unwrap_contract(); + + // The fixture asserts a non-zero returned free balance of the account; + // The ALICE_FALLBACK account is endowed; + // Hence we should not revert + assert_ok!(builder::call(addr).data(ALICE_ADDR.0.to_vec()).build()); + + // The fixture asserts a non-zero returned free balance of the account; + // The ETH_BOB account is not endowed; + // Hence we should revert + assert_err_ignore_postinfo!( + builder::call(addr).data(BOB_ADDR.0.to_vec()).build(), + >::ContractTrapped + ); + }); +} + +#[test] +fn balance_api_returns_free_balance() { + let (binary, _code_hash) = compile_module("balance").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the BOB contract without any extra balance. + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(binary.to_vec())).build_and_unwrap_contract(); + + let value = 0; + // Call BOB which makes it call the balance runtime API. + // The contract code asserts that the returned balance is 0. + assert_ok!(builder::call(addr).value(value).build()); + + let value = 1; + // Calling with value will trap the contract. + assert_err_ignore_postinfo!( + builder::call(addr).value(value).build(), + >::ContractTrapped + ); + }); +} + +#[test] +fn call_depth_is_enforced() { + let (binary, _code_hash) = compile_module("recurse").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let extra_recursions = 1024; + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(binary.to_vec())).build_and_unwrap_contract(); + + // takes the number of recursions + // returns the number of left over recursions + assert_eq!( + u32::from_le_bytes( + builder::bare_call(addr) + .data((limits::CALL_STACK_DEPTH + extra_recursions).encode()) + .build_and_unwrap_result() + .data + .try_into() + .unwrap() + ), + // + 1 because when the call depth is reached the caller contract is trapped without + // the ability to return any data. hence the last call frame is untracked. + extra_recursions + 1, + ); + }); +} + +#[test] +fn gas_consumed_is_linear_for_nested_calls() { + let (code, _code_hash) = compile_module("recurse").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let [gas_0, gas_1, gas_2, gas_max] = { + [0u32, 1u32, 2u32, limits::CALL_STACK_DEPTH] + .iter() + .map(|i| { + let result = builder::bare_call(addr).data(i.encode()).build(); + assert_eq!( + u32::from_le_bytes(result.result.unwrap().data.try_into().unwrap()), + 0 + ); + result.gas_consumed + }) + .collect::>() + .try_into() + .unwrap() + }; + + let gas_per_recursion = gas_2.checked_sub(&gas_1).unwrap(); + assert_eq!(gas_max, gas_0 + gas_per_recursion * limits::CALL_STACK_DEPTH as u64); + }); +} + +#[test] +fn read_only_call_cannot_store() { + let (binary_caller, _code_hash_caller) = compile_module("read_only_call").unwrap(); + let (binary_callee, _code_hash_callee) = compile_module("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(binary_caller)).build_and_unwrap_contract(); + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); + + // Read-only call fails when modifying storage. + assert_err_ignore_postinfo!( + builder::call(addr_caller).data((&addr_callee, 100u32).encode()).build(), + >::ContractTrapped + ); + }); +} + +#[test] +fn read_only_call_cannot_transfer() { + let (binary_caller, _code_hash_caller) = compile_module("call_with_flags_and_value").unwrap(); + let (binary_callee, _code_hash_callee) = compile_module("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(binary_caller)).build_and_unwrap_contract(); + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); + + // Read-only call fails when a non-zero value is set. + assert_err_ignore_postinfo!( + builder::call(addr_caller) + .data( + (addr_callee, pallet_revive_uapi::CallFlags::READ_ONLY.bits(), 100u64).encode() + ) + .build(), + >::StateChangeDenied + ); + }); +} + +#[test] +fn read_only_subsequent_call_cannot_store() { + let (binary_read_only_caller, _code_hash_caller) = compile_module("read_only_call").unwrap(); + let (binary_caller, _code_hash_caller) = compile_module("call_with_flags_and_value").unwrap(); + let (binary_callee, _code_hash_callee) = compile_module("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create contracts: Constructors do nothing. + let Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(binary_read_only_caller)) + .build_and_unwrap_contract(); + let Contract { addr: addr_subsequent_caller, .. } = + builder::bare_instantiate(Code::Upload(binary_caller)).build_and_unwrap_contract(); + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); + + // Subsequent call input. + let input = (&addr_callee, pallet_revive_uapi::CallFlags::empty().bits(), 0u64, 100u32); + + // Read-only call fails when modifying storage. + assert_err_ignore_postinfo!( + builder::call(addr_caller) + .data((&addr_subsequent_caller, input).encode()) + .build(), + >::ContractTrapped + ); + }); +} + +#[test] +fn read_only_call_works() { + let (binary_caller, _code_hash_caller) = compile_module("read_only_call").unwrap(); + let (binary_callee, _code_hash_callee) = compile_module("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let Contract { addr: addr_caller, .. } = + builder::bare_instantiate(Code::Upload(binary_caller)).build_and_unwrap_contract(); + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); + + assert_ok!(builder::call(addr_caller).data(addr_callee.encode()).build()); + }); +} + +#[test] +fn create1_with_value_works() { + let (code, code_hash) = compile_module("create1_with_value").unwrap(); + let value = 42; + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create the contract: Constructor does nothing. + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: Deploys itself using create1 and the expected value + assert_ok!(builder::call(addr).value(value).data(code_hash.encode()).build()); + + // We should see the expected balance at the expected account + let address = crate::address::create1(&addr, 1); + let account_id = ::AddressMapper::to_account_id(&address); + let usable_balance = ::Currency::usable_balance(&account_id); + assert_eq!(usable_balance, value); + }); +} + +#[test] +fn gas_price_api_works() { + let (code, _) = compile_module("gas_price").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It echoes back the value returned by the gas price API. + let received = builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(u64::from_le_bytes(received.data[..].try_into().unwrap()), u64::from(GAS_PRICE)); + }); +} + +#[test] +fn base_fee_api_works() { + let (code, _) = compile_module("base_fee").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It echoes back the value returned by the base fee API. + let received = builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(received.data[..].try_into().unwrap()), U256::zero()); + }); +} + +#[test] +fn call_data_size_api_works() { + let (code, _) = compile_module("call_data_size").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It echoes back the value returned by the call data size API. + let received = builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(u64::from_le_bytes(received.data.try_into().unwrap()), 0); + + let received = builder::bare_call(addr).data(vec![1; 256]).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(u64::from_le_bytes(received.data.try_into().unwrap()), 256); + }); +} + +#[test] +fn call_data_copy_api_works() { + let (code, _) = compile_module("call_data_copy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call fixture: Expects an input of [255; 32] and executes tests. + assert_ok!(builder::call(addr).data(vec![255; 32]).build()); + }); +} + +#[test] +fn static_data_limit_is_enforced() { + let (oom_rw_trailing, _) = compile_module("oom_rw_trailing").unwrap(); + let (oom_rw_included, _) = compile_module("oom_rw_included").unwrap(); + let (oom_ro, _) = compile_module("oom_ro").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + oom_rw_trailing, + deposit_limit::(), + ), + >::StaticMemoryTooLarge + ); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + oom_rw_included, + deposit_limit::(), + ), + >::BlobTooLarge + ); + + assert_err!( + Contracts::upload_code(RuntimeOrigin::signed(ALICE), oom_ro, deposit_limit::(),), + >::BlobTooLarge + ); + }); +} + +#[test] +fn call_diverging_out_len_works() { + let (code, _) = compile_module("call_diverging_out_len").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create the contract: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It will issue calls and deploys, asserting on + // correct output if the supplied output length was smaller than + // than what the callee returned. + assert_ok!(builder::call(addr).build()); + }); +} + +#[test] +fn chain_id_works() { + let (code, _) = compile_module("chain_id").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let chain_id = U256::from(::ChainId::get()); + let received = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_result(); + assert_eq!(received.result.data, chain_id.encode()); + }); +} + +#[test] +fn call_data_load_api_works() { + let (code, _) = compile_module("call_data_load").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It reads a byte for the offset and then returns + // what call data load returned using this byte as the offset. + let input = (3u8, U256::max_value(), U256::max_value()).encode(); + let received = builder::bare_call(addr).data(input).build().result.unwrap(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::max_value()); + + // Edge case + let input = (2u8, U256::from(255).to_big_endian()).encode(); + let received = builder::bare_call(addr).data(input).build().result.unwrap(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::from(65280)); + + // Edge case + let received = builder::bare_call(addr).data(vec![1]).build().result.unwrap(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::zero()); + + // OOB case + let input = (42u8).encode(); + let received = builder::bare_call(addr).data(input).build().result.unwrap(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::zero()); + + // No calldata should return the zero value + let received = builder::bare_call(addr).build().result.unwrap(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::zero()); + }); +} + +#[test] +fn return_data_api_works() { + let (code_return_data_api, _) = compile_module("return_data_api").unwrap(); + let (code_return_with_data, hash_return_with_data) = + compile_module("return_with_data").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Upload the io echoing fixture for later use + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + code_return_with_data, + deposit_limit::(), + )); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code_return_data_api)) + .build_and_unwrap_contract(); + + // Call the contract: It will issue calls and deploys, asserting on + assert_ok!(builder::call(addr) + .value(10 * 1024) + .data(hash_return_with_data.encode()) + .build()); + }); +} + +#[test] +fn immutable_data_works() { + let (code, _) = compile_module("immutable_data").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let data = [0xfe; 8]; + + // Create fixture: Constructor sets the immtuable data + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .data(data.to_vec()) + .build_and_unwrap_contract(); + + let contract = get_contract(&addr); + let account = ::AddressMapper::to_account_id(&addr); + let actual_deposit = + get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account); + + assert_eq!(contract.immutable_data_len(), data.len() as u32); + + // Storing immmutable data charges storage deposit; verify it explicitly. + assert_eq!(actual_deposit, contract_base_deposit(&addr)); + + // make sure it is also recorded in the base deposit + assert_eq!( + get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account), + contract.storage_base_deposit(), + ); + + // Call the contract: Asserts the input to equal the immutable data + assert_ok!(builder::call(addr).data(data.to_vec()).build()); + }); +} + +#[test] +fn sbrk_cannot_be_deployed() { + let (code, _) = compile_module("sbrk").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + code.clone(), + deposit_limit::(), + ), + >::InvalidInstruction + ); + + assert_err!( + builder::bare_instantiate(Code::Upload(code)).build().result, + >::InvalidInstruction + ); + }); +} + +#[test] +fn overweight_basic_block_cannot_be_deployed() { + let (code, _) = compile_module("basic_block").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + code.clone(), + deposit_limit::(), + ), + >::BasicBlockTooLarge + ); + + assert_err!( + builder::bare_instantiate(Code::Upload(code)).build().result, + >::BasicBlockTooLarge + ); + }); +} + +#[test] +fn origin_api_works() { + let (code, _) = compile_module("origin").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: Asserts the origin API to work as expected + assert_ok!(builder::call(addr).build()); + }); +} + +#[test] +fn code_hash_works() { + use crate::precompiles::{Precompile, EVM_REVERT}; + use precompiles::NoInfo; + + let builtin_precompile = H160(NoInfo::::MATCHER.base_address()); + let primitive_precompile = H160::from_low_u64_be(1); + + let (code_hash_code, self_code_hash) = compile_module("code_hash").unwrap(); + let (dummy_code, code_hash) = compile_module("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code_hash_code)).build_and_unwrap_contract(); + let Contract { addr: dummy_addr, .. } = + builder::bare_instantiate(Code::Upload(dummy_code)).build_and_unwrap_contract(); + + // code hash of dummy contract + assert_ok!(builder::call(addr).data((dummy_addr, code_hash).encode()).build()); + // code hash of itself + assert_ok!(builder::call(addr).data((addr, self_code_hash).encode()).build()); + // code hash of primitive pre-compile (exist but have no bytecode) + assert_ok!(builder::call(addr) + .data((primitive_precompile, crate::exec::EMPTY_CODE_HASH).encode()) + .build()); + // code hash of normal pre-compile (do have a bytecode) + assert_ok!(builder::call(addr) + .data((builtin_precompile, sp_io::hashing::keccak_256(&EVM_REVERT)).encode()) + .build()); + + // EOA doesn't exists + assert_err!( + builder::bare_call(addr) + .data((BOB_ADDR, crate::exec::EMPTY_CODE_HASH).encode()) + .build() + .result, + Error::::ContractTrapped + ); + // non-existing will return zero + assert_ok!(builder::call(addr).data((BOB_ADDR, H256::zero()).encode()).build()); + + // create EOA + let _ = ::Currency::set_balance( + &::AddressMapper::to_account_id(&BOB_ADDR), + 1_000_000, + ); + + // EOA returns empty code hash + assert_ok!(builder::call(addr) + .data((BOB_ADDR, crate::exec::EMPTY_CODE_HASH).encode()) + .build()); + }); +} + +#[test] +fn code_size_works() { + let (tester_code, _) = compile_module("extcodesize").unwrap(); + let tester_code_len = tester_code.len() as u64; + + let (dummy_code, _) = compile_module("dummy").unwrap(); + let dummy_code_len = dummy_code.len() as u64; + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr: tester_addr, .. } = + builder::bare_instantiate(Code::Upload(tester_code)).build_and_unwrap_contract(); + let Contract { addr: dummy_addr, .. } = + builder::bare_instantiate(Code::Upload(dummy_code)).build_and_unwrap_contract(); + + // code size of another contract address + assert_ok!(builder::call(tester_addr).data((dummy_addr, dummy_code_len).encode()).build()); + + // code size of own contract address + assert_ok!(builder::call(tester_addr) + .data((tester_addr, tester_code_len).encode()) + .build()); + + // code size of non contract accounts + assert_ok!(builder::call(tester_addr).data(([8u8; 20], 0u64).encode()).build()); + }); +} + +#[test] +fn origin_must_be_mapped() { + let (code, hash) = compile_module("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + ::Currency::set_balance(&ALICE, 1_000_000); + ::Currency::set_balance(&EVE, 1_000_000); + + let eve = RuntimeOrigin::signed(EVE); + + // alice can instantiate as she doesn't need a mapping + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // without a mapping eve can neither call nor instantiate + assert_err!( + builder::bare_call(addr).origin(eve.clone()).build().result, + >::AccountUnmapped + ); + assert_err!( + builder::bare_instantiate(Code::Existing(hash)) + .origin(eve.clone()) + .build() + .result, + >::AccountUnmapped + ); + + // after mapping eve is usable as an origin + >::map_account(eve.clone()).unwrap(); + assert_ok!(builder::bare_call(addr).origin(eve.clone()).build().result); + assert_ok!(builder::bare_instantiate(Code::Existing(hash)).origin(eve).build().result); + }); +} + +#[test] +fn mapped_address_works() { + let (code, _) = compile_module("terminate_and_send_to_argument").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + ::Currency::set_balance(&ALICE, 1_000_000); + + // without a mapping everything will be send to the fallback account + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code.clone())).build_and_unwrap_contract(); + assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 0); + builder::bare_call(addr).data(EVE_ADDR.encode()).build_and_unwrap_result(); + assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 100); + + // after mapping it will be sent to the real eve account + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + // need some balance to pay for the map deposit + ::Currency::set_balance(&EVE, 1_000); + >::map_account(RuntimeOrigin::signed(EVE)).unwrap(); + builder::bare_call(addr).data(EVE_ADDR.encode()).build_and_unwrap_result(); + assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 100); + assert_eq!(::Currency::total_balance(&EVE), 1_100); + }); +} + +#[test] +fn recovery_works() { + let (code, _) = compile_module("terminate_and_send_to_argument").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + ::Currency::set_balance(&ALICE, 1_000_000); + + // eve puts her AccountId20 as argument to terminate but forgot to register + // her AccountId32 first so now the funds are trapped in her fallback account + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code.clone())).build_and_unwrap_contract(); + assert_eq!(::Currency::total_balance(&EVE), 0); + assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 0); + builder::bare_call(addr).data(EVE_ADDR.encode()).build_and_unwrap_result(); + assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 100); + assert_eq!(::Currency::total_balance(&EVE), 0); + + let call = RuntimeCall::Balances(pallet_balances::Call::transfer_all { + dest: EVE, + keep_alive: false, + }); + + // she now uses the recovery function to move all funds from the fallback + // account to her real account + >::dispatch_as_fallback_account(RuntimeOrigin::signed(EVE), Box::new(call)) + .unwrap(); + assert_eq!(::Currency::total_balance(&EVE_FALLBACK), 0); + assert_eq!(::Currency::total_balance(&EVE), 100); + }); +} + +#[test] +fn skip_transfer_works() { + let (code_caller, _) = compile_module("call").unwrap(); + let (code, _) = compile_module("store_call").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + ::Currency::set_balance(&ALICE, 1_000_000); + ::Currency::set_balance(&BOB, 0); + + // when gas is some (transfers enabled): bob has no money: fail + assert_err!( + Pallet::::dry_run_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + input: code.clone().into(), + gas: Some(1u32.into()), + ..Default::default() + }, + Weight::MAX, + |_, _| 0u64, + ), + EthTransactError::Message(format!( + "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" + )) + ); + + // no gas specified (all transfers are skipped): even without money bob can deploy + assert_ok!(Pallet::::dry_run_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + input: code.clone().into(), + ..Default::default() + }, + Weight::MAX, + |_, _| 0u64, + )); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(code_caller)).build_and_unwrap_contract(); + + // call directly: fails with enabled transfers + assert_err!( + Pallet::::dry_run_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + to: Some(addr), + input: 0u32.encode().into(), + gas: Some(1u32.into()), + ..Default::default() + }, + Weight::MAX, + |_, _| 0u64, + ), + EthTransactError::Message(format!( + "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" + )) + ); + + // fails to call through other contract + // we didn't roll back the storage changes done by the previous + // call. So the item already exists. We simply increase the size of + // the storage item to incur some deposits (which bob can't pay). + assert!(Pallet::::dry_run_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + to: Some(caller_addr), + input: (1u32, &addr).encode().into(), + gas: Some(1u32.into()), + ..Default::default() + }, + Weight::MAX, + |_, _| 0u64, + ) + .is_err(),); + + // works when no gas is specified (skip transfer) + assert_ok!(Pallet::::dry_run_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + to: Some(addr), + input: 2u32.encode().into(), + ..Default::default() + }, + Weight::MAX, + |_, _| 0u64, + )); + + // call through contract works when transfers are skipped + assert_ok!(Pallet::::dry_run_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + to: Some(caller_addr), + input: (3u32, &addr).encode().into(), + ..Default::default() + }, + Weight::MAX, + |_, _| 0u64, + )); + + // works with transfers enabled if we don't incur a storage cost + // we shrink the item so its actually a refund + assert_ok!(Pallet::::dry_run_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + to: Some(caller_addr), + input: (2u32, &addr).encode().into(), + gas: Some(1u32.into()), + ..Default::default() + }, + Weight::MAX, + |_, _| 0u64, + )); + + // fails when trying to increase the storage item size + assert!(Pallet::::dry_run_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + to: Some(caller_addr), + input: (3u32, &addr).encode().into(), + gas: Some(1u32.into()), + ..Default::default() + }, + Weight::MAX, + |_, _| 0u64, + ) + .is_err()); + }); +} + +#[test] +fn gas_limit_api_works() { + let (code, _) = compile_module("gas_limit").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It echoes back the value returned by the gas limit API. + let received = builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!( + u64::from_le_bytes(received.data[..].try_into().unwrap()), + ::BlockWeights::get().max_block.ref_time() + ); + }); +} + +#[test] +fn unknown_syscall_rejected() { + let (code, _) = compile_module("unknown_syscall").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + ::Currency::set_balance(&ALICE, 1_000_000); + + assert_err!( + builder::bare_instantiate(Code::Upload(code)).build().result, + >::CodeRejected, + ) + }); +} + +#[test] +fn unstable_interface_rejected() { + let (code, _) = compile_module("unstable_interface").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + ::Currency::set_balance(&ALICE, 1_000_000); + + Test::set_unstable_interface(false); + assert_err!( + builder::bare_instantiate(Code::Upload(code.clone())).build().result, + >::CodeRejected, + ); + + Test::set_unstable_interface(true); + assert_ok!(builder::bare_instantiate(Code::Upload(code)).build().result); + }); +} + +#[test] +fn tracing_works_for_transfers() { + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000); + let mut tracer = CallTracer::new(Default::default(), |_| U256::zero()); + trace(&mut tracer, || { + builder::bare_call(BOB_ADDR).evm_value(10.into()).build_and_unwrap_result(); + }); + + let trace = tracer.collect_trace(); + assert_eq!( + trace, + Some(CallTrace { + from: ALICE_ADDR, + to: BOB_ADDR, + value: Some(U256::from(10)), + call_type: CallType::Call, + ..Default::default() + }) + ) + }); +} + +#[test] +fn call_tracing_works() { + use crate::evm::*; + use CallType::*; + let (code, _code_hash) = compile_module("tracing").unwrap(); + let (binary_callee, _) = compile_module("tracing_callee").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000); + + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(binary_callee)).build_and_unwrap_contract(); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).evm_value(10_000_000.into()).build_and_unwrap_contract(); + + + let tracer_configs = vec![ + CallTracerConfig{ with_logs: false, only_top_call: false}, + CallTracerConfig{ with_logs: false, only_top_call: false}, + CallTracerConfig{ with_logs: false, only_top_call: true}, + ]; + + // Verify that the first trace report the same weight reported by bare_call + // TODO: fix tracing ( https://github.com/paritytech/polkadot-sdk/issues/8362 ) + /* + let mut tracer = CallTracer::new(false, |w| w); + let gas_used = trace(&mut tracer, || { + builder::bare_call(addr).data((3u32, addr_callee).encode()).build().gas_consumed + }); + let trace = tracer.collect_trace().unwrap(); + assert_eq!(&trace.gas_used, &gas_used); + */ + + // Discarding gas usage, check that traces reported are correct + for config in tracer_configs { + let logs = if config.with_logs { + vec![ + CallLog { + address: addr, + topics: Default::default(), + data: b"before".to_vec().into(), + position: 0, + }, + CallLog { + address: addr, + topics: Default::default(), + data: b"after".to_vec().into(), + position: 1, + }, + ] + } else { + vec![] + }; + + let calls = if config.only_top_call { + vec![] + } else { + vec![ + CallTrace { + from: addr, + to: addr_callee, + input: 2u32.encode().into(), + output: hex_literal::hex!( + "08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a546869732066756e6374696f6e20616c77617973206661696c73000000000000" + ).to_vec().into(), + revert_reason: Some("revert: This function always fails".to_string()), + error: Some("execution reverted".to_string()), + call_type: Call, + value: Some(U256::from(0)), + ..Default::default() + }, + CallTrace { + from: addr, + to: addr, + input: (2u32, addr_callee).encode().into(), + call_type: Call, + logs: logs.clone(), + value: Some(U256::from(0)), + calls: vec![ + CallTrace { + from: addr, + to: addr_callee, + input: 1u32.encode().into(), + output: Default::default(), + error: Some("ContractTrapped".to_string()), + call_type: Call, + value: Some(U256::from(0)), + ..Default::default() + }, + CallTrace { + from: addr, + to: addr, + input: (1u32, addr_callee).encode().into(), + call_type: Call, + logs: logs.clone(), + value: Some(U256::from(0)), + calls: vec![ + CallTrace { + from: addr, + to: addr_callee, + input: 0u32.encode().into(), + output: 0u32.to_le_bytes().to_vec().into(), + call_type: Call, + value: Some(U256::from(0)), + ..Default::default() + }, + CallTrace { + from: addr, + to: addr, + input: (0u32, addr_callee).encode().into(), + call_type: Call, + value: Some(U256::from(0)), + calls: vec![ + CallTrace { + from: addr, + to: BOB_ADDR, + value: Some(U256::from(100)), + call_type: CallType::Call, + ..Default::default() + } + ], + ..Default::default() + }, + ], + ..Default::default() + }, + ], + ..Default::default() + }, + ] + }; + + let mut tracer = CallTracer::new(config, |_| U256::zero()); + trace(&mut tracer, || { + builder::bare_call(addr).data((3u32, addr_callee).encode()).build() + }); + + let trace = tracer.collect_trace(); + let expected_trace = CallTrace { + from: ALICE_ADDR, + to: addr, + input: (3u32, addr_callee).encode().into(), + call_type: Call, + logs: logs.clone(), + value: Some(U256::from(0)), + calls: calls, + ..Default::default() + }; + + assert_eq!( + trace, + expected_trace.into(), + ); + } + }); +} + +#[test] +fn create_call_tracing_works() { + use crate::evm::*; + let (code, code_hash) = compile_module("create2_with_value").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000); + + let mut tracer = CallTracer::new(Default::default(), |_| U256::zero()); + + let Contract { addr, .. } = trace(&mut tracer, || { + builder::bare_instantiate(Code::Upload(code.clone())) + .evm_value(100.into()) + .salt(None) + .build_and_unwrap_contract() + }); + + let call_trace = tracer.collect_trace().unwrap(); + assert_eq!( + call_trace, + CallTrace { + from: ALICE_ADDR, + to: addr, + value: Some(100.into()), + input: Bytes(code.clone()), + call_type: CallType::Create, + ..Default::default() + } + ); + + let mut tracer = CallTracer::new(Default::default(), |_| U256::zero()); + let data = b"garbage"; + let input = (code_hash, data).encode(); + trace(&mut tracer, || { + assert_ok!(builder::call(addr).data(input.clone()).build()); + }); + + let call_trace = tracer.collect_trace().unwrap(); + let child_addr = crate::address::create2(&addr, &code, data, &[1u8; 32]); + + assert_eq!( + call_trace, + CallTrace { + from: ALICE_ADDR, + to: addr, + value: Some(0.into()), + input: input.clone().into(), + calls: vec![CallTrace { + from: addr, + input: input.clone().into(), + to: child_addr, + value: Some(0.into()), + call_type: CallType::Create2, + ..Default::default() + },], + ..Default::default() + } + ); + }); +} + +#[test] +fn prestate_tracing_works() { + use crate::evm::*; + use alloc::collections::BTreeMap; + + let (dummy_code, _) = compile_module("dummy").unwrap(); + let (code, _) = compile_module("tracing").unwrap(); + let (callee_code, _) = compile_module("tracing_callee").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000); + + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(callee_code.clone())) + .build_and_unwrap_contract(); + + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code.clone())) + .native_value(10) + .build_and_unwrap_contract(); + + // redact balance so that tests are resilient to weight changes + let alice_redacted_balance = Some(U256::from(1)); + + let test_cases: Vec<(Box, _, _)> = vec![ + ( + Box::new(|| { + builder::bare_call(addr) + .data((3u32, addr_callee).encode()) + .build_and_unwrap_result(); + }), + PrestateTracerConfig { + diff_mode: false, + disable_storage: false, + disable_code: false, + }, + PrestateTrace::Prestate(BTreeMap::from([ + ( + ALICE_ADDR, + PrestateTraceInfo { + balance: alice_redacted_balance, + nonce: Some(2), + ..Default::default() + }, + ), + ( + BOB_ADDR, + PrestateTraceInfo { balance: Some(U256::from(0u64)), ..Default::default() }, + ), + ( + addr_callee, + PrestateTraceInfo { + balance: Some(U256::from(0u64)), + code: Some(Bytes(callee_code.clone())), + nonce: Some(1), + ..Default::default() + }, + ), + ( + addr, + PrestateTraceInfo { + balance: Some(U256::from(10_000_000u64)), + code: Some(Bytes(code.clone())), + nonce: Some(1), + ..Default::default() + }, + ), + ])), + ), + ( + Box::new(|| { + builder::bare_call(addr) + .data((3u32, addr_callee).encode()) + .build_and_unwrap_result(); + }), + PrestateTracerConfig { + diff_mode: true, + disable_storage: false, + disable_code: false, + }, + PrestateTrace::DiffMode { + pre: BTreeMap::from([ + ( + BOB_ADDR, + PrestateTraceInfo { + balance: Some(U256::from(100u64)), + ..Default::default() + }, + ), + ( + addr, + PrestateTraceInfo { + balance: Some(U256::from(9_999_900u64)), + code: Some(Bytes(code.clone())), + nonce: Some(1), + ..Default::default() + }, + ), + ]), + post: BTreeMap::from([ + ( + BOB_ADDR, + PrestateTraceInfo { + balance: Some(U256::from(200u64)), + ..Default::default() + }, + ), + ( + addr, + PrestateTraceInfo { + balance: Some(U256::from(9_999_800u64)), + ..Default::default() + }, + ), + ]), + }, + ), + ( + Box::new(|| { + builder::bare_instantiate(Code::Upload(dummy_code.clone())) + .salt(None) + .build_and_unwrap_result(); + }), + PrestateTracerConfig { + diff_mode: true, + disable_storage: false, + disable_code: false, + }, + PrestateTrace::DiffMode { + pre: BTreeMap::from([( + ALICE_ADDR, + PrestateTraceInfo { + balance: alice_redacted_balance, + nonce: Some(2), + ..Default::default() + }, + )]), + post: BTreeMap::from([ + ( + ALICE_ADDR, + PrestateTraceInfo { + balance: alice_redacted_balance, + nonce: Some(3), + ..Default::default() + }, + ), + ( + create1(&ALICE_ADDR, 1), + PrestateTraceInfo { + code: Some(dummy_code.clone().into()), + balance: Some(U256::from(0)), + nonce: Some(1), + ..Default::default() + }, + ), + ]), + }, + ), + ]; + + for (exec_call, config, expected_trace) in test_cases.into_iter() { + let mut tracer = PrestateTracer::::new(config); + trace(&mut tracer, || { + exec_call(); + }); + + let mut trace = tracer.collect_trace(); + + // redact alice balance + match trace { + PrestateTrace::DiffMode { ref mut pre, ref mut post } => { + pre.get_mut(&ALICE_ADDR).map(|info| { + info.balance = alice_redacted_balance; + }); + post.get_mut(&ALICE_ADDR).map(|info| { + info.balance = alice_redacted_balance; + }); + }, + PrestateTrace::Prestate(ref mut pre) => { + pre.get_mut(&ALICE_ADDR).map(|info| { + info.balance = alice_redacted_balance; + }); + }, + } + + assert_eq!(trace, expected_trace); + } + }); +} + +#[test] +fn unknown_precompiles_revert() { + let (code, _code_hash) = compile_module("read_only_call").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let cases: Vec<(H160, Box)> = vec![( + H160::from_low_u64_be(0x0a), + Box::new(|result| { + assert_err!(result, >::UnsupportedPrecompileAddress); + }), + )]; + + for (callee_addr, assert_result) in cases { + let result = + builder::bare_call(addr).data((callee_addr, [0u8; 0]).encode()).build().result; + assert_result(result); + } + }); +} + +#[test] +fn pure_precompile_works() { + use hex_literal::hex; + + let cases = vec![ + ( + "ECRecover", + H160::from_low_u64_be(1), + hex!("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549").to_vec(), + hex!("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b").to_vec(), + ), + ( + "Sha256", + H160::from_low_u64_be(2), + hex!("ec07171c4f0f0e2b").to_vec(), + hex!("d0591ea667763c69a5f5a3bae657368ea63318b2c9c8349cccaf507e3cbd7c7a").to_vec(), + ), + ( + "Ripemd160", + H160::from_low_u64_be(3), + hex!("ec07171c4f0f0e2b").to_vec(), + hex!("000000000000000000000000a9c5ebaf7589fd8acfd542c3a008956de84fbeb7").to_vec(), + ), + ( + "Identity", + H160::from_low_u64_be(4), + [42u8; 128].to_vec(), + [42u8; 128].to_vec(), + ), + ( + "Modexp", + H160::from_low_u64_be(5), + hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002003fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f").to_vec(), + hex!("0000000000000000000000000000000000000000000000000000000000000001").to_vec(), + ), + ( + "Bn128Add", + H160::from_low_u64_be(6), + hex!("18b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f3726607c2b7f58a84bd6145f00c9c2bc0bb1a187f20ff2c92963a88019e7c6a014eed06614e20c147e940f2d70da3f74c9a17df361706a4485c742bd6788478fa17d7").to_vec(), + hex!("2243525c5efd4b9c3d3c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703301d1d33be6da8e509df21cc35964723180eed7532537db9ae5e7d48f195c915").to_vec(), + ), + ( + "Bn128Mul", + H160::from_low_u64_be(7), + hex!("2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb721611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb20400000000000000000000000000000000000000000000000011138ce750fa15c2").to_vec(), + hex!("070a8d6a982153cae4be29d434e8faef8a47b274a053f5a4ee2a6c9c13c31e5c031b8ce914eba3a9ffb989f9cdd5b0f01943074bf4f0f315690ec3cec6981afc").to_vec(), + ), + ( + "Bn128Pairing", + H160::from_low_u64_be(8), + hex!("1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f593034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf704bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416782bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa").to_vec(), + hex!("0000000000000000000000000000000000000000000000000000000000000001").to_vec(), + ), + ( + "Blake2F", + H160::from_low_u64_be(9), + hex!("0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001").to_vec(), + hex!("08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b").to_vec(), + ), + ]; + + for (description, precompile_addr, input, output) in cases { + let (code, _code_hash) = compile_module("call_and_return").unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .native_value(1_000) + .build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data( + (&precompile_addr, 100u64) + .encode() + .into_iter() + .chain(input) + .collect::>(), + ) + .build_and_unwrap_result(); + + assert_eq!( + Pallet::::evm_balance(&precompile_addr), + U256::from(100), + "{description}: unexpected balance" + ); + assert_eq!( + alloy_core::hex::encode(result.data), + alloy_core::hex::encode(output), + "{description} Unexpected output for precompile: {precompile_addr:?}", + ); + assert_eq!(result.flags, ReturnFlags::empty()); + }); + } +} + +#[test] +fn precompiles_work() { + use crate::precompiles::Precompile; + use alloy_core::sol_types::{Panic, PanicKind, Revert, SolError, SolInterface, SolValue}; + use precompiles::{INoInfo, NoInfo}; + + let precompile_addr = H160(NoInfo::::MATCHER.base_address()); + + let cases = vec![ + ( + INoInfo::INoInfoCalls::identity(INoInfo::identityCall { number: 42u64.into() }) + .abi_encode(), + 42u64.abi_encode(), + RuntimeReturnCode::Success, + ), + ( + INoInfo::INoInfoCalls::reverts(INoInfo::revertsCall { error: "panic".to_string() }) + .abi_encode(), + Revert::from("panic").abi_encode(), + RuntimeReturnCode::CalleeReverted, + ), + ( + INoInfo::INoInfoCalls::panics(INoInfo::panicsCall {}).abi_encode(), + Panic::from(PanicKind::Assert).abi_encode(), + RuntimeReturnCode::CalleeReverted, + ), + ( + INoInfo::INoInfoCalls::errors(INoInfo::errorsCall {}).abi_encode(), + Vec::new(), + RuntimeReturnCode::CalleeTrapped, + ), + // passing non decodeable input reverts with solidity panic + ( + b"invalid".to_vec(), + Panic::from(PanicKind::ResourceError).abi_encode(), + RuntimeReturnCode::CalleeReverted, + ), + ( + INoInfo::INoInfoCalls::passData(INoInfo::passDataCall { + inputLen: limits::CALLDATA_BYTES, + }) + .abi_encode(), + Vec::new(), + RuntimeReturnCode::Success, + ), + ( + INoInfo::INoInfoCalls::passData(INoInfo::passDataCall { + inputLen: limits::CALLDATA_BYTES + 1, + }) + .abi_encode(), + Vec::new(), + RuntimeReturnCode::CalleeTrapped, + ), + ( + INoInfo::INoInfoCalls::returnData(INoInfo::returnDataCall { + returnLen: limits::CALLDATA_BYTES - 4, + }) + .abi_encode(), + vec![42u8; limits::CALLDATA_BYTES as usize - 4], + RuntimeReturnCode::Success, + ), + ( + INoInfo::INoInfoCalls::returnData(INoInfo::returnDataCall { + returnLen: limits::CALLDATA_BYTES + 1, + }) + .abi_encode(), + vec![], + RuntimeReturnCode::CalleeTrapped, + ), + ]; + + for (input, output, error_code) in cases { + let (code, _code_hash) = compile_module("call_and_returncode").unwrap(); + ExtBuilder::default().build().execute_with(|| { + let id = ::AddressMapper::to_account_id(&precompile_addr); + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .native_value(1000) + .build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data( + (&precompile_addr, 0u64).encode().into_iter().chain(input).collect::>(), + ) + .build_and_unwrap_result(); + + // no account or contract info should be created for a NoInfo pre-compile + assert!(get_contract_checked(&precompile_addr).is_none()); + assert!(!System::account_exists(&id)); + assert_eq!(Pallet::::evm_balance(&precompile_addr), U256::zero()); + + assert_eq!(result.flags, ReturnFlags::empty()); + assert_eq!(u32::from_le_bytes(result.data[..4].try_into().unwrap()), error_code as u32); + assert_eq!( + &result.data[4..], + &output, + "Unexpected output for precompile: {precompile_addr:?}", + ); + }); + } +} + +#[test] +fn precompiles_with_info_creates_contract() { + use crate::precompiles::Precompile; + use alloy_core::sol_types::SolInterface; + use precompiles::{IWithInfo, WithInfo}; + + let precompile_addr = H160(WithInfo::::MATCHER.base_address()); + + let cases = vec![( + IWithInfo::IWithInfoCalls::dummy(IWithInfo::dummyCall {}).abi_encode(), + Vec::::new(), + RuntimeReturnCode::Success, + )]; + + for (input, output, error_code) in cases { + let (code, _code_hash) = compile_module("call_and_returncode").unwrap(); + ExtBuilder::default().build().execute_with(|| { + let id = ::AddressMapper::to_account_id(&precompile_addr); + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .native_value(1000) + .build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data( + (&precompile_addr, 0u64).encode().into_iter().chain(input).collect::>(), + ) + .build_and_unwrap_result(); + + // a pre-compile with contract info should create an account on first call + assert!(get_contract_checked(&precompile_addr).is_some()); + assert!(System::account_exists(&id)); + assert_eq!(Pallet::::evm_balance(&precompile_addr), U256::from(0)); + + assert_eq!(result.flags, ReturnFlags::empty()); + assert_eq!(u32::from_le_bytes(result.data[..4].try_into().unwrap()), error_code as u32); + assert_eq!( + &result.data[4..], + &output, + "Unexpected output for precompile: {precompile_addr:?}", + ); + }); + } +} + +#[test] +fn bump_nonce_once_works() { + let (code, hash) = compile_module("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + frame_system::Account::::mutate(&ALICE, |account| account.nonce = 1); + + let _ = ::Currency::set_balance(&BOB, 1_000_000); + frame_system::Account::::mutate(&BOB, |account| account.nonce = 1); + + builder::bare_instantiate(Code::Upload(code.clone())) + .origin(RuntimeOrigin::signed(ALICE)) + .bump_nonce(BumpNonce::Yes) + .salt(None) + .build_and_unwrap_result(); + assert_eq!(System::account_nonce(&ALICE), 2); + + // instantiate again is ok + let result = builder::bare_instantiate(Code::Existing(hash)) + .origin(RuntimeOrigin::signed(ALICE)) + .bump_nonce(BumpNonce::Yes) + .salt(None) + .build() + .result; + assert!(result.is_ok()); + + builder::bare_instantiate(Code::Upload(code.clone())) + .origin(RuntimeOrigin::signed(BOB)) + .bump_nonce(BumpNonce::No) + .salt(None) + .build_and_unwrap_result(); + assert_eq!(System::account_nonce(&BOB), 1); + + // instantiate again should fail + let err = builder::bare_instantiate(Code::Upload(code)) + .origin(RuntimeOrigin::signed(BOB)) + .bump_nonce(BumpNonce::No) + .salt(None) + .build() + .result + .unwrap_err(); + + assert_eq!(err, >::DuplicateContract.into()); + }); +} + +#[test] +fn code_size_for_precompiles_works() { + use crate::precompiles::Precompile; + use precompiles::NoInfo; + + let builtin_precompile = H160(NoInfo::::MATCHER.base_address()); + let primitive_precompile = H160::from_low_u64_be(1); + + let (code, _code_hash) = compile_module("extcodesize").unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .native_value(1000) + .build_and_unwrap_contract(); + + // the primitive pre-compiles return 0 code size on eth + builder::bare_call(addr) + .data((&primitive_precompile, 0u64).encode()) + .build_and_unwrap_result(); + + // other precompiles should return the minimal evm revert code + builder::bare_call(addr) + .data((&builtin_precompile, 5u64).encode()) + .build_and_unwrap_result(); + }); +} + +#[test] +fn call_data_limit_is_enforced_subcalls() { + let (code, _code_hash) = compile_module("call_with_input_size").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let cases: Vec<(u32, Box)> = vec![ + ( + 0_u32, + Box::new(|result| { + assert_ok!(result); + }), + ), + ( + 1_u32, + Box::new(|result| { + assert_ok!(result); + }), + ), + ( + limits::CALLDATA_BYTES, + Box::new(|result| { + assert_ok!(result); + }), + ), + ( + limits::CALLDATA_BYTES + 1, + Box::new(|result| { + assert_err!(result, >::CallDataTooLarge); + }), + ), + ]; + + for (callee_input_size, assert_result) in cases { + let result = builder::bare_call(addr).data(callee_input_size.encode()).build().result; + assert_result(result); + } + }); +} + +#[test] +fn call_data_limit_is_enforced_root_call() { + let (code, _code_hash) = compile_module("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let cases: Vec<(H160, u32, Box)> = vec![ + ( + addr, + 0_u32, + Box::new(|result| { + assert_ok!(result); + }), + ), + ( + addr, + 1_u32, + Box::new(|result| { + assert_ok!(result); + }), + ), + ( + addr, + limits::CALLDATA_BYTES, + Box::new(|result| { + assert_ok!(result); + }), + ), + ( + addr, + limits::CALLDATA_BYTES + 1, + Box::new(|result| { + assert_err!(result, >::CallDataTooLarge); + }), + ), + ( + // limit is not enforced when tx calls EOA + BOB_ADDR, + limits::CALLDATA_BYTES + 1, + Box::new(|result| { + assert_ok!(result); + }), + ), + ]; + + for (addr, callee_input_size, assert_result) in cases { + let result = builder::bare_call(addr) + .data(vec![42; callee_input_size as usize]) + .build() + .result; + assert_result(result); + } + }); +} + +#[test] +fn return_data_limit_is_enforced() { + let (code, _code_hash) = compile_module("return_sized").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let cases: Vec<(u32, Box)> = vec![ + ( + 1_u32, + Box::new(|result| { + assert_ok!(result); + }), + ), + ( + limits::CALLDATA_BYTES, + Box::new(|result| { + assert_ok!(result); + }), + ), + ( + limits::CALLDATA_BYTES + 1, + Box::new(|result| { + assert_err!(result, >::ReturnDataTooLarge); + }), + ), + ]; + + for (return_size, assert_result) in cases { + let result = builder::bare_call(addr).data(return_size.encode()).build().result; + assert_result(result); + } + }); +} diff --git a/substrate/frame/revive/src/vm/mod.rs b/substrate/frame/revive/src/vm/mod.rs index ced372c320ba1..3c65cbbfccab3 100644 --- a/substrate/frame/revive/src/vm/mod.rs +++ b/substrate/frame/revive/src/vm/mod.rs @@ -18,23 +18,16 @@ //! This module provides a means for executing contracts //! represented in vm bytecode. -mod runtime; +pub mod pvm; +mod runtime_costs; -#[cfg(doc)] -pub use crate::vm::runtime::SyscallDoc; - -#[cfg(feature = "runtime-benchmarks")] -pub use crate::vm::runtime::{ReturnData, TrapReason}; - -pub use crate::vm::runtime::{Runtime, RuntimeCosts}; +pub use runtime_costs::RuntimeCosts; use crate::{ exec::{ExecResult, Executable, ExportedFunction, Ext}, gas::{GasMeter, Token}, - limits, - storage::meter::Diff, weights::WeightInfo, - AccountIdOf, BadOrigin, BalanceOf, CodeInfoOf, CodeVec, Config, Error, ExecError, HoldReason, + AccountIdOf, BadOrigin, BalanceOf, CodeInfoOf, CodeVec, Config, Error, HoldReason, PristineCode, Weight, LOG_TARGET, }; use alloc::vec::Vec; @@ -44,7 +37,7 @@ use frame_support::{ ensure, traits::{fungible::MutateHold, tokens::Precision::BestEffort}, }; -use sp_core::{Get, H256, U256}; +use sp_core::{H256, U256}; use sp_runtime::DispatchError; /// Validated Vm module ready for execution. @@ -132,29 +125,6 @@ impl ContractBlob where BalanceOf: Into + TryFrom, { - /// We only check for size and nothing else when the code is uploaded. - pub fn from_code(code: Vec, owner: AccountIdOf) -> Result { - // We do validation only when new code is deployed. This allows us to increase - // the limits later without affecting already deployed code. - let available_syscalls = runtime::list_syscalls(T::UnsafeUnstableInterface::get()); - let code = limits::code::enforce::(code, available_syscalls)?; - - let code_len = code.len() as u32; - let bytes_added = code_len.saturating_add(>::max_encoded_len() as u32); - let deposit = Diff { bytes_added, items_added: 2, ..Default::default() } - .update_contract::(None) - .charge_or_zero(); - let code_info = CodeInfo { - owner, - deposit, - refcount: 0, - code_len, - behaviour_version: Default::default(), - }; - let code_hash = H256(sp_io::hashing::keccak_256(&code)); - Ok(ContractBlob { code, code_info, code_hash }) - } - /// Remove the code from storage and refund the deposit to its owner. /// /// Applies all necessary checks before removing the code. @@ -284,119 +254,6 @@ impl CodeInfo { } } -pub struct PreparedCall<'a, E: Ext> { - module: polkavm::Module, - instance: polkavm::RawInstance, - runtime: Runtime<'a, E, polkavm::RawInstance>, -} - -impl<'a, E: Ext> PreparedCall<'a, E> -where - BalanceOf: Into, - BalanceOf: TryFrom, -{ - pub fn call(mut self) -> ExecResult { - let exec_result = loop { - let interrupt = self.instance.run(); - if let Some(exec_result) = - self.runtime.handle_interrupt(interrupt, &self.module, &mut self.instance) - { - break exec_result - } - }; - let _ = self.runtime.ext().gas_meter_mut().sync_from_executor(self.instance.gas())?; - exec_result - } - - /// The guest memory address at which the aux data is located. - #[cfg(feature = "runtime-benchmarks")] - pub fn aux_data_base(&self) -> u32 { - self.instance.module().memory_map().aux_data_address() - } - - /// Copies `data` to the aux data at address `offset`. - /// - /// It sets `a0` to the beginning of data inside the aux data. - /// It sets `a1` to the value passed. - /// - /// Only used in benchmarking so far. - #[cfg(feature = "runtime-benchmarks")] - pub fn setup_aux_data(&mut self, data: &[u8], offset: u32, a1: u64) -> DispatchResult { - let a0 = self.aux_data_base().saturating_add(offset); - self.instance.write_memory(a0, data).map_err(|err| { - log::debug!(target: LOG_TARGET, "failed to write aux data: {err:?}"); - Error::::CodeRejected - })?; - self.instance.set_reg(polkavm::Reg::A0, a0.into()); - self.instance.set_reg(polkavm::Reg::A1, a1); - Ok(()) - } -} - -impl ContractBlob { - /// Compile and instantiate contract. - /// - /// `aux_data_size` is only used for runtime benchmarks. Real contracts - /// don't make use of this buffer. Hence this should not be set to anything - /// other than `0` when not used for benchmarking. - pub fn prepare_call>( - self, - mut runtime: Runtime, - entry_point: ExportedFunction, - aux_data_size: u32, - ) -> Result, ExecError> { - let mut config = polkavm::Config::default(); - config.set_backend(Some(polkavm::BackendKind::Interpreter)); - config.set_cache_enabled(false); - #[cfg(feature = "std")] - if std::env::var_os("REVIVE_USE_COMPILER").is_some() { - log::warn!(target: LOG_TARGET, "Using PolkaVM compiler backend because env var REVIVE_USE_COMPILER is set"); - config.set_backend(Some(polkavm::BackendKind::Compiler)); - } - let engine = polkavm::Engine::new(&config).expect( - "on-chain (no_std) use of interpreter is hard coded. - interpreter is available on all platforms; qed", - ); - - let mut module_config = polkavm::ModuleConfig::new(); - module_config.set_page_size(limits::PAGE_SIZE); - module_config.set_gas_metering(Some(polkavm::GasMeteringKind::Sync)); - module_config.set_allow_sbrk(false); - module_config.set_aux_data_size(aux_data_size); - let module = polkavm::Module::new(&engine, &module_config, self.code.into_inner().into()) - .map_err(|err| { - log::debug!(target: LOG_TARGET, "failed to create polkavm module: {err:?}"); - Error::::CodeRejected - })?; - - let entry_program_counter = module - .exports() - .find(|export| export.symbol().as_bytes() == entry_point.identifier().as_bytes()) - .ok_or_else(|| >::CodeRejected)? - .program_counter(); - - let gas_limit_polkavm: polkavm::Gas = runtime.ext().gas_meter_mut().engine_fuel_left()?; - - let mut instance = module.instantiate().map_err(|err| { - log::debug!(target: LOG_TARGET, "failed to instantiate polkavm module: {err:?}"); - Error::::CodeRejected - })?; - - instance.set_gas(gas_limit_polkavm); - instance - .set_interpreter_cache_size_limit(Some(polkavm::SetCacheSizeLimitArgs { - max_block_size: limits::code::BASIC_BLOCK_SIZE, - max_cache_size_bytes: limits::code::INTERPRETER_CACHE_BYTES - .try_into() - .map_err(|_| Error::::CodeRejected)?, - })) - .map_err(|_| Error::::CodeRejected)?; - instance.prepare_call_untyped(entry_program_counter, &[]); - - Ok(PreparedCall { module, instance, runtime }) - } -} - impl Executable for ContractBlob where BalanceOf: Into + TryFrom, @@ -414,8 +271,13 @@ where function: ExportedFunction, input_data: Vec, ) -> ExecResult { - let prepared_call = self.prepare_call(Runtime::new(ext, input_data), function, 0)?; - prepared_call.call() + if self.is_pvm() { + let prepared_call = + self.prepare_call(pvm::Runtime::new(ext, input_data), function, 0)?; + prepared_call.call() + } else { + Err(Error::::CodeRejected.into()) + } } fn code(&self) -> &[u8] { diff --git a/substrate/frame/revive/src/vm/pvm.rs b/substrate/frame/revive/src/vm/pvm.rs new file mode 100644 index 0000000000000..b0a2ed8264b24 --- /dev/null +++ b/substrate/frame/revive/src/vm/pvm.rs @@ -0,0 +1,960 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! Environment definition of the vm smart-contract runtime. + +pub mod env; + +#[cfg(doc)] +pub use env::SyscallDoc; + +use crate::{ + evm::runtime::GAS_PRICE, + exec::{ExecError, ExecResult, Ext, Key}, + gas::ChargedAmount, + limits, + precompiles::{All as AllPrecompiles, Precompiles}, + primitives::ExecReturnValue, + BalanceOf, Config, Error, Pallet, RuntimeCosts, LOG_TARGET, SENTINEL, +}; +use alloc::{vec, vec::Vec}; +use codec::Encode; +use core::{fmt, marker::PhantomData, mem}; +use frame_support::{ensure, weights::Weight}; +use pallet_revive_uapi::{CallFlags, ReturnErrorCode, ReturnFlags, StorageFlags}; +use sp_core::{H160, H256, U256}; +use sp_runtime::{DispatchError, RuntimeDebug}; + +/// Abstraction over the memory access within syscalls. +/// +/// The reason for this abstraction is that we run syscalls on the host machine when +/// benchmarking them. In that case we have direct access to the contract's memory. However, when +/// running within PolkaVM we need to resort to copying as we can't map the contracts memory into +/// the host (as of now). +pub trait Memory { + /// Read designated chunk from the sandbox memory into the supplied buffer. + /// + /// Returns `Err` if one of the following conditions occurs: + /// + /// - requested buffer is not within the bounds of the sandbox memory. + fn read_into_buf(&self, ptr: u32, buf: &mut [u8]) -> Result<(), DispatchError>; + + /// Write the given buffer to the designated location in the sandbox memory. + /// + /// Returns `Err` if one of the following conditions occurs: + /// + /// - designated area is not within the bounds of the sandbox memory. + fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError>; + + /// Zero the designated location in the sandbox memory. + /// + /// Returns `Err` if one of the following conditions occurs: + /// + /// - designated area is not within the bounds of the sandbox memory. + fn zero(&mut self, ptr: u32, len: u32) -> Result<(), DispatchError>; + + /// This will reset all compilation artifacts of the currently executing instance. + /// + /// This is used before we call into a new contract to free up some memory. Doing + /// so we make sure that we only ever have to hold one compilation cache at a time + /// independtently of of our call stack depth. + fn reset_interpreter_cache(&mut self); + + /// Read designated chunk from the sandbox memory. + /// + /// Returns `Err` if one of the following conditions occurs: + /// + /// - requested buffer is not within the bounds of the sandbox memory. + fn read(&self, ptr: u32, len: u32) -> Result, DispatchError> { + let mut buf = vec![0u8; len as usize]; + self.read_into_buf(ptr, buf.as_mut_slice())?; + Ok(buf) + } + + /// Same as `read` but reads into a fixed size buffer. + fn read_array(&self, ptr: u32) -> Result<[u8; N], DispatchError> { + let mut buf = [0u8; N]; + self.read_into_buf(ptr, &mut buf)?; + Ok(buf) + } + + /// Read a `u32` from the sandbox memory. + fn read_u32(&self, ptr: u32) -> Result { + let buf: [u8; 4] = self.read_array(ptr)?; + Ok(u32::from_le_bytes(buf)) + } + + /// Read a `U256` from the sandbox memory. + fn read_u256(&self, ptr: u32) -> Result { + let buf: [u8; 32] = self.read_array(ptr)?; + Ok(U256::from_little_endian(&buf)) + } + + /// Read a `H160` from the sandbox memory. + fn read_h160(&self, ptr: u32) -> Result { + let mut buf = H160::default(); + self.read_into_buf(ptr, buf.as_bytes_mut())?; + Ok(buf) + } + + /// Read a `H256` from the sandbox memory. + fn read_h256(&self, ptr: u32) -> Result { + let mut code_hash = H256::default(); + self.read_into_buf(ptr, code_hash.as_bytes_mut())?; + Ok(code_hash) + } +} + +/// Allows syscalls access to the PolkaVM instance they are executing in. +/// +/// In case a contract is executing within PolkaVM its `memory` argument will also implement +/// this trait. The benchmarking implementation of syscalls will only require `Memory` +/// to be implemented. +pub trait PolkaVmInstance: Memory { + fn gas(&self) -> polkavm::Gas; + fn set_gas(&mut self, gas: polkavm::Gas); + fn read_input_regs(&self) -> (u64, u64, u64, u64, u64, u64); + fn write_output(&mut self, output: u64); +} + +// Memory implementation used in benchmarking where guest memory is mapped into the host. +// +// Please note that we could optimize the `read_as_*` functions by decoding directly from +// memory without a copy. However, we don't do that because as it would change the behaviour +// of those functions: A `read_as` with a `len` larger than the actual type can succeed +// in the streaming implementation while it could fail with a segfault in the copy implementation. +#[cfg(feature = "runtime-benchmarks")] +impl Memory for [u8] { + fn read_into_buf(&self, ptr: u32, buf: &mut [u8]) -> Result<(), DispatchError> { + let ptr = ptr as usize; + let bound_checked = + self.get(ptr..ptr + buf.len()).ok_or_else(|| Error::::OutOfBounds)?; + buf.copy_from_slice(bound_checked); + Ok(()) + } + + fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> { + let ptr = ptr as usize; + let bound_checked = + self.get_mut(ptr..ptr + buf.len()).ok_or_else(|| Error::::OutOfBounds)?; + bound_checked.copy_from_slice(buf); + Ok(()) + } + + fn zero(&mut self, ptr: u32, len: u32) -> Result<(), DispatchError> { + <[u8] as Memory>::write(self, ptr, &vec![0; len as usize]) + } + + fn reset_interpreter_cache(&mut self) {} +} + +impl Memory for polkavm::RawInstance { + fn read_into_buf(&self, ptr: u32, buf: &mut [u8]) -> Result<(), DispatchError> { + self.read_memory_into(ptr, buf) + .map(|_| ()) + .map_err(|_| Error::::OutOfBounds.into()) + } + + fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> { + self.write_memory(ptr, buf).map_err(|_| Error::::OutOfBounds.into()) + } + + fn zero(&mut self, ptr: u32, len: u32) -> Result<(), DispatchError> { + self.zero_memory(ptr, len).map_err(|_| Error::::OutOfBounds.into()) + } + + fn reset_interpreter_cache(&mut self) { + self.reset_interpreter_cache(); + } +} + +impl PolkaVmInstance for polkavm::RawInstance { + fn gas(&self) -> polkavm::Gas { + self.gas() + } + + fn set_gas(&mut self, gas: polkavm::Gas) { + self.set_gas(gas) + } + + fn read_input_regs(&self) -> (u64, u64, u64, u64, u64, u64) { + ( + self.reg(polkavm::Reg::A0), + self.reg(polkavm::Reg::A1), + self.reg(polkavm::Reg::A2), + self.reg(polkavm::Reg::A3), + self.reg(polkavm::Reg::A4), + self.reg(polkavm::Reg::A5), + ) + } + + fn write_output(&mut self, output: u64) { + self.set_reg(polkavm::Reg::A0, output); + } +} + +impl From<&ExecReturnValue> for ReturnErrorCode { + fn from(from: &ExecReturnValue) -> Self { + if from.flags.contains(ReturnFlags::REVERT) { + Self::CalleeReverted + } else { + Self::Success + } + } +} + +/// The data passed through when a contract uses `seal_return`. +#[derive(RuntimeDebug)] +pub struct ReturnData { + /// The flags as passed through by the contract. They are still unchecked and + /// will later be parsed into a `ReturnFlags` bitflags struct. + flags: u32, + /// The output buffer passed by the contract as return data. + data: Vec, +} + +/// Enumerates all possible reasons why a trap was generated. +/// +/// This is either used to supply the caller with more information about why an error +/// occurred (the SupervisorError variant). +/// The other case is where the trap does not constitute an error but rather was invoked +/// as a quick way to terminate the application (all other variants). +#[derive(RuntimeDebug)] +pub enum TrapReason { + /// The supervisor trapped the contract because of an error condition occurred during + /// execution in privileged code. + SupervisorError(DispatchError), + /// Signals that trap was generated in response to call `seal_return` host function. + Return(ReturnData), + /// Signals that a trap was generated in response to a successful call to the + /// `seal_terminate` host function. + Termination, +} + +impl> From for TrapReason { + fn from(from: T) -> Self { + Self::SupervisorError(from.into()) + } +} + +impl fmt::Display for TrapReason { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + Ok(()) + } +} + +/// Same as [`Runtime::charge_gas`]. +/// +/// We need this access as a macro because sometimes hiding the lifetimes behind +/// a function won't work out. +macro_rules! charge_gas { + ($runtime:expr, $costs:expr) => {{ + $runtime.ext.gas_meter_mut().charge($costs) + }}; +} + +/// The kind of call that should be performed. +enum CallType { + /// Execute another instantiated contract + Call { value_ptr: u32 }, + /// Execute another contract code in the context (storage, account ID, value) of the caller + /// contract + DelegateCall, +} + +impl CallType { + fn cost(&self) -> RuntimeCosts { + match self { + CallType::Call { .. } => RuntimeCosts::CallBase, + CallType::DelegateCall => RuntimeCosts::DelegateCallBase, + } + } +} + +/// This is only appropriate when writing out data of constant size that does not depend on user +/// input. In this case the costs for this copy was already charged as part of the token at +/// the beginning of the API entry point. +fn already_charged(_: u32) -> Option { + None +} + +/// Helper to extract two `u32` values from a given `u64` register. +fn extract_hi_lo(reg: u64) -> (u32, u32) { + ((reg >> 32) as u32, reg as u32) +} + +/// Provides storage variants to support standard and Etheruem compatible semantics. +enum StorageValue { + /// Indicates that the storage value should be read from a memory buffer. + /// - `ptr`: A pointer to the start of the data in sandbox memory. + /// - `len`: The length (in bytes) of the data. + Memory { ptr: u32, len: u32 }, + + /// Indicates that the storage value is provided inline as a fixed-size (256-bit) value. + /// This is used by set_storage_or_clear() to avoid double reads. + /// This variant is used to implement Ethereum SSTORE-like semantics. + Value(Vec), +} + +/// Controls the output behavior for storage reads, both when a key is found and when it is not. +enum StorageReadMode { + /// VariableOutput mode: if the key exists, the full stored value is returned + /// using the caller‑provided output length. + VariableOutput { output_len_ptr: u32 }, + /// Ethereum compatible(FixedOutput32) mode: always write a 32-byte value into the output + /// buffer. If the key is missing, write 32 bytes of zeros. + FixedOutput32, +} + +/// Can only be used for one call. +pub struct Runtime<'a, E: Ext, M: ?Sized> { + ext: &'a mut E, + input_data: Option>, + _phantom_data: PhantomData, +} + +impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { + pub fn new(ext: &'a mut E, input_data: Vec) -> Self { + Self { ext, input_data: Some(input_data), _phantom_data: Default::default() } + } + + /// Get a mutable reference to the inner `Ext`. + pub fn ext(&mut self) -> &mut E { + self.ext + } + + /// Charge the gas meter with the specified token. + /// + /// Returns `Err(HostError)` if there is not enough gas. + fn charge_gas(&mut self, costs: RuntimeCosts) -> Result { + charge_gas!(self, costs) + } + + /// Adjust a previously charged amount down to its actual amount. + /// + /// This is when a maximum a priori amount was charged and then should be partially + /// refunded to match the actual amount. + fn adjust_gas(&mut self, charged: ChargedAmount, actual_costs: RuntimeCosts) { + self.ext.gas_meter_mut().adjust_gas(charged, actual_costs); + } + + /// Write the given buffer and its length to the designated locations in sandbox memory and + /// charge gas according to the token returned by `create_token`. + /// + /// `out_ptr` is the location in sandbox memory where `buf` should be written to. + /// `out_len_ptr` is an in-out location in sandbox memory. It is read to determine the + /// length of the buffer located at `out_ptr`. If that buffer is smaller than the actual + /// `buf.len()`, only what fits into that buffer is written to `out_ptr`. + /// The actual amount of bytes copied to `out_ptr` is written to `out_len_ptr`. + /// + /// If `out_ptr` is set to the sentinel value of `SENTINEL` and `allow_skip` is true the + /// operation is skipped and `Ok` is returned. This is supposed to help callers to make copying + /// output optional. For example to skip copying back the output buffer of an `seal_call` + /// when the caller is not interested in the result. + /// + /// `create_token` can optionally instruct this function to charge the gas meter with the token + /// it returns. `create_token` receives the variable amount of bytes that are about to be copied + /// by this function. + /// + /// In addition to the error conditions of `Memory::write` this functions returns + /// `Err` if the size of the buffer located at `out_ptr` is too small to fit `buf`. + pub fn write_sandbox_output( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + buf: &[u8], + allow_skip: bool, + create_token: impl FnOnce(u32) -> Option, + ) -> Result<(), DispatchError> { + if allow_skip && out_ptr == SENTINEL { + return Ok(()); + } + + let len = memory.read_u32(out_len_ptr)?; + let buf_len = len.min(buf.len() as u32); + + if let Some(costs) = create_token(buf_len) { + self.charge_gas(costs)?; + } + + memory.write(out_ptr, &buf[..buf_len as usize])?; + memory.write(out_len_ptr, &buf_len.encode()) + } + + /// Same as `write_sandbox_output` but for static size output. + pub fn write_fixed_sandbox_output( + &mut self, + memory: &mut M, + out_ptr: u32, + buf: &[u8], + allow_skip: bool, + create_token: impl FnOnce(u32) -> Option, + ) -> Result<(), DispatchError> { + if buf.is_empty() || (allow_skip && out_ptr == SENTINEL) { + return Ok(()); + } + + let buf_len = buf.len() as u32; + if let Some(costs) = create_token(buf_len) { + self.charge_gas(costs)?; + } + + memory.write(out_ptr, buf) + } + + /// Computes the given hash function on the supplied input. + /// + /// Reads from the sandboxed input buffer into an intermediate buffer. + /// Returns the result directly to the output buffer of the sandboxed memory. + /// + /// It is the callers responsibility to provide an output buffer that + /// is large enough to hold the expected amount of bytes returned by the + /// chosen hash function. + /// + /// # Note + /// + /// The `input` and `output` buffers may overlap. + fn compute_hash_on_intermediate_buffer( + &self, + memory: &mut M, + hash_fn: F, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), DispatchError> + where + F: FnOnce(&[u8]) -> R, + R: AsRef<[u8]>, + { + // Copy input into supervisor memory. + let input = memory.read(input_ptr, input_len)?; + // Compute the hash on the input buffer using the given hash function. + let hash = hash_fn(&input); + // Write the resulting hash back into the sandboxed output buffer. + memory.write(output_ptr, hash.as_ref())?; + Ok(()) + } + + /// Fallible conversion of a `ExecError` to `ReturnErrorCode`. + /// + /// This is used when converting the error returned from a subcall in order to decide + /// whether to trap the caller or allow handling of the error. + fn exec_error_into_return_code(from: ExecError) -> Result { + use crate::exec::ErrorOrigin::Callee; + use ReturnErrorCode::*; + + let transfer_failed = Error::::TransferFailed.into(); + let out_of_gas = Error::::OutOfGas.into(); + let out_of_deposit = Error::::StorageDepositLimitExhausted.into(); + let duplicate_contract = Error::::DuplicateContract.into(); + let unsupported_precompile = Error::::UnsupportedPrecompileAddress.into(); + + // errors in the callee do not trap the caller + match (from.error, from.origin) { + (err, _) if err == transfer_failed => Ok(TransferFailed), + (err, _) if err == duplicate_contract => Ok(DuplicateContractAddress), + (err, _) if err == unsupported_precompile => Err(err), + (err, Callee) if err == out_of_gas || err == out_of_deposit => Ok(OutOfResources), + (_, Callee) => Ok(CalleeTrapped), + (err, _) => Err(err), + } + } + + fn decode_key(&self, memory: &M, key_ptr: u32, key_len: u32) -> Result { + let res = match key_len { + SENTINEL => { + let mut buffer = [0u8; 32]; + memory.read_into_buf(key_ptr, buffer.as_mut())?; + Ok(Key::from_fixed(buffer)) + }, + len => { + ensure!(len <= limits::STORAGE_KEY_BYTES, Error::::DecodingFailed); + let key = memory.read(key_ptr, len)?; + Key::try_from_var(key) + }, + }; + + res.map_err(|_| Error::::DecodingFailed.into()) + } + + fn is_transient(flags: u32) -> Result { + StorageFlags::from_bits(flags) + .ok_or_else(|| >::InvalidStorageFlags.into()) + .map(|flags| flags.contains(StorageFlags::TRANSIENT)) + } + + fn set_storage( + &mut self, + memory: &M, + flags: u32, + key_ptr: u32, + key_len: u32, + value: StorageValue, + ) -> Result { + let transient = Self::is_transient(flags)?; + let costs = |new_bytes: u32, old_bytes: u32| { + if transient { + RuntimeCosts::SetTransientStorage { new_bytes, old_bytes } + } else { + RuntimeCosts::SetStorage { new_bytes, old_bytes } + } + }; + + let value_len = match &value { + StorageValue::Memory { ptr: _, len } => *len, + StorageValue::Value(data) => data.len() as u32, + }; + + let max_size = self.ext.max_value_size(); + let charged = self.charge_gas(costs(value_len, self.ext.max_value_size()))?; + if value_len > max_size { + return Err(Error::::ValueTooLarge.into()); + } + + let key = self.decode_key(memory, key_ptr, key_len)?; + + let value = match value { + StorageValue::Memory { ptr, len } => Some(memory.read(ptr, len)?), + StorageValue::Value(data) => Some(data), + }; + + let write_outcome = if transient { + self.ext.set_transient_storage(&key, value, false)? + } else { + self.ext.set_storage(&key, value, false)? + }; + + self.adjust_gas(charged, costs(value_len, write_outcome.old_len())); + Ok(write_outcome.old_len_with_sentinel()) + } + + fn clear_storage( + &mut self, + memory: &M, + flags: u32, + key_ptr: u32, + key_len: u32, + ) -> Result { + let transient = Self::is_transient(flags)?; + let costs = |len| { + if transient { + RuntimeCosts::ClearTransientStorage(len) + } else { + RuntimeCosts::ClearStorage(len) + } + }; + let charged = self.charge_gas(costs(self.ext.max_value_size()))?; + let key = self.decode_key(memory, key_ptr, key_len)?; + let outcome = if transient { + self.ext.set_transient_storage(&key, None, false)? + } else { + self.ext.set_storage(&key, None, false)? + }; + self.adjust_gas(charged, costs(outcome.old_len())); + Ok(outcome.old_len_with_sentinel()) + } + + fn get_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + read_mode: StorageReadMode, + ) -> Result { + let transient = Self::is_transient(flags)?; + let costs = |len| { + if transient { + RuntimeCosts::GetTransientStorage(len) + } else { + RuntimeCosts::GetStorage(len) + } + }; + let charged = self.charge_gas(costs(self.ext.max_value_size()))?; + let key = self.decode_key(memory, key_ptr, key_len)?; + let outcome = if transient { + self.ext.get_transient_storage(&key) + } else { + self.ext.get_storage(&key) + }; + + if let Some(value) = outcome { + self.adjust_gas(charged, costs(value.len() as u32)); + + match read_mode { + StorageReadMode::FixedOutput32 => { + let mut fixed_output = [0u8; 32]; + let len = value.len().min(fixed_output.len()); + fixed_output[..len].copy_from_slice(&value[..len]); + + self.write_fixed_sandbox_output( + memory, + out_ptr, + &fixed_output, + false, + already_charged, + )?; + Ok(ReturnErrorCode::Success) + }, + StorageReadMode::VariableOutput { output_len_ptr: out_len_ptr } => { + self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &value, + false, + already_charged, + )?; + Ok(ReturnErrorCode::Success) + }, + } + } else { + self.adjust_gas(charged, costs(0)); + + match read_mode { + StorageReadMode::FixedOutput32 => { + self.write_fixed_sandbox_output( + memory, + out_ptr, + &[0u8; 32], + false, + already_charged, + )?; + Ok(ReturnErrorCode::Success) + }, + StorageReadMode::VariableOutput { .. } => Ok(ReturnErrorCode::KeyNotFound), + } + } + } + + fn contains_storage( + &mut self, + memory: &M, + flags: u32, + key_ptr: u32, + key_len: u32, + ) -> Result { + let transient = Self::is_transient(flags)?; + let costs = |len| { + if transient { + RuntimeCosts::ContainsTransientStorage(len) + } else { + RuntimeCosts::ContainsStorage(len) + } + }; + let charged = self.charge_gas(costs(self.ext.max_value_size()))?; + let key = self.decode_key(memory, key_ptr, key_len)?; + let outcome = if transient { + self.ext.get_transient_storage_size(&key) + } else { + self.ext.get_storage_size(&key) + }; + self.adjust_gas(charged, costs(outcome.unwrap_or(0))); + Ok(outcome.unwrap_or(SENTINEL)) + } + + fn take_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + let transient = Self::is_transient(flags)?; + let costs = |len| { + if transient { + RuntimeCosts::TakeTransientStorage(len) + } else { + RuntimeCosts::TakeStorage(len) + } + }; + let charged = self.charge_gas(costs(self.ext.max_value_size()))?; + let key = self.decode_key(memory, key_ptr, key_len)?; + let outcome = if transient { + self.ext.set_transient_storage(&key, None, true)? + } else { + self.ext.set_storage(&key, None, true)? + }; + + if let crate::storage::WriteOutcome::Taken(value) = outcome { + self.adjust_gas(charged, costs(value.len() as u32)); + self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &value, + false, + already_charged, + )?; + Ok(ReturnErrorCode::Success) + } else { + self.adjust_gas(charged, costs(0)); + Ok(ReturnErrorCode::KeyNotFound) + } + } + + fn call( + &mut self, + memory: &mut M, + flags: CallFlags, + call_type: CallType, + callee_ptr: u32, + deposit_ptr: u32, + weight: Weight, + input_data_ptr: u32, + input_data_len: u32, + output_ptr: u32, + output_len_ptr: u32, + ) -> Result { + let callee = memory.read_h160(callee_ptr)?; + let precompile = >::get::(&callee.as_fixed_bytes()); + match &precompile { + Some(precompile) if precompile.has_contract_info() => + self.charge_gas(RuntimeCosts::PrecompileWithInfoBase)?, + Some(_) => self.charge_gas(RuntimeCosts::PrecompileBase)?, + None => self.charge_gas(call_type.cost())?, + }; + + let deposit_limit = memory.read_u256(deposit_ptr)?; + + // we do check this in exec.rs but we want to error out early + if input_data_len > limits::CALLDATA_BYTES { + Err(>::CallDataTooLarge)?; + } + + let input_data = if flags.contains(CallFlags::CLONE_INPUT) { + let input = self.input_data.as_ref().ok_or(Error::::InputForwarded)?; + charge_gas!(self, RuntimeCosts::CallInputCloned(input.len() as u32))?; + input.clone() + } else if flags.contains(CallFlags::FORWARD_INPUT) { + self.input_data.take().ok_or(Error::::InputForwarded)? + } else { + if precompile.is_some() { + self.charge_gas(RuntimeCosts::PrecompileDecode(input_data_len))?; + } else { + self.charge_gas(RuntimeCosts::CopyFromContract(input_data_len))?; + } + memory.read(input_data_ptr, input_data_len)? + }; + + memory.reset_interpreter_cache(); + + let call_outcome = match call_type { + CallType::Call { value_ptr } => { + let read_only = flags.contains(CallFlags::READ_ONLY); + let value = memory.read_u256(value_ptr)?; + if value > 0u32.into() { + // If the call value is non-zero and state change is not allowed, issue an + // error. + if read_only || self.ext.is_read_only() { + return Err(Error::::StateChangeDenied.into()); + } + + self.charge_gas(RuntimeCosts::CallTransferSurcharge { + dust_transfer: Pallet::::has_dust(value), + })?; + } + self.ext.call( + weight, + deposit_limit, + &callee, + value, + input_data, + flags.contains(CallFlags::ALLOW_REENTRY), + read_only, + ) + }, + CallType::DelegateCall => { + if flags.intersects(CallFlags::ALLOW_REENTRY | CallFlags::READ_ONLY) { + return Err(Error::::InvalidCallFlags.into()); + } + self.ext.delegate_call(weight, deposit_limit, callee, input_data) + }, + }; + + match call_outcome { + // `TAIL_CALL` only matters on an `OK` result. Otherwise the call stack comes to + // a halt anyways without anymore code being executed. + Ok(_) if flags.contains(CallFlags::TAIL_CALL) => { + let output = mem::take(self.ext.last_frame_output_mut()); + return Err(TrapReason::Return(ReturnData { + flags: output.flags.bits(), + data: output.data, + })); + }, + Ok(_) => { + let output = mem::take(self.ext.last_frame_output_mut()); + let write_result = self.write_sandbox_output( + memory, + output_ptr, + output_len_ptr, + &output.data, + true, + |len| Some(RuntimeCosts::CopyToContract(len)), + ); + *self.ext.last_frame_output_mut() = output; + write_result?; + Ok(self.ext.last_frame_output().into()) + }, + Err(err) => { + let error_code = Self::exec_error_into_return_code(err)?; + memory.write(output_len_ptr, &0u32.to_le_bytes())?; + Ok(error_code) + }, + } + } + + fn instantiate( + &mut self, + memory: &mut M, + code_hash_ptr: u32, + weight: Weight, + deposit_ptr: u32, + value_ptr: u32, + input_data_ptr: u32, + input_data_len: u32, + address_ptr: u32, + output_ptr: u32, + output_len_ptr: u32, + salt_ptr: u32, + ) -> Result { + let value = match memory.read_u256(value_ptr) { + Ok(value) => { + self.charge_gas(RuntimeCosts::Instantiate { + input_data_len, + balance_transfer: Pallet::::has_balance(value), + dust_transfer: Pallet::::has_dust(value), + })?; + value + }, + Err(err) => { + self.charge_gas(RuntimeCosts::Instantiate { + input_data_len: 0, + balance_transfer: false, + dust_transfer: false, + })?; + return Err(err.into()); + }, + }; + let deposit_limit: U256 = memory.read_u256(deposit_ptr)?; + let code_hash = memory.read_h256(code_hash_ptr)?; + if input_data_len > limits::CALLDATA_BYTES { + Err(>::CallDataTooLarge)?; + } + let input_data = memory.read(input_data_ptr, input_data_len)?; + let salt = if salt_ptr == SENTINEL { + None + } else { + let salt: [u8; 32] = memory.read_array(salt_ptr)?; + Some(salt) + }; + + memory.reset_interpreter_cache(); + + match self.ext.instantiate( + weight, + deposit_limit, + code_hash, + value, + input_data, + salt.as_ref(), + ) { + Ok(address) => { + if !self.ext.last_frame_output().flags.contains(ReturnFlags::REVERT) { + self.write_fixed_sandbox_output( + memory, + address_ptr, + &address.as_bytes(), + true, + already_charged, + )?; + } + let output = mem::take(self.ext.last_frame_output_mut()); + let write_result = self.write_sandbox_output( + memory, + output_ptr, + output_len_ptr, + &output.data, + true, + |len| Some(RuntimeCosts::CopyToContract(len)), + ); + *self.ext.last_frame_output_mut() = output; + write_result?; + Ok(self.ext.last_frame_output().into()) + }, + Err(err) => Ok(Self::exec_error_into_return_code(err)?), + } + } +} + +pub struct PreparedCall<'a, E: Ext> { + module: polkavm::Module, + instance: polkavm::RawInstance, + runtime: Runtime<'a, E, polkavm::RawInstance>, +} + +impl<'a, E: Ext> PreparedCall<'a, E> +where + BalanceOf: Into, + BalanceOf: TryFrom, +{ + pub fn call(mut self) -> ExecResult { + let exec_result = loop { + let interrupt = self.instance.run(); + if let Some(exec_result) = + self.runtime.handle_interrupt(interrupt, &self.module, &mut self.instance) + { + break exec_result + } + }; + let _ = self.runtime.ext().gas_meter_mut().sync_from_executor(self.instance.gas())?; + exec_result + } + + /// The guest memory address at which the aux data is located. + #[cfg(feature = "runtime-benchmarks")] + pub fn aux_data_base(&self) -> u32 { + self.instance.module().memory_map().aux_data_address() + } + + /// Copies `data` to the aux data at address `offset`. + /// + /// It sets `a0` to the beginning of data inside the aux data. + /// It sets `a1` to the value passed. + /// + /// Only used in benchmarking so far. + #[cfg(feature = "runtime-benchmarks")] + pub fn setup_aux_data( + &mut self, + data: &[u8], + offset: u32, + a1: u64, + ) -> frame_support::dispatch::DispatchResult { + let a0 = self.aux_data_base().saturating_add(offset); + self.instance.write_memory(a0, data).map_err(|err| { + log::debug!(target: LOG_TARGET, "failed to write aux data: {err:?}"); + Error::::CodeRejected + })?; + self.instance.set_reg(polkavm::Reg::A0, a0.into()); + self.instance.set_reg(polkavm::Reg::A1, a1); + Ok(()) + } +} diff --git a/substrate/frame/revive/src/vm/pvm/env.rs b/substrate/frame/revive/src/vm/pvm/env.rs new file mode 100644 index 0000000000000..0255670a4f529 --- /dev/null +++ b/substrate/frame/revive/src/vm/pvm/env.rs @@ -0,0 +1,1037 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +use super::*; + +use crate::{ + address::AddressMapper, + exec::Ext, + limits, + primitives::ExecReturnValue, + storage::meter::Diff, + vm::{ExportedFunction, RuntimeCosts}, + AccountIdOf, BalanceOf, CodeInfo, Config, ContractBlob, Error, Weight, SENTINEL, +}; +use alloc::vec::Vec; +use codec::{Encode, MaxEncodedLen}; +use core::mem; +use frame_support::traits::Get; +use pallet_revive_proc_macro::define_env; +use pallet_revive_uapi::{CallFlags, ReturnErrorCode, ReturnFlags}; +use sp_core::{H160, H256, U256}; +use sp_io::hashing::keccak_256; +use sp_runtime::DispatchError; + +impl ContractBlob { + /// Compile and instantiate contract. + /// + /// `aux_data_size` is only used for runtime benchmarks. Real contracts + /// don't make use of this buffer. Hence this should not be set to anything + /// other than `0` when not used for benchmarking. + pub fn prepare_call>( + self, + mut runtime: Runtime, + entry_point: ExportedFunction, + aux_data_size: u32, + ) -> Result, ExecError> { + let mut config = polkavm::Config::default(); + config.set_backend(Some(polkavm::BackendKind::Interpreter)); + config.set_cache_enabled(false); + #[cfg(feature = "std")] + if std::env::var_os("REVIVE_USE_COMPILER").is_some() { + log::warn!(target: LOG_TARGET, "Using PolkaVM compiler backend because env var REVIVE_USE_COMPILER is set"); + config.set_backend(Some(polkavm::BackendKind::Compiler)); + } + let engine = polkavm::Engine::new(&config).expect( + "on-chain (no_std) use of interpreter is hard coded. + interpreter is available on all platforms; qed", + ); + + let mut module_config = polkavm::ModuleConfig::new(); + module_config.set_page_size(limits::PAGE_SIZE); + module_config.set_gas_metering(Some(polkavm::GasMeteringKind::Sync)); + module_config.set_allow_sbrk(false); + module_config.set_aux_data_size(aux_data_size); + let module = polkavm::Module::new(&engine, &module_config, self.code.into_inner().into()) + .map_err(|err| { + log::debug!(target: LOG_TARGET, "failed to create polkavm module: {err:?}"); + Error::::CodeRejected + })?; + + let entry_program_counter = module + .exports() + .find(|export| export.symbol().as_bytes() == entry_point.identifier().as_bytes()) + .ok_or_else(|| >::CodeRejected)? + .program_counter(); + + let gas_limit_polkavm: polkavm::Gas = runtime.ext().gas_meter_mut().engine_fuel_left()?; + + let mut instance = module.instantiate().map_err(|err| { + log::debug!(target: LOG_TARGET, "failed to instantiate polkavm module: {err:?}"); + Error::::CodeRejected + })?; + + instance.set_gas(gas_limit_polkavm); + instance + .set_interpreter_cache_size_limit(Some(polkavm::SetCacheSizeLimitArgs { + max_block_size: limits::code::BASIC_BLOCK_SIZE, + max_cache_size_bytes: limits::code::INTERPRETER_CACHE_BYTES + .try_into() + .map_err(|_| Error::::CodeRejected)?, + })) + .map_err(|_| Error::::CodeRejected)?; + instance.prepare_call_untyped(entry_program_counter, &[]); + + Ok(PreparedCall { module, instance, runtime }) + } +} + +impl ContractBlob +where + BalanceOf: Into + TryFrom, +{ + /// We only check for size and nothing else when the code is uploaded. + pub fn from_pvm_code(code: Vec, owner: AccountIdOf) -> Result { + // We do validation only when new code is deployed. This allows us to increase + // the limits later without affecting already deployed code. + let available_syscalls = list_syscalls(T::UnsafeUnstableInterface::get()); + let code = limits::code::enforce::(code, available_syscalls)?; + + let code_len = code.len() as u32; + let bytes_added = code_len.saturating_add(>::max_encoded_len() as u32); + let deposit = Diff { bytes_added, items_added: 2, ..Default::default() } + .update_contract::(None) + .charge_or_zero(); + let code_info = CodeInfo { + owner, + deposit, + refcount: 0, + code_len, + behaviour_version: Default::default(), + }; + let code_hash = H256(sp_io::hashing::keccak_256(&code)); + Ok(ContractBlob { code, code_info, code_hash }) + } +} + +impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { + pub fn handle_interrupt( + &mut self, + interrupt: Result, + module: &polkavm::Module, + instance: &mut M, + ) -> Option { + use polkavm::InterruptKind::*; + + match interrupt { + Err(error) => { + // in contrast to the other returns this "should" not happen: log level error + log::error!(target: LOG_TARGET, "polkavm execution error: {error}"); + Some(Err(Error::::ExecutionFailed.into())) + }, + Ok(Finished) => + Some(Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })), + Ok(Trap) => Some(Err(Error::::ContractTrapped.into())), + Ok(Segfault(_)) => Some(Err(Error::::ExecutionFailed.into())), + Ok(NotEnoughGas) => Some(Err(Error::::OutOfGas.into())), + Ok(Step) => None, + Ok(Ecalli(idx)) => { + // This is a special hard coded syscall index which is used by benchmarks + // to abort contract execution. It is used to terminate the execution without + // breaking up a basic block. The fixed index is used so that the benchmarks + // don't have to deal with import tables. + if cfg!(feature = "runtime-benchmarks") && idx == SENTINEL { + return Some(Ok(ExecReturnValue { + flags: ReturnFlags::empty(), + data: Vec::new(), + })) + } + let Some(syscall_symbol) = module.imports().get(idx) else { + return Some(Err(>::InvalidSyscall.into())); + }; + match self.handle_ecall(instance, syscall_symbol.as_bytes()) { + Ok(None) => None, + Ok(Some(return_value)) => { + instance.write_output(return_value); + None + }, + Err(TrapReason::Return(ReturnData { flags, data })) => + match ReturnFlags::from_bits(flags) { + None => Some(Err(Error::::InvalidCallFlags.into())), + Some(flags) => Some(Ok(ExecReturnValue { flags, data })), + }, + Err(TrapReason::Termination) => Some(Ok(Default::default())), + Err(TrapReason::SupervisorError(error)) => Some(Err(error.into())), + } + }, + } + } +} + +// This is the API exposed to contracts. +// +// # Note +// +// Any input that leads to a out of bound error (reading or writing) or failing to decode +// data passed to the supervisor will lead to a trap. This is not documented explicitly +// for every function. +#[define_env] +pub mod env { + /// Noop function used to benchmark the time it takes to execute an empty function. + /// + /// Marked as stable because it needs to be called from benchmarks even when the benchmarked + /// parachain has unstable functions disabled. + #[cfg(feature = "runtime-benchmarks")] + #[stable] + fn noop(&mut self, memory: &mut M) -> Result<(), TrapReason> { + Ok(()) + } + + /// Set the value at the given key in the contract storage. + /// See [`pallet_revive_uapi::HostFn::set_storage_v2`] + #[stable] + #[mutating] + fn set_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + value_ptr: u32, + value_len: u32, + ) -> Result { + self.set_storage( + memory, + flags, + key_ptr, + key_len, + StorageValue::Memory { ptr: value_ptr, len: value_len }, + ) + } + + /// Sets the storage at a fixed 256-bit key with a fixed 256-bit value. + /// See [`pallet_revive_uapi::HostFn::set_storage_or_clear`]. + #[stable] + #[mutating] + fn set_storage_or_clear( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + value_ptr: u32, + ) -> Result { + let value = memory.read(value_ptr, 32)?; + + if value.iter().all(|&b| b == 0) { + self.clear_storage(memory, flags, key_ptr, SENTINEL) + } else { + self.set_storage(memory, flags, key_ptr, SENTINEL, StorageValue::Value(value)) + } + } + + /// Retrieve the value under the given key from storage. + /// See [`pallet_revive_uapi::HostFn::get_storage`] + #[stable] + fn get_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + self.get_storage( + memory, + flags, + key_ptr, + key_len, + out_ptr, + StorageReadMode::VariableOutput { output_len_ptr: out_len_ptr }, + ) + } + + /// Reads the storage at a fixed 256-bit key and writes back a fixed 256-bit value. + /// See [`pallet_revive_uapi::HostFn::get_storage_or_zero`]. + #[stable] + fn get_storage_or_zero( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + out_ptr: u32, + ) -> Result<(), TrapReason> { + let _ = self.get_storage( + memory, + flags, + key_ptr, + SENTINEL, + out_ptr, + StorageReadMode::FixedOutput32, + )?; + + Ok(()) + } + + /// Make a call to another contract. + /// See [`pallet_revive_uapi::HostFn::call`]. + #[stable] + fn call( + &mut self, + memory: &mut M, + flags_and_callee: u64, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_and_value: u64, + input_data: u64, + output_data: u64, + ) -> Result { + let (flags, callee_ptr) = extract_hi_lo(flags_and_callee); + let (deposit_ptr, value_ptr) = extract_hi_lo(deposit_and_value); + let (input_data_len, input_data_ptr) = extract_hi_lo(input_data); + let (output_len_ptr, output_ptr) = extract_hi_lo(output_data); + + self.call( + memory, + CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, + CallType::Call { value_ptr }, + callee_ptr, + deposit_ptr, + Weight::from_parts(ref_time_limit, proof_size_limit), + input_data_ptr, + input_data_len, + output_ptr, + output_len_ptr, + ) + } + + /// Execute code in the context (storage, caller, value) of the current contract. + /// See [`pallet_revive_uapi::HostFn::delegate_call`]. + #[stable] + fn delegate_call( + &mut self, + memory: &mut M, + flags_and_callee: u64, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_ptr: u32, + input_data: u64, + output_data: u64, + ) -> Result { + let (flags, address_ptr) = extract_hi_lo(flags_and_callee); + let (input_data_len, input_data_ptr) = extract_hi_lo(input_data); + let (output_len_ptr, output_ptr) = extract_hi_lo(output_data); + + self.call( + memory, + CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, + CallType::DelegateCall, + address_ptr, + deposit_ptr, + Weight::from_parts(ref_time_limit, proof_size_limit), + input_data_ptr, + input_data_len, + output_ptr, + output_len_ptr, + ) + } + + /// Instantiate a contract with the specified code hash. + /// See [`pallet_revive_uapi::HostFn::instantiate`]. + #[stable] + #[mutating] + fn instantiate( + &mut self, + memory: &mut M, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_and_value: u64, + input_data: u64, + output_data: u64, + address_and_salt: u64, + ) -> Result { + let (deposit_ptr, value_ptr) = extract_hi_lo(deposit_and_value); + let (input_data_len, code_hash_ptr) = extract_hi_lo(input_data); + let (output_len_ptr, output_ptr) = extract_hi_lo(output_data); + let (address_ptr, salt_ptr) = extract_hi_lo(address_and_salt); + let Some(input_data_ptr) = code_hash_ptr.checked_add(32) else { + return Err(Error::::OutOfBounds.into()); + }; + let Some(input_data_len) = input_data_len.checked_sub(32) else { + return Err(Error::::OutOfBounds.into()); + }; + + self.instantiate( + memory, + code_hash_ptr, + Weight::from_parts(ref_time_limit, proof_size_limit), + deposit_ptr, + value_ptr, + input_data_ptr, + input_data_len, + address_ptr, + output_ptr, + output_len_ptr, + salt_ptr, + ) + } + + /// Returns the total size of the contract call input data. + /// See [`pallet_revive_uapi::HostFn::call_data_size `]. + #[stable] + fn call_data_size(&mut self, memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::CallDataSize)?; + Ok(self + .input_data + .as_ref() + .map(|input| input.len().try_into().expect("usize fits into u64; qed")) + .unwrap_or_default()) + } + + /// Stores the input passed by the caller into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::call_data_copy`]. + #[stable] + fn call_data_copy( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len: u32, + offset: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::CallDataCopy(out_len))?; + + let Some(input) = self.input_data.as_ref() else { + return Err(Error::::InputForwarded.into()); + }; + + let start = offset as usize; + if start >= input.len() { + memory.zero(out_ptr, out_len)?; + return Ok(()); + } + + let end = start.saturating_add(out_len as usize).min(input.len()); + memory.write(out_ptr, &input[start..end])?; + + let bytes_written = (end - start) as u32; + memory.zero(out_ptr.saturating_add(bytes_written), out_len - bytes_written)?; + + Ok(()) + } + + /// Stores the U256 value at given call input `offset` into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::call_data_load`]. + #[stable] + fn call_data_load( + &mut self, + memory: &mut M, + out_ptr: u32, + offset: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::CallDataLoad)?; + + let Some(input) = self.input_data.as_ref() else { + return Err(Error::::InputForwarded.into()); + }; + + let mut data = [0; 32]; + let start = offset as usize; + let data = if start >= input.len() { + data // Any index is valid to request; OOB offsets return zero. + } else { + let end = start.saturating_add(32).min(input.len()); + data[..end - start].copy_from_slice(&input[start..end]); + data.reverse(); + data // Solidity expects right-padded data + }; + + self.write_fixed_sandbox_output(memory, out_ptr, &data, false, already_charged)?; + + Ok(()) + } + + /// Cease contract execution and save a data buffer as a result of the execution. + /// See [`pallet_revive_uapi::HostFn::return_value`]. + #[stable] + fn seal_return( + &mut self, + memory: &mut M, + flags: u32, + data_ptr: u32, + data_len: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::CopyFromContract(data_len))?; + if data_len > limits::CALLDATA_BYTES { + Err(>::ReturnDataTooLarge)?; + } + Err(TrapReason::Return(ReturnData { flags, data: memory.read(data_ptr, data_len)? })) + } + + /// Stores the address of the caller into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::caller`]. + #[stable] + fn caller(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::Caller)?; + let caller = ::AddressMapper::to_address(self.ext.caller().account_id()?); + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + caller.as_bytes(), + false, + already_charged, + )?) + } + + /// Stores the address of the call stack origin into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::origin`]. + #[stable] + fn origin(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::Origin)?; + let origin = ::AddressMapper::to_address(self.ext.origin().account_id()?); + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + origin.as_bytes(), + false, + already_charged, + )?) + } + + /// Retrieve the code hash for a specified contract address. + /// See [`pallet_revive_uapi::HostFn::code_hash`]. + #[stable] + fn code_hash(&mut self, memory: &mut M, addr_ptr: u32, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::CodeHash)?; + let address = memory.read_h160(addr_ptr)?; + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &self.ext.code_hash(&address).as_bytes(), + false, + already_charged, + )?) + } + + /// Retrieve the code size for a given contract address. + /// See [`pallet_revive_uapi::HostFn::code_size`]. + #[stable] + fn code_size(&mut self, memory: &mut M, addr_ptr: u32) -> Result { + self.charge_gas(RuntimeCosts::CodeSize)?; + let address = memory.read_h160(addr_ptr)?; + Ok(self.ext.code_size(&address)) + } + + /// Stores the address of the current contract into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::address`]. + #[stable] + fn address(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::Address)?; + let address = self.ext.address(); + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + address.as_bytes(), + false, + already_charged, + )?) + } + + /// Stores the price for the specified amount of weight into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::weight_to_fee`]. + #[stable] + fn weight_to_fee( + &mut self, + memory: &mut M, + ref_time_limit: u64, + proof_size_limit: u64, + out_ptr: u32, + ) -> Result<(), TrapReason> { + let weight = Weight::from_parts(ref_time_limit, proof_size_limit); + self.charge_gas(RuntimeCosts::WeightToFee)?; + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &self.ext.get_weight_price(weight).encode(), + false, + already_charged, + )?) + } + + /// Stores the immutable data into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::get_immutable_data`]. + #[stable] + fn get_immutable_data( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + // quering the length is free as it is stored with the contract metadata + let len = self.ext.immutable_data_len(); + self.charge_gas(RuntimeCosts::GetImmutableData(len))?; + let data = self.ext.get_immutable_data()?; + self.write_sandbox_output(memory, out_ptr, out_len_ptr, &data, false, already_charged)?; + Ok(()) + } + + /// Attaches the supplied immutable data to the currently executing contract. + /// See [`pallet_revive_uapi::HostFn::set_immutable_data`]. + #[stable] + fn set_immutable_data(&mut self, memory: &mut M, ptr: u32, len: u32) -> Result<(), TrapReason> { + if len > limits::IMMUTABLE_BYTES { + return Err(Error::::OutOfBounds.into()); + } + self.charge_gas(RuntimeCosts::SetImmutableData(len))?; + let buf = memory.read(ptr, len)?; + let data = buf.try_into().expect("bailed out earlier; qed"); + self.ext.set_immutable_data(data)?; + Ok(()) + } + + /// Stores the *free* balance of the current account into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::balance`]. + #[stable] + fn balance(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::Balance)?; + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &self.ext.balance().to_little_endian(), + false, + already_charged, + )?) + } + + /// Stores the *free* balance of the supplied address into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::balance`]. + #[stable] + fn balance_of( + &mut self, + memory: &mut M, + addr_ptr: u32, + out_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::BalanceOf)?; + let address = memory.read_h160(addr_ptr)?; + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &self.ext.balance_of(&address).to_little_endian(), + false, + already_charged, + )?) + } + + /// Returns the chain ID. + /// See [`pallet_revive_uapi::HostFn::chain_id`]. + #[stable] + fn chain_id(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &U256::from(::ChainId::get()).to_little_endian(), + false, + |_| Some(RuntimeCosts::CopyToContract(32)), + )?) + } + + /// Returns the block ref_time limit. + /// See [`pallet_revive_uapi::HostFn::gas_limit`]. + #[stable] + fn gas_limit(&mut self, memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::GasLimit)?; + Ok(::BlockWeights::get().max_block.ref_time()) + } + + /// Stores the value transferred along with this call/instantiate into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::value_transferred`]. + #[stable] + fn value_transferred(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::ValueTransferred)?; + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &self.ext.value_transferred().to_little_endian(), + false, + already_charged, + )?) + } + + /// Returns the simulated ethereum `GASPRICE` value. + /// See [`pallet_revive_uapi::HostFn::gas_price`]. + #[stable] + fn gas_price(&mut self, memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::GasPrice)?; + Ok(GAS_PRICE.into()) + } + + /// Returns the simulated ethereum `BASEFEE` value. + /// See [`pallet_revive_uapi::HostFn::base_fee`]. + #[stable] + fn base_fee(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::BaseFee)?; + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &U256::zero().to_little_endian(), + false, + already_charged, + )?) + } + + /// Load the latest block timestamp into the supplied buffer + /// See [`pallet_revive_uapi::HostFn::now`]. + #[stable] + fn now(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::Now)?; + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &self.ext.now().to_little_endian(), + false, + already_charged, + )?) + } + + /// Deposit a contract event with the data buffer and optional list of topics. + /// See [pallet_revive_uapi::HostFn::deposit_event] + #[stable] + #[mutating] + fn deposit_event( + &mut self, + memory: &mut M, + topics_ptr: u32, + num_topic: u32, + data_ptr: u32, + data_len: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::DepositEvent { num_topic, len: data_len })?; + + if num_topic > limits::NUM_EVENT_TOPICS { + return Err(Error::::TooManyTopics.into()); + } + + if data_len > self.ext.max_value_size() { + return Err(Error::::ValueTooLarge.into()); + } + + let topics: Vec = match num_topic { + 0 => Vec::new(), + _ => { + let mut v = Vec::with_capacity(num_topic as usize); + let topics_len = num_topic * H256::len_bytes() as u32; + let buf = memory.read(topics_ptr, topics_len)?; + for chunk in buf.chunks_exact(H256::len_bytes()) { + v.push(H256::from_slice(chunk)); + } + v + }, + }; + + let event_data = memory.read(data_ptr, data_len)?; + self.ext.deposit_event(topics, event_data); + Ok(()) + } + + /// Stores the current block number of the current contract into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::block_number`]. + #[stable] + fn block_number(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::BlockNumber)?; + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &self.ext.block_number().to_little_endian(), + false, + already_charged, + )?) + } + + /// Stores the block hash at given block height into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::block_hash`]. + #[stable] + fn block_hash( + &mut self, + memory: &mut M, + block_number_ptr: u32, + out_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::BlockHash)?; + let block_number = memory.read_u256(block_number_ptr)?; + let block_hash = self.ext.block_hash(block_number).unwrap_or(H256::zero()); + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &block_hash.as_bytes(), + false, + already_charged, + )?) + } + + /// Stores the current block author into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::block_author`]. + #[stable] + fn block_author(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::BlockAuthor)?; + let block_author = self.ext.block_author().unwrap_or(H160::zero()); + + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &block_author.as_bytes(), + false, + already_charged, + )?) + } + + /// Computes the KECCAK 256-bit hash on the given input buffer. + /// See [`pallet_revive_uapi::HostFn::hash_keccak_256`]. + #[stable] + fn hash_keccak_256( + &mut self, + memory: &mut M, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::HashKeccak256(input_len))?; + Ok(self.compute_hash_on_intermediate_buffer( + memory, keccak_256, input_ptr, input_len, output_ptr, + )?) + } + + /// Stores the length of the data returned by the last call into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::return_data_size`]. + #[stable] + fn return_data_size(&mut self, memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::ReturnDataSize)?; + Ok(self + .ext + .last_frame_output() + .data + .len() + .try_into() + .expect("usize fits into u64; qed")) + } + + /// Stores data returned by the last call, starting from `offset`, into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::return_data`]. + #[stable] + fn return_data_copy( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + offset: u32, + ) -> Result<(), TrapReason> { + let output = mem::take(self.ext.last_frame_output_mut()); + let result = if offset as usize > output.data.len() { + Err(Error::::OutOfBounds.into()) + } else { + self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &output.data[offset as usize..], + false, + |len| Some(RuntimeCosts::CopyToContract(len)), + ) + }; + *self.ext.last_frame_output_mut() = output; + Ok(result?) + } + + /// Returns the amount of ref_time left. + /// See [`pallet_revive_uapi::HostFn::ref_time_left`]. + #[stable] + fn ref_time_left(&mut self, memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::RefTimeLeft)?; + Ok(self.ext.gas_meter().gas_left().ref_time()) + } + + /// Checks whether the caller of the current contract is the origin of the whole call stack. + /// See [`pallet_revive_uapi::HostFn::caller_is_origin`]. + fn caller_is_origin(&mut self, _memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::CallerIsOrigin)?; + Ok(self.ext.caller_is_origin() as u32) + } + + /// Checks whether the caller of the current contract is root. + /// See [`pallet_revive_uapi::HostFn::caller_is_root`]. + fn caller_is_root(&mut self, _memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::CallerIsRoot)?; + Ok(self.ext.caller_is_root() as u32) + } + + /// Clear the value at the given key in the contract storage. + /// See [`pallet_revive_uapi::HostFn::clear_storage`] + #[mutating] + fn clear_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + ) -> Result { + self.clear_storage(memory, flags, key_ptr, key_len) + } + + /// Checks whether there is a value stored under the given key. + /// See [`pallet_revive_uapi::HostFn::contains_storage`] + fn contains_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + ) -> Result { + self.contains_storage(memory, flags, key_ptr, key_len) + } + + /// Calculates Ethereum address from the ECDSA compressed public key and stores + /// See [`pallet_revive_uapi::HostFn::ecdsa_to_eth_address`]. + fn ecdsa_to_eth_address( + &mut self, + memory: &mut M, + key_ptr: u32, + out_ptr: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::EcdsaToEthAddress)?; + let mut compressed_key: [u8; 33] = [0; 33]; + memory.read_into_buf(key_ptr, &mut compressed_key)?; + let result = self.ext.ecdsa_to_eth_address(&compressed_key); + match result { + Ok(eth_address) => { + memory.write(out_ptr, eth_address.as_ref())?; + Ok(ReturnErrorCode::Success) + }, + Err(_) => Ok(ReturnErrorCode::EcdsaRecoveryFailed), + } + } + + /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::minimum_balance`]. + fn minimum_balance(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::MinimumBalance)?; + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &self.ext.minimum_balance().to_little_endian(), + false, + already_charged, + )?) + } + + /// Retrieve the code hash of the currently executing contract. + /// See [`pallet_revive_uapi::HostFn::own_code_hash`]. + fn own_code_hash(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::OwnCodeHash)?; + let code_hash = *self.ext.own_code_hash(); + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + code_hash.as_bytes(), + false, + already_charged, + )?) + } + + /// Replace the contract code at the specified address with new code. + /// See [`pallet_revive_uapi::HostFn::set_code_hash`]. + /// + /// Disabled until the internal implementation takes care of collecting + /// the immutable data of the new code hash. + #[mutating] + fn set_code_hash(&mut self, memory: &mut M, code_hash_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::SetCodeHash)?; + let code_hash: H256 = memory.read_h256(code_hash_ptr)?; + self.ext.set_code_hash(code_hash)?; + Ok(()) + } + + /// Verify a sr25519 signature + /// See [`pallet_revive_uapi::HostFn::sr25519_verify`]. + fn sr25519_verify( + &mut self, + memory: &mut M, + signature_ptr: u32, + pub_key_ptr: u32, + message_len: u32, + message_ptr: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::Sr25519Verify(message_len))?; + + let mut signature: [u8; 64] = [0; 64]; + memory.read_into_buf(signature_ptr, &mut signature)?; + + let mut pub_key: [u8; 32] = [0; 32]; + memory.read_into_buf(pub_key_ptr, &mut pub_key)?; + + let message: Vec = memory.read(message_ptr, message_len)?; + + if self.ext.sr25519_verify(&signature, &message, &pub_key) { + Ok(ReturnErrorCode::Success) + } else { + Ok(ReturnErrorCode::Sr25519VerifyFailed) + } + } + + /// Retrieve and remove the value under the given key from storage. + /// See [`pallet_revive_uapi::HostFn::take_storage`] + #[mutating] + fn take_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + self.take_storage(memory, flags, key_ptr, key_len, out_ptr, out_len_ptr) + } + + /// Remove the calling account and transfer remaining **free** balance. + /// See [`pallet_revive_uapi::HostFn::terminate`]. + #[mutating] + fn terminate(&mut self, memory: &mut M, beneficiary_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::Terminate)?; + let beneficiary = memory.read_h160(beneficiary_ptr)?; + self.ext.terminate(&beneficiary)?; + Err(TrapReason::Termination) + } + + /// Stores the amount of weight left into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::weight_left`]. + fn weight_left( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::WeightLeft)?; + let gas_left = &self.ext.gas_meter().gas_left().encode(); + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + gas_left, + false, + already_charged, + )?) + } +} diff --git a/substrate/frame/revive/src/vm/runtime.rs b/substrate/frame/revive/src/vm/runtime.rs deleted file mode 100644 index 3a1cc07ab31a3..0000000000000 --- a/substrate/frame/revive/src/vm/runtime.rs +++ /dev/null @@ -1,2111 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// 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. - -//! Environment definition of the vm smart-contract runtime. - -use crate::{ - address::AddressMapper, - evm::runtime::GAS_PRICE, - exec::{ExecError, ExecResult, Ext, Key}, - gas::{ChargedAmount, Token}, - limits, - precompiles::{All as AllPrecompiles, Precompiles}, - primitives::ExecReturnValue, - weights::WeightInfo, - Config, Error, Pallet, LOG_TARGET, SENTINEL, -}; -use alloc::{vec, vec::Vec}; -use codec::Encode; -use core::{fmt, marker::PhantomData, mem}; -use frame_support::{ensure, traits::Get, weights::Weight}; -use pallet_revive_proc_macro::define_env; -use pallet_revive_uapi::{CallFlags, ReturnErrorCode, ReturnFlags, StorageFlags}; -use sp_core::{H160, H256, U256}; -use sp_io::hashing::keccak_256; -use sp_runtime::{DispatchError, RuntimeDebug}; - -/// Abstraction over the memory access within syscalls. -/// -/// The reason for this abstraction is that we run syscalls on the host machine when -/// benchmarking them. In that case we have direct access to the contract's memory. However, when -/// running within PolkaVM we need to resort to copying as we can't map the contracts memory into -/// the host (as of now). -pub trait Memory { - /// Read designated chunk from the sandbox memory into the supplied buffer. - /// - /// Returns `Err` if one of the following conditions occurs: - /// - /// - requested buffer is not within the bounds of the sandbox memory. - fn read_into_buf(&self, ptr: u32, buf: &mut [u8]) -> Result<(), DispatchError>; - - /// Write the given buffer to the designated location in the sandbox memory. - /// - /// Returns `Err` if one of the following conditions occurs: - /// - /// - designated area is not within the bounds of the sandbox memory. - fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError>; - - /// Zero the designated location in the sandbox memory. - /// - /// Returns `Err` if one of the following conditions occurs: - /// - /// - designated area is not within the bounds of the sandbox memory. - fn zero(&mut self, ptr: u32, len: u32) -> Result<(), DispatchError>; - - /// This will reset all compilation artifacts of the currently executing instance. - /// - /// This is used before we call into a new contract to free up some memory. Doing - /// so we make sure that we only ever have to hold one compilation cache at a time - /// independtently of of our call stack depth. - fn reset_interpreter_cache(&mut self); - - /// Read designated chunk from the sandbox memory. - /// - /// Returns `Err` if one of the following conditions occurs: - /// - /// - requested buffer is not within the bounds of the sandbox memory. - fn read(&self, ptr: u32, len: u32) -> Result, DispatchError> { - let mut buf = vec![0u8; len as usize]; - self.read_into_buf(ptr, buf.as_mut_slice())?; - Ok(buf) - } - - /// Same as `read` but reads into a fixed size buffer. - fn read_array(&self, ptr: u32) -> Result<[u8; N], DispatchError> { - let mut buf = [0u8; N]; - self.read_into_buf(ptr, &mut buf)?; - Ok(buf) - } - - /// Read a `u32` from the sandbox memory. - fn read_u32(&self, ptr: u32) -> Result { - let buf: [u8; 4] = self.read_array(ptr)?; - Ok(u32::from_le_bytes(buf)) - } - - /// Read a `U256` from the sandbox memory. - fn read_u256(&self, ptr: u32) -> Result { - let buf: [u8; 32] = self.read_array(ptr)?; - Ok(U256::from_little_endian(&buf)) - } - - /// Read a `H160` from the sandbox memory. - fn read_h160(&self, ptr: u32) -> Result { - let mut buf = H160::default(); - self.read_into_buf(ptr, buf.as_bytes_mut())?; - Ok(buf) - } - - /// Read a `H256` from the sandbox memory. - fn read_h256(&self, ptr: u32) -> Result { - let mut code_hash = H256::default(); - self.read_into_buf(ptr, code_hash.as_bytes_mut())?; - Ok(code_hash) - } -} - -/// Allows syscalls access to the PolkaVM instance they are executing in. -/// -/// In case a contract is executing within PolkaVM its `memory` argument will also implement -/// this trait. The benchmarking implementation of syscalls will only require `Memory` -/// to be implemented. -pub trait PolkaVmInstance: Memory { - fn gas(&self) -> polkavm::Gas; - fn set_gas(&mut self, gas: polkavm::Gas); - fn read_input_regs(&self) -> (u64, u64, u64, u64, u64, u64); - fn write_output(&mut self, output: u64); -} - -// Memory implementation used in benchmarking where guest memory is mapped into the host. -// -// Please note that we could optimize the `read_as_*` functions by decoding directly from -// memory without a copy. However, we don't do that because as it would change the behaviour -// of those functions: A `read_as` with a `len` larger than the actual type can succeed -// in the streaming implementation while it could fail with a segfault in the copy implementation. -#[cfg(feature = "runtime-benchmarks")] -impl Memory for [u8] { - fn read_into_buf(&self, ptr: u32, buf: &mut [u8]) -> Result<(), DispatchError> { - let ptr = ptr as usize; - let bound_checked = - self.get(ptr..ptr + buf.len()).ok_or_else(|| Error::::OutOfBounds)?; - buf.copy_from_slice(bound_checked); - Ok(()) - } - - fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> { - let ptr = ptr as usize; - let bound_checked = - self.get_mut(ptr..ptr + buf.len()).ok_or_else(|| Error::::OutOfBounds)?; - bound_checked.copy_from_slice(buf); - Ok(()) - } - - fn zero(&mut self, ptr: u32, len: u32) -> Result<(), DispatchError> { - <[u8] as Memory>::write(self, ptr, &vec![0; len as usize]) - } - - fn reset_interpreter_cache(&mut self) {} -} - -impl Memory for polkavm::RawInstance { - fn read_into_buf(&self, ptr: u32, buf: &mut [u8]) -> Result<(), DispatchError> { - self.read_memory_into(ptr, buf) - .map(|_| ()) - .map_err(|_| Error::::OutOfBounds.into()) - } - - fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> { - self.write_memory(ptr, buf).map_err(|_| Error::::OutOfBounds.into()) - } - - fn zero(&mut self, ptr: u32, len: u32) -> Result<(), DispatchError> { - self.zero_memory(ptr, len).map_err(|_| Error::::OutOfBounds.into()) - } - - fn reset_interpreter_cache(&mut self) { - self.reset_interpreter_cache(); - } -} - -impl PolkaVmInstance for polkavm::RawInstance { - fn gas(&self) -> polkavm::Gas { - self.gas() - } - - fn set_gas(&mut self, gas: polkavm::Gas) { - self.set_gas(gas) - } - - fn read_input_regs(&self) -> (u64, u64, u64, u64, u64, u64) { - ( - self.reg(polkavm::Reg::A0), - self.reg(polkavm::Reg::A1), - self.reg(polkavm::Reg::A2), - self.reg(polkavm::Reg::A3), - self.reg(polkavm::Reg::A4), - self.reg(polkavm::Reg::A5), - ) - } - - fn write_output(&mut self, output: u64) { - self.set_reg(polkavm::Reg::A0, output); - } -} - -impl From<&ExecReturnValue> for ReturnErrorCode { - fn from(from: &ExecReturnValue) -> Self { - if from.flags.contains(ReturnFlags::REVERT) { - Self::CalleeReverted - } else { - Self::Success - } - } -} - -/// The data passed through when a contract uses `seal_return`. -#[derive(RuntimeDebug)] -pub struct ReturnData { - /// The flags as passed through by the contract. They are still unchecked and - /// will later be parsed into a `ReturnFlags` bitflags struct. - flags: u32, - /// The output buffer passed by the contract as return data. - data: Vec, -} - -/// Enumerates all possible reasons why a trap was generated. -/// -/// This is either used to supply the caller with more information about why an error -/// occurred (the SupervisorError variant). -/// The other case is where the trap does not constitute an error but rather was invoked -/// as a quick way to terminate the application (all other variants). -#[derive(RuntimeDebug)] -pub enum TrapReason { - /// The supervisor trapped the contract because of an error condition occurred during - /// execution in privileged code. - SupervisorError(DispatchError), - /// Signals that trap was generated in response to call `seal_return` host function. - Return(ReturnData), - /// Signals that a trap was generated in response to a successful call to the - /// `seal_terminate` host function. - Termination, -} - -impl> From for TrapReason { - fn from(from: T) -> Self { - Self::SupervisorError(from.into()) - } -} - -impl fmt::Display for TrapReason { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - Ok(()) - } -} - -#[cfg_attr(test, derive(Debug, PartialEq, Eq))] -#[derive(Copy, Clone)] -pub enum RuntimeCosts { - /// Base Weight of calling a host function. - HostFn, - /// Weight charged for copying data from the sandbox. - CopyFromContract(u32), - /// Weight charged for copying data to the sandbox. - CopyToContract(u32), - /// Weight of calling `seal_call_data_load``. - CallDataLoad, - /// Weight of calling `seal_call_data_copy`. - CallDataCopy(u32), - /// Weight of calling `seal_caller`. - Caller, - /// Weight of calling `seal_call_data_size`. - CallDataSize, - /// Weight of calling `seal_return_data_size`. - ReturnDataSize, - /// Weight of calling `seal_to_account_id`. - ToAccountId, - /// Weight of calling `seal_origin`. - Origin, - /// Weight of calling `seal_code_hash`. - CodeHash, - /// Weight of calling `seal_own_code_hash`. - OwnCodeHash, - /// Weight of calling `seal_code_size`. - CodeSize, - /// Weight of calling `seal_caller_is_origin`. - CallerIsOrigin, - /// Weight of calling `caller_is_root`. - CallerIsRoot, - /// Weight of calling `seal_address`. - Address, - /// Weight of calling `seal_ref_time_left`. - RefTimeLeft, - /// Weight of calling `seal_weight_left`. - WeightLeft, - /// Weight of calling `seal_balance`. - Balance, - /// Weight of calling `seal_balance_of`. - BalanceOf, - /// Weight of calling `seal_value_transferred`. - ValueTransferred, - /// Weight of calling `seal_minimum_balance`. - MinimumBalance, - /// Weight of calling `seal_block_number`. - BlockNumber, - /// Weight of calling `seal_block_hash`. - BlockHash, - /// Weight of calling `seal_block_author`. - BlockAuthor, - /// Weight of calling `seal_gas_price`. - GasPrice, - /// Weight of calling `seal_base_fee`. - BaseFee, - /// Weight of calling `seal_now`. - Now, - /// Weight of calling `seal_gas_limit`. - GasLimit, - /// Weight of calling `seal_weight_to_fee`. - WeightToFee, - /// Weight of calling `seal_terminate`. - Terminate, - /// Weight of calling `seal_deposit_event` with the given number of topics and event size. - DepositEvent { num_topic: u32, len: u32 }, - /// Weight of calling `seal_set_storage` for the given storage item sizes. - SetStorage { old_bytes: u32, new_bytes: u32 }, - /// Weight of calling `seal_clear_storage` per cleared byte. - ClearStorage(u32), - /// Weight of calling `seal_contains_storage` per byte of the checked item. - ContainsStorage(u32), - /// Weight of calling `seal_get_storage` with the specified size in storage. - GetStorage(u32), - /// Weight of calling `seal_take_storage` for the given size. - TakeStorage(u32), - /// Weight of calling `seal_set_transient_storage` for the given storage item sizes. - SetTransientStorage { old_bytes: u32, new_bytes: u32 }, - /// Weight of calling `seal_clear_transient_storage` per cleared byte. - ClearTransientStorage(u32), - /// Weight of calling `seal_contains_transient_storage` per byte of the checked item. - ContainsTransientStorage(u32), - /// Weight of calling `seal_get_transient_storage` with the specified size in storage. - GetTransientStorage(u32), - /// Weight of calling `seal_take_transient_storage` for the given size. - TakeTransientStorage(u32), - /// Base weight of calling `seal_call`. - CallBase, - /// Weight of calling `seal_delegate_call` for the given input size. - DelegateCallBase, - /// Weight of calling a precompile. - PrecompileBase, - /// Weight of calling a precompile that has a contract info. - PrecompileWithInfoBase, - /// Weight of reading and decoding the input to a precompile. - PrecompileDecode(u32), - /// Weight of the transfer performed during a call. - /// parameter `dust_transfer` indicates whether the transfer has a `dust` value. - CallTransferSurcharge { dust_transfer: bool }, - /// Weight per byte that is cloned by supplying the `CLONE_INPUT` flag. - CallInputCloned(u32), - /// Weight of calling `seal_instantiate`. - Instantiate { input_data_len: u32, balance_transfer: bool, dust_transfer: bool }, - /// Weight of calling `Ripemd160` precompile for the given input size. - Ripemd160(u32), - /// Weight of calling `Sha256` precompile for the given input size. - HashSha256(u32), - /// Weight of calling `seal_hash_keccak_256` for the given input size. - HashKeccak256(u32), - /// Weight of calling the `System::hashBlake256` precompile function for the given input - /// size. - HashBlake256(u32), - /// Weight of calling `System::hashBlake128` precompile function for the given input size. - HashBlake128(u32), - /// Weight of calling `ECERecover` precompile. - EcdsaRecovery, - /// Weight of calling `seal_sr25519_verify` for the given input size. - Sr25519Verify(u32), - /// Weight charged by a precompile. - Precompile(Weight), - /// Weight of calling `seal_set_code_hash` - SetCodeHash, - /// Weight of calling `ecdsa_to_eth_address` - EcdsaToEthAddress, - /// Weight of calling `get_immutable_dependency` - GetImmutableData(u32), - /// Weight of calling `set_immutable_dependency` - SetImmutableData(u32), - /// Weight of calling `Bn128Add` precompile - Bn128Add, - /// Weight of calling `Bn128Add` precompile - Bn128Mul, - /// Weight of calling `Bn128Pairing` precompile for the given number of input pairs. - Bn128Pairing(u32), - /// Weight of calling `Identity` precompile for the given number of input length. - Identity(u32), - /// Weight of calling `Blake2F` precompile for the given number of rounds. - Blake2F(u32), - /// Weight of calling `Modexp` precompile - Modexp(u64), -} - -/// For functions that modify storage, benchmarks are performed with one item in the -/// storage. To account for the worst-case scenario, the weight of the overhead of -/// writing to or reading from full storage is included. For transient storage writes, -/// the rollback weight is added to reflect the worst-case scenario for this operation. -macro_rules! cost_storage { - (write_transient, $name:ident $(, $arg:expr )*) => { - T::WeightInfo::$name($( $arg ),*) - .saturating_add(T::WeightInfo::rollback_transient_storage()) - .saturating_add(T::WeightInfo::set_transient_storage_full() - .saturating_sub(T::WeightInfo::set_transient_storage_empty())) - }; - - (read_transient, $name:ident $(, $arg:expr )*) => { - T::WeightInfo::$name($( $arg ),*) - .saturating_add(T::WeightInfo::get_transient_storage_full() - .saturating_sub(T::WeightInfo::get_transient_storage_empty())) - }; - - (write, $name:ident $(, $arg:expr )*) => { - T::WeightInfo::$name($( $arg ),*) - .saturating_add(T::WeightInfo::set_storage_full() - .saturating_sub(T::WeightInfo::set_storage_empty())) - }; - - (read, $name:ident $(, $arg:expr )*) => { - T::WeightInfo::$name($( $arg ),*) - .saturating_add(T::WeightInfo::get_storage_full() - .saturating_sub(T::WeightInfo::get_storage_empty())) - }; -} - -macro_rules! cost_args { - // cost_args!(name, a, b, c) -> T::WeightInfo::name(a, b, c).saturating_sub(T::WeightInfo::name(0, 0, 0)) - ($name:ident, $( $arg: expr ),+) => { - (T::WeightInfo::$name($( $arg ),+).saturating_sub(cost_args!(@call_zero $name, $( $arg ),+))) - }; - // Transform T::WeightInfo::name(a, b, c) into T::WeightInfo::name(0, 0, 0) - (@call_zero $name:ident, $( $arg:expr ),*) => { - T::WeightInfo::$name($( cost_args!(@replace_token $arg) ),*) - }; - // Replace the token with 0. - (@replace_token $_in:tt) => { 0 }; -} - -impl Token for RuntimeCosts { - fn influence_lowest_gas_limit(&self) -> bool { - true - } - - fn weight(&self) -> Weight { - use self::RuntimeCosts::*; - match *self { - HostFn => cost_args!(noop_host_fn, 1), - CopyToContract(len) => T::WeightInfo::seal_copy_to_contract(len), - CopyFromContract(len) => T::WeightInfo::seal_return(len), - CallDataSize => T::WeightInfo::seal_call_data_size(), - ReturnDataSize => T::WeightInfo::seal_return_data_size(), - CallDataLoad => T::WeightInfo::seal_call_data_load(), - CallDataCopy(len) => T::WeightInfo::seal_call_data_copy(len), - Caller => T::WeightInfo::seal_caller(), - Origin => T::WeightInfo::seal_origin(), - ToAccountId => T::WeightInfo::seal_to_account_id(), - CodeHash => T::WeightInfo::seal_code_hash(), - CodeSize => T::WeightInfo::seal_code_size(), - OwnCodeHash => T::WeightInfo::seal_own_code_hash(), - CallerIsOrigin => T::WeightInfo::seal_caller_is_origin(), - CallerIsRoot => T::WeightInfo::seal_caller_is_root(), - Address => T::WeightInfo::seal_address(), - RefTimeLeft => T::WeightInfo::seal_ref_time_left(), - WeightLeft => T::WeightInfo::seal_weight_left(), - Balance => T::WeightInfo::seal_balance(), - BalanceOf => T::WeightInfo::seal_balance_of(), - ValueTransferred => T::WeightInfo::seal_value_transferred(), - MinimumBalance => T::WeightInfo::seal_minimum_balance(), - BlockNumber => T::WeightInfo::seal_block_number(), - BlockHash => T::WeightInfo::seal_block_hash(), - BlockAuthor => T::WeightInfo::seal_block_author(), - GasPrice => T::WeightInfo::seal_gas_price(), - BaseFee => T::WeightInfo::seal_base_fee(), - Now => T::WeightInfo::seal_now(), - GasLimit => T::WeightInfo::seal_gas_limit(), - WeightToFee => T::WeightInfo::seal_weight_to_fee(), - Terminate => T::WeightInfo::seal_terminate(), - DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len), - SetStorage { new_bytes, old_bytes } => { - cost_storage!(write, seal_set_storage, new_bytes, old_bytes) - }, - ClearStorage(len) => cost_storage!(write, seal_clear_storage, len), - ContainsStorage(len) => cost_storage!(read, seal_contains_storage, len), - GetStorage(len) => cost_storage!(read, seal_get_storage, len), - TakeStorage(len) => cost_storage!(write, seal_take_storage, len), - SetTransientStorage { new_bytes, old_bytes } => { - cost_storage!(write_transient, seal_set_transient_storage, new_bytes, old_bytes) - }, - ClearTransientStorage(len) => { - cost_storage!(write_transient, seal_clear_transient_storage, len) - }, - ContainsTransientStorage(len) => { - cost_storage!(read_transient, seal_contains_transient_storage, len) - }, - GetTransientStorage(len) => { - cost_storage!(read_transient, seal_get_transient_storage, len) - }, - TakeTransientStorage(len) => { - cost_storage!(write_transient, seal_take_transient_storage, len) - }, - CallBase => T::WeightInfo::seal_call(0, 0, 0), - DelegateCallBase => T::WeightInfo::seal_delegate_call(), - PrecompileBase => T::WeightInfo::seal_call_precompile(0, 0), - PrecompileWithInfoBase => T::WeightInfo::seal_call_precompile(1, 0), - PrecompileDecode(len) => cost_args!(seal_call_precompile, 0, len), - CallTransferSurcharge { dust_transfer } => - cost_args!(seal_call, 1, dust_transfer.into(), 0), - CallInputCloned(len) => cost_args!(seal_call, 0, 0, len), - Instantiate { input_data_len, balance_transfer, dust_transfer } => - T::WeightInfo::seal_instantiate( - input_data_len, - balance_transfer.into(), - dust_transfer.into(), - ), - HashSha256(len) => T::WeightInfo::sha2_256(len), - Ripemd160(len) => T::WeightInfo::ripemd_160(len), - HashKeccak256(len) => T::WeightInfo::seal_hash_keccak_256(len), - HashBlake256(len) => T::WeightInfo::hash_blake2_256(len), - HashBlake128(len) => T::WeightInfo::hash_blake2_128(len), - EcdsaRecovery => T::WeightInfo::ecdsa_recover(), - Sr25519Verify(len) => T::WeightInfo::seal_sr25519_verify(len), - Precompile(weight) => weight, - SetCodeHash => T::WeightInfo::seal_set_code_hash(), - EcdsaToEthAddress => T::WeightInfo::seal_ecdsa_to_eth_address(), - GetImmutableData(len) => T::WeightInfo::seal_get_immutable_data(len), - SetImmutableData(len) => T::WeightInfo::seal_set_immutable_data(len), - Bn128Add => T::WeightInfo::bn128_add(), - Bn128Mul => T::WeightInfo::bn128_mul(), - Bn128Pairing(len) => T::WeightInfo::bn128_pairing(len), - Identity(len) => T::WeightInfo::identity(len), - Blake2F(rounds) => T::WeightInfo::blake2f(rounds), - Modexp(gas) => { - use frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND; - /// Current approximation of the gas/s consumption considering - /// EVM execution over compiled WASM (on 4.4Ghz CPU). - /// Given the 2000ms Weight, from which 75% only are used for transactions, - /// the total EVM execution gas limit is: GAS_PER_SECOND * 2 * 0.75 ~= 60_000_000. - const GAS_PER_SECOND: u64 = 40_000_000; - - /// Approximate ratio of the amount of Weight per Gas. - /// u64 works for approximations because Weight is a very small unit compared to - /// gas. - const WEIGHT_PER_GAS: u64 = WEIGHT_REF_TIME_PER_SECOND / GAS_PER_SECOND; - Weight::from_parts(gas.saturating_mul(WEIGHT_PER_GAS), 0) - }, - } - } -} - -/// Same as [`Runtime::charge_gas`]. -/// -/// We need this access as a macro because sometimes hiding the lifetimes behind -/// a function won't work out. -macro_rules! charge_gas { - ($runtime:expr, $costs:expr) => {{ - $runtime.ext.gas_meter_mut().charge($costs) - }}; -} - -/// The kind of call that should be performed. -enum CallType { - /// Execute another instantiated contract - Call { value_ptr: u32 }, - /// Execute another contract code in the context (storage, account ID, value) of the caller - /// contract - DelegateCall, -} - -impl CallType { - fn cost(&self) -> RuntimeCosts { - match self { - CallType::Call { .. } => RuntimeCosts::CallBase, - CallType::DelegateCall => RuntimeCosts::DelegateCallBase, - } - } -} - -/// This is only appropriate when writing out data of constant size that does not depend on user -/// input. In this case the costs for this copy was already charged as part of the token at -/// the beginning of the API entry point. -fn already_charged(_: u32) -> Option { - None -} - -/// Helper to extract two `u32` values from a given `u64` register. -fn extract_hi_lo(reg: u64) -> (u32, u32) { - ((reg >> 32) as u32, reg as u32) -} - -/// Provides storage variants to support standard and Etheruem compatible semantics. -enum StorageValue { - /// Indicates that the storage value should be read from a memory buffer. - /// - `ptr`: A pointer to the start of the data in sandbox memory. - /// - `len`: The length (in bytes) of the data. - Memory { ptr: u32, len: u32 }, - - /// Indicates that the storage value is provided inline as a fixed-size (256-bit) value. - /// This is used by set_storage_or_clear() to avoid double reads. - /// This variant is used to implement Ethereum SSTORE-like semantics. - Value(Vec), -} - -/// Controls the output behavior for storage reads, both when a key is found and when it is not. -enum StorageReadMode { - /// VariableOutput mode: if the key exists, the full stored value is returned - /// using the caller‑provided output length. - VariableOutput { output_len_ptr: u32 }, - /// Ethereum commpatible(FixedOutput32) mode: always write a 32-byte value into the output - /// buffer. If the key is missing, write 32 bytes of zeros. - FixedOutput32, -} - -/// Can only be used for one call. -pub struct Runtime<'a, E: Ext, M: ?Sized> { - ext: &'a mut E, - input_data: Option>, - _phantom_data: PhantomData, -} - -impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { - pub fn handle_interrupt( - &mut self, - interrupt: Result, - module: &polkavm::Module, - instance: &mut M, - ) -> Option { - use polkavm::InterruptKind::*; - - match interrupt { - Err(error) => { - // in contrast to the other returns this "should" not happen: log level error - log::error!(target: LOG_TARGET, "polkavm execution error: {error}"); - Some(Err(Error::::ExecutionFailed.into())) - }, - Ok(Finished) => - Some(Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })), - Ok(Trap) => Some(Err(Error::::ContractTrapped.into())), - Ok(Segfault(_)) => Some(Err(Error::::ExecutionFailed.into())), - Ok(NotEnoughGas) => Some(Err(Error::::OutOfGas.into())), - Ok(Step) => None, - Ok(Ecalli(idx)) => { - // This is a special hard coded syscall index which is used by benchmarks - // to abort contract execution. It is used to terminate the execution without - // breaking up a basic block. The fixed index is used so that the benchmarks - // don't have to deal with import tables. - if cfg!(feature = "runtime-benchmarks") && idx == SENTINEL { - return Some(Ok(ExecReturnValue { - flags: ReturnFlags::empty(), - data: Vec::new(), - })) - } - let Some(syscall_symbol) = module.imports().get(idx) else { - return Some(Err(>::InvalidSyscall.into())); - }; - match self.handle_ecall(instance, syscall_symbol.as_bytes()) { - Ok(None) => None, - Ok(Some(return_value)) => { - instance.write_output(return_value); - None - }, - Err(TrapReason::Return(ReturnData { flags, data })) => - match ReturnFlags::from_bits(flags) { - None => Some(Err(Error::::InvalidCallFlags.into())), - Some(flags) => Some(Ok(ExecReturnValue { flags, data })), - }, - Err(TrapReason::Termination) => Some(Ok(Default::default())), - Err(TrapReason::SupervisorError(error)) => Some(Err(error.into())), - } - }, - } - } -} - -impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { - pub fn new(ext: &'a mut E, input_data: Vec) -> Self { - Self { ext, input_data: Some(input_data), _phantom_data: Default::default() } - } - - /// Get a mutable reference to the inner `Ext`. - pub fn ext(&mut self) -> &mut E { - self.ext - } - - /// Charge the gas meter with the specified token. - /// - /// Returns `Err(HostError)` if there is not enough gas. - fn charge_gas(&mut self, costs: RuntimeCosts) -> Result { - charge_gas!(self, costs) - } - - /// Adjust a previously charged amount down to its actual amount. - /// - /// This is when a maximum a priori amount was charged and then should be partially - /// refunded to match the actual amount. - fn adjust_gas(&mut self, charged: ChargedAmount, actual_costs: RuntimeCosts) { - self.ext.gas_meter_mut().adjust_gas(charged, actual_costs); - } - - /// Write the given buffer and its length to the designated locations in sandbox memory and - /// charge gas according to the token returned by `create_token`. - /// - /// `out_ptr` is the location in sandbox memory where `buf` should be written to. - /// `out_len_ptr` is an in-out location in sandbox memory. It is read to determine the - /// length of the buffer located at `out_ptr`. If that buffer is smaller than the actual - /// `buf.len()`, only what fits into that buffer is written to `out_ptr`. - /// The actual amount of bytes copied to `out_ptr` is written to `out_len_ptr`. - /// - /// If `out_ptr` is set to the sentinel value of `SENTINEL` and `allow_skip` is true the - /// operation is skipped and `Ok` is returned. This is supposed to help callers to make copying - /// output optional. For example to skip copying back the output buffer of an `seal_call` - /// when the caller is not interested in the result. - /// - /// `create_token` can optionally instruct this function to charge the gas meter with the token - /// it returns. `create_token` receives the variable amount of bytes that are about to be copied - /// by this function. - /// - /// In addition to the error conditions of `Memory::write` this functions returns - /// `Err` if the size of the buffer located at `out_ptr` is too small to fit `buf`. - pub fn write_sandbox_output( - &mut self, - memory: &mut M, - out_ptr: u32, - out_len_ptr: u32, - buf: &[u8], - allow_skip: bool, - create_token: impl FnOnce(u32) -> Option, - ) -> Result<(), DispatchError> { - if allow_skip && out_ptr == SENTINEL { - return Ok(()); - } - - let len = memory.read_u32(out_len_ptr)?; - let buf_len = len.min(buf.len() as u32); - - if let Some(costs) = create_token(buf_len) { - self.charge_gas(costs)?; - } - - memory.write(out_ptr, &buf[..buf_len as usize])?; - memory.write(out_len_ptr, &buf_len.encode()) - } - - /// Same as `write_sandbox_output` but for static size output. - pub fn write_fixed_sandbox_output( - &mut self, - memory: &mut M, - out_ptr: u32, - buf: &[u8], - allow_skip: bool, - create_token: impl FnOnce(u32) -> Option, - ) -> Result<(), DispatchError> { - if buf.is_empty() || (allow_skip && out_ptr == SENTINEL) { - return Ok(()); - } - - let buf_len = buf.len() as u32; - if let Some(costs) = create_token(buf_len) { - self.charge_gas(costs)?; - } - - memory.write(out_ptr, buf) - } - - /// Computes the given hash function on the supplied input. - /// - /// Reads from the sandboxed input buffer into an intermediate buffer. - /// Returns the result directly to the output buffer of the sandboxed memory. - /// - /// It is the callers responsibility to provide an output buffer that - /// is large enough to hold the expected amount of bytes returned by the - /// chosen hash function. - /// - /// # Note - /// - /// The `input` and `output` buffers may overlap. - fn compute_hash_on_intermediate_buffer( - &self, - memory: &mut M, - hash_fn: F, - input_ptr: u32, - input_len: u32, - output_ptr: u32, - ) -> Result<(), DispatchError> - where - F: FnOnce(&[u8]) -> R, - R: AsRef<[u8]>, - { - // Copy input into supervisor memory. - let input = memory.read(input_ptr, input_len)?; - // Compute the hash on the input buffer using the given hash function. - let hash = hash_fn(&input); - // Write the resulting hash back into the sandboxed output buffer. - memory.write(output_ptr, hash.as_ref())?; - Ok(()) - } - - /// Fallible conversion of a `ExecError` to `ReturnErrorCode`. - /// - /// This is used when converting the error returned from a subcall in order to decide - /// whether to trap the caller or allow handling of the error. - fn exec_error_into_return_code(from: ExecError) -> Result { - use crate::exec::ErrorOrigin::Callee; - use ReturnErrorCode::*; - - let transfer_failed = Error::::TransferFailed.into(); - let out_of_gas = Error::::OutOfGas.into(); - let out_of_deposit = Error::::StorageDepositLimitExhausted.into(); - let duplicate_contract = Error::::DuplicateContract.into(); - let unsupported_precompile = Error::::UnsupportedPrecompileAddress.into(); - - // errors in the callee do not trap the caller - match (from.error, from.origin) { - (err, _) if err == transfer_failed => Ok(TransferFailed), - (err, _) if err == duplicate_contract => Ok(DuplicateContractAddress), - (err, _) if err == unsupported_precompile => Err(err), - (err, Callee) if err == out_of_gas || err == out_of_deposit => Ok(OutOfResources), - (_, Callee) => Ok(CalleeTrapped), - (err, _) => Err(err), - } - } - - fn decode_key(&self, memory: &M, key_ptr: u32, key_len: u32) -> Result { - let res = match key_len { - SENTINEL => { - let mut buffer = [0u8; 32]; - memory.read_into_buf(key_ptr, buffer.as_mut())?; - Ok(Key::from_fixed(buffer)) - }, - len => { - ensure!(len <= limits::STORAGE_KEY_BYTES, Error::::DecodingFailed); - let key = memory.read(key_ptr, len)?; - Key::try_from_var(key) - }, - }; - - res.map_err(|_| Error::::DecodingFailed.into()) - } - - fn is_transient(flags: u32) -> Result { - StorageFlags::from_bits(flags) - .ok_or_else(|| >::InvalidStorageFlags.into()) - .map(|flags| flags.contains(StorageFlags::TRANSIENT)) - } - - fn set_storage( - &mut self, - memory: &M, - flags: u32, - key_ptr: u32, - key_len: u32, - value: StorageValue, - ) -> Result { - let transient = Self::is_transient(flags)?; - let costs = |new_bytes: u32, old_bytes: u32| { - if transient { - RuntimeCosts::SetTransientStorage { new_bytes, old_bytes } - } else { - RuntimeCosts::SetStorage { new_bytes, old_bytes } - } - }; - - let value_len = match &value { - StorageValue::Memory { ptr: _, len } => *len, - StorageValue::Value(data) => data.len() as u32, - }; - - let max_size = self.ext.max_value_size(); - let charged = self.charge_gas(costs(value_len, self.ext.max_value_size()))?; - if value_len > max_size { - return Err(Error::::ValueTooLarge.into()); - } - - let key = self.decode_key(memory, key_ptr, key_len)?; - - let value = match value { - StorageValue::Memory { ptr, len } => Some(memory.read(ptr, len)?), - StorageValue::Value(data) => Some(data), - }; - - let write_outcome = if transient { - self.ext.set_transient_storage(&key, value, false)? - } else { - self.ext.set_storage(&key, value, false)? - }; - - self.adjust_gas(charged, costs(value_len, write_outcome.old_len())); - Ok(write_outcome.old_len_with_sentinel()) - } - - fn clear_storage( - &mut self, - memory: &M, - flags: u32, - key_ptr: u32, - key_len: u32, - ) -> Result { - let transient = Self::is_transient(flags)?; - let costs = |len| { - if transient { - RuntimeCosts::ClearTransientStorage(len) - } else { - RuntimeCosts::ClearStorage(len) - } - }; - let charged = self.charge_gas(costs(self.ext.max_value_size()))?; - let key = self.decode_key(memory, key_ptr, key_len)?; - let outcome = if transient { - self.ext.set_transient_storage(&key, None, false)? - } else { - self.ext.set_storage(&key, None, false)? - }; - self.adjust_gas(charged, costs(outcome.old_len())); - Ok(outcome.old_len_with_sentinel()) - } - - fn get_storage( - &mut self, - memory: &mut M, - flags: u32, - key_ptr: u32, - key_len: u32, - out_ptr: u32, - read_mode: StorageReadMode, - ) -> Result { - let transient = Self::is_transient(flags)?; - let costs = |len| { - if transient { - RuntimeCosts::GetTransientStorage(len) - } else { - RuntimeCosts::GetStorage(len) - } - }; - let charged = self.charge_gas(costs(self.ext.max_value_size()))?; - let key = self.decode_key(memory, key_ptr, key_len)?; - let outcome = if transient { - self.ext.get_transient_storage(&key) - } else { - self.ext.get_storage(&key) - }; - - if let Some(value) = outcome { - self.adjust_gas(charged, costs(value.len() as u32)); - - match read_mode { - StorageReadMode::FixedOutput32 => { - let mut fixed_output = [0u8; 32]; - let len = value.len().min(fixed_output.len()); - fixed_output[..len].copy_from_slice(&value[..len]); - - self.write_fixed_sandbox_output( - memory, - out_ptr, - &fixed_output, - false, - already_charged, - )?; - Ok(ReturnErrorCode::Success) - }, - StorageReadMode::VariableOutput { output_len_ptr: out_len_ptr } => { - self.write_sandbox_output( - memory, - out_ptr, - out_len_ptr, - &value, - false, - already_charged, - )?; - Ok(ReturnErrorCode::Success) - }, - } - } else { - self.adjust_gas(charged, costs(0)); - - match read_mode { - StorageReadMode::FixedOutput32 => { - self.write_fixed_sandbox_output( - memory, - out_ptr, - &[0u8; 32], - false, - already_charged, - )?; - Ok(ReturnErrorCode::Success) - }, - StorageReadMode::VariableOutput { .. } => Ok(ReturnErrorCode::KeyNotFound), - } - } - } - - fn contains_storage( - &mut self, - memory: &M, - flags: u32, - key_ptr: u32, - key_len: u32, - ) -> Result { - let transient = Self::is_transient(flags)?; - let costs = |len| { - if transient { - RuntimeCosts::ContainsTransientStorage(len) - } else { - RuntimeCosts::ContainsStorage(len) - } - }; - let charged = self.charge_gas(costs(self.ext.max_value_size()))?; - let key = self.decode_key(memory, key_ptr, key_len)?; - let outcome = if transient { - self.ext.get_transient_storage_size(&key) - } else { - self.ext.get_storage_size(&key) - }; - self.adjust_gas(charged, costs(outcome.unwrap_or(0))); - Ok(outcome.unwrap_or(SENTINEL)) - } - - fn take_storage( - &mut self, - memory: &mut M, - flags: u32, - key_ptr: u32, - key_len: u32, - out_ptr: u32, - out_len_ptr: u32, - ) -> Result { - let transient = Self::is_transient(flags)?; - let costs = |len| { - if transient { - RuntimeCosts::TakeTransientStorage(len) - } else { - RuntimeCosts::TakeStorage(len) - } - }; - let charged = self.charge_gas(costs(self.ext.max_value_size()))?; - let key = self.decode_key(memory, key_ptr, key_len)?; - let outcome = if transient { - self.ext.set_transient_storage(&key, None, true)? - } else { - self.ext.set_storage(&key, None, true)? - }; - - if let crate::storage::WriteOutcome::Taken(value) = outcome { - self.adjust_gas(charged, costs(value.len() as u32)); - self.write_sandbox_output( - memory, - out_ptr, - out_len_ptr, - &value, - false, - already_charged, - )?; - Ok(ReturnErrorCode::Success) - } else { - self.adjust_gas(charged, costs(0)); - Ok(ReturnErrorCode::KeyNotFound) - } - } - - fn call( - &mut self, - memory: &mut M, - flags: CallFlags, - call_type: CallType, - callee_ptr: u32, - deposit_ptr: u32, - weight: Weight, - input_data_ptr: u32, - input_data_len: u32, - output_ptr: u32, - output_len_ptr: u32, - ) -> Result { - let callee = memory.read_h160(callee_ptr)?; - let precompile = >::get::(&callee.as_fixed_bytes()); - match &precompile { - Some(precompile) if precompile.has_contract_info() => - self.charge_gas(RuntimeCosts::PrecompileWithInfoBase)?, - Some(_) => self.charge_gas(RuntimeCosts::PrecompileBase)?, - None => self.charge_gas(call_type.cost())?, - }; - - let deposit_limit = memory.read_u256(deposit_ptr)?; - - // we do check this in exec.rs but we want to error out early - if input_data_len > limits::CALLDATA_BYTES { - Err(>::CallDataTooLarge)?; - } - - let input_data = if flags.contains(CallFlags::CLONE_INPUT) { - let input = self.input_data.as_ref().ok_or(Error::::InputForwarded)?; - charge_gas!(self, RuntimeCosts::CallInputCloned(input.len() as u32))?; - input.clone() - } else if flags.contains(CallFlags::FORWARD_INPUT) { - self.input_data.take().ok_or(Error::::InputForwarded)? - } else { - if precompile.is_some() { - self.charge_gas(RuntimeCosts::PrecompileDecode(input_data_len))?; - } else { - self.charge_gas(RuntimeCosts::CopyFromContract(input_data_len))?; - } - memory.read(input_data_ptr, input_data_len)? - }; - - memory.reset_interpreter_cache(); - - let call_outcome = match call_type { - CallType::Call { value_ptr } => { - let read_only = flags.contains(CallFlags::READ_ONLY); - let value = memory.read_u256(value_ptr)?; - if value > 0u32.into() { - // If the call value is non-zero and state change is not allowed, issue an - // error. - if read_only || self.ext.is_read_only() { - return Err(Error::::StateChangeDenied.into()); - } - - self.charge_gas(RuntimeCosts::CallTransferSurcharge { - dust_transfer: Pallet::::has_dust(value), - })?; - } - self.ext.call( - weight, - deposit_limit, - &callee, - value, - input_data, - flags.contains(CallFlags::ALLOW_REENTRY), - read_only, - ) - }, - CallType::DelegateCall => { - if flags.intersects(CallFlags::ALLOW_REENTRY | CallFlags::READ_ONLY) { - return Err(Error::::InvalidCallFlags.into()); - } - self.ext.delegate_call(weight, deposit_limit, callee, input_data) - }, - }; - - match call_outcome { - // `TAIL_CALL` only matters on an `OK` result. Otherwise the call stack comes to - // a halt anyways without anymore code being executed. - Ok(_) if flags.contains(CallFlags::TAIL_CALL) => { - let output = mem::take(self.ext.last_frame_output_mut()); - return Err(TrapReason::Return(ReturnData { - flags: output.flags.bits(), - data: output.data, - })); - }, - Ok(_) => { - let output = mem::take(self.ext.last_frame_output_mut()); - let write_result = self.write_sandbox_output( - memory, - output_ptr, - output_len_ptr, - &output.data, - true, - |len| Some(RuntimeCosts::CopyToContract(len)), - ); - *self.ext.last_frame_output_mut() = output; - write_result?; - Ok(self.ext.last_frame_output().into()) - }, - Err(err) => { - let error_code = Self::exec_error_into_return_code(err)?; - memory.write(output_len_ptr, &0u32.to_le_bytes())?; - Ok(error_code) - }, - } - } - - fn instantiate( - &mut self, - memory: &mut M, - code_hash_ptr: u32, - weight: Weight, - deposit_ptr: u32, - value_ptr: u32, - input_data_ptr: u32, - input_data_len: u32, - address_ptr: u32, - output_ptr: u32, - output_len_ptr: u32, - salt_ptr: u32, - ) -> Result { - let value = match memory.read_u256(value_ptr) { - Ok(value) => { - self.charge_gas(RuntimeCosts::Instantiate { - input_data_len, - balance_transfer: Pallet::::has_balance(value), - dust_transfer: Pallet::::has_dust(value), - })?; - value - }, - Err(err) => { - self.charge_gas(RuntimeCosts::Instantiate { - input_data_len: 0, - balance_transfer: false, - dust_transfer: false, - })?; - return Err(err.into()); - }, - }; - let deposit_limit: U256 = memory.read_u256(deposit_ptr)?; - let code_hash = memory.read_h256(code_hash_ptr)?; - if input_data_len > limits::CALLDATA_BYTES { - Err(>::CallDataTooLarge)?; - } - let input_data = memory.read(input_data_ptr, input_data_len)?; - let salt = if salt_ptr == SENTINEL { - None - } else { - let salt: [u8; 32] = memory.read_array(salt_ptr)?; - Some(salt) - }; - - memory.reset_interpreter_cache(); - - match self.ext.instantiate( - weight, - deposit_limit, - code_hash, - value, - input_data, - salt.as_ref(), - ) { - Ok(address) => { - if !self.ext.last_frame_output().flags.contains(ReturnFlags::REVERT) { - self.write_fixed_sandbox_output( - memory, - address_ptr, - &address.as_bytes(), - true, - already_charged, - )?; - } - let output = mem::take(self.ext.last_frame_output_mut()); - let write_result = self.write_sandbox_output( - memory, - output_ptr, - output_len_ptr, - &output.data, - true, - |len| Some(RuntimeCosts::CopyToContract(len)), - ); - *self.ext.last_frame_output_mut() = output; - write_result?; - Ok(self.ext.last_frame_output().into()) - }, - Err(err) => Ok(Self::exec_error_into_return_code(err)?), - } - } -} - -// This is the API exposed to contracts. -// -// # Note -// -// Any input that leads to a out of bound error (reading or writing) or failing to decode -// data passed to the supervisor will lead to a trap. This is not documented explicitly -// for every function. -#[define_env] -pub mod env { - /// Noop function used to benchmark the time it takes to execute an empty function. - /// - /// Marked as stable because it needs to be called from benchmarks even when the benchmarked - /// parachain has unstable functions disabled. - #[cfg(feature = "runtime-benchmarks")] - #[stable] - fn noop(&mut self, memory: &mut M) -> Result<(), TrapReason> { - Ok(()) - } - - /// Set the value at the given key in the contract storage. - /// See [`pallet_revive_uapi::HostFn::set_storage_v2`] - #[stable] - #[mutating] - fn set_storage( - &mut self, - memory: &mut M, - flags: u32, - key_ptr: u32, - key_len: u32, - value_ptr: u32, - value_len: u32, - ) -> Result { - self.set_storage( - memory, - flags, - key_ptr, - key_len, - StorageValue::Memory { ptr: value_ptr, len: value_len }, - ) - } - - /// Sets the storage at a fixed 256-bit key with a fixed 256-bit value. - /// See [`pallet_revive_uapi::HostFn::set_storage_or_clear`]. - #[stable] - #[mutating] - fn set_storage_or_clear( - &mut self, - memory: &mut M, - flags: u32, - key_ptr: u32, - value_ptr: u32, - ) -> Result { - let value = memory.read(value_ptr, 32)?; - - if value.iter().all(|&b| b == 0) { - self.clear_storage(memory, flags, key_ptr, SENTINEL) - } else { - self.set_storage(memory, flags, key_ptr, SENTINEL, StorageValue::Value(value)) - } - } - - /// Retrieve the value under the given key from storage. - /// See [`pallet_revive_uapi::HostFn::get_storage`] - #[stable] - fn get_storage( - &mut self, - memory: &mut M, - flags: u32, - key_ptr: u32, - key_len: u32, - out_ptr: u32, - out_len_ptr: u32, - ) -> Result { - self.get_storage( - memory, - flags, - key_ptr, - key_len, - out_ptr, - StorageReadMode::VariableOutput { output_len_ptr: out_len_ptr }, - ) - } - - /// Reads the storage at a fixed 256-bit key and writes back a fixed 256-bit value. - /// See [`pallet_revive_uapi::HostFn::get_storage_or_zero`]. - #[stable] - fn get_storage_or_zero( - &mut self, - memory: &mut M, - flags: u32, - key_ptr: u32, - out_ptr: u32, - ) -> Result<(), TrapReason> { - let _ = self.get_storage( - memory, - flags, - key_ptr, - SENTINEL, - out_ptr, - StorageReadMode::FixedOutput32, - )?; - - Ok(()) - } - - /// Make a call to another contract. - /// See [`pallet_revive_uapi::HostFn::call`]. - #[stable] - fn call( - &mut self, - memory: &mut M, - flags_and_callee: u64, - ref_time_limit: u64, - proof_size_limit: u64, - deposit_and_value: u64, - input_data: u64, - output_data: u64, - ) -> Result { - let (flags, callee_ptr) = extract_hi_lo(flags_and_callee); - let (deposit_ptr, value_ptr) = extract_hi_lo(deposit_and_value); - let (input_data_len, input_data_ptr) = extract_hi_lo(input_data); - let (output_len_ptr, output_ptr) = extract_hi_lo(output_data); - - self.call( - memory, - CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, - CallType::Call { value_ptr }, - callee_ptr, - deposit_ptr, - Weight::from_parts(ref_time_limit, proof_size_limit), - input_data_ptr, - input_data_len, - output_ptr, - output_len_ptr, - ) - } - - /// Execute code in the context (storage, caller, value) of the current contract. - /// See [`pallet_revive_uapi::HostFn::delegate_call`]. - #[stable] - fn delegate_call( - &mut self, - memory: &mut M, - flags_and_callee: u64, - ref_time_limit: u64, - proof_size_limit: u64, - deposit_ptr: u32, - input_data: u64, - output_data: u64, - ) -> Result { - let (flags, address_ptr) = extract_hi_lo(flags_and_callee); - let (input_data_len, input_data_ptr) = extract_hi_lo(input_data); - let (output_len_ptr, output_ptr) = extract_hi_lo(output_data); - - self.call( - memory, - CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, - CallType::DelegateCall, - address_ptr, - deposit_ptr, - Weight::from_parts(ref_time_limit, proof_size_limit), - input_data_ptr, - input_data_len, - output_ptr, - output_len_ptr, - ) - } - - /// Instantiate a contract with the specified code hash. - /// See [`pallet_revive_uapi::HostFn::instantiate`]. - #[stable] - #[mutating] - fn instantiate( - &mut self, - memory: &mut M, - ref_time_limit: u64, - proof_size_limit: u64, - deposit_and_value: u64, - input_data: u64, - output_data: u64, - address_and_salt: u64, - ) -> Result { - let (deposit_ptr, value_ptr) = extract_hi_lo(deposit_and_value); - let (input_data_len, code_hash_ptr) = extract_hi_lo(input_data); - let (output_len_ptr, output_ptr) = extract_hi_lo(output_data); - let (address_ptr, salt_ptr) = extract_hi_lo(address_and_salt); - let Some(input_data_ptr) = code_hash_ptr.checked_add(32) else { - return Err(Error::::OutOfBounds.into()); - }; - let Some(input_data_len) = input_data_len.checked_sub(32) else { - return Err(Error::::OutOfBounds.into()); - }; - - self.instantiate( - memory, - code_hash_ptr, - Weight::from_parts(ref_time_limit, proof_size_limit), - deposit_ptr, - value_ptr, - input_data_ptr, - input_data_len, - address_ptr, - output_ptr, - output_len_ptr, - salt_ptr, - ) - } - - /// Returns the total size of the contract call input data. - /// See [`pallet_revive_uapi::HostFn::call_data_size `]. - #[stable] - fn call_data_size(&mut self, memory: &mut M) -> Result { - self.charge_gas(RuntimeCosts::CallDataSize)?; - Ok(self - .input_data - .as_ref() - .map(|input| input.len().try_into().expect("usize fits into u64; qed")) - .unwrap_or_default()) - } - - /// Stores the input passed by the caller into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::call_data_copy`]. - #[stable] - fn call_data_copy( - &mut self, - memory: &mut M, - out_ptr: u32, - out_len: u32, - offset: u32, - ) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::CallDataCopy(out_len))?; - - let Some(input) = self.input_data.as_ref() else { - return Err(Error::::InputForwarded.into()); - }; - - let start = offset as usize; - if start >= input.len() { - memory.zero(out_ptr, out_len)?; - return Ok(()); - } - - let end = start.saturating_add(out_len as usize).min(input.len()); - memory.write(out_ptr, &input[start..end])?; - - let bytes_written = (end - start) as u32; - memory.zero(out_ptr.saturating_add(bytes_written), out_len - bytes_written)?; - - Ok(()) - } - - /// Stores the U256 value at given call input `offset` into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::call_data_load`]. - #[stable] - fn call_data_load( - &mut self, - memory: &mut M, - out_ptr: u32, - offset: u32, - ) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::CallDataLoad)?; - - let Some(input) = self.input_data.as_ref() else { - return Err(Error::::InputForwarded.into()); - }; - - let mut data = [0; 32]; - let start = offset as usize; - let data = if start >= input.len() { - data // Any index is valid to request; OOB offsets return zero. - } else { - let end = start.saturating_add(32).min(input.len()); - data[..end - start].copy_from_slice(&input[start..end]); - data.reverse(); - data // Solidity expects right-padded data - }; - - self.write_fixed_sandbox_output(memory, out_ptr, &data, false, already_charged)?; - - Ok(()) - } - - /// Cease contract execution and save a data buffer as a result of the execution. - /// See [`pallet_revive_uapi::HostFn::return_value`]. - #[stable] - fn seal_return( - &mut self, - memory: &mut M, - flags: u32, - data_ptr: u32, - data_len: u32, - ) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::CopyFromContract(data_len))?; - if data_len > limits::CALLDATA_BYTES { - Err(>::ReturnDataTooLarge)?; - } - Err(TrapReason::Return(ReturnData { flags, data: memory.read(data_ptr, data_len)? })) - } - - /// Stores the address of the caller into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::caller`]. - #[stable] - fn caller(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::Caller)?; - let caller = ::AddressMapper::to_address(self.ext.caller().account_id()?); - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - caller.as_bytes(), - false, - already_charged, - )?) - } - - /// Stores the address of the call stack origin into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::origin`]. - #[stable] - fn origin(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::Origin)?; - let origin = ::AddressMapper::to_address(self.ext.origin().account_id()?); - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - origin.as_bytes(), - false, - already_charged, - )?) - } - - /// Retrieve the code hash for a specified contract address. - /// See [`pallet_revive_uapi::HostFn::code_hash`]. - #[stable] - fn code_hash(&mut self, memory: &mut M, addr_ptr: u32, out_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::CodeHash)?; - let address = memory.read_h160(addr_ptr)?; - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &self.ext.code_hash(&address).as_bytes(), - false, - already_charged, - )?) - } - - /// Retrieve the code size for a given contract address. - /// See [`pallet_revive_uapi::HostFn::code_size`]. - #[stable] - fn code_size(&mut self, memory: &mut M, addr_ptr: u32) -> Result { - self.charge_gas(RuntimeCosts::CodeSize)?; - let address = memory.read_h160(addr_ptr)?; - Ok(self.ext.code_size(&address)) - } - - /// Stores the address of the current contract into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::address`]. - #[stable] - fn address(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::Address)?; - let address = self.ext.address(); - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - address.as_bytes(), - false, - already_charged, - )?) - } - - /// Stores the price for the specified amount of weight into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::weight_to_fee`]. - #[stable] - fn weight_to_fee( - &mut self, - memory: &mut M, - ref_time_limit: u64, - proof_size_limit: u64, - out_ptr: u32, - ) -> Result<(), TrapReason> { - let weight = Weight::from_parts(ref_time_limit, proof_size_limit); - self.charge_gas(RuntimeCosts::WeightToFee)?; - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &self.ext.get_weight_price(weight).encode(), - false, - already_charged, - )?) - } - - /// Stores the immutable data into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::get_immutable_data`]. - #[stable] - fn get_immutable_data( - &mut self, - memory: &mut M, - out_ptr: u32, - out_len_ptr: u32, - ) -> Result<(), TrapReason> { - // quering the length is free as it is stored with the contract metadata - let len = self.ext.immutable_data_len(); - self.charge_gas(RuntimeCosts::GetImmutableData(len))?; - let data = self.ext.get_immutable_data()?; - self.write_sandbox_output(memory, out_ptr, out_len_ptr, &data, false, already_charged)?; - Ok(()) - } - - /// Attaches the supplied immutable data to the currently executing contract. - /// See [`pallet_revive_uapi::HostFn::set_immutable_data`]. - #[stable] - fn set_immutable_data(&mut self, memory: &mut M, ptr: u32, len: u32) -> Result<(), TrapReason> { - if len > limits::IMMUTABLE_BYTES { - return Err(Error::::OutOfBounds.into()); - } - self.charge_gas(RuntimeCosts::SetImmutableData(len))?; - let buf = memory.read(ptr, len)?; - let data = buf.try_into().expect("bailed out earlier; qed"); - self.ext.set_immutable_data(data)?; - Ok(()) - } - - /// Stores the *free* balance of the current account into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::balance`]. - #[stable] - fn balance(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::Balance)?; - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &self.ext.balance().to_little_endian(), - false, - already_charged, - )?) - } - - /// Stores the *free* balance of the supplied address into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::balance`]. - #[stable] - fn balance_of( - &mut self, - memory: &mut M, - addr_ptr: u32, - out_ptr: u32, - ) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::BalanceOf)?; - let address = memory.read_h160(addr_ptr)?; - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &self.ext.balance_of(&address).to_little_endian(), - false, - already_charged, - )?) - } - - /// Returns the chain ID. - /// See [`pallet_revive_uapi::HostFn::chain_id`]. - #[stable] - fn chain_id(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &U256::from(::ChainId::get()).to_little_endian(), - false, - |_| Some(RuntimeCosts::CopyToContract(32)), - )?) - } - - /// Returns the block ref_time limit. - /// See [`pallet_revive_uapi::HostFn::gas_limit`]. - #[stable] - fn gas_limit(&mut self, memory: &mut M) -> Result { - self.charge_gas(RuntimeCosts::GasLimit)?; - Ok(::BlockWeights::get().max_block.ref_time()) - } - - /// Stores the value transferred along with this call/instantiate into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::value_transferred`]. - #[stable] - fn value_transferred(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::ValueTransferred)?; - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &self.ext.value_transferred().to_little_endian(), - false, - already_charged, - )?) - } - - /// Returns the simulated ethereum `GASPRICE` value. - /// See [`pallet_revive_uapi::HostFn::gas_price`]. - #[stable] - fn gas_price(&mut self, memory: &mut M) -> Result { - self.charge_gas(RuntimeCosts::GasPrice)?; - Ok(GAS_PRICE.into()) - } - - /// Returns the simulated ethereum `BASEFEE` value. - /// See [`pallet_revive_uapi::HostFn::base_fee`]. - #[stable] - fn base_fee(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::BaseFee)?; - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &U256::zero().to_little_endian(), - false, - already_charged, - )?) - } - - /// Load the latest block timestamp into the supplied buffer - /// See [`pallet_revive_uapi::HostFn::now`]. - #[stable] - fn now(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::Now)?; - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &self.ext.now().to_little_endian(), - false, - already_charged, - )?) - } - - /// Deposit a contract event with the data buffer and optional list of topics. - /// See [pallet_revive_uapi::HostFn::deposit_event] - #[stable] - #[mutating] - fn deposit_event( - &mut self, - memory: &mut M, - topics_ptr: u32, - num_topic: u32, - data_ptr: u32, - data_len: u32, - ) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::DepositEvent { num_topic, len: data_len })?; - - if num_topic > limits::NUM_EVENT_TOPICS { - return Err(Error::::TooManyTopics.into()); - } - - if data_len > self.ext.max_value_size() { - return Err(Error::::ValueTooLarge.into()); - } - - let topics: Vec = match num_topic { - 0 => Vec::new(), - _ => { - let mut v = Vec::with_capacity(num_topic as usize); - let topics_len = num_topic * H256::len_bytes() as u32; - let buf = memory.read(topics_ptr, topics_len)?; - for chunk in buf.chunks_exact(H256::len_bytes()) { - v.push(H256::from_slice(chunk)); - } - v - }, - }; - - let event_data = memory.read(data_ptr, data_len)?; - self.ext.deposit_event(topics, event_data); - Ok(()) - } - - /// Stores the current block number of the current contract into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::block_number`]. - #[stable] - fn block_number(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::BlockNumber)?; - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &self.ext.block_number().to_little_endian(), - false, - already_charged, - )?) - } - - /// Stores the block hash at given block height into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::block_hash`]. - #[stable] - fn block_hash( - &mut self, - memory: &mut M, - block_number_ptr: u32, - out_ptr: u32, - ) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::BlockHash)?; - let block_number = memory.read_u256(block_number_ptr)?; - let block_hash = self.ext.block_hash(block_number).unwrap_or(H256::zero()); - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &block_hash.as_bytes(), - false, - already_charged, - )?) - } - - /// Stores the current block author into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::block_author`]. - #[stable] - fn block_author(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::BlockAuthor)?; - let block_author = self.ext.block_author().unwrap_or(H160::zero()); - - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &block_author.as_bytes(), - false, - already_charged, - )?) - } - - /// Computes the KECCAK 256-bit hash on the given input buffer. - /// See [`pallet_revive_uapi::HostFn::hash_keccak_256`]. - #[stable] - fn hash_keccak_256( - &mut self, - memory: &mut M, - input_ptr: u32, - input_len: u32, - output_ptr: u32, - ) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::HashKeccak256(input_len))?; - Ok(self.compute_hash_on_intermediate_buffer( - memory, keccak_256, input_ptr, input_len, output_ptr, - )?) - } - - /// Stores the length of the data returned by the last call into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::return_data_size`]. - #[stable] - fn return_data_size(&mut self, memory: &mut M) -> Result { - self.charge_gas(RuntimeCosts::ReturnDataSize)?; - Ok(self - .ext - .last_frame_output() - .data - .len() - .try_into() - .expect("usize fits into u64; qed")) - } - - /// Stores data returned by the last call, starting from `offset`, into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::return_data`]. - #[stable] - fn return_data_copy( - &mut self, - memory: &mut M, - out_ptr: u32, - out_len_ptr: u32, - offset: u32, - ) -> Result<(), TrapReason> { - let output = mem::take(self.ext.last_frame_output_mut()); - let result = if offset as usize > output.data.len() { - Err(Error::::OutOfBounds.into()) - } else { - self.write_sandbox_output( - memory, - out_ptr, - out_len_ptr, - &output.data[offset as usize..], - false, - |len| Some(RuntimeCosts::CopyToContract(len)), - ) - }; - *self.ext.last_frame_output_mut() = output; - Ok(result?) - } - - /// Returns the amount of ref_time left. - /// See [`pallet_revive_uapi::HostFn::ref_time_left`]. - #[stable] - fn ref_time_left(&mut self, memory: &mut M) -> Result { - self.charge_gas(RuntimeCosts::RefTimeLeft)?; - Ok(self.ext.gas_meter().gas_left().ref_time()) - } - - /// Checks whether the caller of the current contract is the origin of the whole call stack. - /// See [`pallet_revive_uapi::HostFn::caller_is_origin`]. - fn caller_is_origin(&mut self, _memory: &mut M) -> Result { - self.charge_gas(RuntimeCosts::CallerIsOrigin)?; - Ok(self.ext.caller_is_origin() as u32) - } - - /// Checks whether the caller of the current contract is root. - /// See [`pallet_revive_uapi::HostFn::caller_is_root`]. - fn caller_is_root(&mut self, _memory: &mut M) -> Result { - self.charge_gas(RuntimeCosts::CallerIsRoot)?; - Ok(self.ext.caller_is_root() as u32) - } - - /// Clear the value at the given key in the contract storage. - /// See [`pallet_revive_uapi::HostFn::clear_storage`] - #[mutating] - fn clear_storage( - &mut self, - memory: &mut M, - flags: u32, - key_ptr: u32, - key_len: u32, - ) -> Result { - self.clear_storage(memory, flags, key_ptr, key_len) - } - - /// Checks whether there is a value stored under the given key. - /// See [`pallet_revive_uapi::HostFn::contains_storage`] - fn contains_storage( - &mut self, - memory: &mut M, - flags: u32, - key_ptr: u32, - key_len: u32, - ) -> Result { - self.contains_storage(memory, flags, key_ptr, key_len) - } - - /// Calculates Ethereum address from the ECDSA compressed public key and stores - /// See [`pallet_revive_uapi::HostFn::ecdsa_to_eth_address`]. - fn ecdsa_to_eth_address( - &mut self, - memory: &mut M, - key_ptr: u32, - out_ptr: u32, - ) -> Result { - self.charge_gas(RuntimeCosts::EcdsaToEthAddress)?; - let mut compressed_key: [u8; 33] = [0; 33]; - memory.read_into_buf(key_ptr, &mut compressed_key)?; - let result = self.ext.ecdsa_to_eth_address(&compressed_key); - match result { - Ok(eth_address) => { - memory.write(out_ptr, eth_address.as_ref())?; - Ok(ReturnErrorCode::Success) - }, - Err(_) => Ok(ReturnErrorCode::EcdsaRecoveryFailed), - } - } - - /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::minimum_balance`]. - fn minimum_balance(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::MinimumBalance)?; - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &self.ext.minimum_balance().to_little_endian(), - false, - already_charged, - )?) - } - - /// Retrieve the code hash of the currently executing contract. - /// See [`pallet_revive_uapi::HostFn::own_code_hash`]. - fn own_code_hash(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::OwnCodeHash)?; - let code_hash = *self.ext.own_code_hash(); - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - code_hash.as_bytes(), - false, - already_charged, - )?) - } - - /// Replace the contract code at the specified address with new code. - /// See [`pallet_revive_uapi::HostFn::set_code_hash`]. - /// - /// Disabled until the internal implementation takes care of collecting - /// the immutable data of the new code hash. - #[mutating] - fn set_code_hash(&mut self, memory: &mut M, code_hash_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::SetCodeHash)?; - let code_hash: H256 = memory.read_h256(code_hash_ptr)?; - self.ext.set_code_hash(code_hash)?; - Ok(()) - } - - /// Verify a sr25519 signature - /// See [`pallet_revive_uapi::HostFn::sr25519_verify`]. - fn sr25519_verify( - &mut self, - memory: &mut M, - signature_ptr: u32, - pub_key_ptr: u32, - message_len: u32, - message_ptr: u32, - ) -> Result { - self.charge_gas(RuntimeCosts::Sr25519Verify(message_len))?; - - let mut signature: [u8; 64] = [0; 64]; - memory.read_into_buf(signature_ptr, &mut signature)?; - - let mut pub_key: [u8; 32] = [0; 32]; - memory.read_into_buf(pub_key_ptr, &mut pub_key)?; - - let message: Vec = memory.read(message_ptr, message_len)?; - - if self.ext.sr25519_verify(&signature, &message, &pub_key) { - Ok(ReturnErrorCode::Success) - } else { - Ok(ReturnErrorCode::Sr25519VerifyFailed) - } - } - - /// Retrieve and remove the value under the given key from storage. - /// See [`pallet_revive_uapi::HostFn::take_storage`] - #[mutating] - fn take_storage( - &mut self, - memory: &mut M, - flags: u32, - key_ptr: u32, - key_len: u32, - out_ptr: u32, - out_len_ptr: u32, - ) -> Result { - self.take_storage(memory, flags, key_ptr, key_len, out_ptr, out_len_ptr) - } - - /// Remove the calling account and transfer remaining **free** balance. - /// See [`pallet_revive_uapi::HostFn::terminate`]. - #[mutating] - fn terminate(&mut self, memory: &mut M, beneficiary_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::Terminate)?; - let beneficiary = memory.read_h160(beneficiary_ptr)?; - self.ext.terminate(&beneficiary)?; - Err(TrapReason::Termination) - } - - /// Stores the amount of weight left into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::weight_left`]. - fn weight_left( - &mut self, - memory: &mut M, - out_ptr: u32, - out_len_ptr: u32, - ) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::WeightLeft)?; - let gas_left = &self.ext.gas_meter().gas_left().encode(); - Ok(self.write_sandbox_output( - memory, - out_ptr, - out_len_ptr, - gas_left, - false, - already_charged, - )?) - } -} diff --git a/substrate/frame/revive/src/vm/runtime_costs.rs b/substrate/frame/revive/src/vm/runtime_costs.rs new file mode 100644 index 0000000000000..7c71a1bb1b933 --- /dev/null +++ b/substrate/frame/revive/src/vm/runtime_costs.rs @@ -0,0 +1,315 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +use crate::{gas::Token, weights::WeightInfo, Config}; +use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}; + +/// Current approximation of the gas/s consumption considering +/// EVM execution over compiled WASM (on 4.4Ghz CPU). +/// Given the 2000ms Weight, from which 75% only are used for transactions, +/// the total EVM execution gas limit is: GAS_PER_SECOND * 2 * 0.75 ~= 60_000_000. +const GAS_PER_SECOND: u64 = 40_000_000; + +/// Approximate ratio of the amount of Weight per Gas. +/// u64 works for approximations because Weight is a very small unit compared to +/// gas. +const WEIGHT_PER_GAS: u64 = WEIGHT_REF_TIME_PER_SECOND / GAS_PER_SECOND; + +#[cfg_attr(test, derive(Debug, PartialEq, Eq))] +#[derive(Copy, Clone)] +pub enum RuntimeCosts { + /// Base Weight of calling a host function. + HostFn, + /// Weight charged for copying data from the sandbox. + CopyFromContract(u32), + /// Weight charged for copying data to the sandbox. + CopyToContract(u32), + /// Weight of calling `seal_call_data_load``. + CallDataLoad, + /// Weight of calling `seal_call_data_copy`. + CallDataCopy(u32), + /// Weight of calling `seal_caller`. + Caller, + /// Weight of calling `seal_call_data_size`. + CallDataSize, + /// Weight of calling `seal_return_data_size`. + ReturnDataSize, + /// Weight of calling `seal_to_account_id`. + ToAccountId, + /// Weight of calling `seal_origin`. + Origin, + /// Weight of calling `seal_code_hash`. + CodeHash, + /// Weight of calling `seal_own_code_hash`. + OwnCodeHash, + /// Weight of calling `seal_code_size`. + CodeSize, + /// Weight of calling `seal_caller_is_origin`. + CallerIsOrigin, + /// Weight of calling `caller_is_root`. + CallerIsRoot, + /// Weight of calling `seal_address`. + Address, + /// Weight of calling `seal_ref_time_left`. + RefTimeLeft, + /// Weight of calling `seal_weight_left`. + WeightLeft, + /// Weight of calling `seal_balance`. + Balance, + /// Weight of calling `seal_balance_of`. + BalanceOf, + /// Weight of calling `seal_value_transferred`. + ValueTransferred, + /// Weight of calling `seal_minimum_balance`. + MinimumBalance, + /// Weight of calling `seal_block_number`. + BlockNumber, + /// Weight of calling `seal_block_hash`. + BlockHash, + /// Weight of calling `seal_block_author`. + BlockAuthor, + /// Weight of calling `seal_gas_price`. + GasPrice, + /// Weight of calling `seal_base_fee`. + BaseFee, + /// Weight of calling `seal_now`. + Now, + /// Weight of calling `seal_gas_limit`. + GasLimit, + /// Weight of calling `seal_weight_to_fee`. + WeightToFee, + /// Weight of calling `seal_terminate`. + Terminate, + /// Weight of calling `seal_deposit_event` with the given number of topics and event size. + DepositEvent { num_topic: u32, len: u32 }, + /// Weight of calling `seal_set_storage` for the given storage item sizes. + SetStorage { old_bytes: u32, new_bytes: u32 }, + /// Weight of calling `seal_clear_storage` per cleared byte. + ClearStorage(u32), + /// Weight of calling `seal_contains_storage` per byte of the checked item. + ContainsStorage(u32), + /// Weight of calling `seal_get_storage` with the specified size in storage. + GetStorage(u32), + /// Weight of calling `seal_take_storage` for the given size. + TakeStorage(u32), + /// Weight of calling `seal_set_transient_storage` for the given storage item sizes. + SetTransientStorage { old_bytes: u32, new_bytes: u32 }, + /// Weight of calling `seal_clear_transient_storage` per cleared byte. + ClearTransientStorage(u32), + /// Weight of calling `seal_contains_transient_storage` per byte of the checked item. + ContainsTransientStorage(u32), + /// Weight of calling `seal_get_transient_storage` with the specified size in storage. + GetTransientStorage(u32), + /// Weight of calling `seal_take_transient_storage` for the given size. + TakeTransientStorage(u32), + /// Base weight of calling `seal_call`. + CallBase, + /// Weight of calling `seal_delegate_call` for the given input size. + DelegateCallBase, + /// Weight of calling a precompile. + PrecompileBase, + /// Weight of calling a precompile that has a contract info. + PrecompileWithInfoBase, + /// Weight of reading and decoding the input to a precompile. + PrecompileDecode(u32), + /// Weight of the transfer performed during a call. + /// parameter `dust_transfer` indicates whether the transfer has a `dust` value. + CallTransferSurcharge { dust_transfer: bool }, + /// Weight per byte that is cloned by supplying the `CLONE_INPUT` flag. + CallInputCloned(u32), + /// Weight of calling `seal_instantiate`. + Instantiate { input_data_len: u32, balance_transfer: bool, dust_transfer: bool }, + /// Weight of calling `Ripemd160` precompile for the given input size. + Ripemd160(u32), + /// Weight of calling `Sha256` precompile for the given input size. + HashSha256(u32), + /// Weight of calling the `System::hashBlake256` precompile function for the given input + HashKeccak256(u32), + /// Weight of calling the `System::hash_blake2_256` precompile function for the given input + /// size. + HashBlake256(u32), + /// Weight of calling `System::hashBlake128` precompile function for the given input size. + HashBlake128(u32), + /// Weight of calling `ECERecover` precompile. + EcdsaRecovery, + /// Weight of calling `seal_sr25519_verify` for the given input size. + Sr25519Verify(u32), + /// Weight charged by a precompile. + Precompile(Weight), + /// Weight of calling `seal_set_code_hash` + SetCodeHash, + /// Weight of calling `ecdsa_to_eth_address` + EcdsaToEthAddress, + /// Weight of calling `get_immutable_dependency` + GetImmutableData(u32), + /// Weight of calling `set_immutable_dependency` + SetImmutableData(u32), + /// Weight of calling `Bn128Add` precompile + Bn128Add, + /// Weight of calling `Bn128Add` precompile + Bn128Mul, + /// Weight of calling `Bn128Pairing` precompile for the given number of input pairs. + Bn128Pairing(u32), + /// Weight of calling `Identity` precompile for the given number of input length. + Identity(u32), + /// Weight of calling `Blake2F` precompile for the given number of rounds. + Blake2F(u32), + /// Weight of calling `Modexp` precompile + Modexp(u64), +} + +/// For functions that modify storage, benchmarks are performed with one item in the +/// storage. To account for the worst-case scenario, the weight of the overhead of +/// writing to or reading from full storage is included. For transient storage writes, +/// the rollback weight is added to reflect the worst-case scenario for this operation. +macro_rules! cost_storage { + (write_transient, $name:ident $(, $arg:expr )*) => { + T::WeightInfo::$name($( $arg ),*) + .saturating_add(T::WeightInfo::rollback_transient_storage()) + .saturating_add(T::WeightInfo::set_transient_storage_full() + .saturating_sub(T::WeightInfo::set_transient_storage_empty())) + }; + + (read_transient, $name:ident $(, $arg:expr )*) => { + T::WeightInfo::$name($( $arg ),*) + .saturating_add(T::WeightInfo::get_transient_storage_full() + .saturating_sub(T::WeightInfo::get_transient_storage_empty())) + }; + + (write, $name:ident $(, $arg:expr )*) => { + T::WeightInfo::$name($( $arg ),*) + .saturating_add(T::WeightInfo::set_storage_full() + .saturating_sub(T::WeightInfo::set_storage_empty())) + }; + + (read, $name:ident $(, $arg:expr )*) => { + T::WeightInfo::$name($( $arg ),*) + .saturating_add(T::WeightInfo::get_storage_full() + .saturating_sub(T::WeightInfo::get_storage_empty())) + }; +} + +macro_rules! cost_args { + // cost_args!(name, a, b, c) -> T::WeightInfo::name(a, b, c).saturating_sub(T::WeightInfo::name(0, 0, 0)) + ($name:ident, $( $arg: expr ),+) => { + (T::WeightInfo::$name($( $arg ),+).saturating_sub(cost_args!(@call_zero $name, $( $arg ),+))) + }; + // Transform T::WeightInfo::name(a, b, c) into T::WeightInfo::name(0, 0, 0) + (@call_zero $name:ident, $( $arg:expr ),*) => { + T::WeightInfo::$name($( cost_args!(@replace_token $arg) ),*) + }; + // Replace the token with 0. + (@replace_token $_in:tt) => { 0 }; +} + +impl Token for RuntimeCosts { + fn influence_lowest_gas_limit(&self) -> bool { + true + } + + fn weight(&self) -> Weight { + use self::RuntimeCosts::*; + match *self { + HostFn => cost_args!(noop_host_fn, 1), + CopyToContract(len) => T::WeightInfo::seal_copy_to_contract(len), + CopyFromContract(len) => T::WeightInfo::seal_return(len), + CallDataSize => T::WeightInfo::seal_call_data_size(), + ReturnDataSize => T::WeightInfo::seal_return_data_size(), + CallDataLoad => T::WeightInfo::seal_call_data_load(), + CallDataCopy(len) => T::WeightInfo::seal_call_data_copy(len), + Caller => T::WeightInfo::seal_caller(), + Origin => T::WeightInfo::seal_origin(), + ToAccountId => T::WeightInfo::seal_to_account_id(), + CodeHash => T::WeightInfo::seal_code_hash(), + CodeSize => T::WeightInfo::seal_code_size(), + OwnCodeHash => T::WeightInfo::seal_own_code_hash(), + CallerIsOrigin => T::WeightInfo::seal_caller_is_origin(), + CallerIsRoot => T::WeightInfo::seal_caller_is_root(), + Address => T::WeightInfo::seal_address(), + RefTimeLeft => T::WeightInfo::seal_ref_time_left(), + WeightLeft => T::WeightInfo::seal_weight_left(), + Balance => T::WeightInfo::seal_balance(), + BalanceOf => T::WeightInfo::seal_balance_of(), + ValueTransferred => T::WeightInfo::seal_value_transferred(), + MinimumBalance => T::WeightInfo::seal_minimum_balance(), + BlockNumber => T::WeightInfo::seal_block_number(), + BlockHash => T::WeightInfo::seal_block_hash(), + BlockAuthor => T::WeightInfo::seal_block_author(), + GasPrice => T::WeightInfo::seal_gas_price(), + BaseFee => T::WeightInfo::seal_base_fee(), + Now => T::WeightInfo::seal_now(), + GasLimit => T::WeightInfo::seal_gas_limit(), + WeightToFee => T::WeightInfo::seal_weight_to_fee(), + Terminate => T::WeightInfo::seal_terminate(), + DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len), + SetStorage { new_bytes, old_bytes } => { + cost_storage!(write, seal_set_storage, new_bytes, old_bytes) + }, + ClearStorage(len) => cost_storage!(write, seal_clear_storage, len), + ContainsStorage(len) => cost_storage!(read, seal_contains_storage, len), + GetStorage(len) => cost_storage!(read, seal_get_storage, len), + TakeStorage(len) => cost_storage!(write, seal_take_storage, len), + SetTransientStorage { new_bytes, old_bytes } => { + cost_storage!(write_transient, seal_set_transient_storage, new_bytes, old_bytes) + }, + ClearTransientStorage(len) => { + cost_storage!(write_transient, seal_clear_transient_storage, len) + }, + ContainsTransientStorage(len) => { + cost_storage!(read_transient, seal_contains_transient_storage, len) + }, + GetTransientStorage(len) => { + cost_storage!(read_transient, seal_get_transient_storage, len) + }, + TakeTransientStorage(len) => { + cost_storage!(write_transient, seal_take_transient_storage, len) + }, + CallBase => T::WeightInfo::seal_call(0, 0, 0), + DelegateCallBase => T::WeightInfo::seal_delegate_call(), + PrecompileBase => T::WeightInfo::seal_call_precompile(0, 0), + PrecompileWithInfoBase => T::WeightInfo::seal_call_precompile(1, 0), + PrecompileDecode(len) => cost_args!(seal_call_precompile, 0, len), + CallTransferSurcharge { dust_transfer } => + cost_args!(seal_call, 1, dust_transfer.into(), 0), + CallInputCloned(len) => cost_args!(seal_call, 0, 0, len), + Instantiate { input_data_len, balance_transfer, dust_transfer } => + T::WeightInfo::seal_instantiate( + input_data_len, + balance_transfer.into(), + dust_transfer.into(), + ), + HashSha256(len) => T::WeightInfo::sha2_256(len), + Ripemd160(len) => T::WeightInfo::ripemd_160(len), + HashKeccak256(len) => T::WeightInfo::seal_hash_keccak_256(len), + HashBlake256(len) => T::WeightInfo::hash_blake2_256(len), + HashBlake128(len) => T::WeightInfo::hash_blake2_128(len), + EcdsaRecovery => T::WeightInfo::ecdsa_recover(), + Sr25519Verify(len) => T::WeightInfo::seal_sr25519_verify(len), + Precompile(weight) => weight, + SetCodeHash => T::WeightInfo::seal_set_code_hash(), + EcdsaToEthAddress => T::WeightInfo::seal_ecdsa_to_eth_address(), + GetImmutableData(len) => T::WeightInfo::seal_get_immutable_data(len), + SetImmutableData(len) => T::WeightInfo::seal_set_immutable_data(len), + Bn128Add => T::WeightInfo::bn128_add(), + Bn128Mul => T::WeightInfo::bn128_mul(), + Bn128Pairing(len) => T::WeightInfo::bn128_pairing(len), + Identity(len) => T::WeightInfo::identity(len), + Blake2F(rounds) => T::WeightInfo::blake2f(rounds), + Modexp(gas) => Weight::from_parts(gas.saturating_mul(WEIGHT_PER_GAS), 0), + } + } +} diff --git a/substrate/primitives/runtime/Cargo.toml b/substrate/primitives/runtime/Cargo.toml index 10f9ab7daec3b..2f739f7a7ab2b 100644 --- a/substrate/primitives/runtime/Cargo.toml +++ b/substrate/primitives/runtime/Cargo.toml @@ -57,6 +57,7 @@ default = ["std"] std = [ "binary-merkle-tree/std", "codec/std", + "either/std", "either/use_std", "hash256-std-hasher/std", "log/std",