diff --git a/Cargo.lock b/Cargo.lock index 4796e34e5a5..2640e98533a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,124 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "alloy-eip2124" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675264c957689f0fd75f5993a73123c2cc3b5c235a38f5b9037fe6c826bfb2c0" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "crc", + "serde", + "thiserror 2.0.12", +] + +[[package]] +name = "alloy-eip2930" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b15b13d38b366d01e818fe8e710d4d702ef7499eacd44926a06171dd9585d0c" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "k256", + "serde", + "thiserror 2.0.12", +] + +[[package]] +name = "alloy-eips" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e86967eb559920e4b9102e4cb825fe30f2e9467988353ce4809f0d3f2c90cd4" +dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "auto_impl", + "c-kzg", + "derive_more 2.0.1", + "either", + "once_cell", + "serde", + "sha2 0.10.8", +] + +[[package]] +name = "alloy-primitives" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eacedba97e65cdc7ab592f2b22ef5d3ab8d60b2056bc3a6e6363577e8270ec6f" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if 1.0.0", + "const-hex", + "derive_more 2.0.1", + "foldhash", + "hashbrown 0.15.2", + "indexmap 2.8.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.8.5", + "ruint", + "rustc-hash 2.1.1", + "serde", + "sha3", + "tiny-keccak 2.0.2", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6" +dependencies = [ + "alloy-rlp-derive", + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "alloy-serde" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a1cd73fc054de6353c7f22ff9b846b0f0f145cd0112da07d4119e41e9959207" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -130,6 +248,130 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint 0.4.6", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -154,7 +396,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -170,7 +412,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -254,9 +496,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.87" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", @@ -278,6 +520,16 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[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.2.1" @@ -308,9 +560,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.27.0" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bbe221bbf523b625a4dd8585c7f38166e31167ec2ca98051dbcb4c3b6e825d2" +checksum = "77926887776171ced7d662120a75998e444d3750c951abfe07f90da130514b1f" dependencies = [ "bindgen 0.69.5", "cc", @@ -354,9 +606,9 @@ checksum = "77c6d128af408d8ebd08331f0331cf2cf20d19e6c44a7aec58791641ecc8c0b5" [[package]] name = "base64ct" -version = "1.7.1" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb97d56060ee67d285efb8001fec9d2a4c710c32efd2e14b5cbb5ba71930fc2d" +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" [[package]] name = "bech32" @@ -396,7 +648,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn 2.0.100", "which", @@ -419,12 +671,21 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn 2.0.100", "which", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", +] + [[package]] name = "bit-vec" version = "0.5.1" @@ -437,6 +698,28 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitcoin-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -448,6 +731,9 @@ name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +dependencies = [ + "serde", +] [[package]] name = "bitvec" @@ -457,6 +743,7 @@ checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", + "serde", "tap", "wyz", ] @@ -502,6 +789,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blst" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c79a94619fade3c0b887670333513a67ac28a6a7e653eb260bf0d4103db38d" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + [[package]] name = "brotli-decompressor" version = "4.0.2" @@ -545,6 +844,21 @@ dependencies = [ "serde", ] +[[package]] +name = "c-kzg" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0307f72feab3300336fb803a57134159f6e20139af1357f36c54cb90d8e8928" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + [[package]] name = "cast" version = "0.3.0" @@ -652,7 +966,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", - "half 2.4.1", + "half 2.5.0", ] [[package]] @@ -747,6 +1061,19 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "const-hex" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "hex", + "proptest", + "serde", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -816,6 +1143,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.4.2" @@ -983,7 +1325,7 @@ dependencies = [ "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "rustc_version", + "rustc_version 0.4.1", "subtle", "zeroize", ] @@ -1095,7 +1437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fab9d9a7e9ff7a4762c5c378deb4158d6aaeaeab86952ecf64221159dcd20809" dependencies = [ "subtle", - "thiserror", + "thiserror 1.0.69", "zeroize", ] @@ -1140,14 +1482,36 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", "serde", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive-where" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "derive_arbitrary" version = "1.4.1" @@ -1176,7 +1540,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "derive_more-impl", + "derive_more-impl 1.0.0", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl 2.0.1", ] [[package]] @@ -1190,6 +1563,18 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "unicode-xid", +] + [[package]] name = "devise" version = "0.4.2" @@ -1331,6 +1716,9 @@ name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] [[package]] name = "elliptic-curve" @@ -1362,6 +1750,17 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "env_logger" version = "0.10.2" @@ -1410,7 +1809,7 @@ dependencies = [ "serde", "serde_json", "sha3", - "thiserror", + "thiserror 1.0.69", "uint", ] @@ -1558,6 +1957,28 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + [[package]] name = "ff" version = "0.13.1" @@ -1627,9 +2048,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" @@ -1791,14 +2212,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -1896,9 +2317,9 @@ checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" [[package]] name = "half" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1" dependencies = [ "cfg-if 1.0.0", "crunchy", @@ -1946,6 +2367,7 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash", + "serde", ] [[package]] @@ -1974,6 +2396,18 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec", +] [[package]] name = "hex-literal" @@ -2010,14 +2444,15 @@ dependencies = [ [[package]] name = "honggfuzz" -version = "0.5.56" +version = "0.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c76b6234c13c9ea73946d1379d33186151148e0da231506b964b44f3d023505" +checksum = "fc563d4f41b17364d5c48ded509f2bcf1c3f6ae9c7f203055b4a5c325072d57e" dependencies = [ "arbitrary", "lazy_static", "memmap2", - "rustc_version", + "rustc_version 0.4.1", + "semver 1.0.26", ] [[package]] @@ -2470,6 +2905,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2519,6 +2964,52 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +[[package]] +name = "libsecp256k1" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79019718125edc905a079a70cfa5f3820bc76139fc91d6f9abc27ea2a887139" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.5", + "serde", + "sha2 0.9.9", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -2527,9 +3018,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" [[package]] name = "litemap" @@ -2917,7 +3408,7 @@ dependencies = [ "oasis-cbor-derive", "oasis-cbor-value", "serde", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2951,7 +3442,7 @@ dependencies = [ "oasis-runtime-sdk", "rand_core 0.6.4", "rand_xorshift", - "thiserror", + "thiserror 1.0.69", "wee_alloc", ] @@ -2965,7 +3456,7 @@ dependencies = [ "oasis-cbor", "oasis-runtime-sdk", "sha2 0.10.8", - "thiserror", + "thiserror 1.0.69", "x25519-dalek", ] @@ -2997,7 +3488,7 @@ dependencies = [ "hex", "oasis-cbor", "oasis-runtime-sdk", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3021,7 +3512,7 @@ dependencies = [ "secret-sharing", "sgx-isa", "sp800-185", - "thiserror", + "thiserror 1.0.69", "tiny-keccak 2.0.2", "tokio", "x25519-dalek", @@ -3082,7 +3573,7 @@ dependencies = [ "tendermint-light-client", "tendermint-proto", "tendermint-rpc", - "thiserror", + "thiserror 1.0.69", "tiny-keccak 2.0.2", "tokio", "tokio-retry", @@ -3126,7 +3617,7 @@ dependencies = [ "sha2 0.10.8", "sha3", "slog", - "thiserror", + "thiserror 1.0.69", "tiny-keccak 2.0.2", "tokio", "tokio-retry", @@ -3152,7 +3643,7 @@ dependencies = [ "pretty_assertions", "rand_core 0.6.4", "snap", - "thiserror", + "thiserror 1.0.69", "walrus", "wasm3", "wasmprinter", @@ -3190,7 +3681,39 @@ dependencies = [ "sha2 0.10.8", "sha3", "substrate-bn", - "thiserror", + "thiserror 1.0.69", + "uint", + "x25519-dalek", +] + +[[package]] +name = "oasis-runtime-sdk-evm-new" +version = "0.6.0" +dependencies = [ + "anyhow", + "base64", + "blake3", + "criterion", + "ethabi", + "ethereum 0.15.0", + "fixed-hash", + "hex", + "hmac", + "honggfuzz", + "k256", + "oasis-cbor", + "oasis-runtime-sdk", + "once_cell", + "primitive-types", + "rand 0.8.5", + "rand_core 0.6.4", + "revm", + "rlp", + "serde", + "serde_json", + "sha2 0.10.8", + "sha3", + "thiserror 1.0.69", "uint", "x25519-dalek", ] @@ -3219,7 +3742,7 @@ dependencies = [ "oasis-runtime-sdk-evm", "once_cell", "rustc-hex", - "thiserror", + "thiserror 1.0.69", "zeroize", ] @@ -3252,9 +3775,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.0" +version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" +checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" [[package]] name = "oorandom" @@ -3416,25 +3939,79 @@ dependencies = [ ] [[package]] -name = "peg-runtime" -version = "0.8.5" +name = "peg-runtime" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +dependencies = [ + "memchr", + "thiserror 2.0.12", + "ucd-trie", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] [[package]] -name = "pem-rfc7468" -version = "0.7.0" +name = "phf_macros" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ - "base64ct", + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] -name = "percent-encoding" -version = "2.3.1" +name = "phf_shared" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] [[package]] name = "pin-project" @@ -3567,9 +4144,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.30" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" +checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb" dependencies = [ "proc-macro2", "syn 2.0.100", @@ -3662,6 +4239,26 @@ dependencies = [ "yansi", ] +[[package]] +name = "proptest" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +dependencies = [ + "bit-set", + "bit-vec 0.8.0", + "bitflags 2.9.0", + "lazy_static", + "num-traits", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_xorshift", + "regex-syntax 0.8.5", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "prost" version = "0.13.5" @@ -3685,6 +4282,12 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.40" @@ -3694,6 +4297,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "radium" version = "0.7.0" @@ -3722,6 +4331,7 @@ dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", + "serde", ] [[package]] @@ -3789,7 +4399,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.2", ] [[package]] @@ -3909,6 +4519,173 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "revm" +version = "20.0.0" +source = "git+https://github.com/bluealloy/revm?tag=v66#398ef740aff6b0d15f2a6a155367b0257954a103" +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 = "1.0.0" +source = "git+https://github.com/bluealloy/revm?tag=v66#398ef740aff6b0d15f2a6a155367b0257954a103" +dependencies = [ + "bitvec", + "phf", + "revm-primitives", + "serde", +] + +[[package]] +name = "revm-context" +version = "1.0.0" +source = "git+https://github.com/bluealloy/revm?tag=v66#398ef740aff6b0d15f2a6a155367b0257954a103" +dependencies = [ + "cfg-if 1.0.0", + "derive-where", + "revm-bytecode", + "revm-context-interface", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-context-interface" +version = "1.0.0" +source = "git+https://github.com/bluealloy/revm?tag=v66#398ef740aff6b0d15f2a6a155367b0257954a103" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "auto_impl", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-database" +version = "1.0.0" +source = "git+https://github.com/bluealloy/revm?tag=v66#398ef740aff6b0d15f2a6a155367b0257954a103" +dependencies = [ + "alloy-eips", + "auto_impl", + "revm-bytecode", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-database-interface" +version = "1.0.0" +source = "git+https://github.com/bluealloy/revm?tag=v66#398ef740aff6b0d15f2a6a155367b0257954a103" +dependencies = [ + "auto_impl", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-handler" +version = "1.0.0" +source = "git+https://github.com/bluealloy/revm?tag=v66#398ef740aff6b0d15f2a6a155367b0257954a103" +dependencies = [ + "auto_impl", + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database-interface", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-inspector" +version = "1.0.0" +source = "git+https://github.com/bluealloy/revm?tag=v66#398ef740aff6b0d15f2a6a155367b0257954a103" +dependencies = [ + "auto_impl", + "revm-context", + "revm-database-interface", + "revm-handler", + "revm-interpreter", + "revm-primitives", + "revm-state", + "serde", + "serde_json", +] + +[[package]] +name = "revm-interpreter" +version = "16.0.0" +source = "git+https://github.com/bluealloy/revm?tag=v66#398ef740aff6b0d15f2a6a155367b0257954a103" +dependencies = [ + "revm-bytecode", + "revm-context-interface", + "revm-primitives", + "serde", +] + +[[package]] +name = "revm-precompile" +version = "17.0.0" +source = "git+https://github.com/bluealloy/revm?tag=v66#398ef740aff6b0d15f2a6a155367b0257954a103" +dependencies = [ + "aurora-engine-modexp", + "c-kzg", + "cfg-if 1.0.0", + "k256", + "libsecp256k1", + "once_cell", + "p256", + "revm-primitives", + "ripemd", + "secp256k1", + "sha2 0.10.8", + "substrate-bn", +] + +[[package]] +name = "revm-primitives" +version = "16.0.0" +source = "git+https://github.com/bluealloy/revm?tag=v66#398ef740aff6b0d15f2a6a155367b0257954a103" +dependencies = [ + "alloy-primitives", + "enumn", + "serde", +] + +[[package]] +name = "revm-state" +version = "1.0.0" +source = "git+https://github.com/bluealloy/revm?tag=v66#398ef740aff6b0d15f2a6a155367b0257954a103" +dependencies = [ + "bitflags 2.9.0", + "revm-bytecode", + "revm-primitives", + "serde", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -4061,7 +4838,7 @@ dependencies = [ "serde", "serde_with", "sp800-185", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-retry", "zeroize", @@ -4125,6 +4902,38 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ruint" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825df406ec217a8116bd7b06897c6cc8f65ffefc15d030ae2c9540acc9ed50b6" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand 0.8.5", + "rlp", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -4137,19 +4946,34 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc-hex" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver", + "semver 1.0.26", ] [[package]] @@ -4176,29 +5000,29 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" +checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" dependencies = [ "bitflags 2.9.0", "errno", "libc", - "linux-raw-sys 0.9.2", + "linux-raw-sys 0.9.3", "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.23" +version = "0.23.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" dependencies = [ "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.0", "subtle", "zeroize", ] @@ -4214,7 +5038,7 @@ dependencies = [ "mbedtls", "rustls", "rustls-mbedtls-provider-utils", - "rustls-webpki", + "rustls-webpki 0.102.8", "yasna 0.3.2", ] @@ -4261,6 +5085,17 @@ name = "rustls-webpki" version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa4eeac2588ffff23e9d7a7e9b3f971c5fb5b7ebc9452745e0c232c64f83b2f" dependencies = [ "aws-lc-rs", "ring", @@ -4274,6 +5109,18 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.20" @@ -4489,6 +5336,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + [[package]] name = "secret-sharing" version = "0.1.0" @@ -4502,16 +5369,34 @@ dependencies = [ "rand_core 0.6.4", "sha3", "subtle", - "thiserror", + "thiserror 1.0.69", "zeroize", ] +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.219" @@ -4557,6 +5442,7 @@ version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ + "indexmap 2.8.0", "itoa", "memchr", "ryu", @@ -4668,6 +5554,16 @@ dependencies = [ "keccak", ] +[[package]] +name = "sha3-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if 1.0.0", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -4786,7 +5682,7 @@ dependencies = [ "chacha20poly1305", "curve25519-dalek", "rand_core 0.6.4", - "rustc_version", + "rustc_version 0.4.1", "sha2 0.10.8", "subtle", ] @@ -4956,15 +5852,14 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.18.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" +checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600" dependencies = [ - "cfg-if 1.0.0", "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.2", "once_cell", - "rustix 1.0.2", + "rustix 1.0.3", "windows-sys 0.59.0", ] @@ -5075,7 +5970,7 @@ dependencies = [ "peg", "pin-project", "rand 0.8.5", - "semver", + "semver 1.0.26", "serde", "serde_bytes", "serde_json", @@ -5084,7 +5979,7 @@ dependencies = [ "tendermint", "tendermint-config", "tendermint-proto", - "thiserror", + "thiserror 1.0.69", "time", "url", "uuid", @@ -5107,7 +6002,7 @@ dependencies = [ "oasis-cbor", "oasis-runtime-sdk", "oasis-runtime-sdk-evm", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5127,7 +6022,7 @@ dependencies = [ "oasis-cbor", "oasis-runtime-sdk", "once_cell", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5145,7 +6040,7 @@ dependencies = [ "oasis-cbor", "oasis-runtime-sdk", "oasis-runtime-sdk-contracts", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5155,7 +6050,7 @@ dependencies = [ "oasis-cbor", "oasis-runtime-sdk", "oasis-runtime-sdk-evm", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5166,7 +6061,7 @@ dependencies = [ "futures", "oasis-cbor", "oasis-runtime-sdk", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5175,7 +6070,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -5189,6 +6093,17 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -5199,11 +6114,20 @@ dependencies = [ "once_cell", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "time" -version = "0.3.39" +version = "0.3.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" +checksum = "9d9c75b47bdff86fa3334a3db91356b8d7d86a9b839dab7d0bdc5c3d3a077618" dependencies = [ "deranged", "itoa", @@ -5216,15 +6140,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" +checksum = "29aa485584182073ed57fd5004aa09c371f021325014694e432313345865fd04" dependencies = [ "num-conv", "time-core", @@ -5285,9 +6209,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.44.0" +version = "1.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" dependencies = [ "backtrace", "bytes", @@ -5346,9 +6270,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" dependencies = [ "bytes", "futures-core", @@ -5498,6 +6422,12 @@ dependencies = [ "serde", ] +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "uint" version = "0.9.5" @@ -5510,6 +6440,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "uncased" version = "0.9.10" @@ -5583,7 +6519,7 @@ dependencies = [ "serde", "serde_json", "smallvec", - "thiserror", + "thiserror 1.0.69", "utf-8", ] @@ -5618,9 +6554,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.15.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" [[package]] name = "valuable" @@ -5644,6 +6580,15 @@ dependencies = [ "nix", ] +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -5696,9 +6641,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -5788,7 +6733,7 @@ dependencies = [ "cty", "impl-trait-for-tuples", "rs-libc", - "thiserror", + "thiserror 1.0.69", "wasm3-sys", ] @@ -5827,7 +6772,7 @@ checksum = "0f51cad774fb3c9461ab9bccc9c62dfb7388397b5deda31bf40e8108ccd678b2" dependencies = [ "bitflags 2.9.0", "indexmap 2.8.0", - "semver", + "semver 1.0.26", ] [[package]] @@ -5957,9 +6902,9 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-sys" @@ -6120,9 +7065,9 @@ dependencies = [ [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags 2.9.0", ] @@ -6173,7 +7118,7 @@ dependencies = [ "nom", "oid-registry 0.6.1", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -6190,7 +7135,7 @@ dependencies = [ "nom", "oid-registry 0.7.1", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] diff --git a/Cargo.toml b/Cargo.toml index 02a92625bd2..35762d54a4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ # Runtime SDK Modules. "runtime-sdk/modules/contracts", "runtime-sdk/modules/evm", + "runtime-sdk/modules/evm-new", "runtime-sdk/modules/rofl-market", # Smart Contract SDK. diff --git a/runtime-sdk/modules/evm-new/Cargo.toml b/runtime-sdk/modules/evm-new/Cargo.toml new file mode 100644 index 00000000000..d65261c8093 --- /dev/null +++ b/runtime-sdk/modules/evm-new/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "oasis-runtime-sdk-evm-new" +description = "New EVM module for the Oasis Runtime SDK." +version = "0.6.0" +authors = ["Oasis Protocol Foundation "] +edition = "2021" +license = "Apache-2.0" + +[dependencies] +cbor = { version = "0.5.1", package = "oasis-cbor" } +oasis-runtime-sdk = { path = "../.." } + +# Third party. +anyhow = "1.0" +base64 = "0.22.1" +blake3 = { version = "~1.5.1", features = ["traits-preview"] } +thiserror = "1.0" +hex = "0.4.2" +k256 = "0.13.1" +sha2 = "0.10.8" +sha3 = { version = "0.10", default-features = false } +once_cell = "1.8.0" +x25519-dalek = "2.0.1" +hmac = "0.12.1" +rand_core = { version = "0.6.4", default-features = false } + +# Ethereum. +ethabi = { version = "18.0.0", default-features = false, features = ["std"] } +ethereum = "0.15" +revm = { git = "https://github.com/bluealloy/revm", tag = "v66", default-features = false, features = ["std", "serde", "optional_eip3607"] } +fixed-hash = "0.8.0" +primitive-types = { version = "0.12", default-features = false, features = ["rlp", "num-traits"] } +rlp = "0.5.2" +uint = "0.9.1" + +# Fuzzing. +honggfuzz = "0.5.56" +serde = { version = "1.0.203", features = ["derive"], optional = true } +serde_json = { version = "1.0.116", features = ["raw_value"], optional = true } + +[dev-dependencies] +criterion = "0.5.1" +oasis-runtime-sdk = { path = "../..", features = ["test"] } +rand = "0.8.5" +serde = { version = "1.0.203", features = ["derive"] } +serde_json = { version = "1.0.116", features = ["raw_value"] } +ethabi = { version = "18.0.0", default-features = false, features = ["std", "full-serde"] } + +[features] +default = [] +test = ["serde", "serde_json"] diff --git a/runtime-sdk/modules/evm-new/src/db.rs b/runtime-sdk/modules/evm-new/src/db.rs new file mode 100644 index 00000000000..105c0578fa7 --- /dev/null +++ b/runtime-sdk/modules/evm-new/src/db.rs @@ -0,0 +1,220 @@ +use revm::{ + database::DBErrorMarker, + primitives::{Address, B256, KECCAK_EMPTY, U256}, + state::{Account, AccountInfo, Bytecode}, + Database, DatabaseCommit, +}; +use std::{collections::HashMap, error::Error, fmt, vec::Vec}; + +use std::marker::PhantomData; + +use oasis_runtime_sdk::{ + context::Context, core::common::crypto::hash::Hash, modules::accounts::API as _, + state::CurrentState, types::token, Runtime, +}; + +use crate::{state, types, Config}; + +pub struct OasisDB<'ctx, C: Context, Cfg: Config> { + ctx: &'ctx C, + _cfg: PhantomData, + origin: Address, + origin_nonce_incremented: bool, +} + +impl<'ctx, C: Context, Cfg: Config> OasisDB<'ctx, C, Cfg> { + pub fn new(ctx: &'ctx C, origin: Address) -> Self { + Self { + ctx, + _cfg: PhantomData, + origin, + origin_nonce_incremented: false, + } + } +} + +#[derive(Debug)] +pub struct DBError(pub String); + +impl DBErrorMarker for DBError {} +impl Error for DBError {} + +impl fmt::Display for DBError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for DBError { + fn from(s: String) -> Self { + Self(s) + } +} + +// Implement read-only parts of the database. +impl<'ctx, C: Context, Cfg: Config> Database for OasisDB<'ctx, C, Cfg> { + type Error = DBError; + + /// Get basic account information. + fn basic(&mut self, address: Address) -> Result, Self::Error> { + // Derive SDK account address from the Ethereum address. + let sdk_address = Cfg::map_address(address); + + print!("*** {:#?}", address); + + // Fetch balance and nonce from SDK accounts. Note that these can never fail. + let balance = + ::Accounts::get_balance(sdk_address, Cfg::TOKEN_DENOMINATION) + .unwrap(); + let mut nonce = ::Accounts::get_nonce(sdk_address).unwrap(); + + // If this is the caller's address, the caller nonce has not yet been incremented + // based on the EVM semantics and this is not a simulation context, return the + // nonce decremented by one to cancel out the Oasis SDK nonce changes. + // https://github.com/oasisprotocol/oasis-sdk/commit/eda6e0d67c2b2664182a0d60408875af32562a7f + let is_simulation = CurrentState::with_env(|env| env.is_simulation()); + if address == self.origin && !self.origin_nonce_incremented && !is_simulation { + nonce = nonce.saturating_sub(1); + print!(" ! "); + } + + // Fetch code for this address from storage. + let code = CurrentState::with_store(|store| { + let codes = state::codes(store); + + if let Some(code) = codes.get::<_, Vec>(address) { + if !code.is_empty() { + Some(Bytecode::new_raw(code.into())) + } else { + None + } + } else { + None + } + }); + + // Calculate hash of code if it exists. + let code_hash = match code { + None => KECCAK_EMPTY, + Some(ref bc) => bc.hash_slow(), + }; + + println!(": {:#?} {:#?}", balance, nonce); + + Ok(Some(AccountInfo { + nonce, + balance: U256::from(balance), + code, + code_hash, + })) + } + + /// Get account code by its hash (unimplemented). + fn code_by_hash(&mut self, _code_hash: B256) -> Result { + println!("###### code_by_hash called ######"); + Err("getting code by hash is not supported".to_string().into()) + } + + /// Get storage value of address at index. + fn storage(&mut self, address: Address, index: U256) -> Result { + let address: types::H160 = address.into_array().into(); + let index: types::H256 = index.to_be_bytes().into(); + + let res: types::H256 = state::with_storage::(self.ctx, &address, |store| { + store.get(index).unwrap_or_default() + }); + Ok(U256::from_be_bytes(res.into())) + } + + /// Get block hash by block number. + fn block_hash(&mut self, number: u64) -> Result { + CurrentState::with_store(|store| { + let block_hashes = state::block_hashes(store); + + if let Some(hash) = block_hashes.get::<_, Hash>(&number.to_be_bytes()) { + Ok(B256::from_slice(hash.as_ref())) + } else { + Ok(B256::default()) + } + }) + } +} + +// Implement committing. +impl<'ctx, C: Context, Cfg: Config> DatabaseCommit for OasisDB<'ctx, C, Cfg> { + fn commit(&mut self, changes: HashMap) { + for (address, account) in changes { + if !account.is_touched() { + continue; + } + + // Derive SDK account address from the Ethereum address. + let sdk_address = Cfg::map_address(address); + + println!( + "### {:#?}: {:?} {:?}", + address, account.info.balance, account.info.nonce + ); + + // Update account's balance, nonce, and code (if any). + ::Accounts::set_balance( + sdk_address, + &token::BaseUnits::new(account.info.balance.to::(), Cfg::TOKEN_DENOMINATION), + ); + + // XXX + //::Accounts::set_nonce(sdk_address, account.info.nonce); + + // XXX: This is probably not the right place to put this... + let is_simulation = CurrentState::with_env(|env| env.is_simulation()); + if address == self.origin && !is_simulation { + self.origin_nonce_incremented = true; + } + + if account.info.code.is_some() { + let code = account.info.code.unwrap().bytecode().to_vec(); + CurrentState::with_store(|store| { + let mut codes = state::codes(store); + if !code.is_empty() { + codes.insert(address, code); + } else { + codes.remove(address); + } + }); + } else { + CurrentState::with_store(|store| { + let mut codes = state::codes(store); + codes.remove(address); + }); + } + + // Apply account's storage changes. + let storage_changes = account + .storage + .into_iter() + .map(|(key, value)| (key, value.present_value())); + for (key, value) in storage_changes { + let index: types::H256 = key.to_be_bytes().into(); + let val: types::H256 = value.to_be_bytes().into(); + + if value == U256::default() { + state::with_storage::( + self.ctx, + &address.into_array().into(), + |store| { + store.remove(index); + }, + ); + } else { + state::with_storage::( + self.ctx, + &address.into_array().into(), + |store| { + store.insert(index, val); + }, + ); + } + } + } + } +} diff --git a/runtime-sdk/modules/evm-new/src/derive_caller.rs b/runtime-sdk/modules/evm-new/src/derive_caller.rs new file mode 100644 index 00000000000..014c9d0c3db --- /dev/null +++ b/runtime-sdk/modules/evm-new/src/derive_caller.rs @@ -0,0 +1,21 @@ +use oasis_runtime_sdk::types::{ + address::SignatureAddressSpec, + transaction::{AddressSpec, AuthInfo, CallerAddress}, +}; + +use crate::{types::H160, Error}; + +pub fn from_sigspec(spec: &SignatureAddressSpec) -> Result { + match spec { + SignatureAddressSpec::Secp256k1Eth(pk) => Ok(H160::from_slice(&pk.to_eth_address())), + _ => Err(Error::InvalidSignerType), + } +} + +pub fn from_tx_auth_info(ai: &AuthInfo) -> Result { + match &ai.signer_info[0].address_spec { + AddressSpec::Signature(spec) => from_sigspec(spec), + AddressSpec::Internal(CallerAddress::EthAddress(address)) => Ok(address.into()), + _ => Err(Error::InvalidSignerType), + } +} diff --git a/runtime-sdk/modules/evm-new/src/lib.rs b/runtime-sdk/modules/evm-new/src/lib.rs new file mode 100644 index 00000000000..07e24fabf92 --- /dev/null +++ b/runtime-sdk/modules/evm-new/src/lib.rs @@ -0,0 +1,768 @@ +//! EVM module. +#![feature(array_chunks)] +#![feature(test)] + +pub mod db; +pub mod derive_caller; +pub mod precompile; +pub mod raw_tx; +mod signed_call; +pub mod state; +pub mod types; + +use base64::prelude::*; +use revm::{ + context::TxEnv, + context_interface::result::{ExecutionResult, Output}, + precompile::PrecompileWithAddress, + primitives::{hardfork::SpecId, Bytes, TxKind}, + Context as EvmContext, Database, ExecuteCommitEvm, ExecuteEvm, MainBuilder, MainContext, +}; + +use oasis_runtime_sdk::{ + callformat, + context::Context, + handler, migration, + module::{self, Module as _}, + modules::{ + accounts::API as _, + core::{Error as CoreError, API as _}, + }, + runtime::Runtime, + sdk_derive, + state::{CurrentState, Mode, Options, TransactionResult, TransactionWithMeta}, + types::{ + address::{self, Address}, + token, transaction, + transaction::Transaction, + }, +}; +use thiserror::Error; +use types::{H160, H256, U256}; + +/// Unique module name. +/// Note: We use the same name here as the old EVM module, so that we can +/// access the same storage. +const MODULE_NAME: &str = "evm"; + +#[cfg(any(test, feature = "test"))] +pub mod mock; +#[cfg(test)] +mod test; + +/// Module configuration. +pub trait Config: 'static { + /// The chain ID to supply when a contract requests it. Ethereum-format transactions must use + /// this chain ID. + const CHAIN_ID: u64; + + /// Token denomination used as the native EVM token. + const TOKEN_DENOMINATION: token::Denomination; + + /// Whether to use confidential storage by default, and transaction data encryption. + const CONFIDENTIAL: bool = false; + + /// Whether to refund unused transaction fee. + const REFUND_UNUSED_FEE: bool = true; + + /// Maximum result size in bytes. + const MAX_RESULT_SIZE: usize = 1024; + + /// Maps an Ethereum address into an SDK account address. + fn map_address(address: revm::primitives::Address) -> Address { + Address::new( + address::ADDRESS_V0_SECP256K1ETH_CONTEXT, + address::ADDRESS_V0_VERSION, + address.as_ref(), + ) + } + + /// Provides additional precompiles that should be available to the EVM. + /// + /// If any of the precompile addresses returned is the same as for one of + /// the builtin precompiles, then the returned implementation will + /// overwrite the builtin implementation. + fn additional_precompiles() -> Option> { + None + } +} + +pub struct Module { + _cfg: std::marker::PhantomData, +} + +/// Errors emitted by the EVM module. +#[derive(Error, Debug, oasis_runtime_sdk::Error)] +pub enum Error { + #[error("invalid argument")] + #[sdk_error(code = 1)] + InvalidArgument, + + #[error("execution failed: {0}")] + #[sdk_error(code = 2)] + ExecutionFailed(String), + + #[error("invalid signer type")] + #[sdk_error(code = 3)] + InvalidSignerType, + + #[error("fee overflow")] + #[sdk_error(code = 4)] + FeeOverflow, + + #[error("gas limit too low: {0} required")] + #[sdk_error(code = 5)] + GasLimitTooLow(u64), + + #[error("insufficient balance")] + #[sdk_error(code = 6)] + InsufficientBalance, + + #[error("forbidden by policy")] + #[sdk_error(code = 7)] + Forbidden, + + #[error("reverted: {0}")] + #[sdk_error(code = 8)] + Reverted(String), + + #[error("forbidden by policy: this node only allows simulating calls that use up to {0} gas")] + #[sdk_error(code = 9)] + SimulationTooExpensive(u64), + + #[error("invalid signed simulate call query: {0}")] + #[sdk_error(code = 10)] + InvalidSignedSimulateCall(&'static str), + + #[error("core: {0}")] + #[sdk_error(transparent)] + Core(#[from] CoreError), +} + +/// Gas costs. +#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] +pub struct GasCosts {} + +/// Parameters for the EVM module. +#[derive(Clone, Default, Debug, cbor::Encode, cbor::Decode)] +pub struct Parameters { + /// Gas costs. + pub gas_costs: GasCosts, +} + +impl module::Parameters for Parameters { + type Error = (); + + fn validate_basic(&self) -> Result<(), Self::Error> { + Ok(()) + } +} + +/// Genesis state for the EVM module. +#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] +pub struct Genesis { + pub parameters: Parameters, +} + +/// Local configuration that can be provided by the node operator. +#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] +pub struct LocalConfig { + /// Maximum gas limit that can be passed to the `evm.SimulateCall` query. Queries + /// with a higher gas limit will be rejected. A special value of `0` indicates + /// no limit. Default: 0. + #[cbor(optional)] + pub query_simulate_call_max_gas: u64, +} + +/// Events emitted by the EVM module. +#[derive(Debug, cbor::Encode, oasis_runtime_sdk::Event)] +#[cbor(untagged)] +pub enum Event { + #[sdk_event(code = 1)] + Log { + address: H160, + topics: Vec, + data: Vec, + }, +} + +/// Interface that can be called from other modules. +pub trait API { + /// Perform an Ethereum CREATE transaction. + /// Returns 160-bit address of created contract. + fn create(ctx: &C, value: U256, init_code: Vec) -> Result, Error>; + + /// Perform an Ethereum CALL transaction. + fn call( + ctx: &C, + address: H160, + value: U256, + data: Vec, + ) -> Result, Error>; + + /// Peek into EVM storage. + /// Returns 256-bit value stored at given contract address and index (slot) + /// in the storage. + fn get_storage(ctx: &C, address: H160, index: H256) -> Result, Error>; + + /// Peek into EVM code storage. + /// Returns EVM bytecode of contract at given address. + fn get_code(ctx: &C, address: H160) -> Result, Error>; + + /// Get EVM account balance. + fn get_balance(ctx: &C, address: H160) -> Result; + + /// Simulate an Ethereum CALL. + /// + /// If the EVM is confidential, it may accept _signed queries_, which are + /// formatted as either a [`sdk::types::transaction::Call`] or + /// [`types::SignedCallDataPack`] encoded and packed into the `data` field + /// of [`types::SimulateCallQuery`]. + fn simulate_call(ctx: &C, call: types::SimulateCallQuery) + -> Result, Error>; +} + +impl API for Module { + fn create(ctx: &C, value: U256, init_code: Vec) -> Result, Error> { + let caller = Self::derive_caller()?; + + if !ctx.should_execute_contracts() { + // Only fast checks are allowed. + return Ok(vec![]); + } + + let (tx_call_format, tx_index, is_simulation) = CurrentState::with_env(|env| { + (env.tx_call_format(), env.tx_index(), env.is_simulation()) + }); + + // Create output (the contract address) does not need to be encrypted because it's + // trivially computable by anyone who can observe the create tx and receipt status. + // Therefore, we don't need the `tx_metadata` or to encode the result. + let (init_code, _tx_metadata) = + Self::decode_call_data(ctx, init_code, tx_call_format, tx_index, true)? + .expect("processing always proceeds"); + + // If in simulation, this must be EstimateGas query. + // Use estimate mode if not doing binary search for exact gas costs. + let estimate_gas = + is_simulation && ::Core::estimate_gas_search_max_iters(ctx) == 0; + + Self::evm_create(ctx, caller, value, init_code, estimate_gas) + } + + fn call( + ctx: &C, + address: H160, + value: U256, + data: Vec, + ) -> Result, Error> { + let caller = Self::derive_caller()?; + + if !ctx.should_execute_contracts() { + // Only fast checks are allowed. + return Ok(vec![]); + } + + let (tx_call_format, tx_index, is_simulation) = CurrentState::with_env(|env| { + (env.tx_call_format(), env.tx_index(), env.is_simulation()) + }); + + let (data, tx_metadata) = + Self::decode_call_data(ctx, data, tx_call_format, tx_index, true)? + .expect("processing always proceeds"); + + // If in simulation, this must be EstimateGas query. + // Use estimate mode if not doing binary search for exact gas costs. + let estimate_gas = + is_simulation && ::Core::estimate_gas_search_max_iters(ctx) == 0; + + let evm_result = Self::evm_call(ctx, caller, address, value, data, estimate_gas); + Self::encode_evm_result(ctx, evm_result, tx_metadata) + } + + fn get_storage(_ctx: &C, address: H160, index: H256) -> Result, Error> { + state::with_public_storage(&address, |store| { + let result: H256 = store.get(index).unwrap_or_default(); + Ok(result.as_bytes().to_vec()) + }) + } + + fn get_code(_ctx: &C, address: H160) -> Result, Error> { + CurrentState::with_store(|store| { + let codes = state::codes(store); + Ok(codes.get(address).unwrap_or_default()) + }) + } + + fn get_balance(_ctx: &C, address: H160) -> Result { + let address = Cfg::map_address(address.0.into()); + Ok( + ::Accounts::get_balance(address, Cfg::TOKEN_DENOMINATION) + .unwrap_or_default(), + ) + } + + fn simulate_call( + ctx: &C, + call: types::SimulateCallQuery, + ) -> Result, Error> { + let ( + types::SimulateCallQuery { + gas_price, + gas_limit, + caller, + address, + value, + data, + }, + tx_metadata, + ) = Self::decode_simulate_call_query(ctx, call)?; + + let (method, body, exec): (_, _, Box Result<_, _>>) = match address { + Some(address) => { + // Address is set, this is a simulated `evm.Call`. + ( + "evm.Call", + cbor::to_value(types::Call { + address, + value, + data: data.clone(), + }), + Box::new(move || Self::evm_call(ctx, caller, address, value, data, false)), + ) + } + None => { + // Address is not set, this is a simulated `evm.Create`. + ( + "evm.Create", + cbor::to_value(types::Create { + value, + init_code: data.clone(), + }), + Box::new(|| Self::evm_create(ctx, caller, value, data, false)), + ) + } + }; + let tx = transaction::Transaction { + version: 1, + call: transaction::Call { + format: transaction::CallFormat::Plain, + method: method.to_owned(), + body, + ..Default::default() + }, + auth_info: transaction::AuthInfo { + signer_info: vec![transaction::SignerInfo { + address_spec: transaction::AddressSpec::Internal( + transaction::CallerAddress::EthAddress(caller.into()), + ), + nonce: 0, + }], + fee: transaction::Fee { + amount: token::BaseUnits::new( + gas_price + .checked_mul(U256::from(gas_limit)) + .ok_or(Error::FeeOverflow)? + .as_u128(), + Cfg::TOKEN_DENOMINATION, + ), + gas: gas_limit, + consensus_messages: 0, + proxy: None, + }, + ..Default::default() + }, + }; + + let evm_result = CurrentState::with_transaction_opts( + Options::new() + .with_tx(TransactionWithMeta::internal(tx)) + .with_mode(Mode::Simulate), + || TransactionResult::Rollback(exec()), + ); + Self::encode_evm_result(ctx, evm_result, tx_metadata) + } +} + +impl Module { + fn evm_execute( + ctx: &C, + estimate_gas: bool, + caller: H160, + f: F, + ) -> Result, Error> { + let is_query = CurrentState::with_env(|env| !env.is_execute()); + + // Prepare the environment. + let mut db = db::OasisDB::<'_, C, Cfg>::new(ctx, caller.0.into()); + let nonce = db + .basic(caller.0.into()) + .unwrap() + .map_or(0, |account| account.nonce); + let mut evm = EvmContext::mainnet() + .with_db(db) + .modify_cfg_chained(|cfg| { + cfg.spec = SpecId::SHANGHAI; + cfg.disable_eip3607 = true; + }) + // TODO: Port precompiles to the new API. + /*.append_handler_register(|handler| { + // Load standard precompiles. + // + // For Shanghai spec these include the following: ecrecover, + // sha256, ripemd160, identity, bn128::{add,mul,pair}, blake2, + // modexp. + let precompiles = handler.pre_execution.load_precompiles(); + handler.pre_execution.load_precompiles = Arc::new(move || { + // Start with standard precompiles. + let mut precompiles = precompiles.clone(); + // Extend with Oasis-specific precompiles. + precompiles.extend(precompile::new()); + // Extend with module-specific precompiles (if any). + if let Some(additional_precompiles) = Cfg::additional_precompiles() { + precompiles.extend(additional_precompiles); + } + precompiles + }); + })*/ + .modify_tx_chained(|tx| { + tx.nonce = nonce; + }) + .modify_tx_chained(f) + .build_mainnet(); + + // Run the transaction. + let tx_result = if estimate_gas { + match evm.transact(evm.tx.clone()) { + Ok(result) => result.result, + Err(err) => return Err(crate::Error::ExecutionFailed(format!("{:?}", err))), + } + } else { + match evm.transact_commit(evm.tx.clone()) { + Ok(result) => result, + Err(err) => return Err(crate::Error::ExecutionFailed(format!("{:?}", err))), + } + }; + + match tx_result { + ExecutionResult::Success { + reason: _, + gas_used, + gas_refunded: _, + logs, + output, + } => { + let data = match output { + Output::Call(out) => out, + Output::Create(_, Some(addr)) => addr.into_array().into(), + Output::Create(_, None) => Bytes::new(), + }; + + // Clamp data based on maximum allowed result size. + let data = if !is_query && data.len() > Cfg::MAX_RESULT_SIZE { + data[..Cfg::MAX_RESULT_SIZE].to_vec() + } else { + data.to_vec() + }; + + // Use gas and refund unused gas. + // XXX: check + ::Core::use_tx_gas(gas_used)?; + ::Accounts::set_refund_unused_tx_fee(Cfg::REFUND_UNUSED_FEE); + + // Emit logs as events. + CurrentState::with(|state| { + for log in logs { + state.emit_event(crate::Event::Log { + address: H160::from_slice(&log.address.into_array()), + topics: log + .topics() + .iter() + .map(|&topic| H256::from_slice(topic.as_slice())) + .collect(), + data: log.data.data.to_vec(), + }); + } + }); + + Ok(data) + } + ExecutionResult::Revert { gas_used, output } => { + // Clamp data based on maximum allowed result size. + // XXX: to_vec maybe not needed (check encoding) + let data = if !is_query && output.len() > Cfg::MAX_RESULT_SIZE { + output[..Cfg::MAX_RESULT_SIZE].to_vec() + } else { + output.to_vec() + }; + + // Use gas and refund unused gas. + // XXX: check + ::Core::use_tx_gas(gas_used)?; + ::Accounts::set_refund_unused_tx_fee(Cfg::REFUND_UNUSED_FEE); + + Err(Error::Reverted(BASE64_STANDARD.encode(data))) + } + ExecutionResult::Halt { reason, gas_used } => { + // Use gas and refund unused gas. + // XXX: check + ::Core::use_tx_gas(gas_used)?; + ::Accounts::set_refund_unused_tx_fee(Cfg::REFUND_UNUSED_FEE); + + Err(crate::Error::ExecutionFailed(format!("{:?}", reason))) + } + } + } + + fn evm_create( + ctx: &C, + caller: H160, + value: U256, + init_code: Vec, + estimate_gas: bool, + ) -> Result, Error> { + Self::evm_execute(ctx, estimate_gas, caller, |tx| { + tx.gas_limit = ::Core::remaining_tx_gas(); + tx.gas_price = CurrentState::with_env(|env| env.tx_auth_info().fee.gas_price()); + tx.caller = caller.0.into(); + tx.kind = TxKind::Create; + tx.value = revm::primitives::U256::from_be_bytes(value.into()); + tx.data = init_code.into(); + }) + } + + fn evm_call( + ctx: &C, + caller: H160, + address: H160, + value: U256, + data: Vec, + estimate_gas: bool, + ) -> Result, Error> { + Self::evm_execute(ctx, estimate_gas, caller, |tx| { + tx.gas_limit = ::Core::remaining_tx_gas(); + tx.gas_price = CurrentState::with_env(|env| env.tx_auth_info().fee.gas_price()); + tx.caller = caller.0.into(); + tx.kind = TxKind::Call(address.0.into()); + tx.value = revm::primitives::U256::from_be_bytes(value.into()); + tx.data = data.into(); + }) + } + + fn derive_caller() -> Result { + CurrentState::with_env(|env| derive_caller::from_tx_auth_info(env.tx_auth_info())) + } + + /// Returns the decrypted call data or `None` if this transaction is simulated in + /// a context that may not include a key manager (i.e. SimulateCall but not EstimateGas). + fn decode_call_data( + ctx: &C, + data: Vec, + format: transaction::CallFormat, // The tx call format. + tx_index: usize, + assume_km_reachable: bool, + ) -> Result, callformat::Metadata)>, Error> { + if !Cfg::CONFIDENTIAL || format != transaction::CallFormat::Plain { + // Either the runtime is non-confidential and all txs are plaintext, or the tx + // is sent using a confidential call format and the tx has already been decrypted. + return Ok(Some((data, callformat::Metadata::Empty))); + } + match cbor::from_slice(&data) { + Ok(call) => Self::decode_call(ctx, call, tx_index, assume_km_reachable), + Err(_) => Ok(Some((data, callformat::Metadata::Empty))), // It's not encrypted. + } + } + + /// Returns the decrypted call data or `None` if this transaction is simulated in + /// a context that may not include a key manager (i.e. SimulateCall but not EstimateGas). + fn decode_call( + ctx: &C, + call: transaction::Call, + tx_index: usize, + assume_km_reachable: bool, + ) -> Result, callformat::Metadata)>, Error> { + match callformat::decode_call_ex(ctx, call, tx_index, assume_km_reachable)? { + Some(( + transaction::Call { + body: cbor::Value::ByteString(data), + .. + }, + metadata, + )) => Ok(Some((data, metadata))), + Some((_, _)) => { + Err(CoreError::InvalidCallFormat(anyhow::anyhow!("invalid inner data")).into()) + } + None => Ok(None), + } + } + + fn decode_simulate_call_query( + ctx: &C, + call: types::SimulateCallQuery, + ) -> Result<(types::SimulateCallQuery, callformat::Metadata), Error> { + if !Cfg::CONFIDENTIAL { + return Ok((call, callformat::Metadata::Empty)); + } + + if let Ok(types::SignedCallDataPack { + data, + leash, + signature, + }) = cbor::from_slice(&call.data) + { + let (data, tx_metadata) = + Self::decode_call(ctx, data, 0, true)?.expect("processing always proceeds"); + return Ok(( + signed_call::verify::<_, Cfg>( + ctx, + types::SimulateCallQuery { data, ..call }, + leash, + signature, + )?, + tx_metadata, + )); + } + + // The call is not signed, but it must be encoded as an oasis-sdk call. + let tx_call_format = transaction::CallFormat::Plain; // Queries cannot be encrypted. + let (data, tx_metadata) = Self::decode_call_data(ctx, call.data, tx_call_format, 0, true)? + .expect("processing always proceeds"); + Ok(( + types::SimulateCallQuery { + caller: Default::default(), // The sender cannot be spoofed. + data, + ..call + }, + tx_metadata, + )) + } + + fn encode_evm_result( + ctx: &C, + evm_result: Result, Error>, + tx_metadata: callformat::Metadata, // Potentially parsed from an inner enveloped tx. + ) -> Result, Error> { + if matches!(tx_metadata, callformat::Metadata::Empty) { + // Either the runtime is non-confidential and all responses are plaintext, + // or the tx was sent using a confidential call format and dispatcher will + // encrypt the call in the normal way. + return evm_result; + } + // Always propagate errors in plaintext. + let call_result = module::CallResult::Ok(evm_result?.into()); + Ok(cbor::to_vec(callformat::encode_result_ex( + ctx, + call_result, + tx_metadata, + true, + ))) + } +} + +#[sdk_derive(Module)] +impl Module { + const NAME: &'static str = MODULE_NAME; + const VERSION: u32 = 3; + type Error = Error; + type Event = Event; + type Parameters = Parameters; + type Genesis = Genesis; + + #[migration(init)] + fn init(genesis: Genesis) { + // Set genesis parameters. + Self::set_params(genesis.parameters); + } + + #[migration(from = 2)] + fn migrate_v2_to_v3() { + // TODO? + } + + #[handler(call = "evm.Create")] + fn tx_create(ctx: &C, body: types::Create) -> Result, Error> { + Self::create(ctx, body.value, body.init_code) + } + + #[handler(call = "evm.Call")] + fn tx_call(ctx: &C, body: types::Call) -> Result, Error> { + Self::call(ctx, body.address, body.value, body.data) + } + + #[handler(query = "evm.Storage")] + fn query_storage(ctx: &C, body: types::StorageQuery) -> Result, Error> { + Self::get_storage(ctx, body.address, body.index) + } + + #[handler(query = "evm.Code")] + fn query_code(ctx: &C, body: types::CodeQuery) -> Result, Error> { + Self::get_code(ctx, body.address) + } + + #[handler(query = "evm.Balance")] + fn query_balance(ctx: &C, body: types::BalanceQuery) -> Result { + Self::get_balance(ctx, body.address) + } + + #[handler(query = "evm.SimulateCall", expensive, allow_private_km)] + fn query_simulate_call( + ctx: &C, + body: types::SimulateCallQuery, + ) -> Result, Error> { + let cfg: LocalConfig = ctx.local_config(MODULE_NAME).unwrap_or_default(); + if cfg.query_simulate_call_max_gas > 0 && body.gas_limit > cfg.query_simulate_call_max_gas { + return Err(Error::SimulationTooExpensive( + cfg.query_simulate_call_max_gas, + )); + } + Self::simulate_call(ctx, body) + } +} + +impl module::TransactionHandler for Module { + fn decode_tx( + _ctx: &C, + scheme: &str, + body: &[u8], + ) -> Result, CoreError> { + match scheme { + "evm.ethereum.v0" => { + let min_gas_price = + ::Core::min_gas_price(&Cfg::TOKEN_DENOMINATION) + .unwrap_or_default(); + + Ok(Some( + raw_tx::decode( + body, + Some(Cfg::CHAIN_ID), + min_gas_price, + &Cfg::TOKEN_DENOMINATION, + ) + .map_err(CoreError::MalformedTransaction)?, + )) + } + _ => Ok(None), + } + } +} + +impl module::BlockHandler for Module { + fn end_block(ctx: &C) { + CurrentState::with_store(|store| { + // Update the list of historic block hashes. + let block_number = ctx.runtime_header().round; + let block_hash = ctx.runtime_header().encoded_hash(); + let mut block_hashes = state::block_hashes(store); + + let current_number = block_number; + block_hashes.insert(block_number.to_be_bytes(), block_hash); + + if current_number > state::BLOCK_HASH_WINDOW_SIZE { + let start_number = current_number - state::BLOCK_HASH_WINDOW_SIZE; + block_hashes.remove(start_number.to_be_bytes()); + } + }); + } +} + +impl module::InvariantHandler for Module {} diff --git a/runtime-sdk/modules/evm-new/src/mock.rs b/runtime-sdk/modules/evm-new/src/mock.rs new file mode 100644 index 00000000000..a882490475f --- /dev/null +++ b/runtime-sdk/modules/evm-new/src/mock.rs @@ -0,0 +1,285 @@ +//! Mock functionality for use during testing. +use base64::prelude::*; +use uint::hex::FromHex; + +use oasis_runtime_sdk::{ + callformat, + core::common::crypto::mrae::deoxysii, + dispatcher, + error::RuntimeError, + module, + testing::mock::{CallOptions, Signer}, + types::{address::SignatureAddressSpec, transaction}, + Context, +}; + +use crate::{ + derive_caller, + types::{self, H160}, +}; + +/// A mock EVM signer for use during tests. +pub struct EvmSigner(Signer); + +impl EvmSigner { + /// Create a new mock signer using the given nonce and signature spec. + pub fn new(nonce: u64, sigspec: SignatureAddressSpec) -> Self { + Self(Signer::new(nonce, sigspec)) + } + + /// Dispatch a call to the given EVM contract method. + pub fn call_evm( + &mut self, + ctx: &C, + address: H160, + name: &str, + param_types: &[ethabi::ParamType], + params: &[ethabi::Token], + ) -> dispatcher::DispatchResult + where + C: Context, + { + self.call_evm_opts(ctx, address, name, param_types, params, Default::default()) + } + + /// Dispatch a call to the given EVM contract method with the given options. + pub fn call_evm_opts( + &mut self, + ctx: &C, + address: H160, + name: &str, + param_types: &[ethabi::ParamType], + params: &[ethabi::Token], + opts: CallOptions, + ) -> dispatcher::DispatchResult + where + C: Context, + { + let data = [ + ethabi::short_signature(name, param_types).to_vec(), + ethabi::encode(params), + ] + .concat(); + + self.call_opts( + ctx, + "evm.Call", + types::Call { + address, + value: 0.into(), + data, + }, + opts, + ) + } + + /// Ethereum address for this signer. + pub fn address(&self) -> H160 { + derive_caller::from_sigspec(self.sigspec()).expect("caller should be evm-compatible") + } + + /// Dispatch a query to the given EVM contract method. + pub fn query_evm_call( + &self, + ctx: &C, + address: H160, + name: &str, + param_types: &[ethabi::ParamType], + params: &[ethabi::Token], + ) -> Result, RuntimeError> + where + C: Context, + { + self.query_evm_call_opts(ctx, address, name, param_types, params, Default::default()) + } + + /// Dispatch a query to the given EVM contract method. + pub fn query_evm_call_opts( + &self, + ctx: &C, + address: H160, + name: &str, + param_types: &[ethabi::ParamType], + params: &[ethabi::Token], + opts: QueryOptions, + ) -> Result, RuntimeError> + where + C: Context, + { + let data = [ + ethabi::short_signature(name, param_types).to_vec(), + ethabi::encode(params), + ] + .concat(); + + self.query_evm_opts(ctx, Some(address), data, opts) + } + + /// Dispatch a query to simulate EVM contract creation. + pub fn query_evm_create(&self, ctx: &C, init_code: Vec) -> Result, RuntimeError> + where + C: Context, + { + self.query_evm_opts(ctx, None, init_code, Default::default()) + } + + /// Dispatch a query to simulate EVM contract creation. + pub fn query_evm_create_opts( + &self, + ctx: &C, + init_code: Vec, + opts: QueryOptions, + ) -> Result, RuntimeError> + where + C: Context, + { + self.query_evm_opts(ctx, None, init_code, opts) + } + + /// Dispatch a query to the EVM. + pub fn query_evm_opts( + &self, + ctx: &C, + address: Option, + mut data: Vec, + opts: QueryOptions, + ) -> Result, RuntimeError> + where + C: Context, + { + // Handle optional encryption. + let client_keypair = deoxysii::generate_key_pair(); + if opts.encrypt { + data = cbor::to_vec( + callformat::encode_call( + ctx, + transaction::Call { + format: transaction::CallFormat::EncryptedX25519DeoxysII, + method: "".into(), + body: cbor::Value::from(data), + ..Default::default() + }, + &client_keypair, + ) + .unwrap(), + ); + } + + let mut result: Vec = self.query( + ctx, + "evm.SimulateCall", + types::SimulateCallQuery { + gas_price: 0.into(), + gas_limit: opts.gas_limit, + caller: opts.caller.unwrap_or_else(|| self.address()), + address, + value: 0.into(), + data, + }, + )?; + + // Handle optional decryption. + if opts.encrypt { + let call_result: transaction::CallResult = + cbor::from_slice(&result).expect("result from EVM should be properly encoded"); + let call_result = callformat::decode_result( + ctx, + transaction::CallFormat::EncryptedX25519DeoxysII, + call_result, + &client_keypair, + ) + .expect("callformat decoding should succeed"); + + result = match call_result { + module::CallResult::Ok(v) => { + cbor::from_value(v).expect("result from EVM should be correct") + } + module::CallResult::Failed { + module, + code, + message, + } => return Err(RuntimeError::new(&module, code, &message)), + module::CallResult::Aborted(e) => panic!("aborted with error: {e}"), + }; + } + + Ok(result) + } +} + +impl std::ops::Deref for EvmSigner { + type Target = Signer; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for EvmSigner { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Options for making queries. +pub struct QueryOptions { + /// Whether the call should be encrypted. + pub encrypt: bool, + /// Gas limit. + pub gas_limit: u64, + /// Use specified caller instead of signer. + pub caller: Option, +} + +impl Default for QueryOptions { + fn default() -> Self { + Self { + encrypt: false, + gas_limit: 10_000_000, + caller: None, + } + } +} + +/// Load contract bytecode from a hex-encoded string. +pub fn load_contract_bytecode(raw: &str) -> Vec { + Vec::from_hex(raw.split_whitespace().collect::()) + .expect("compiled contract should be a valid hex string") +} + +/// Decode a basic revert reason. +pub fn decode_reverted(msg: &str) -> Option { + decode_reverted_abi( + msg, + ethabi::AbiError { + name: "Error".to_string(), + inputs: vec![ethabi::Param { + name: "message".to_string(), + kind: ethabi::ParamType::String, + internal_type: None, + }], + }, + )? + .pop() + .unwrap() + .into_string() +} + +/// Decode a revert reason accoording to the given API. +pub fn decode_reverted_abi(msg: &str, abi: ethabi::AbiError) -> Option> { + let raw = decode_reverted_raw(msg)?; + + // Strip (and validate) error signature. + let signature = abi.signature(); + let raw = raw.strip_prefix(&signature.as_bytes()[..4])?; + + Some(abi.decode(raw).unwrap()) +} + +/// Decode a base64-encoded revert reason. +pub fn decode_reverted_raw(msg: &str) -> Option> { + // Trim the optional reverted prefix. + let msg = msg.trim_start_matches("reverted: "); + + BASE64_STANDARD.decode(msg).ok() +} diff --git a/runtime-sdk/modules/evm-new/src/precompile/confidential.rs b/runtime-sdk/modules/evm-new/src/precompile/confidential.rs new file mode 100644 index 00000000000..cf1ab77e10f --- /dev/null +++ b/runtime-sdk/modules/evm-new/src/precompile/confidential.rs @@ -0,0 +1,323 @@ +//! Implements the confidential precompiles. +/*use std::{collections::HashMap, convert::TryInto}; + +use ethabi::{ParamType, Token}; +use hmac::{Hmac, Mac}; +use once_cell::sync::Lazy; +use revm::{ + precompile::{ + calc_linear_cost_u32, PrecompileError, PrecompileErrors, PrecompileOutput, PrecompileResult, + }, + primitives::Bytes, +}; + +use oasis_runtime_sdk::{ + core::common::crypto::mrae::deoxysii::{DeoxysII, KEY_SIZE, NONCE_SIZE}, + crypto::signature::{self, SignatureType}, +}; + +/// Length of an EVM word, in bytes. +pub const WORD: usize = 32; + +/// The base cost for x25519 key derivation. +const X25519_KEY_DERIVATION_BASE_COST: u64 = 1_100; + +/// The cost for converting a Curve25519 secret key to public key. +/// It's one scalar multiplication, so it shouldn't be too expensive. +const CURVE25519_COMPUTE_PUBLIC_COST: u64 = 1_000; + +/// The base setup cost for encryption and decryption. +const DEOXYSII_BASE_COST: u64 = 100; +/// The cost for encryption and decryption per word of input. +const DEOXYSII_WORD_COST: u64 = 10; + +/// The cost of a key pair generation operation, per method. +static KEYPAIR_GENERATE_BASE_COST: Lazy> = Lazy::new(|| { + HashMap::from([ + (SignatureType::Ed25519_Oasis, 1_000), + (SignatureType::Ed25519_Pure, 1_000), + (SignatureType::Ed25519_PrehashedSha512, 1_000), + (SignatureType::Secp256k1_Oasis, 1_500), + (SignatureType::Secp256k1_PrehashedKeccak256, 1_500), + (SignatureType::Secp256k1_PrehashedSha256, 1_500), + (SignatureType::Secp256r1_PrehashedSha256, 4_000), + (SignatureType::Secp384r1_PrehashedSha384, 18_000), + (SignatureType::Sr25519_Pure, 1_000), + ]) +}); + +/// The costs of a message signing operation. +static SIGN_MESSAGE_COST: Lazy> = Lazy::new(|| { + HashMap::from([ + (SignatureType::Ed25519_Oasis, (1_500, 8)), + (SignatureType::Ed25519_Pure, (1_500, 8)), + (SignatureType::Ed25519_PrehashedSha512, (1_500, 0)), + (SignatureType::Secp256k1_Oasis, (3_000, 8)), + (SignatureType::Secp256k1_PrehashedKeccak256, (3_000, 0)), + (SignatureType::Secp256k1_PrehashedSha256, (3_000, 0)), + (SignatureType::Secp256r1_PrehashedSha256, (9_000, 0)), + (SignatureType::Secp384r1_PrehashedSha384, (43_200, 0)), + (SignatureType::Sr25519_Pure, (1_500, 8)), + ]) +}); + +/// The costs of a signature verification operation. +static VERIFY_MESSAGE_COST: Lazy> = Lazy::new(|| { + HashMap::from([ + (SignatureType::Ed25519_Oasis, (2_000, 8)), + (SignatureType::Ed25519_Pure, (2_000, 8)), + (SignatureType::Ed25519_PrehashedSha512, (2_000, 0)), + (SignatureType::Secp256k1_Oasis, (3_000, 8)), + (SignatureType::Secp256k1_PrehashedKeccak256, (3_000, 0)), + (SignatureType::Secp256k1_PrehashedSha256, (3_000, 0)), + (SignatureType::Secp256r1_PrehashedSha256, (7_900, 0)), + (SignatureType::Secp384r1_PrehashedSha384, (37_920, 0)), + (SignatureType::Sr25519_Pure, (2_000, 8)), + ]) +}); + +// TODO: call_random_bytes + +pub(super) fn call_curve25519_compute_public(input: &Bytes, gas_limit: u64) -> PrecompileResult { + let cost = CURVE25519_COMPUTE_PUBLIC_COST; + if cost > gas_limit { + return Err(PrecompileError::OutOfGas.into()); + } + + // Input encoding: bytes32 private. + if input.len() != 32 { + return Err(PrecompileError::Other("input length must be 32 bytes".into()).into()); + } + + let input = input.as_ref(); + let private = <&[u8; WORD]>::try_from(input).unwrap(); + let secret = x25519_dalek::StaticSecret::from(*private); + let output = x25519_dalek::PublicKey::from(&secret).as_bytes().to_vec(); + Ok(PrecompileOutput::new(cost, output.into())) +} + +pub(super) fn call_x25519_derive(input: &Bytes, gas_limit: u64) -> PrecompileResult { + let cost = calc_linear_cost_u32(input.len(), X25519_KEY_DERIVATION_BASE_COST, 0); + if cost > gas_limit { + return Err(PrecompileError::OutOfGas.into()); + } + + // Input encoding: bytes32 public || bytes32 private. + let mut public = [0u8; WORD]; + let mut private = [0u8; WORD]; + if input.len() != 2 * WORD { + return Err(PrecompileError::Other("input length must be 64 bytes".into()).into()); + } + public.copy_from_slice(&input[0..WORD]); + private.copy_from_slice(&input[WORD..]); + + let public = x25519_dalek::PublicKey::from(public); + let private = x25519_dalek::StaticSecret::from(private); + + let mut kdf = Hmac::::new_from_slice(b"MRAE_Box_Deoxys-II-256-128") + .map_err(|_| PrecompileError::Other("unable to create key derivation function".into()))?; + kdf.update(private.diffie_hellman(&public).as_bytes()); + + let mut derived_key = [0u8; KEY_SIZE]; + let digest = kdf.finalize(); + derived_key.copy_from_slice(&digest.into_bytes()[..KEY_SIZE]); + + let output = derived_key.to_vec(); + Ok(PrecompileOutput::new(cost, output.into())) +} + +#[allow(clippy::type_complexity)] +fn decode_deoxysii_call_args( + input: &[u8], +) -> Result<([u8; KEY_SIZE], [u8; NONCE_SIZE], Vec, Vec), PrecompileErrors> { + let mut call_args = ethabi::decode( + &[ + ParamType::FixedBytes(32), // key + ParamType::FixedBytes(32), // nonce + ParamType::Bytes, // plain or ciphertext + ParamType::Bytes, // associated data + ], + input, + ) + .map_err(|e| PrecompileError::Other(e.to_string()))?; + let ad = call_args.pop().unwrap().into_bytes().unwrap(); + let text = call_args.pop().unwrap().into_bytes().unwrap(); + let nonce_bytes = call_args.pop().unwrap().into_fixed_bytes().unwrap(); + let key_bytes = call_args.pop().unwrap().into_fixed_bytes().unwrap(); + + let mut nonce = [0u8; NONCE_SIZE]; + nonce.copy_from_slice(&nonce_bytes[..NONCE_SIZE]); + let mut key = [0u8; KEY_SIZE]; + key.copy_from_slice(&key_bytes); + + Ok((key, nonce, text, ad)) +} + +pub(super) fn call_deoxysii_seal(input: &Bytes, gas_limit: u64) -> PrecompileResult { + let cost = calc_linear_cost_u32(input.len(), DEOXYSII_BASE_COST, DEOXYSII_WORD_COST); + if cost > gas_limit { + return Err(PrecompileError::OutOfGas.into()); + } + + let (key, nonce, text, ad) = decode_deoxysii_call_args(input)?; + let deoxysii = DeoxysII::new(&key); + let encrypted = deoxysii.seal(&nonce, text, ad); + + Ok(PrecompileOutput::new(cost, encrypted.into())) +} + +pub(super) fn call_deoxysii_open(input: &Bytes, gas_limit: u64) -> PrecompileResult { + let cost = calc_linear_cost_u32(input.len(), DEOXYSII_BASE_COST, DEOXYSII_WORD_COST); + if cost > gas_limit { + return Err(PrecompileError::OutOfGas.into()); + } + + let (key, nonce, ciphertext, ad) = decode_deoxysii_call_args(input)?; + let ciphertext = ciphertext.to_vec(); + let deoxysii = DeoxysII::new(&key); + + match deoxysii.open(&nonce, ciphertext, ad) { + Ok(decrypted) => Ok(PrecompileOutput::new(cost, decrypted.into())), + Err(_) => Err(PrecompileError::Other("revert".into()).into()), // XXX: How to exit with a revert? + } +} + +pub(super) fn call_keypair_generate(input: &Bytes, gas_limit: u64) -> PrecompileResult { + let mut call_args = ethabi::decode( + &[ + ParamType::Uint(256), // method + ParamType::Bytes, // seed + ], + input, + ) + .map_err(|e| PrecompileError::Other(e.to_string()))?; + + let seed = call_args.pop().unwrap().into_bytes().unwrap(); + let method: usize = call_args + .pop() + .unwrap() + .into_uint() + .unwrap() + .try_into() + .map_err(|_| PrecompileError::Other("method identifier out of bounds".into()))?; + + let sig_type: SignatureType = >::try_into(method) + .map_err(|_| PrecompileError::Other("method identifier out of bounds".into()))? + .try_into() + .map_err(|_| PrecompileError::Other("unknown signature type".into()))?; + + let cost = calc_linear_cost_u32( + input.len(), + *KEYPAIR_GENERATE_BASE_COST + .get(&sig_type) + .ok_or(PrecompileError::Other("unknown signature type".into()))?, + 0, + ); + if cost > gas_limit { + return Err(PrecompileError::OutOfGas.into()); + } + + let signer = signature::MemorySigner::new_from_seed(sig_type, &seed) + .map_err(|err| PrecompileError::Other(format!("error creating signer: {err}")))?; + let public = signer.public_key().as_bytes().to_vec(); + let private = signer.to_bytes(); + + let output = ethabi::encode(&[Token::Bytes(public), Token::Bytes(private)]); + Ok(PrecompileOutput::new(cost, output.into())) +} + +pub(super) fn call_sign(input: &Bytes, gas_limit: u64) -> PrecompileResult { + let mut call_args = ethabi::decode( + &[ + ParamType::Uint(256), // signature type + ParamType::Bytes, // private key + ParamType::Bytes, // context or precomputed hash bytes + ParamType::Bytes, // message; should be zero-length if precomputed hash given + ], + input, + ) + .map_err(|e| PrecompileError::Other(e.to_string()))?; + + let message = call_args.pop().unwrap().into_bytes().unwrap(); + let ctx_or_hash = call_args.pop().unwrap().into_bytes().unwrap(); + let pk = call_args.pop().unwrap().into_bytes().unwrap(); + let method = call_args + .pop() + .unwrap() + .into_uint() + .unwrap() + .try_into() + .map_err(|_| PrecompileError::Other("signature type identifier out of bounds".into()))?; + + let sig_type: SignatureType = >::try_into(method) + .map_err(|_| PrecompileError::Other("signature type identifier out of bounds".into()))? + .try_into() + .map_err(|_| PrecompileError::Other("unknown signature type".into()))?; + + let costs = *SIGN_MESSAGE_COST + .get(&sig_type) + .ok_or(PrecompileError::Other("unknown signature type".into()))?; + let cost = calc_linear_cost_u32(input.len(), costs.0, costs.1); + if cost > gas_limit { + return Err(PrecompileError::OutOfGas.into()); + } + + let signer = signature::MemorySigner::from_bytes(sig_type, &pk) + .map_err(|e| PrecompileError::Other(format!("error creating signer: {e}")))?; + + let result = signer.sign_by_type(sig_type, &ctx_or_hash, &message); + let result = + result.map_err(|e| PrecompileError::Other(format!("error signing message: {e}")))?; + let output: Vec = result.into(); + + Ok(PrecompileOutput::new(cost, output.into())) +} + +pub(super) fn call_verify(input: &Bytes, gas_limit: u64) -> PrecompileResult { + let mut call_args = ethabi::decode( + &[ + ParamType::Uint(256), // signature type + ParamType::Bytes, // public key + ParamType::Bytes, // context or precomputed hash bytes + ParamType::Bytes, // message; should be zero-length if precomputed hash given + ParamType::Bytes, // signature + ], + input, + ) + .map_err(|e| PrecompileError::Other(e.to_string()))?; + + let signature = call_args.pop().unwrap().into_bytes().unwrap(); + let message = call_args.pop().unwrap().into_bytes().unwrap(); + let ctx_or_hash = call_args.pop().unwrap().into_bytes().unwrap(); + let pk = call_args.pop().unwrap().into_bytes().unwrap(); + let method = call_args + .pop() + .unwrap() + .into_uint() + .unwrap() + .try_into() + .map_err(|_| PrecompileError::Other("signature type identifier out of bounds".into()))?; + + let sig_type: SignatureType = >::try_into(method) + .map_err(|_| PrecompileError::Other("signature type identifier out of bounds".into()))? + .try_into() + .map_err(|_| PrecompileError::Other("unknown signature type".into()))?; + + let costs = *VERIFY_MESSAGE_COST + .get(&sig_type) + .ok_or(PrecompileError::Other("unknown signature type".into()))?; + let cost = calc_linear_cost_u32(input.len(), costs.0, costs.1); + if cost > gas_limit { + return Err(PrecompileError::OutOfGas.into()); + } + + let signature: signature::Signature = signature.into(); + let public_key = signature::PublicKey::from_bytes(sig_type, &pk) + .map_err(|_| PrecompileError::Other("error reading public key".into()))?; + + let result = public_key.verify_by_type(sig_type, &ctx_or_hash, &message, &signature); + + let output = ethabi::encode(&[Token::Bool(result.is_ok())]); + Ok(PrecompileOutput::new(cost, output.into())) +} +*/ diff --git a/runtime-sdk/modules/evm-new/src/precompile/gas.rs b/runtime-sdk/modules/evm-new/src/precompile/gas.rs new file mode 100644 index 00000000000..08c78014810 --- /dev/null +++ b/runtime-sdk/modules/evm-new/src/precompile/gas.rs @@ -0,0 +1,52 @@ +/*use ethabi::{ParamType, Token}; +use revm::{ + precompile::{PrecompileError, PrecompileOutput, PrecompileResult}, + primitives::{Bytes, Env}, +}; + +const GAS_USED_COST: u64 = 10; +const PAD_GAS_COST: u64 = 10; + +pub(super) fn call_gas_used(_input: &Bytes, gas_limit: u64, _env: &Env) -> PrecompileResult { + let cost = GAS_USED_COST; + if cost > gas_limit { + return Err(PrecompileError::OutOfGas.into()); + } + + // TODO + let used_gas = cost; // handle.used_gas(); // XXX + + let output = ethabi::encode(&[Token::Uint(used_gas.into())]); + Ok(PrecompileOutput::new(cost, output.into())) +} + +pub(super) fn call_pad_gas(input: &Bytes, gas_limit: u64, _env: &Env) -> PrecompileResult { + let cost = PAD_GAS_COST; + if cost > gas_limit { + return Err(PrecompileError::OutOfGas.into()); + } + + // Decode args. + let mut call_args = ethabi::decode(&[ParamType::Uint(128)], input) + .map_err(|e| PrecompileError::Other(e.to_string()))?; + let gas_amount_big = call_args.pop().unwrap().into_uint().unwrap(); + let gas_amount = gas_amount_big.try_into().unwrap_or(u64::MAX); + + // Obtain total used gas so far. + let used_gas = cost; // handle.used_gas(); // XXX + + // Fail if more gas that the desired padding was already used. + if gas_amount < used_gas { + return Err(PrecompileError::Other( + "gas pad amount less than already used gas".to_string(), + ) + .into()); + } + + // Record the remainder so that the gas use is padded to the desired amount. + // TODO + //handle.record_cost(gas_amount - used_gas)?; // XXX + + Ok(PrecompileOutput::new(cost, Bytes::new())) +} +*/ diff --git a/runtime-sdk/modules/evm-new/src/precompile/mod.rs b/runtime-sdk/modules/evm-new/src/precompile/mod.rs new file mode 100644 index 00000000000..bf02224bbd4 --- /dev/null +++ b/runtime-sdk/modules/evm-new/src/precompile/mod.rs @@ -0,0 +1,68 @@ +use revm::{precompile::PrecompileWithAddress, primitives::Address}; + +mod confidential; +mod gas; +mod sha2; + +/// Helper to generate the precompile address for Oasis-specific precompiles. +#[inline] +pub const fn oasis_addr(a18: u8, a19: u8) -> Address { + Address::new([ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, a18, a19, + ]) +} + +pub fn new() -> Vec { + // TODO: Port precompiles to the new revm API. + vec![] + /*vec![ + // Oasis-specific, confidential. + // TODO: random_bytes + /*PrecompileWithAddress( + oasis_addr(0, 1), + Precompile::Env(confidential::call_random_bytes), + ),*/ + PrecompileWithAddress( + oasis_addr(0, 2), + Precompile::Standard(confidential::call_x25519_derive), + ), + PrecompileWithAddress( + oasis_addr(0, 3), + Precompile::Standard(confidential::call_deoxysii_seal), + ), + PrecompileWithAddress( + oasis_addr(0, 4), + Precompile::Standard(confidential::call_deoxysii_open), + ), + PrecompileWithAddress( + oasis_addr(0, 5), + Precompile::Standard(confidential::call_keypair_generate), + ), + PrecompileWithAddress( + oasis_addr(0, 6), + Precompile::Standard(confidential::call_sign), + ), + PrecompileWithAddress( + oasis_addr(0, 7), + Precompile::Standard(confidential::call_verify), + ), + PrecompileWithAddress( + oasis_addr(0, 8), + Precompile::Standard(confidential::call_curve25519_compute_public), + ), + PrecompileWithAddress(oasis_addr(0, 9), Precompile::Env(gas::call_gas_used)), + PrecompileWithAddress(oasis_addr(0, 10), Precompile::Env(gas::call_pad_gas)), + // Oasis-specific, general. + PrecompileWithAddress( + oasis_addr(1, 1), + Precompile::Standard(sha2::call_sha512_256), + ), + PrecompileWithAddress(oasis_addr(1, 2), Precompile::Standard(sha2::call_sha512)), + // TODO: subcall + /*PrecompileWithAddress( + oasis_addr(1, 3), + Precompile::Env(subcall::call_subcall), + ),*/ + PrecompileWithAddress(oasis_addr(1, 4), Precompile::Standard(sha2::call_sha384)), + ]*/ +} diff --git a/runtime-sdk/modules/evm-new/src/precompile/sha2.rs b/runtime-sdk/modules/evm-new/src/precompile/sha2.rs new file mode 100644 index 00000000000..20da0595839 --- /dev/null +++ b/runtime-sdk/modules/evm-new/src/precompile/sha2.rs @@ -0,0 +1,27 @@ +//! Implements SHA2 precompiles. +/*use revm::{ + precompile::{calc_linear_cost_u32, PrecompileError, PrecompileOutput, PrecompileResult}, + primitives::Bytes, +}; + +macro_rules! make_hasher { + ($name:ident, $hasher:ident) => { + pub(super) fn $name(input: &Bytes, gas_limit: u64) -> PrecompileResult { + // Costs were computed by benchmarking and comparing to SHA256 + // and using the SHA256 costs (defined by EVM spec). + // See benches/criterion_benchmark.rs for the benchmarks. + let cost = calc_linear_cost_u32(input.len(), 115, 13); + if cost > gas_limit { + Err(PrecompileError::OutOfGas.into()) + } else { + let output = ::digest(input).to_vec(); + Ok(PrecompileOutput::new(cost, output.into())) + } + } + }; +} + +make_hasher!(call_sha512_256, Sha512_256); +make_hasher!(call_sha384, Sha384); +make_hasher!(call_sha512, Sha512); +*/ diff --git a/runtime-sdk/modules/evm-new/src/raw_tx.rs b/runtime-sdk/modules/evm-new/src/raw_tx.rs new file mode 100644 index 00000000000..763863413d7 --- /dev/null +++ b/runtime-sdk/modules/evm-new/src/raw_tx.rs @@ -0,0 +1,444 @@ +use std::convert::TryInto; + +use anyhow::{anyhow, Context as _}; +use ethereum::{self, EnvelopedDecodable}; +use k256::elliptic_curve::scalar::IsHigh; + +use oasis_runtime_sdk::{ + crypto::signature, + types::{address, token, transaction}, +}; + +use crate::types; + +pub fn recover_low( + sig: &k256::ecdsa::Signature, + sig_recid: k256::ecdsa::RecoveryId, + sig_hash: &primitive_types::H256, +) -> Result { + if sig.s().is_high().into() { + return Err(anyhow!("signature s high")); + } + k256::ecdsa::VerifyingKey::recover_from_prehash( + sig_hash.as_fixed_bytes().as_ref(), + sig, + sig_recid, + ) + .with_context(|| "recover verify key from digest") +} + +pub fn decode( + body: &[u8], + expected_chain_id: Option, + min_gas_price: u128, + denom: &token::Denomination, +) -> Result { + let ( + chain_id, + sig, + sig_recid, + sig_hash, + eth_action, + eth_value, + eth_input, + eth_nonce, + eth_gas_price, + eth_gas_limit, + ) = match ethereum::TransactionV2::decode(body) + .map_err(|_| anyhow!("decoding transaction rlp"))? + { + ethereum::TransactionV2::Legacy(eth_tx) => { + let sig = k256::ecdsa::Signature::from_scalars( + eth_tx.signature.r().to_fixed_bytes(), + eth_tx.signature.s().to_fixed_bytes(), + ) + .with_context(|| "signature from_scalars")?; + let sig_recid = k256::ecdsa::RecoveryId::from_byte(eth_tx.signature.standard_v()) + .ok_or(anyhow!("bad recovery id"))?; + let message = ethereum::LegacyTransactionMessage::from(eth_tx); + + ( + message.chain_id.or(expected_chain_id), + sig, + sig_recid, + message.hash(), + message.action, + message.value, + message.input, + message.nonce, + message.gas_price, + message.gas_limit, + ) + } + ethereum::TransactionV2::EIP2930(eth_tx) => { + let sig = k256::ecdsa::Signature::from_scalars( + eth_tx.r.to_fixed_bytes(), + eth_tx.s.to_fixed_bytes(), + ) + .with_context(|| "signature from_scalars")?; + let sig_recid = k256::ecdsa::RecoveryId::new(eth_tx.odd_y_parity, false); + let message = ethereum::EIP2930TransactionMessage::from(eth_tx); + + ( + Some(message.chain_id), + sig, + sig_recid, + message.hash(), + message.action, + message.value, + message.input, + message.nonce, + message.gas_price, + message.gas_limit, + ) + } + ethereum::TransactionV2::EIP1559(eth_tx) => { + let sig = k256::ecdsa::Signature::from_scalars( + eth_tx.r.to_fixed_bytes(), + eth_tx.s.to_fixed_bytes(), + ) + .with_context(|| "signature from_scalars")?; + let sig_recid = k256::ecdsa::RecoveryId::new(eth_tx.odd_y_parity, false); + let message = ethereum::EIP1559TransactionMessage::from(eth_tx); + + if message.max_fee_per_gas < message.max_priority_fee_per_gas { + return Err(anyhow!("invalid gas price")); + } + let base_fee_per_gas = min_gas_price.into(); + if message.max_fee_per_gas < base_fee_per_gas { + return Err(anyhow!("gas price too low")); + } + + let priority_fee_per_gas = std::cmp::min( + message.max_priority_fee_per_gas, + message.max_fee_per_gas.saturating_sub(base_fee_per_gas), + ); + let effective_gas_price = priority_fee_per_gas.saturating_add(base_fee_per_gas); + + ( + Some(message.chain_id), + sig, + sig_recid, + message.hash(), + message.action, + message.value, + message.input, + message.nonce, + effective_gas_price, + message.gas_limit, + ) + } + }; + if chain_id != expected_chain_id { + return Err(anyhow!( + "chain ID {:?}, expected {:?}", + chain_id, + expected_chain_id + )); + } + let (method, body) = match eth_action { + ethereum::TransactionAction::Call(eth_address) => ( + "evm.Call", + cbor::to_value(types::Call { + address: eth_address.into(), + value: eth_value.into(), + data: eth_input, + }), + ), + ethereum::TransactionAction::Create => ( + "evm.Create", + cbor::to_value(types::Create { + value: eth_value.into(), + init_code: eth_input, + }), + ), + }; + let key = recover_low(&sig, sig_recid, &sig_hash)?; + let nonce: u64 = eth_nonce + .try_into() + .map_err(|e| anyhow!("converting nonce: {}", e))?; + let gas_price: u128 = eth_gas_price + .try_into() + .map_err(|e| anyhow!("converting gas price: {}", e))?; + let gas_limit: u64 = eth_gas_limit + .try_into() + .map_err(|e| anyhow!("converting gas limit: {}", e))?; + let resolved_fee_amount = gas_price + .checked_mul(gas_limit as u128) + .ok_or_else(|| anyhow!("computing total fee amount"))?; + + Ok(transaction::Transaction { + version: transaction::LATEST_TRANSACTION_VERSION, + call: transaction::Call { + format: transaction::CallFormat::Plain, + method: method.to_owned(), + body, + ..Default::default() + }, + auth_info: transaction::AuthInfo { + signer_info: vec![transaction::SignerInfo { + address_spec: transaction::AddressSpec::Signature( + address::SignatureAddressSpec::Secp256k1Eth( + signature::secp256k1::PublicKey::from_bytes( + k256::EncodedPoint::from(&key).as_bytes(), + ) + .with_context(|| "sdk secp256k1 public key from bytes")?, + ), + ), + nonce, + }], + fee: transaction::Fee { + amount: token::BaseUnits::new(resolved_fee_amount, denom.clone()), + gas: gas_limit, + consensus_messages: 0, // Dynamic number of consensus messages, limited by gas. + proxy: None, + }, + ..Default::default() + }, + }) +} + +#[cfg(test)] +mod test { + use std::str::FromStr as _; + + use hex::FromHex as _; + + use oasis_runtime_sdk::types::token; + + use crate::{derive_caller, types}; + + use super::decode; + + #[allow(clippy::too_many_arguments)] + fn decode_expect_call( + raw: &str, + expected_chain_id: Option, + expected_to: &str, + expected_value: u128, + expected_data: &str, + expected_gas_limit: u64, + expected_gas_price: u128, + expected_from: &str, + expected_nonce: u64, + min_gas_price: u128, + ) { + let tx = decode( + &Vec::from_hex(raw).unwrap(), + expected_chain_id, + min_gas_price, + &token::Denomination::NATIVE, + ) + .unwrap(); + println!("{:?}", &tx); + assert_eq!(tx.call.method, "evm.Call"); + let body: types::Call = cbor::from_value(tx.call.body).unwrap(); + assert_eq!(body.address, types::H160::from_str(expected_to).unwrap()); + assert_eq!(body.value, types::U256::from(expected_value)); + assert_eq!(body.data, Vec::from_hex(expected_data).unwrap()); + assert_eq!(tx.auth_info.signer_info.len(), 1); + assert_eq!( + derive_caller::from_tx_auth_info(&tx.auth_info).unwrap(), + types::H160::from_str(expected_from).unwrap(), + ); + assert_eq!(tx.auth_info.signer_info[0].nonce, expected_nonce); + assert_eq!( + tx.auth_info.fee.amount.0, + expected_gas_limit as u128 * expected_gas_price, + ); + assert_eq!(tx.auth_info.fee.amount.1, token::Denomination::NATIVE); + assert_eq!(tx.auth_info.fee.gas, expected_gas_limit); + } + + #[allow(clippy::too_many_arguments)] + fn decode_expect_create( + raw: &str, + expected_chain_id: Option, + expected_value: u128, + expected_init_code: &str, + expected_gas_limit: u64, + expected_gas_price: u128, + expected_from: &str, + expected_nonce: u64, + min_gas_price: u128, + ) { + let tx = decode( + &Vec::from_hex(raw).unwrap(), + expected_chain_id, + min_gas_price, + &token::Denomination::NATIVE, + ) + .unwrap(); + println!("{:?}", &tx); + assert_eq!(tx.call.method, "evm.Create"); + let body: types::Create = cbor::from_value(tx.call.body).unwrap(); + assert_eq!(body.value, types::U256::from(expected_value)); + assert_eq!(body.init_code, Vec::from_hex(expected_init_code).unwrap()); + assert_eq!(tx.auth_info.signer_info.len(), 1); + assert_eq!( + derive_caller::from_tx_auth_info(&tx.auth_info).unwrap(), + types::H160::from_str(expected_from).unwrap(), + ); + assert_eq!(tx.auth_info.signer_info[0].nonce, expected_nonce); + assert_eq!( + tx.auth_info.fee.amount.0, + expected_gas_limit as u128 * expected_gas_price, + ); + assert_eq!(tx.auth_info.fee.amount.1, token::Denomination::NATIVE); + assert_eq!(tx.auth_info.fee.gas, expected_gas_limit); + } + + fn decode_expect_invalid(raw: &str, expected_chain_id: Option) { + let e = decode( + &Vec::from_hex(raw).unwrap(), + expected_chain_id, + 0, + &token::Denomination::NATIVE, + ) + .unwrap_err(); + eprintln!("Decoding error (expected): {:?}", e); + } + + fn decode_expect_from_mismatch( + raw: &str, + expected_chain_id: Option, + unexpected_from: &str, + ) { + match decode( + &Vec::from_hex(raw).unwrap(), + expected_chain_id, + 0, + &token::Denomination::NATIVE, + ) { + Ok(tx) => { + assert_ne!( + derive_caller::from_tx_auth_info(&tx.auth_info).unwrap(), + types::H160::from_str(unexpected_from).unwrap(), + ); + } + Err(e) => { + // Returning Err is fine too. + eprintln!("Decoding error (expected): {:?}", e); + } + } + } + + #[test] + fn test_decode_basic() { + // https://github.com/ethereum/tests/blob/v10.0/BasicTests/txtest.json + let legacy_tx = "f86b8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc10000801ba0eab47c1a49bf2fe5d40e01d313900e19ca485867d462fe06e139e3a536c6d4f4a014a569d327dcda4b29f74f93c0e9729d2f49ad726e703f9cd90dbb0fbf6649f1"; + decode_expect_call( + legacy_tx, + None, + "13978aee95f38490e9769c39b2773ed763d9cd5f", + 10_000_000_000_000_000, + "", + 10_000, + 1_000_000_000_000, + // "cow" test account + "cd2a3d9f938e13cd947ec05abc7fe734df8dd826", + 0, + 1_000, + ); + decode_expect_call( + legacy_tx, + Some(1), // Legacy pre-EIP-155 transaction should work with any chain ID. + "13978aee95f38490e9769c39b2773ed763d9cd5f", + 10_000_000_000_000_000, + "", + 10_000, + 1_000_000_000_000, + // "cow" test account + "cd2a3d9f938e13cd947ec05abc7fe734df8dd826", + 0, + 1_000, + ); + decode_expect_create( + // We're using a transaction normalized from the original (below) to have low `s`. + // f87f8085e8d4a510008227108080af6025515b525b600a37f260003556601b596020356000355760015b525b54602052f260255860005b525b54602052f21ba05afed0244d0da90b67cf8979b0f246432a5112c0d31e8d5eedd2bc17b171c694a0bb1035c834677c2e1185b8dc90ca6d1fa585ab3d7ef23707e1a497a98e752d1b + "f87f8085e8d4a510008227108080af6025515b525b600a37f260003556601b596020356000355760015b525b54602052f260255860005b525b54602052f21ca05afed0244d0da90b67cf8979b0f246432a5112c0d31e8d5eedd2bc17b171c694a044efca37cb9883d1ee7a47236f3592df152931a930566933de2dc6e341c11426", + None, + 0, + "6025515b525b600a37f260003556601b596020356000355760015b525b54602052f260255860005b525b54602052f2", + 10_000, + 1_000_000_000_000, + // "horse" test account + "13978aee95f38490e9769c39b2773ed763d9cd5f", + 0, + 1_000, + ); + } + + #[test] + fn test_decode_chain_id() { + // Test with mismatching expect_chain_id to exercise our check. + decode_expect_invalid( + // Taken from test_decode_types with chain ID of 1. + "01f86301028203e882c35094cccccccccccccccccccccccccccccccccccccccc8080c080a0260f95e555a1282ef49912ff849b2007f023c44529dc8fb7ecca7693cccb64caa06252cf8af2a49f4cb76fd7172feaece05124edec02db242886b36963a30c2606", + Some(5), + ); + } + + #[test] + fn test_decode_types() { + // https://github.com/ethereum/tests/blob/v10.0/BlockchainTests/ValidBlocks/bcEIP1559/transType.json + + // Legacy. + decode_expect_call( + "f861018203e882c35094cccccccccccccccccccccccccccccccccccccccc80801ca021539ef96c70ab75350c594afb494458e211c8c722a7a0ffb7025c03b87ad584a01d5395fe48edb306f614f0cd682b8c2537537f5fd3e3275243c42e9deff8e93d", + None, + "cccccccccccccccccccccccccccccccccccccccc", + 0, + "", + 50_000, + 1_000, + "d02d72e067e77158444ef2020ff2d325f929b363", + 1, + 1_000, + ); + + // Legacy. + decode_expect_call( + "01f86301028203e882c35094cccccccccccccccccccccccccccccccccccccccc8080c080a0260f95e555a1282ef49912ff849b2007f023c44529dc8fb7ecca7693cccb64caa06252cf8af2a49f4cb76fd7172feaece05124edec02db242886b36963a30c2606", + Some(1), + "cccccccccccccccccccccccccccccccccccccccc", + 0, + "", + 50_000, + 1_000, + "d02d72e067e77158444ef2020ff2d325f929b363", + 2, + 1_000, + ); + + // EIP-1559 + // maxFeePerGas = 1000 + // maxPriorityFeePerGas = 100 + decode_expect_call( + "02f8640103648203e882c35094cccccccccccccccccccccccccccccccccccccccc8080c001a08480e6848952a15ae06192b8051d213d689bdccdf8f14cf69f61725e44e5e80aa057c2af627175a2ac812dab661146dfc7b9886e885c257ad9c9175c3fcec2202e", + Some(1), + "cccccccccccccccccccccccccccccccccccccccc", + 0, + "", + 50_000, + 500, // min(100, 1000 - 400) + 400 + "d02d72e067e77158444ef2020ff2d325f929b363", + 3, + 400, + ); + } + + #[test] + fn test_decode_verify() { + // Altered signature, out of bounds r = n. + decode_expect_invalid("f86b8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc10000801ba0fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141a014a569d327dcda4b29f74f93c0e9729d2f49ad726e703f9cd90dbb0fbf6649f1", None); + // Altered signature, high s. + decode_expect_invalid("f86b8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc10000801ca0eab47c1a49bf2fe5d40e01d313900e19ca485867d462fe06e139e3a536c6d4f4a0eb5a962cd82325b4d608b06c3f168d618b652f7440d8609ee6c4a37d10cff750", None); + // Altered signature, s decreased by one. + decode_expect_from_mismatch( + "f86b8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc10000801ba0eab47c1a49bf2fe5d40e01d313900e19ca485867d462fe06e139e3a536c6d4f4a014a569d327dcda4b29f74f93c0e9729d2f49ad726e703f9cd90dbb0fbf6649f0", + None, + "cd2a3d9f938e13cd947ec05abc7fe734df8dd826", + ); + } +} diff --git a/runtime-sdk/modules/evm-new/src/signed_call.rs b/runtime-sdk/modules/evm-new/src/signed_call.rs new file mode 100644 index 00000000000..ed1d05c61d3 --- /dev/null +++ b/runtime-sdk/modules/evm-new/src/signed_call.rs @@ -0,0 +1,336 @@ +use std::convert::TryFrom as _; + +use ethabi::Token; +use once_cell::sync::OnceCell; +use sha3::{Digest as _, Keccak256}; + +use oasis_runtime_sdk::{ + context::Context, core::common::crypto::hash::Hash, modules::accounts::API as _, + state::CurrentState, +}; + +use crate::{ + state, + types::{Leash, SimulateCallQuery}, + Config, Error, Runtime, +}; + +/// Verifies the signature on signed query and whether it is appropriately leashed. +/// +/// See [`crate::types::SignedSimulateCallEnvelope`] for details on the signature format. +pub(crate) fn verify( + ctx: &C, + query: SimulateCallQuery, + leash: Leash, + mut signature: [u8; 65], +) -> Result { + // First, verify the signature since it's cheap compared to accessing state to verify the leash. + if signature[64] >= 27 { + // Some wallets generate a high recovery id, which isn't tolerated by the ecdsa crate. + signature[64] -= 27 + } + let sig = k256::ecdsa::Signature::try_from(&signature[..64]) + .map_err(|_| Error::InvalidSignedSimulateCall("invalid signature"))?; + let sig_recid = k256::ecdsa::RecoveryId::from_byte(signature[64]) + .ok_or(Error::InvalidSignedSimulateCall("invalid signature"))?; + let signed_message = hash_call_toplevel::(&query, &leash); + let signer_pk = crate::raw_tx::recover_low(&sig, sig_recid, &signed_message.into()) + .map_err(|_| Error::InvalidSignedSimulateCall("signature recovery failed"))?; + let signer_addr_digest = Keccak256::digest(&signer_pk.to_encoded_point(false).as_bytes()[1..]); + if &signer_addr_digest[12..] != query.caller.as_ref() { + return Err(Error::InvalidSignedSimulateCall("signer != caller")); + } + + // Next, verify the leash. + let current_block = ctx.runtime_header().round; + let sdk_address = Cfg::map_address(query.caller.0.into()); + let nonce = ::Accounts::get_nonce(sdk_address).unwrap(); + if nonce > leash.nonce { + return Err(Error::InvalidSignedSimulateCall("stale nonce")); + } + + let base_block_hash = CurrentState::with_store(|store| { + let block_hashes = state::block_hashes(store); + match block_hashes.get::<_, Hash>(&leash.block_number.to_be_bytes()) { + Some(hash) => Ok(hash), + None => Err(Error::InvalidSignedSimulateCall("base block not found")), + } + })?; + if base_block_hash.as_ref() != leash.block_hash.as_ref() { + return Err(Error::InvalidSignedSimulateCall("unexpected base block")); + } + + #[allow(clippy::unnecessary_lazy_evaluations)] + let block_delta = current_block + .checked_sub(leash.block_number) + .unwrap_or_else(|| leash.block_number - current_block); + if block_delta > leash.block_range { + return Err(Error::InvalidSignedSimulateCall( + "current block out of range", + )); + } + + Ok(query) +} + +macro_rules! leash_type_str { + () => { + concat!( + "Leash", + "(", + "uint64 nonce", + ",uint64 blockNumber", + ",bytes32 blockHash", + ",uint64 blockRange", + ")", + ) + }; +} + +fn hash_call_toplevel(query: &SimulateCallQuery, leash: &Leash) -> [u8; 32] { + let call_struct_hash = hash_call(query, leash); + let domain_separator = hash_domain::(); + let mut encoded_call = [0u8; 66]; + encoded_call[0..2].copy_from_slice(b"\x19\x01"); + encoded_call[2..34].copy_from_slice(domain_separator); + encoded_call[34..].copy_from_slice(&call_struct_hash); + Keccak256::digest(encoded_call).into() +} + +fn hash_call(query: &SimulateCallQuery, leash: &Leash) -> [u8; 32] { + const CALL_TYPE_STR: &str = concat!( + "Call", + "(", + "address from", + ",address to", + ",uint64 gasLimit", + ",uint256 gasPrice", + ",uint256 value", + ",bytes data", + ",Leash leash", + ")", + leash_type_str!() + ); + hash_encoded(&[ + encode_bytes(CALL_TYPE_STR), + Token::Address(query.caller.0.into()), + Token::Address(query.address.unwrap_or_default().0.into()), + Token::Uint(query.gas_limit.into()), + Token::Uint(ethabi::ethereum_types::U256(query.gas_price.0)), + Token::Uint(ethabi::ethereum_types::U256(query.value.0)), + encode_bytes(&query.data), + Token::Uint(hash_leash(leash).into()), + ]) +} + +fn hash_leash(leash: &Leash) -> [u8; 32] { + hash_encoded(&[ + encode_bytes(leash_type_str!()), + Token::Uint(leash.nonce.into()), + Token::Uint(leash.block_number.into()), + Token::Uint(leash.block_hash.0.into()), + Token::Uint(leash.block_range.into()), + ]) +} + +fn hash_domain() -> &'static [u8; 32] { + static DOMAIN_SEPARATOR: OnceCell<[u8; 32]> = OnceCell::new(); // Not `Lazy` because of generic. + DOMAIN_SEPARATOR.get_or_init(|| { + const DOMAIN_TYPE_STR: &str = "EIP712Domain(string name,string version,uint256 chainId)"; + hash_encoded(&[ + encode_bytes(DOMAIN_TYPE_STR), + encode_bytes("oasis-runtime-sdk/evm: signed query"), + encode_bytes("1.0.0"), + Token::Uint(Cfg::CHAIN_ID.into()), + ]) + }) +} + +fn encode_bytes(s: impl AsRef<[u8]>) -> Token { + Token::FixedBytes(Keccak256::digest(s.as_ref()).to_vec()) +} + +fn hash_encoded(tokens: &[Token]) -> [u8; 32] { + Keccak256::digest(ethabi::encode(tokens)).into() +} + +#[cfg(test)] +mod test { + use super::*; + + use oasis_runtime_sdk::{modules::accounts, testing::mock}; + + use crate::{ + test::{ConfidentialEVMConfig as C10lCfg, EVMConfig as Cfg}, + types::{SignedCallDataPack, SimulateCallQuery, H160}, + Module as EVMModule, + }; + + type Accounts = accounts::Module; + + /// This was generated using the `@oasislabs/sapphire-paratime` JS lib. + const SIGNED_CALL_DATA_PACK: &str = +"a36464617461a164626f64794401020304656c65617368a4656e6f6e63651903e76a626c6f636b5f686173685820c92b675c7013e33aa88feaae520eb0ede155e7cacb3c4587e0923cba9953f8bb6b626c6f636b5f72616e6765036c626c6f636b5f6e756d626572182a697369676e6174757265584148bca100e84d13a80b131c62b9b87caf07e4da6542a9e1ea16d8042ba08cc1e31f10ae924d8c137882204e9217423194014ce04fa2130c14f27b148858733c7b1c"; + + fn make_signed_call() -> (SimulateCallQuery, SignedCallDataPack) { + let data_pack: SignedCallDataPack = + cbor::from_slice(&hex::decode(SIGNED_CALL_DATA_PACK).unwrap()).unwrap(); + ( + SimulateCallQuery { + gas_price: 123u64.into(), + gas_limit: 10, + caller: "0x11e244400Cf165ade687077984F09c3A037b868F" + .parse() + .unwrap(), + address: Some( + "0xb5ed90452AAC09f294a0BE877CBf2Dc4D55e096f" + .parse() + .unwrap(), + ), + value: 42u64.into(), + data: cbor::from_value(data_pack.data.body.clone()).unwrap(), + }, + data_pack, + ) + } + + fn setup_nonce(caller: &H160, leash: &Leash) { + let sdk_address = C10lCfg::map_address((*caller).0.into()); + Accounts::set_nonce(sdk_address, leash.nonce); + } + + fn setup_stale_nonce(caller: &H160, leash: &Leash) { + let sdk_address = C10lCfg::map_address((*caller).0.into()); + Accounts::set_nonce(sdk_address, leash.nonce + 1); + } + + fn setup_block(leash: &Leash) { + CurrentState::with_store(|store| { + let mut block_hashes = state::block_hashes(store); + block_hashes.insert::<_, Hash>( + &leash.block_number.to_be_bytes(), + leash.block_hash.as_ref().into(), + ); + }); + } + + #[test] + fn test_verify_ok() { + let (query, data_pack) = make_signed_call(); + + let mut mock = mock::Mock::default(); + mock.runtime_header.round = data_pack.leash.block_number; + let mut ctx = mock.create_ctx(); + + setup_nonce(&query.caller, &data_pack.leash); + setup_block(&data_pack.leash); + + verify::<_, C10lCfg>(&mut ctx, query, data_pack.leash, data_pack.signature).unwrap(); + } + + #[test] + fn test_verify_bad_signature() { + let (query, mut data_pack) = make_signed_call(); + + let mut mock = mock::Mock::default(); + mock.runtime_header.round = data_pack.leash.block_number; + let mut ctx = mock.create_ctx(); + + setup_nonce(&query.caller, &data_pack.leash); + setup_block(&data_pack.leash); + + data_pack.signature[0] ^= 1; + assert!(matches!( + verify::<_, C10lCfg>(&mut ctx, query, data_pack.leash, data_pack.signature) + .unwrap_err(), + Error::InvalidSignedSimulateCall("signer != caller") + )); + } + + #[test] + fn test_verify_bad_nonce() { + let (query, data_pack) = make_signed_call(); + + let mut mock = mock::Mock::default(); + mock.runtime_header.round = data_pack.leash.block_number; + let mut ctx = mock.create_ctx(); + + setup_stale_nonce(&query.caller, &data_pack.leash); + setup_block(&data_pack.leash); + + assert!(matches!( + verify::<_, C10lCfg>(&mut ctx, query, data_pack.leash, data_pack.signature) + .unwrap_err(), + Error::InvalidSignedSimulateCall("stale nonce") + )); + } + + #[test] + fn test_verify_bad_base_block() { + let (query, data_pack) = make_signed_call(); + + let mut mock = mock::Mock::default(); + mock.runtime_header.round = data_pack.leash.block_number; + let mut ctx = mock.create_ctx(); + + setup_nonce(&query.caller, &data_pack.leash); + + assert!(matches!( + verify::<_, C10lCfg>(&mut ctx, query, data_pack.leash, data_pack.signature) + .unwrap_err(), + Error::InvalidSignedSimulateCall("base block not found") + )); + } + + #[test] + fn test_verify_bad_range() { + let (query, data_pack) = make_signed_call(); + + let mut mock = mock::Mock::default(); + let mut ctx = mock.create_ctx(); + + setup_nonce(&query.caller, &data_pack.leash); + setup_block(&data_pack.leash); + + assert!(matches!( + verify::<_, C10lCfg>(&mut ctx, query, data_pack.leash, data_pack.signature) + .unwrap_err(), + Error::InvalidSignedSimulateCall("current block out of range") + )); + } + + #[test] + fn test_decode_simulate_call_query() { + let (unsigned_body, data_pack) = make_signed_call(); + let signed_body = SimulateCallQuery { + data: cbor::to_vec(data_pack.clone()), + ..unsigned_body + }; + + let mut mock = mock::Mock::default(); + let mut ctx = mock.create_ctx(); + + let mut c10l_mock = mock::Mock::default(); + c10l_mock.runtime_header.round = data_pack.leash.block_number; + let mut c10l_ctx = c10l_mock.create_ctx(); + + setup_nonce(&signed_body.caller, &data_pack.leash); + setup_block(&data_pack.leash); + + let mut non_c10l_decode = |body: &SimulateCallQuery| { + EVMModule::::decode_simulate_call_query(&mut ctx, body.clone()) + }; + let mut c10l_decode = |body: &SimulateCallQuery| { + EVMModule::::decode_simulate_call_query(&mut c10l_ctx, body.clone()) + }; + + assert!(EVMModule::::decode_simulate_call_query( + &mut mock::Mock::default().create_ctx(), + signed_body.clone() + ) + .is_err()); // Check that errors are propagated (in this case leash invalidity). + + assert_eq!(c10l_decode(&signed_body).unwrap().0, unsigned_body); + assert_eq!(non_c10l_decode(&unsigned_body).unwrap().0, unsigned_body); + } +} diff --git a/runtime-sdk/modules/evm-new/src/state.rs b/runtime-sdk/modules/evm-new/src/state.rs new file mode 100644 index 00000000000..71d35fe4bee --- /dev/null +++ b/runtime-sdk/modules/evm-new/src/state.rs @@ -0,0 +1,122 @@ +use oasis_runtime_sdk::{ + context::Context, + state::CurrentState, + storage::{ConfidentialStore, HashedStore, PrefixStore, Store, TypedStore}, +}; + +use crate::{types::H160, Config}; + +/// Prefix for Ethereum account code in our storage (maps H160 -> Vec). +pub const CODES: &[u8] = &[0x01]; +/// Prefix for Ethereum account storage in our storage (maps H160||H256 -> H256). +pub const STORAGES: &[u8] = &[0x02]; +/// Prefix for Ethereum block hashes (only for last BLOCK_HASH_WINDOW_SIZE blocks +/// excluding current) storage in our storage (maps Round -> H256). +pub const BLOCK_HASHES: &[u8] = &[0x03]; +/// Prefix for Ethereum account storage in our confidential storage (maps H160||H256 -> H256). +pub const CONFIDENTIAL_STORAGES: &[u8] = &[0x04]; + +/// Confidential store key pair ID domain separation context base. +pub const CONFIDENTIAL_STORE_KEY_PAIR_ID_CONTEXT_BASE: &[u8] = b"oasis-runtime-sdk/evm: state"; +const CONTEXT_KEY_CONFIDENTIAL_STORE_INSTANCE_COUNT: &str = "evm.ConfidentialStoreCounter"; + +/// The number of hash blocks that can be obtained from the current blockchain. +pub const BLOCK_HASH_WINDOW_SIZE: u64 = 256; + +/// Run closure with the store of the provided contract address. Based on configuration this will +/// be either confidential or public storage. +pub fn with_storage(ctx: &C, address: &H160, f: F) -> R +where + Cfg: Config, + C: Context, + F: FnOnce(&mut TypedStore<&mut dyn Store>) -> R, +{ + if Cfg::CONFIDENTIAL { + with_confidential_storage(ctx, address, f) + } else { + with_public_storage(address, f) + } +} + +/// Run closure with the public store of the provided contract address. +pub fn with_public_storage(address: &H160, f: F) -> R +where + F: FnOnce(&mut TypedStore<&mut dyn Store>) -> R, +{ + CurrentState::with_store(|store| { + let mut store = + HashedStore::<_, blake3::Hasher>::new(contract_storage(store, STORAGES, address)); + let mut store = TypedStore::new(&mut store as &mut dyn Store); + f(&mut store) + }) +} + +/// Run closure with the confidential store of the provided contract address. +pub fn with_confidential_storage<'a, C, F, R>(ctx: &'a C, address: &'a H160, f: F) -> R +where + C: Context, + F: FnOnce(&mut TypedStore<&mut dyn Store>) -> R, +{ + let kmgr_client = ctx + .key_manager() + .expect("key manager must be available to use confidentiality"); + let key_id = oasis_runtime_sdk::keymanager::get_key_pair_id([ + CONFIDENTIAL_STORE_KEY_PAIR_ID_CONTEXT_BASE, + address.as_ref(), + ]); + let keypair = kmgr_client + .get_or_create_keys(key_id) + .expect("unable to retrieve confidential storage keys"); + let confidential_key = keypair.state_key; + + // These values are used to derive the confidential store nonce: + let round = ctx.runtime_header().round; + let instance_count: usize = CurrentState::with(|state| { + // One state is used per tx batch, so the instance count will monotonically increase. + let cnt = *state + .block_value(CONTEXT_KEY_CONFIDENTIAL_STORE_INSTANCE_COUNT) + .or_default(); + state + .block_value(CONTEXT_KEY_CONFIDENTIAL_STORE_INSTANCE_COUNT) + .set(cnt + 1); + cnt + }); + + CurrentState::with(|state| { + let mode = state.env().mode() as u8; + let contract_storages = contract_storage(state.store(), CONFIDENTIAL_STORAGES, address); + let mut confidential_storages = ConfidentialStore::new_with_key( + contract_storages, + confidential_key.0, + &[ + round.to_le_bytes().as_slice(), + instance_count.to_le_bytes().as_slice(), + &[mode], + ], + ); + let mut store = TypedStore::new(&mut confidential_storages as &mut dyn Store); + f(&mut store) + }) +} + +fn contract_storage<'a, S: Store + 'a>( + state: S, + prefix: &'a [u8], + address: &'a H160, +) -> PrefixStore { + let store = PrefixStore::new(state, &crate::MODULE_NAME); + let storages = PrefixStore::new(store, prefix); + PrefixStore::new(storages, address) +} + +/// Get a typed store for codes of all contracts. +pub fn codes<'a, S: Store + 'a>(state: S) -> TypedStore { + let store = PrefixStore::new(state, &crate::MODULE_NAME); + TypedStore::new(PrefixStore::new(store, &CODES)) +} + +/// Get a typed store for historic block hashes. +pub fn block_hashes<'a, S: Store + 'a>(state: S) -> TypedStore { + let store = PrefixStore::new(state, &crate::MODULE_NAME); + TypedStore::new(PrefixStore::new(store, &BLOCK_HASHES)) +} diff --git a/runtime-sdk/modules/evm-new/src/test.rs b/runtime-sdk/modules/evm-new/src/test.rs new file mode 100644 index 00000000000..2d4b1edc2cf --- /dev/null +++ b/runtime-sdk/modules/evm-new/src/test.rs @@ -0,0 +1,1223 @@ +//! Tests for the EVM module. +use std::collections::BTreeMap; + +use ethabi::{ParamType, Token}; +use sha3::Digest as _; +use uint::hex::FromHex; + +use oasis_runtime_sdk::{ + callformat, + crypto::{self, signature::secp256k1}, + error::Error as _, + module::{self, InvariantHandler as _, TransactionHandler as _}, + modules::{ + accounts::{self, Module as Accounts, ADDRESS_FEE_ACCUMULATOR, API as _}, + core::{self, Module as Core}, + }, + state::{self, CurrentState, Mode, Options, TransactionResult}, + testing::{keys, mock, mock::CallOptions}, + types::{ + address::{Address, SignatureAddressSpec}, + token::{self, Denomination}, + transaction, + transaction::Fee, + }, + Runtime, Version, +}; + +use crate::{ + derive_caller, + mock::{decode_reverted, decode_reverted_raw, load_contract_bytecode, EvmSigner, QueryOptions}, + types::{self, H160}, + Config, Genesis, Module as EVMModule, +}; + +/// Test contract code. +static TEST_CONTRACT_CODE_HEX: &str = + include_str!("../../../../tests/e2e/evm/contracts/evm_erc20_test_compiled.hex"); +static FAUCET_CONTRACT_CODE_HEX: &str = + include_str!("../../../../tests/e2e/evm/contracts/faucet/faucet.hex"); + +pub(crate) struct EVMConfig; + +impl Config for EVMConfig { + const CHAIN_ID: u64 = 0xa515; + + const TOKEN_DENOMINATION: Denomination = Denomination::NATIVE; +} + +pub(crate) struct ConfidentialEVMConfig; + +impl Config for ConfidentialEVMConfig { + const CHAIN_ID: u64 = 0x5afe; + + const TOKEN_DENOMINATION: Denomination = Denomination::NATIVE; + + const CONFIDENTIAL: bool = true; +} + +fn load_erc20() -> Vec { + Vec::from_hex( + TEST_CONTRACT_CODE_HEX + .split_whitespace() + .collect::(), + ) + .expect("compiled ERC20 contract should be a valid hex string") +} + +fn check_derivation(seed: &str, priv_hex: &str, addr_hex: &str) { + let priv_bytes = sha3::Keccak256::digest(seed.as_bytes()); + assert_eq!( + priv_bytes.as_slice(), + Vec::from_hex(priv_hex).unwrap().as_slice() + ); + let priv_key = k256::ecdsa::SigningKey::from_bytes(&priv_bytes).unwrap(); + let pub_key = priv_key.verifying_key(); + let sdk_pub_key = + secp256k1::PublicKey::from_bytes(pub_key.to_encoded_point(true).as_bytes()).unwrap(); + let addr = + derive_caller::from_sigspec(&SignatureAddressSpec::Secp256k1Eth(sdk_pub_key)).unwrap(); + assert_eq!(addr.as_bytes(), Vec::from_hex(addr_hex).unwrap().as_slice()); +} + +#[test] +fn test_evm_caller_addr_derivation() { + // https://github.com/ethereum/tests/blob/v10.0/BasicTests/keyaddrtest.json + check_derivation( + "cow", + "c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4", + "cd2a3d9f938e13cd947ec05abc7fe734df8dd826", + ); + check_derivation( + "horse", + "c87f65ff3f271bf5dc8643484f66b200109caffe4bf98c4cb393dc35740b28c0", + "13978aee95f38490e9769c39b2773ed763d9cd5f", + ); + + let expected = + H160::from_slice(&Vec::::from_hex("dce075e1c39b1ae0b75d554558b6451a226ffe00").unwrap()); + let derived = derive_caller::from_sigspec(&keys::dave::sigspec()).unwrap(); + assert_eq!(derived, expected); +} + +fn do_test_evm_calls(force_plain: bool) { + let mut mock = mock::Mock::default(); + let ctx = mock.create_ctx_for_runtime::(true); + let client_keypair = + oasis_runtime_sdk::core::common::crypto::mrae::deoxysii::generate_key_pair(); + + macro_rules! encode_data { + ($data:expr) => { + if C::CONFIDENTIAL && !force_plain { + cbor::to_vec( + callformat::encode_call( + &ctx, + transaction::Call { + format: transaction::CallFormat::EncryptedX25519DeoxysII, + method: "".into(), + body: cbor::Value::from($data), + ..Default::default() + }, + &client_keypair, + ) + .unwrap(), + ) + } else { + $data + } + }; + } + + macro_rules! decode_result { + ($tx_ctx:ident, $result:expr$(,)?) => { + match $result { + Ok(evm_result) => { + if C::CONFIDENTIAL && !force_plain { + let call_result: transaction::CallResult = + cbor::from_slice(&evm_result).unwrap(); + callformat::decode_result( + &$tx_ctx, + transaction::CallFormat::EncryptedX25519DeoxysII, + call_result, + &client_keypair, + ) + .expect("bad decode") + } else { + module::CallResult::Ok(cbor::Value::from(evm_result)) + } + } + Err(e) => e.into_call_result(), + } + }; + } + + Core::::init(core::Genesis { + parameters: core::Parameters { + max_batch_gas: 10_000_000, + ..Default::default() + }, + }); + + Accounts::init(accounts::Genesis { + balances: { + let mut b = BTreeMap::new(); + // Dave. + b.insert(keys::dave::address(), { + let mut d = BTreeMap::new(); + d.insert(Denomination::NATIVE, 1_000_000); + d + }); + b + }, + total_supplies: { + let mut ts = BTreeMap::new(); + ts.insert(Denomination::NATIVE, 1_000_000); + ts + }, + ..Default::default() + }); + + EVMModule::::init(Genesis { + parameters: Default::default(), + }); + + let erc20 = load_erc20(); + + // Test the Create transaction. + let create_tx = transaction::Transaction { + version: 1, + call: transaction::Call { + format: transaction::CallFormat::Plain, + method: "evm.Create".to_owned(), + body: cbor::to_value(types::Create { + value: 0.into(), + init_code: encode_data!(erc20), + }), + ..Default::default() + }, + auth_info: transaction::AuthInfo { + signer_info: vec![transaction::SignerInfo::new_sigspec( + keys::dave::sigspec(), + 0, + )], + fee: transaction::Fee { + gas: 1_000_000, + ..Default::default() + }, + ..Default::default() + }, + }; + // Run authentication handler to simulate nonce increments. + Accounts::authenticate_tx(&ctx, &create_tx).unwrap(); + + let call = create_tx.call.clone(); + let erc20_addr = + CurrentState::with_transaction_opts(Options::new().with_tx(create_tx.into()), || { + let addr = H160::from_slice( + &EVMModule::::tx_create(&ctx, cbor::from_value(call.body).unwrap()).unwrap(), + ); + EVMModule::::check_invariants(&ctx).expect("invariants should hold"); + + TransactionResult::Commit(addr) + }); + + // Test the Call transaction. + let name_method: Vec = Vec::from_hex("06fdde03".to_owned() + &"0".repeat(64 - 8)).unwrap(); + let call_name_tx = transaction::Transaction { + version: 1, + call: transaction::Call { + format: transaction::CallFormat::Plain, + method: "evm.Call".to_owned(), + body: cbor::to_value(types::Call { + address: erc20_addr, + value: 0.into(), + data: encode_data!(name_method), + }), + ..Default::default() + }, + auth_info: transaction::AuthInfo { + signer_info: vec![transaction::SignerInfo::new_sigspec( + keys::dave::sigspec(), + 1, + )], + fee: transaction::Fee { + gas: 25_000, + ..Default::default() + }, + ..Default::default() + }, + }; + // Run authentication handler to simulate nonce increments. + Accounts::authenticate_tx(&ctx, &call_name_tx).unwrap(); + + let call = call_name_tx.call.clone(); + let erc20_name = + CurrentState::with_transaction_opts(Options::new().with_tx(call_name_tx.into()), || { + let name: Vec = cbor::from_value( + decode_result!( + ctx, + EVMModule::::tx_call(&ctx, cbor::from_value(call.body).unwrap()) + ) + .unwrap(), + ) + .unwrap(); + + EVMModule::::check_invariants(&ctx).expect("invariants should hold"); + + TransactionResult::Commit(name) + }); + + assert_eq!(erc20_name.len(), 96); + assert_eq!(erc20_name[63], 0x04); // Name is 4 bytes long. + assert_eq!(erc20_name[64..68], vec![0x54, 0x65, 0x73, 0x74]); // "Test". +} + +#[test] +fn test_evm_calls() { + do_test_evm_calls::(false); +} + +#[test] +fn test_c10l_evm_calls_enc() { + let _guard = crypto::signature::context::test_using_chain_context(); + crypto::signature::context::set_chain_context(Default::default(), "test"); + do_test_evm_calls::(false); +} + +#[test] +fn test_c10l_evm_calls_plain() { + let _guard = crypto::signature::context::test_using_chain_context(); + crypto::signature::context::set_chain_context(Default::default(), "test"); + do_test_evm_calls::(true /* force_plain */); +} + +#[test] +fn test_c10l_evm_balance_transfer() { + let _guard = crypto::signature::context::test_using_chain_context(); + crypto::signature::context::set_chain_context(Default::default(), "test"); + let mut mock = mock::Mock::default(); + let ctx = mock.create_ctx(); + + Core::::init(core::Genesis { + parameters: core::Parameters { + max_batch_gas: 10_000_000, + ..Default::default() + }, + }); + + Accounts::init(accounts::Genesis { + balances: BTreeMap::from([( + keys::dave::address(), + BTreeMap::from([(Denomination::NATIVE, 1_000_000)]), + )]), + total_supplies: BTreeMap::from([(Denomination::NATIVE, 1_000_000)]), + ..Default::default() + }); + + EVMModule::::init(Genesis { + parameters: Default::default(), + }); + + let recipient = ethabi::Address::repeat_byte(42); + let transfer_tx = transaction::Transaction { + version: 1, + call: transaction::Call { + format: transaction::CallFormat::Plain, + method: "evm.Call".to_owned(), + body: cbor::to_value(types::Call { + address: recipient.into(), + value: 12345u64.into(), + data: vec![], + }), + ..Default::default() + }, + auth_info: transaction::AuthInfo { + signer_info: vec![transaction::SignerInfo::new_sigspec( + keys::dave::sigspec(), + 0, + )], + fee: transaction::Fee { + gas: 1_000_000, + ..Default::default() + }, + ..Default::default() + }, + }; + // Run authentication handler to simulate nonce increments. + Accounts::authenticate_tx(&ctx, &transfer_tx).unwrap(); + + let call = transfer_tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(transfer_tx.into()), || { + EVMModule::::tx_call(&ctx, cbor::from_value(call.body).unwrap()) + .unwrap(); + EVMModule::::check_invariants(&ctx).expect("invariants should hold"); + }); + + let recipient_balance = EVMModule::::query_balance( + &ctx, + types::BalanceQuery { + address: recipient.into(), + }, + ) + .unwrap(); + assert_eq!(recipient_balance, 12345u64.into()); +} + +#[test] +fn test_c10l_enc_call_identity_decoded() { + // Calls sent using the Oasis encrypted envelope format (not inner-enveloped) + // should not be decoded: + let mut mock = mock::Mock::default(); + let ctx = mock.create_ctx_for_runtime::>(true); + let data = vec![1, 2, 3, 4, 5]; + let (decoded_data, metadata) = EVMModule::::decode_call_data( + &ctx, + data.clone(), + transaction::CallFormat::EncryptedX25519DeoxysII, + 0, + true, + ) + .expect("decode failed") + .expect("km is unreachable"); + assert_eq!(data, decoded_data); + assert!(matches!(metadata, callformat::Metadata::Empty)); +} + +struct CoreConfig; + +impl core::Config for CoreConfig {} + +/// EVM test runtime. +struct EVMRuntime(C); + +impl Runtime for EVMRuntime { + const VERSION: Version = Version::new(0, 0, 0); + + type Core = Core; + type Accounts = Accounts; + + type Modules = (Core, Accounts, EVMModule); + + fn genesis_state() -> ::Genesis { + ( + core::Genesis { + parameters: core::Parameters { + max_batch_gas: 10_000_000, + min_gas_price: BTreeMap::from([(token::Denomination::NATIVE, 0)]), + ..Default::default() + }, + }, + accounts::Genesis { + balances: { + let mut b = BTreeMap::new(); + // Dave. + b.insert(keys::dave::address(), { + let mut d = BTreeMap::new(); + d.insert(Denomination::NATIVE, 1_000_000); + d + }); + b + }, + total_supplies: { + let mut ts = BTreeMap::new(); + ts.insert(Denomination::NATIVE, 1_000_000); + ts + }, + ..Default::default() + }, + Genesis { + parameters: Default::default(), + }, + ) + } +} + +fn do_test_evm_runtime() { + let mut mock = mock::Mock::default(); + let ctx = mock.create_ctx_for_runtime::>(true); + let client_keypair = + oasis_runtime_sdk::core::common::crypto::mrae::deoxysii::generate_key_pair(); + + // This is a macro to avoid mucking with borrow scopes. + macro_rules! encode_data { + ($data:expr) => { + if C::CONFIDENTIAL { + cbor::to_vec( + callformat::encode_call( + &ctx, + transaction::Call { + format: transaction::CallFormat::EncryptedX25519DeoxysII, + method: "".into(), + body: cbor::Value::from($data), + ..Default::default() + }, + &client_keypair, + ) + .unwrap(), + ) + } else { + $data + } + }; + } + + macro_rules! decode_result { + ($tx_ctx:ident, $result:expr$(,)?) => { + match $result { + Ok(evm_result) => { + if C::CONFIDENTIAL { + let call_result: transaction::CallResult = + cbor::from_slice(&evm_result).unwrap(); + callformat::decode_result( + &$tx_ctx, + transaction::CallFormat::EncryptedX25519DeoxysII, + call_result, + &client_keypair, + ) + .expect("bad decode") + } else { + module::CallResult::Ok(cbor::Value::from(evm_result)) + } + } + Err(e) => e.into_call_result(), + } + }; + } + + EVMRuntime::::migrate(&ctx); + + let erc20 = load_erc20(); + + // Test the Create transaction. + let create_tx = transaction::Transaction { + version: 1, + call: transaction::Call { + format: transaction::CallFormat::Plain, + method: "evm.Create".to_owned(), + body: cbor::to_value(types::Create { + value: 0.into(), + init_code: encode_data!(erc20.clone()), + }), + ..Default::default() + }, + auth_info: transaction::AuthInfo { + signer_info: vec![transaction::SignerInfo::new_sigspec( + keys::dave::sigspec(), + 0, + )], + fee: transaction::Fee { + gas: 1_000_000, + ..Default::default() + }, + ..Default::default() + }, + }; + // Run authentication handler to simulate nonce increments. + as Runtime>::Modules::authenticate_tx(&ctx, &create_tx).unwrap(); + + let call = create_tx.call.clone(); + let erc20_addr = + CurrentState::with_transaction_opts(Options::new().with_tx(create_tx.into()), || { + let tx = EVMModule::::tx_create(&ctx, cbor::from_value(call.body).unwrap()); + assert_eq!(tx.is_ok(), true, "tx_create failed"); + let addr = H160::from_slice(&tx.unwrap()); + EVMModule::::check_invariants(&ctx).expect("invariants should hold"); + + TransactionResult::Commit(addr) + }); + + // Make sure the derived address matches the expected value. If this fails it likely indicates + // a problem with nonce increment semantics between the SDK and EVM. + assert_eq!( + erc20_addr, + "0x3e6a6598a229b84e1411005d55003d88e3b11067" + .parse() + .unwrap(), + "derived address should be correct" + ); + + // Submitting an invalid create transaction should fail. + let out_of_gas_create = transaction::Transaction { + version: 1, + call: transaction::Call { + format: transaction::CallFormat::Plain, + method: "evm.Create".to_owned(), + body: cbor::to_value(types::Create { + value: 0.into(), + init_code: encode_data!(erc20), + }), + ..Default::default() + }, + auth_info: transaction::AuthInfo { + signer_info: vec![transaction::SignerInfo::new_sigspec( + keys::dave::sigspec(), + 1, + )], + fee: transaction::Fee { + gas: 10, // Not enough gas. + ..Default::default() + }, + ..Default::default() + }, + }; + // Run authentication handler to simulate nonce increments. + as Runtime>::Modules::authenticate_tx(&ctx, &out_of_gas_create).unwrap(); + + let call = out_of_gas_create.call.clone(); + CurrentState::with_transaction_opts( + Options::new().with_tx(out_of_gas_create.clone().into()), + || { + assert!(!decode_result!( + ctx, + EVMModule::::tx_create(&ctx, cbor::from_value(call.body).unwrap()) + ) + .is_success()); + }, + ); + + // CheckTx should not fail. + let call = out_of_gas_create.call.clone(); + CurrentState::with_transaction_opts( + Options::new() + .with_mode(state::Mode::Check) + .with_tx(out_of_gas_create.clone().into()), + || { + let rsp = EVMModule::::tx_create(&ctx, cbor::from_value(call.body).unwrap()) + .expect("call should succeed with empty result"); + + assert_eq!( + rsp, + Vec::::new(), + "check tx should return an empty response" + ); + }, + ); + + // Test the Call transaction. + let name_method: Vec = Vec::from_hex("06fdde03".to_owned() + &"0".repeat(64 - 8)).unwrap(); + let call_name_tx = transaction::Transaction { + version: 1, + call: transaction::Call { + format: transaction::CallFormat::Plain, + method: "evm.Call".to_owned(), + body: cbor::to_value(types::Call { + address: erc20_addr, + value: 0.into(), + data: encode_data!(name_method), + }), + ..Default::default() + }, + auth_info: transaction::AuthInfo { + signer_info: vec![transaction::SignerInfo::new_sigspec( + keys::dave::sigspec(), + 2, + )], + fee: transaction::Fee { + gas: 25_000, + ..Default::default() + }, + ..Default::default() + }, + }; + // Run authentication handler to simulate nonce increments. + as Runtime>::Modules::authenticate_tx(&ctx, &call_name_tx).unwrap(); + + // Test transaction call in simulate mode. + let call = call_name_tx.call.clone(); + CurrentState::with_transaction_opts( + Options::new() + .with_mode(Mode::Simulate) + .with_tx(call_name_tx.clone().into()), + || { + let erc20_name: Vec = cbor::from_value( + decode_result!( + ctx, + EVMModule::::tx_call(&ctx, cbor::from_value(call.body).unwrap()) + ) + .unwrap(), + ) + .unwrap(); + + EVMModule::::check_invariants(&ctx).expect("invariants should hold"); + + assert_eq!(erc20_name.len(), 96); + assert_eq!(erc20_name[63], 0x04); // Name is 4 bytes long. + assert_eq!(erc20_name[64..68], vec![0x54, 0x65, 0x73, 0x74]); // "Test". + }, + ); + + let call = call_name_tx.call.clone(); + let erc20_name = + CurrentState::with_transaction_opts(Options::new().with_tx(call_name_tx.into()), || { + let name: Vec = cbor::from_value( + decode_result!( + ctx, + EVMModule::::tx_call(&ctx, cbor::from_value(call.body).unwrap()) + ) + .unwrap(), + ) + .unwrap(); + + EVMModule::::check_invariants(&ctx).expect("invariants should hold"); + + TransactionResult::Commit(name) + }); + assert_eq!(erc20_name.len(), 96); + assert_eq!(erc20_name[63], 0x04); // Name is 4 bytes long. + assert_eq!(erc20_name[64..68], vec![0x54, 0x65, 0x73, 0x74]); // "Test". + + // Test the Call transaction with more complicated parameters + // (transfer 0x1000 coins to 0xc001d00d). + let transfer_method: Vec = Vec::from_hex( + "a9059cbb".to_owned() + + &"0".repeat(64 - 4) + + &"1000".to_owned() + + &"0".repeat(64 - 8) + + &"c001d00d".to_owned(), + ) + .unwrap(); + let call_transfer_tx = transaction::Transaction { + version: 1, + call: transaction::Call { + format: transaction::CallFormat::Plain, + method: "evm.Call".to_owned(), + body: cbor::to_value(types::Call { + address: erc20_addr, + value: 0.into(), + data: encode_data!(transfer_method.clone()), + }), + ..Default::default() + }, + auth_info: transaction::AuthInfo { + signer_info: vec![transaction::SignerInfo::new_sigspec( + keys::dave::sigspec(), + 3, + )], + fee: transaction::Fee { + gas: 64_000, + ..Default::default() + }, + ..Default::default() + }, + }; + // Run authentication handler to simulate nonce increments. + as Runtime>::Modules::authenticate_tx(&ctx, &call_transfer_tx).unwrap(); + + let call = call_transfer_tx.call.clone(); + let transfer_ret = CurrentState::with_transaction_opts( + Options::new().with_tx(call_transfer_tx.into()), + || { + let ret: Vec = cbor::from_value( + decode_result!( + ctx, + EVMModule::::tx_call(&ctx, cbor::from_value(call.body).unwrap()) + ) + .unwrap(), + ) + .unwrap(); + + EVMModule::::check_invariants(&ctx).expect("invariants should hold"); + + TransactionResult::Commit(ret) + }, + ); + assert_eq!( + transfer_ret, + Vec::::from_hex("0".repeat(64 - 1) + &"1".to_owned()).unwrap() + ); // OK. + + // Submitting an invalid call transaction should fail. + let out_of_gas_tx = transaction::Transaction { + version: 1, + call: transaction::Call { + format: transaction::CallFormat::Plain, + method: "evm.Call".to_owned(), + body: cbor::to_value(types::Call { + address: erc20_addr, + value: 0.into(), + data: encode_data!(transfer_method), + }), + ..Default::default() + }, + auth_info: transaction::AuthInfo { + signer_info: vec![transaction::SignerInfo::new_sigspec( + keys::dave::sigspec(), + 4, + )], + fee: transaction::Fee { + gas: 10, // Not enough gas. + ..Default::default() + }, + ..Default::default() + }, + }; + as Runtime>::Modules::authenticate_tx(&ctx, &out_of_gas_tx).unwrap(); + + let call = out_of_gas_tx.call.clone(); + CurrentState::with_transaction_opts( + Options::new().with_tx(out_of_gas_tx.clone().into()), + || { + assert!(!decode_result!( + ctx, + EVMModule::::tx_call(&ctx, cbor::from_value(call.body).unwrap()) + ) + .is_success()); + }, + ); + + // CheckTx should not fail. + let call = out_of_gas_tx.call.clone(); + CurrentState::with_transaction_opts( + Options::new() + .with_mode(state::Mode::Check) + .with_tx(out_of_gas_tx.clone().into()), + || { + let rsp = EVMModule::::tx_call(&ctx, cbor::from_value(call.body).unwrap()) + .expect("call should succeed with empty result"); + + assert_eq!( + rsp, + Vec::::new(), + "check tx should return an empty response" + ); + }, + ); +} + +#[test] +fn test_evm_runtime() { + do_test_evm_runtime::(); +} + +#[test] +fn test_c10l_evm_runtime() { + let _guard = crypto::signature::context::test_using_chain_context(); + crypto::signature::context::set_chain_context(Default::default(), "test"); + do_test_evm_runtime::(); +} + +#[test] +fn test_c10l_queries() { + let mut mock = mock::Mock::default(); + let ctx = mock.create_ctx_for_runtime::>(true); + let mut signer = EvmSigner::new(0, keys::dave::sigspec()); + + EVMRuntime::::migrate(&ctx); + + static QUERY_CONTRACT_CODE_HEX: &str = + include_str!("../../../../tests/e2e/evm/contracts/query/query.hex"); + + // Simulate contract creation. + let result = signer + .query_evm_create(&ctx, load_contract_bytecode(QUERY_CONTRACT_CODE_HEX)) + .expect("query should succeed"); + let contract_address1 = H160::from_slice(&result); + + let result = signer + .query_evm_create_opts( + &ctx, + load_contract_bytecode(QUERY_CONTRACT_CODE_HEX), + QueryOptions { + encrypt: true, + ..Default::default() + }, + ) + .expect("query should succeed"); + let contract_address2 = H160::from_slice(&result); + + assert_eq!(contract_address1, contract_address2); + + // Create contract. + let dispatch_result = signer.call( + &ctx, + "evm.Create", + types::Create { + value: 0.into(), + init_code: load_contract_bytecode(QUERY_CONTRACT_CODE_HEX), + }, + ); + let result = dispatch_result.result.unwrap(); + let result: Vec = cbor::from_value(result).unwrap(); + let contract_address = H160::from_slice(&result); + + // Call the `test` method on the contract via a query. + let result = signer + .query_evm_call(&ctx, contract_address, "test", &[], &[]) + .expect("query should succeed"); + + let mut result = + ethabi::decode(&[ParamType::Address], &result).expect("output should be correct"); + + let test = result.pop().unwrap().into_address().unwrap(); + assert_eq!(test, Default::default(), "msg.signer should be zeroized"); + + // Test call with confidential envelope. + let result = signer + .query_evm_call_opts( + &ctx, + contract_address, + "test", + &[], + &[], + QueryOptions { + encrypt: true, + ..Default::default() + }, + ) + .expect("query should succeed"); + + let mut result = + ethabi::decode(&[ParamType::Address], &result).expect("output should be correct"); + + let test = result.pop().unwrap().into_address().unwrap(); + assert_eq!(test, Default::default(), "msg.signer should be zeroized"); +} + +#[test] +fn test_fee_refunds() { + let mut mock = mock::Mock::default(); + let ctx = mock.create_ctx_for_runtime::>(true); + let mut signer = EvmSigner::new(0, keys::dave::sigspec()); + + EVMRuntime::::migrate(&ctx); + + // Give Dave some tokens. + Accounts::mint( + keys::dave::address(), + &token::BaseUnits(1_000_000_000, Denomination::NATIVE), + ) + .unwrap(); + + // Create contract. + let dispatch_result = signer.call( + &ctx, + "evm.Create", + types::Create { + value: 0.into(), + init_code: load_contract_bytecode(TEST_CONTRACT_CODE_HEX), + }, + ); + let result = dispatch_result.result.unwrap(); + let result: Vec = cbor::from_value(result).unwrap(); + let contract_address = H160::from_slice(&result); + + // Call the `name` method on the contract. + let dispatch_result = signer.call_evm_opts( + &ctx, + contract_address, + "name", + &[], + &[], + CallOptions { + fee: Fee { + amount: token::BaseUnits::new(1_000_000, Denomination::NATIVE), + gas: 100_000, + ..Default::default() + }, + ..Default::default() + }, + ); + assert!(dispatch_result.result.is_success(), "call should succeed"); + + // Make sure two events were emitted and are properly formatted. + let tags = &dispatch_result.tags; + assert_eq!(tags.len(), 2, "two events should have been emitted"); + assert_eq!(tags[0].key, b"accounts\x00\x00\x00\x01"); // accounts.Transfer (code = 1) event + assert_eq!(tags[1].key, b"core\x00\x00\x00\x01"); // core.GasUsed (code = 1) event + + #[derive(Debug, Default, cbor::Decode)] + struct TransferEvent { + from: Address, + to: Address, + amount: token::BaseUnits, + } + + let events: Vec = cbor::from_slice(&tags[0].value).unwrap(); + assert_eq!(events.len(), 1); // One event for fee payment. + let event = &events[0]; + assert_eq!(event.from, keys::dave::address()); + assert_eq!(event.to, *ADDRESS_FEE_ACCUMULATOR); + assert_eq!( + event.amount, + token::BaseUnits::new(242_700, Denomination::NATIVE) + ); + + #[derive(Debug, Default, cbor::Decode)] + struct GasUsedEvent { + amount: u64, + } + + let events: Vec = cbor::from_slice(&tags[1].value).unwrap(); + assert_eq!(events.len(), 1); + assert_eq!(events[0].amount, 24_270); + + // Call the `transfer` method on the contract with invalid parameters so it reverts. + let dispatch_result = signer.call_evm_opts( + &ctx, + contract_address, + "transfer", + &[ParamType::Address, ParamType::Uint(256)], + &[ + Token::Address(contract_address.into()), + Token::Uint(u128::MAX.into()), // Too much so it reverts. + ], + CallOptions { + fee: Fee { + amount: token::BaseUnits::new(1_000_000, Denomination::NATIVE), + gas: 100_000, + ..Default::default() + }, + ..Default::default() + }, + ); + if let module::CallResult::Failed { + module, + code, + message, + } = dispatch_result.result + { + assert_eq!(module, "evm"); + assert_eq!(code, 8); + assert_eq!( + decode_reverted(&message).unwrap(), + "ERC20: transfer amount exceeds balance" + ); + } else { + panic!("call should revert"); + } + + // Make sure two events were emitted and are properly formatted. + let tags = &dispatch_result.tags; + assert_eq!(tags.len(), 2, "two events should have been emitted"); + assert_eq!(tags[0].key, b"accounts\x00\x00\x00\x01"); // accounts.Transfer (code = 1) event + assert_eq!(tags[1].key, b"core\x00\x00\x00\x01"); // core.GasUsed (code = 1) event + + let events: Vec = cbor::from_slice(&tags[0].value).unwrap(); + assert_eq!(events.len(), 1); // One event for fee payment. + let event = &events[0]; + assert_eq!(event.from, keys::dave::address()); + assert_eq!(event.to, *ADDRESS_FEE_ACCUMULATOR); + assert_eq!( + event.amount, + token::BaseUnits::new(245_850, Denomination::NATIVE) // Note the refund. + ); + + let events: Vec = cbor::from_slice(&tags[1].value).unwrap(); + assert_eq!(events.len(), 1); + assert_eq!(events[0].amount, 24_585); +} + +#[test] +fn test_transfer_event() { + let mut mock = mock::Mock::default(); + let mut ctx = mock.create_ctx_for_runtime::>(true); + let mut signer = EvmSigner::new(0, keys::dave::sigspec()); + + EVMRuntime::::migrate(&mut ctx); + + // XXX: Give Dave some tokens to pay for fees. + Accounts::mint( + keys::dave::address(), + &token::BaseUnits(1_000_000, Denomination::NATIVE), + ) + .unwrap(); + + // Create contract. + let dispatch_result = signer.call( + &mut ctx, + "evm.Create", + types::Create { + value: 0.into(), + init_code: load_contract_bytecode(FAUCET_CONTRACT_CODE_HEX), + }, + ); + println!("###1 {:?}", dispatch_result.result); + assert!(dispatch_result.result.is_success(), "create should succeed"); + let result = dispatch_result.result.unwrap(); + let result: Vec = cbor::from_value(result).unwrap(); + let contract_address = H160::from_slice(&result); + let contract_address_native = EVMConfig::map_address(contract_address.0.into()); + + // Give the faucet some tokens. + println!( + "*** Balance before mint: {:?}", + Accounts::get_balance(contract_address_native, Denomination::NATIVE) + ); + let result = Accounts::mint( + contract_address_native, + &token::BaseUnits(1_000_000_000_000, Denomination::NATIVE), + ); + println!("###2 {:?}", result); + assert!(result.is_ok(), "mint should succeed"); + println!( + "*** Balance after mint: {:?}", + Accounts::get_balance(contract_address_native, Denomination::NATIVE) + ); + + // Call the `withdraw` method on the contract; this initiates a native token transfer from within EVM. + let dispatch_result = signer.call_evm_opts( + &mut ctx, + contract_address, + "withdraw", + &[ParamType::Uint(256)], + &[Token::Uint(1_000_000_000.into())], + CallOptions { + fee: Fee { + amount: token::BaseUnits::new(1_000_000, Denomination::NATIVE), + gas: 100_000, + ..Default::default() + }, + ..Default::default() + }, + ); + println!("###3 {:?}", dispatch_result.result); + assert!( + dispatch_result.result.is_success(), + "withdraw call should succeed" + ); + + // Make sure two events were emitted and are properly formatted. + let tags = &dispatch_result.tags; + assert_eq!(tags.len(), 2, "two events should have been emitted"); + assert_eq!(tags[0].key, b"accounts\x00\x00\x00\x01"); // accounts.Transfer (code = 1) events + assert_eq!(tags[1].key, b"core\x00\x00\x00\x01"); // core.GasUsed (code = 1) event + + #[derive(Debug, Default, cbor::Decode)] + struct TransferEvent { + from: Address, + to: Address, + amount: token::BaseUnits, + } + + let events: Vec = cbor::from_slice(&tags[0].value).unwrap(); + println!("###4 Events: {:#?}", events); + assert_eq!(events.len(), 2); // One event for fee payment, one for the withdrawal. + let event = &events[0]; + assert_eq!(event.from, contract_address_native); + assert_eq!(event.to, keys::dave::address()); + assert_eq!( + event.amount, + token::BaseUnits::new(1_000_000_000, Denomination::NATIVE) + ); + let event = &events[1]; + assert_eq!(event.from, keys::dave::address()); + assert_eq!(event.to, *ADDRESS_FEE_ACCUMULATOR); + assert_eq!( + event.amount, + token::BaseUnits::new(283_430, Denomination::NATIVE) + ); + + #[derive(Debug, Default, cbor::Decode)] + struct GasUsedEvent { + amount: u64, + } + + let events: Vec = cbor::from_slice(&tags[1].value).unwrap(); + assert_eq!(events.len(), 1); + assert_eq!(events[0].amount, 28_343); +} + +#[test] +fn test_return_value_limits() { + let mut mock = mock::Mock::default(); + let ctx = mock.create_ctx_for_runtime::>(true); + let mut signer = EvmSigner::new(0, keys::dave::sigspec()); + + EVMRuntime::::migrate(&ctx); + + // Give Dave some tokens. + Accounts::mint( + keys::dave::address(), + &token::BaseUnits(1_000_000_000, Denomination::NATIVE), + ) + .unwrap(); + + static RETVAL_CONTRACT_CODE_HEX: &str = + include_str!("../../../../tests/e2e/evm/contracts/retval/retval.hex"); + + // Create contract. + let dispatch_result = signer.call( + &ctx, + "evm.Create", + types::Create { + value: 0.into(), + init_code: load_contract_bytecode(RETVAL_CONTRACT_CODE_HEX), + }, + ); + let result = dispatch_result.result.unwrap(); + let result: Vec = cbor::from_value(result).unwrap(); + let contract_address = H160::from_slice(&result); + + // Call the `testSuccess` method on the contract. + let dispatch_result = signer.call_evm_opts( + &ctx, + contract_address, + "testSuccess", + &[], + &[], + CallOptions { + fee: Fee { + amount: token::BaseUnits::new(1_000_000, Denomination::NATIVE), + gas: 100_000, + ..Default::default() + }, + ..Default::default() + }, + ); + let result: Vec = cbor::from_value(dispatch_result.result.unwrap()).unwrap(); + assert_eq!(result.len(), 1024, "result should be correctly trimmed"); + // Actual payload is ABI-encoded so the raw result starts at offset 64. + assert_eq!(result[64], 0xFF, "result should be correct"); + assert_eq!(result[1023], 0x42, "result should be correct"); + + // Call the `testRevert` method on the contract. + let dispatch_result = signer.call_evm_opts( + &ctx, + contract_address, + "testRevert", + &[], + &[], + CallOptions { + fee: Fee { + amount: token::BaseUnits::new(1_000_000, Denomination::NATIVE), + gas: 100_000, + ..Default::default() + }, + ..Default::default() + }, + ); + if let module::CallResult::Failed { + module, + code, + message, + } = dispatch_result.result + { + assert_eq!(module, "evm"); + assert_eq!(code, 8); + let message = decode_reverted_raw(&message).unwrap(); + // Actual payload is ABI-encoded so the raw result starts at offset 68. + assert_eq!(message[68], 0xFF, "result should be correct"); + assert_eq!(message[1023], 0x42, "result should be correct"); + } else { + panic!("call should revert"); + } + + // Make sure that in query context, the return value is not trimmed. + let ctx = mock.create_ctx_for_runtime::>(true); + + let result = signer + .query_evm_call_opts( + &ctx, + contract_address, + "testSuccess", + &[], + &[], + Default::default(), + ) + .expect("query should succeed"); + + assert_eq!(result.len(), 1120, "result should not be trimmed"); + // Actual payload is ABI-encoded so the raw result starts at offset 64. + assert_eq!(result[64], 0xFF, "result should be correct"); + assert_eq!(result[1023], 0x42, "result should be correct"); +} diff --git a/runtime-sdk/modules/evm-new/src/types.rs b/runtime-sdk/modules/evm-new/src/types.rs new file mode 100644 index 00000000000..bd954c05447 --- /dev/null +++ b/runtime-sdk/modules/evm-new/src/types.rs @@ -0,0 +1,222 @@ +//! EVM module types. + +/// Transaction body for creating an EVM contract. +#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] +pub struct Create { + pub value: U256, + pub init_code: Vec, +} + +/// Transaction body for calling an EVM contract. +#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] +pub struct Call { + pub address: H160, + pub value: U256, + pub data: Vec, +} + +/// Transaction body for peeking into EVM storage. +#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] +pub struct StorageQuery { + pub address: H160, + pub index: H256, +} + +/// Transaction body for peeking into EVM code storage. +#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] +pub struct CodeQuery { + pub address: H160, +} + +/// Transaction body for fetching EVM account's balance. +#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] +pub struct BalanceQuery { + pub address: H160, +} + +/// Transaction body for simulating an EVM call. +#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] +#[cfg_attr(test, derive(PartialEq, Eq))] +pub struct SimulateCallQuery { + pub gas_price: U256, + pub gas_limit: u64, + pub caller: H160, + #[cbor(optional)] + pub address: Option, + pub value: U256, + pub data: Vec, +} + +/// An envelope containing the encryption-enveloped data of a [`SimulateCallQuery`] +/// and a signature generated according to [EIP-712](https://eips.ethereum.org/EIPS/eip-712) +/// over the unmodified Eth call. +/// +/// EIP-712 is used so that the signed message can be easily verified by the user. +/// MetaMask, for instance, shows each field as itself, whereas a standard `eth_personalSign` +/// would show an opaque CBOR-encoded [`SimulateCallQuery`]. +/// +/// The EIP-712 type parameters for a signed query are: +/// ```ignore +/// { +/// domain: { +/// name: 'oasis-runtime-sdk/evm: signed query', +/// version: '1.0.0', +/// chainId, +/// }, +/// types: { +/// Call: [ +/// { name: 'from', type: 'address' }, +/// { name: 'to', type: 'address' }, +/// { name: 'value', type: 'uint256' }, +/// { name: 'gasPrice', type: 'uint256' }, +/// { name: 'gasLimit', type: 'uint64' }, +/// { name: 'data', type: 'bytes' }, +/// { name: 'leash', type: 'Leash' }, +/// ], +/// Leash: [ +/// { name: 'nonce', type: 'uint64' }, +/// { name: 'blockNumber', type: 'uint64' }, +/// { name: 'blockHash', type: 'uint256' }, +/// { name: 'blockRange', type: 'uint64' }, +/// ], +/// }, +/// } +/// ``` +#[derive(Clone, Debug, cbor::Encode, cbor::Decode)] +#[cbor(no_default)] +pub struct SignedCallDataPack { + pub data: oasis_runtime_sdk::types::transaction::Call, + pub leash: Leash, + pub signature: [u8; 65], +} + +#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)] +#[cfg_attr(test, derive(PartialEq, Eq))] +pub struct Leash { + /// The maximum account nonce that will be tolerated. + pub nonce: u64, + /// The base block number. + pub block_number: u64, + /// The expeced hash at `block_number`. + pub block_hash: H256, + /// The range of the leash past `block_number`. + pub block_range: u64, +} + +// The rest of the file contains wrappers for primitive_types::{H160, H256, U256}, +// so that we can implement cbor::{Encode, Decode} for them, ugh. +// Remove this once oasis-cbor#8 is implemented. +// +// Thanks to Nick for providing the fancy macros below :) + +// This `mod` exists solely to place an `#[allow(...)]` around the generated code. +#[allow(clippy::assign_op_pattern, clippy::non_canonical_clone_impl)] +mod eth { + use std::convert::TryFrom; + + use thiserror::Error; + + #[derive(Error, Debug)] + pub enum NoError {} + + macro_rules! construct_fixed_hash { + ($name:ident($num_bytes:literal)) => { + fixed_hash::construct_fixed_hash! { + pub struct $name($num_bytes); + } + + impl cbor::Encode for $name { + fn into_cbor_value(self) -> cbor::Value { + cbor::Value::ByteString(self.as_bytes().to_vec()) + } + } + + impl cbor::Decode for $name { + fn try_default() -> Result { + Ok(Default::default()) + } + + fn try_from_cbor_value(value: cbor::Value) -> Result { + match value { + cbor::Value::ByteString(v) => { + if v.len() == $num_bytes { + Ok(Self::from_slice(&v)) + } else { + Err(cbor::DecodeError::UnexpectedIntegerSize) + } + } + _ => Err(cbor::DecodeError::UnexpectedType), + } + } + } + + impl TryFrom<&[u8]> for $name { + type Error = NoError; + + fn try_from(bytes: &[u8]) -> Result { + Ok(Self::from_slice(bytes)) + } + } + }; + } + + macro_rules! construct_uint { + ($name:ident($num_words:tt)) => { + uint::construct_uint! { + pub struct $name($num_words); + } + + impl cbor::Encode for $name { + fn into_cbor_value(self) -> cbor::Value { + let mut out = [0u8; $num_words * 8]; + self.to_big_endian(&mut out); + cbor::Value::ByteString(out.to_vec()) + } + } + + impl cbor::Decode for $name { + fn try_default() -> Result { + Ok(Default::default()) + } + + fn try_from_cbor_value(value: cbor::Value) -> Result { + match value { + cbor::Value::ByteString(v) => { + if v.len() <= $num_words * 8 { + Ok(Self::from_big_endian(&v)) + } else { + Err(cbor::DecodeError::UnexpectedIntegerSize) + } + } + _ => Err(cbor::DecodeError::UnexpectedType), + } + } + } + }; + } + + construct_fixed_hash!(H160(20)); + construct_fixed_hash!(H256(32)); + construct_uint!(U256(4)); + + macro_rules! impl_upstream_conversions { + ($($ty:ident),* $(,)?) => { + $( + impl From<$ty> for primitive_types::$ty { + fn from(t: $ty) -> Self { + Self(t.0) + } + } + + impl From for $ty { + fn from(t: primitive_types::$ty) -> Self { + Self(t.0) + } + } + )* + } + } + + impl_upstream_conversions!(H160, H256, U256); +} +pub use eth::{H160, H256, U256};